Bug Summary

File:librecad/src/lib/filters/rs_filterdxfrw.cpp
Warning:line 26595, column 8
Forming reference to null pointer

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name rs_filterdxfrw.cpp -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 2 -fhalf-no-semantic-interposition -mframe-pointer=none -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/home/runner/work/LibreCAD/LibreCAD/librecad/src -fcoverage-compilation-dir=/home/runner/work/LibreCAD/LibreCAD/librecad/src -resource-dir /usr/lib/llvm-18/lib/clang/18 -D _REENTRANT -D DWGSUPPORT -D MUPARSER_STATIC -D QC_APPDIR="librecad" -D LC_VERSION=2.2.2_alpha1-670-g0ffb38d79 -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 cmd -I lib/actions -I lib/actions/visual_snap -I lib/actions/options -I lib/creation -I lib/debug -I lib/engine -I lib/engine/document -I lib/engine/document/blocks -I lib/engine/document/container -I lib/engine/document/dimstyles -I lib/engine/document/entities -I lib/engine/document/entities/support -I lib/engine/document/fonts -I lib/engine/document/io -I lib/engine/document/layers -I lib/engine/document/patterns -I lib/engine/document/selection -I lib/engine/document/textstyles -I lib/engine/document/ucs -I lib/engine/document/variables -I lib/engine/document/views -I lib/engine/clipboard -I lib/engine/overlays -I lib/engine/overlays/angles_base -I lib/engine/overlays/highlight -I lib/engine/overlays/preview -I lib/engine/overlays/references -I lib/engine/overlays/crosshair -I lib/engine/overlays/info_cursor -I lib/engine/overlays/overlay_box -I lib/engine/overlays/ucs_mark -I lib/engine/undo -I lib/engine/utils -I lib/engine/settings -I lib/fileio -I lib/filters -I lib/generators -I lib/generators/makercamsvg -I lib/generators/layers -I lib/generators/image -I lib/gui -I lib/gui/grid -I lib/gui/render -I lib/gui/render/headless -I lib/gui/render/widget -I lib/information -I lib/math -I lib/modification -I lib/selection -I lib/selection/metaentity -I lib/selection/metaentity/entities -I lib/printing -I lib/properties -I actions -I actions/dock_widgets -I actions/dock_widgets/block -I actions/dock_widgets/entity_info -I actions/dock_widgets/layer -I actions/dock_widgets/library -I actions/dock_widgets/ucs_list -I actions/drawing -I actions/drawing/draw -I actions/drawing/draw/arc -I actions/drawing/draw/circle -I actions/drawing/draw/curve -I actions/drawing/draw/spline -I actions/drawing/draw/dimensions -I actions/drawing/draw/ellipse -I actions/drawing/draw/hatch -I actions/drawing/draw/image -I actions/drawing/draw/line -I actions/drawing/draw/point -I actions/drawing/draw/rect -I actions/drawing/draw/polygon -I actions/drawing/draw/misc -I actions/drawing/draw/line/shapes -I actions/drawing/draw/line/misc -I actions/drawing/draw/line/shapes/rect -I actions/drawing/draw/line/shapes/polygon -I actions/drawing/draw/polyline -I actions/drawing/draw/text -I actions/drawing/edit -I actions/drawing/info -I actions/drawing/pick -I actions/drawing/modify -I actions/drawing/pen -I actions/drawing/rel_zero -I actions/drawing/selection -I actions/drawing/snap -I actions/drawing/zoom -I actions/file -I actions/options -I actions/print_preview -I ui -I ui/action_options -I ui/action_options/circle -I ui/action_options/curve -I ui/action_options/spline -I ui/action_options/dimensions -I ui/action_options/edit -I ui/action_options/image -I ui/action_options/info -I ui/action_options/insert -I ui/action_options/line -I ui/action_options/rect -I ui/action_options/polygon -I ui/action_options/misc -I ui/action_options/modify -I ui/action_options/ellipse -I ui/action_options/other -I ui/action_options/polyline -I ui/action_options/point -I ui/action_options/print_preview -I ui/action_options/selection -I ui/action_options/snap -I ui/action_options/text -I ui/actions -I ui/components -I ui/components/relative_position_assistant -I ui/components/comboboxes -I ui/components/containers -I ui/components/creators -I ui/components/layouts -I ui/components/pen -I ui/components/status_bar -I ui/components/toolbars -I ui/components/utils -I ui/dialogs -I ui/dialogs/actions -I ui/dialogs/actions/modify -I ui/dialogs/actions/quick_selection -I ui/dialogs/modify -I ui/dialogs/entity -I ui/dialogs/creators -I ui/dialogs/file -I ui/dialogs/file/export -I ui/dialogs/file/export/layers -I ui/dialogs/file/export/image -I ui/dialogs/file/export/makercam -I ui/dialogs/main -I ui/dialogs/settings -I ui/dialogs/settings/dimstyles -I ui/dialogs/settings/dimstyles/dimstyle_manager -I ui/dialogs/settings/dimstyles/dimstyle_manager/support -I ui/dialogs/settings/options_device -I ui/dialogs/settings/options_drawing -I ui/dialogs/settings/options_general -I ui/dialogs/settings/options_widget -I ui/dialogs/settings/shortcuts -I ui/dock_widgets -I ui/dock_widgets/block_widget -I ui/dock_widgets/command_line -I ui/dock_widgets/entity_info -I ui/dock_widgets/layer_widget -I ui/dock_widgets/layers_tree -I ui/dock_widgets/library_widget -I ui/dock_widgets/pen_palette -I ui/dock_widgets/pen_wizard -I ui/dock_widgets/property_sheet -I ui/dock_widgets/property_sheet/lib -I ui/dock_widgets/property_sheet/lib/properties -I ui/dock_widgets/property_sheet/lib/view -I ui/dock_widgets/property_sheet/lib/view/edit -I ui/dock_widgets/property_sheet/lib/widgets -I ui/dock_widgets/property_sheet/lib/widgets/sheet -I ui/dock_widgets/property_sheet/metaentity -I ui/dock_widgets/property_sheet/metaentity/entities -I ui/dock_widgets/property_sheet/metaentity/entities/document -I ui/dock_widgets/property_sheet/properties -I ui/dock_widgets/property_sheet/properties/action -I ui/dock_widgets/property_sheet/properties/bool -I ui/dock_widgets/property_sheet/properties/color -I ui/dock_widgets/property_sheet/properties/double -I ui/dock_widgets/property_sheet/properties/enum -I ui/dock_widgets/property_sheet/properties/int -I ui/dock_widgets/property_sheet/properties/layer -I ui/dock_widgets/property_sheet/properties/linetype -I ui/dock_widgets/property_sheet/properties/linewidth -I ui/dock_widgets/property_sheet/properties/rect -I ui/dock_widgets/property_sheet/properties/rsvector -I ui/dock_widgets/property_sheet/properties/string -I ui/dock_widgets/views_list -I ui/dock_widgets/ucs_list -I ui/dock_widgets/workspaces -I ui/dock_widgets/cad -I ui/main -I ui/main/init -I ui/main/persistence -I ui/main/release_check -I ui/main/support -I ui/main/fontviewer -I ui/main/workspaces -I ui/view -I main -I main/console_dxf2pdf -I test -I plugins -I ../res -I ../res/arrows -I ../res/controls -I ../res/dxf -I ../res/gdt -I ../res/icons -I ../res/images -I ../../../Qt/6.9.0/gcc_64/include -I ../../../Qt/6.9.0/gcc_64/include/QtSvg -I ../../../Qt/6.9.0/gcc_64/include/QtPrintSupport -I ../../../Qt/6.9.0/gcc_64/include/QtWidgets -I ../../../Qt/6.9.0/gcc_64/include/QtGui -I ../../../Qt/6.9.0/gcc_64/include/QtNetwork -I ../../../Qt/6.9.0/gcc_64/include/QtCore -I ../../generated/librecad/moc -I ../../generated/librecad/ui -I ../../../Qt/6.9.0/gcc_64/mkspecs/linux-g++ -internal-isystem /usr/bin/../lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14 -internal-isystem /usr/bin/../lib/gcc/x86_64-linux-gnu/14/../../../../include/x86_64-linux-gnu/c++/14 -internal-isystem /usr/bin/../lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/backward -internal-isystem /usr/lib/llvm-18/lib/clang/18/include -internal-isystem /usr/local/include -internal-isystem /usr/bin/../lib/gcc/x86_64-linux-gnu/14/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -O2 -std=gnu++1z -fdeprecated-macro -ferror-limit 19 -fgnuc-version=4.2.1 -fskip-odr-check-in-gmf -fcxx-exceptions -fexceptions -vectorize-loops -vectorize-slp -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /home/runner/work/LibreCAD/LibreCAD/out/2026-09-15-234850-5000-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 <iomanip>
38#include <iostream>
39#include <limits>
40#include <list>
41#include <map>
42#include <memory>
43#include <optional>
44#include <set>
45#include <sstream>
46#include <stack>
47#include <type_traits>
48#include <utility>
49#include <vector>
50
51#include <QCryptographicHash>
52#include <QDir>
53#include <QFile>
54#include <QFileInfo>
55#include <QLocale>
56#include <QRegularExpression>
57#include <QTemporaryFile>
58#if QT_VERSION((6<<16)|(9<<8)|(0)) >= QT_VERSION_CHECK(6, 4, 0)((6<<16)|(4<<8)|(0))
59#include <QByteArrayView>
60#endif
61#if QT_VERSION((6<<16)|(9<<8)|(0)) >= QT_VERSION_CHECK(6, 0, 0)((6<<16)|(0<<8)|(0))
62#include <QStringConverter>
63#endif
64#include <QStringList>
65
66#include "drw_acis.h"
67#include "dwg_fixed_handles.h"
68#include "dxf_format.h"
69#include "intern/dwgbufferw.h"
70#include "intern/dwgsafety.h"
71#include "lc_containertraverser.h"
72#include "lc_defaults.h"
73#include "lc_dimarc.h"
74#include "lc_dimarrowregistry.h"
75#include "lc_dimordinate.h"
76#include "lc_dimstyle.h"
77#include "lc_dwgadvancedmetadata.h"
78#include "lc_extentitydata.h"
79#include "lc_hyperbola.h"
80#include "lc_hyperbolaspline.h"
81#include "lc_mleader.h"
82#include "lc_parabola.h"
83#include "lc_parabolaspline.h"
84#include "lc_splinepoints.h"
85#include "lc_tolerance.h"
86#include "lc_wipeout.h"
87#include "rs_arc.h"
88#include "rs_block.h"
89#include "rs_blocklist.h"
90#include "rs_circle.h"
91#include "rs_dialogfactory.h"
92#include "rs_dialogfactoryinterface.h"
93#include "rs_dimaligned.h"
94#include "rs_dimangular.h"
95#include "rs_dimdiametric.h"
96#include "rs_dimlinear.h"
97#include "rs_dimradial.h"
98#include "rs_ellipse.h"
99#include "rs_filterdxfrw.h"
100#include "rs_graphicview.h"
101#include "rs_hatch.h"
102#include "rs_image.h"
103#include "rs_insert.h"
104#include "rs_layer.h"
105#include "rs_leader.h"
106#include "rs_line.h"
107#include "rs_math.h"
108#include "rs_mtext.h"
109#include "rs_point.h"
110#include "rs_polyline.h"
111#include "rs_solid.h"
112#include "rs_spline.h"
113#include "rs_system.h"
114#include "rs_text.h"
115
116#ifdef DWGSUPPORT1
117#include "lc_graphicviewport.h"
118#include "lc_ucs.h"
119#include "lc_view.h"
120#include "libdwgr.h"
121#include "rs_debug.h"
122#endif // DWGSUPPORT
123
124namespace {
125
126constexpr int kImportedSplineFallbackSamples = 96;
127
128template <typename T, typename = void>
129struct hasDwgReplayState : std::false_type {};
130
131template <typename T>
132struct hasDwgReplayState<
133 T, std::void_t<decltype(std::declval<const T &>().replayState)>>
134 : std::true_type {};
135
136std::string normalizeDwgTableName(const std::string &name) {
137 std::string normalized = name;
138 std::transform(
139 normalized.begin(), normalized.end(), normalized.begin(),
140 [](unsigned char c) { return static_cast<char>(std::toupper(c)); });
141 return normalized;
142}
143
144std::string normalizeDwgDataStorageHandleKey(std::string value) {
145 value.erase(
146 std::remove_if(value.begin(), value.end(),
147 [](unsigned char c) { return std::isspace(c) != 0; }),
148 value.end());
149 if (value.size() >= 2 && value[0] == '0' &&
150 (value[1] == 'x' || value[1] == 'X')) {
151 value.erase(0, 2);
152 }
153 for (char &c : value)
154 c = static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
155 const std::size_t firstNonZero = value.find_first_not_of('0');
156 if (firstNonZero == std::string::npos)
157 return "0";
158 value.erase(0, firstNonZero);
159 return value;
160}
161
162enum class XDataReadResult {
163 Absent,
164 Malformed,
165 Valid,
166};
167
168XDataReadResult readXDataString(const DRW_Variant *value, int code,
169 std::string &result) {
170 if (value == nullptr || value->code() != code)
171 return XDataReadResult::Absent;
172 if (value->type() != DRW_Variant::STRING || value->content.s == nullptr)
173 return XDataReadResult::Malformed;
174 result = *value->content.s;
175 return XDataReadResult::Valid;
176}
177
178XDataReadResult readXDataInteger(const DRW_Variant *value, int code,
179 std::int64_t &result,
180 bool allowInteger64 = true) {
181 if (value == nullptr || value->code() != code)
182 return XDataReadResult::Absent;
183 if (value->type() == DRW_Variant::INTEGER) {
184 result = value->i_val();
185 return XDataReadResult::Valid;
186 }
187 if (allowInteger64 && value->type() == DRW_Variant::INTEGER64) {
188 result = value->i64_val();
189 return XDataReadResult::Valid;
190 }
191 return XDataReadResult::Malformed;
192}
193
194XDataReadResult readXDataDouble(const DRW_Variant *value, int code,
195 double &result) {
196 if (value == nullptr || value->code() != code)
197 return XDataReadResult::Absent;
198 if (value->type() != DRW_Variant::DOUBLE || !std::isfinite(value->d_val())) {
199 return XDataReadResult::Malformed;
200 }
201 result = value->d_val();
202 return XDataReadResult::Valid;
203}
204
205XDataReadResult readXDataCoord(const DRW_Variant *value, int code,
206 DRW_Coord &result) {
207 if (value == nullptr || value->code() != code)
208 return XDataReadResult::Absent;
209 if (value->type() != DRW_Variant::COORD || value->coord() == nullptr ||
210 !std::isfinite(value->coord()->x) || !std::isfinite(value->coord()->y) ||
211 !std::isfinite(value->coord()->z)) {
212 return XDataReadResult::Malformed;
213 }
214 result = *value->coord();
215 return XDataReadResult::Valid;
216}
217
218XDataReadResult readXDataBinary(const DRW_Variant *value, int code,
219 const std::vector<std::uint8_t> *&result) {
220 if (value == nullptr || value->code() != code)
221 return XDataReadResult::Absent;
222 if (value->type() != DRW_Variant::BINARY || value->content.b == nullptr)
223 return XDataReadResult::Malformed;
224 result = value->binary();
225 return result == nullptr ? XDataReadResult::Malformed
226 : XDataReadResult::Valid;
227}
228
229XDataReadResult readXDataMarker(const DRW_Variant *value, std::string &result) {
230 return readXDataString(value, 1001, result);
231}
232
233XDataReadResult readXDataHexString(const DRW_Variant *value, int code,
234 QByteArray &result) {
235 std::string text;
236 const XDataReadResult status = readXDataString(value, code, text);
237 if (status != XDataReadResult::Valid)
238 return status;
239 if (text.size() % 2 != 0)
240 return XDataReadResult::Malformed;
241 for (const unsigned char character : text) {
242 if (!std::isxdigit(character))
243 return XDataReadResult::Malformed;
244 }
245 result = QByteArray::fromHex(QByteArray::fromStdString(text));
246 return XDataReadResult::Valid;
247}
248
249bool fitsXDataInt32(std::int64_t value) {
250 return value >= std::numeric_limits<std::int32_t>::min() &&
251 value <= std::numeric_limits<std::int32_t>::max();
252}
253
254DRW_Variant *checkedDimStyleVariable(const DRW_Dimstyle &style,
255 const char *key) {
256 static const std::map<std::string, DRW_Variant::TYPE> expectedTypes = {
257 {"$DIMBLK", DRW_Variant::STRING},
258 {"$DIMBLK1", DRW_Variant::STRING},
259 {"$DIMBLK2", DRW_Variant::STRING},
260 {"$DIMLTEX1", DRW_Variant::STRING},
261 {"$DIMLTEX2", DRW_Variant::STRING},
262 {"$DIMLTYPE", DRW_Variant::STRING},
263 {"$DIMTXSTY", DRW_Variant::STRING},
264 {"$DIMPOST", DRW_Variant::STRING},
265 {"$DIMAPOST", DRW_Variant::STRING},
266 {"$DIMLDRBLK", DRW_Variant::STRING},
267 {"$DIMASZ", DRW_Variant::DOUBLE},
268 {"$DIMTSZ", DRW_Variant::DOUBLE},
269 {"$DIMSCALE", DRW_Variant::DOUBLE},
270 {"$DIMLFAC", DRW_Variant::DOUBLE},
271 {"$DIMEXO", DRW_Variant::DOUBLE},
272 {"$DIMEXE", DRW_Variant::DOUBLE},
273 {"$DIMFXL", DRW_Variant::DOUBLE},
274 {"$DIMDLE", DRW_Variant::DOUBLE},
275 {"$DIMDLI", DRW_Variant::DOUBLE},
276 {"$DIMGAP", DRW_Variant::DOUBLE},
277 {"$DIMTXT", DRW_Variant::DOUBLE},
278 {"$DIMTVP", DRW_Variant::DOUBLE},
279 {"$DIMALTF", DRW_Variant::DOUBLE},
280 {"$DIMRND", DRW_Variant::DOUBLE},
281 {"$DIMALTRND", DRW_Variant::DOUBLE},
282 {"$DIMCEN", DRW_Variant::DOUBLE},
283 {"$DIMTM", DRW_Variant::DOUBLE},
284 {"$DIMTP", DRW_Variant::DOUBLE},
285 {"$DIMTFAC", DRW_Variant::DOUBLE},
286 {"$DIMSAH", DRW_Variant::INTEGER},
287 {"$DIMSOXD", DRW_Variant::INTEGER},
288 {"$DIMFXLON", DRW_Variant::INTEGER},
289 {"$DIMLWE", DRW_Variant::INTEGER},
290 {"$DIMCLRE", DRW_Variant::INTEGER},
291 {"$DIMSE1", DRW_Variant::INTEGER},
292 {"$DIMSE2", DRW_Variant::INTEGER},
293 {"$DIMLWD", DRW_Variant::INTEGER},
294 {"$DIMCLRD", DRW_Variant::INTEGER},
295 {"$DIMSD1", DRW_Variant::INTEGER},
296 {"$DIMSD2", DRW_Variant::INTEGER},
297 {"$DIMTOFL", DRW_Variant::INTEGER},
298 {"$DIMTOH", DRW_Variant::INTEGER},
299 {"$DIMTIH", DRW_Variant::INTEGER},
300 {"$DIMJUST", DRW_Variant::INTEGER},
301 {"$DIMCLRT", DRW_Variant::INTEGER},
302 {"$DIMTAD", DRW_Variant::INTEGER},
303 {"$DIMTIX", DRW_Variant::INTEGER},
304 {"$DIMTFILL", DRW_Variant::INTEGER},
305 {"$DIMTFILLCLR", DRW_Variant::INTEGER},
306 {"$DIMTXTDIRECTION", DRW_Variant::INTEGER},
307 {"$DIMUPT", DRW_Variant::INTEGER},
308 {"$DIMTMOVE", DRW_Variant::INTEGER},
309 {"$DIMATFIT", DRW_Variant::INTEGER},
310 {"$DIMZIN", DRW_Variant::INTEGER},
311 {"$DIMAZIN", DRW_Variant::INTEGER},
312 {"$DIMTZIN", DRW_Variant::INTEGER},
313 {"$DIMALTZ", DRW_Variant::INTEGER},
314 {"$DIMALTTZ", DRW_Variant::INTEGER},
315 {"$DIMLUNIT", DRW_Variant::INTEGER},
316 {"$DIMDSEP", DRW_Variant::INTEGER},
317 {"$DIMDEC", DRW_Variant::INTEGER},
318 {"$DIMALT", DRW_Variant::INTEGER},
319 {"$DIMALTU", DRW_Variant::INTEGER},
320 {"$DIMALTD", DRW_Variant::INTEGER},
321 {"$DIMAUNIT", DRW_Variant::INTEGER},
322 {"$DIMADEC", DRW_Variant::INTEGER},
323 {"$DIMFRAC", DRW_Variant::INTEGER},
324 {"$DIMTDEC", DRW_Variant::INTEGER},
325 {"$DIMALTTD", DRW_Variant::INTEGER},
326 {"$DIMTOL", DRW_Variant::INTEGER},
327 {"$DIMTOLJ", DRW_Variant::INTEGER},
328 {"$DIMLIM", DRW_Variant::INTEGER},
329 {"$DIMARCSYM", DRW_Variant::INTEGER}};
330 if (key == nullptr)
331 return nullptr;
332 const auto expected = expectedTypes.find(key);
333 if (expected == expectedTypes.end())
334 return nullptr;
335 DRW_Variant *value = style.get(key);
336 if (value == nullptr || value->type() != expected->second)
337 return nullptr;
338 if (value->type() == DRW_Variant::STRING)
339 return value->content.s == nullptr ? nullptr : value;
340 if (value->type() == DRW_Variant::DOUBLE)
341 return std::isfinite(value->d_val()) ? value : nullptr;
342 return value;
343}
344
345std::string dwgDataStorageNumericHandleKey(std::uint64_t handle) {
346 std::ostringstream stream;
347 stream << std::uppercase << std::hex << handle;
348 return normalizeDwgDataStorageHandleKey(stream.str());
349}
350
351bool hasCustomDxfApplicationGroups(
352 const std::list<std::list<DRW_Variant>> &appData) {
353 for (const auto &group : appData) {
354 if (group.empty())
355 return true;
356 const DRW_Variant &opener = group.front();
357 if (opener.code() != 102 || opener.type() != DRW_Variant::STRING ||
358 opener.content.s == nullptr) {
359 return true;
360 }
361 std::string name = *opener.content.s;
362 if (!name.empty() && name.front() == '{')
363 name.erase(name.begin());
364 if (name != "ACAD_REACTORS" && name != "ACAD_XDICTIONARY")
365 return true;
366 }
367 return false;
368}
369
370bool hasDxfTableAppDataUnsupportedByDwg(const DRW_TableEntry &entry) {
371 return hasCustomDxfApplicationGroups(entry.appData);
372}
373
374bool isDwgSpaceBlockOwner(std::uint32_t handle) {
375 return handle == DRW::DwgModelSpaceBlockRecordHandle ||
376 handle == DRW::DwgPaperSpaceBlockRecordHandle ||
377 handle == DRW::DxfModelSpaceBlockRecordHandle ||
378 handle == DRW::DxfPaperSpaceBlockRecordHandle;
379}
380
381enum class DwgMLeaderWriteRoute : std::uint8_t { Invalid, Native, Fallback };
382
383enum class DwgUnderlaySidecarCoordinateMode : std::uint8_t {
384 Position,
385 Extrusion,
386 Clip,
387 InverseClip
388};
389
390struct DwgUnderlaySidecar {
391 DRW_Underlay value;
392 bool gotPosition = false;
393 bool hasOriginalClip = false;
394 bool noClip = false;
395};
396
397bool parseDwgUnderlaySidecar(const RS_Entity *entity,
398 DwgUnderlaySidecar &result) {
399 if (entity == nullptr || entity->rtti() != RS2::EntityPolyline ||
400 !entity->hasDrwExtData())
401 return false;
402
403 DwgUnderlaySidecar parsed;
404 bool inGroup = false;
405 bool sawApplication = false;
406 int stringOrdinal = 0;
407 bool scale3 = false;
408 std::size_t scalarCount = 0;
409 std::size_t displayValueCount = 0;
410 DwgUnderlaySidecarCoordinateMode coordinateMode =
411 DwgUnderlaySidecarCoordinateMode::Position;
412
413 for (const auto &variant : entity->getDrwExtData()) {
414 if (variant == nullptr) {
415 if (inGroup)
416 return false;
417 continue;
418 }
419 const int code = variant->code();
420 if (code == 1001) {
421 std::string marker;
422 const XDataReadResult markerStatus =
423 readXDataMarker(variant.get(), marker);
424 if (markerStatus != XDataReadResult::Valid) {
425 if (inGroup)
426 return false;
427 continue;
428 }
429 inGroup = marker == "LibreCAD_UNDERLAY";
430 sawApplication = sawApplication || inGroup;
431 continue;
432 }
433 if (!inGroup)
434 continue;
435
436 switch (code) {
437 case 1000: {
438 std::string value;
439 if (readXDataString(variant.get(), 1000, value) != XDataReadResult::Valid)
440 return false;
441 if (stringOrdinal < 2) {
442 if (stringOrdinal == 1) {
443 if (value == "PDF")
444 parsed.value.kind = DRW_Underlay::PDF;
445 else if (value == "DGN")
446 parsed.value.kind = DRW_Underlay::DGN;
447 else if (value == "DWF")
448 parsed.value.kind = DRW_Underlay::DWF;
449 else
450 return false;
451 }
452 ++stringOrdinal;
453 } else if (value == "SCALE3") {
454 scale3 = true;
455 } else if (value == "EXTRUSION") {
456 coordinateMode = DwgUnderlaySidecarCoordinateMode::Extrusion;
457 } else if (value == "CLIP_ORIGINAL") {
458 parsed.hasOriginalClip = true;
459 coordinateMode = DwgUnderlaySidecarCoordinateMode::Clip;
460 } else if (value == "NO_CLIP") {
461 parsed.noClip = true;
462 } else if (value == "INVERSE_CLIP") {
463 coordinateMode = DwgUnderlaySidecarCoordinateMode::InverseClip;
464 }
465 break;
466 }
467 case 1071: {
468 std::int64_t value = 0;
469 if (readXDataInteger(variant.get(), 1071, value) !=
470 XDataReadResult::Valid)
471 return false;
472 if (value < 0 || static_cast<std::uint64_t>(value) >
473 std::numeric_limits<std::uint32_t>::max())
474 return false;
475 parsed.value.definitionHandle = static_cast<std::uint32_t>(value);
476 break;
477 }
478 case 1010: {
479 DRW_Coord point;
480 if (readXDataCoord(variant.get(), 1010, point) != XDataReadResult::Valid)
481 return false;
482 switch (coordinateMode) {
483 case DwgUnderlaySidecarCoordinateMode::Position:
484 if (parsed.gotPosition)
485 return false;
486 parsed.value.position = point;
487 parsed.gotPosition = true;
488 break;
489 case DwgUnderlaySidecarCoordinateMode::Extrusion:
490 parsed.value.extPoint = point;
491 break;
492 case DwgUnderlaySidecarCoordinateMode::Clip:
493 parsed.value.clipBoundary.push_back(point);
494 break;
495 case DwgUnderlaySidecarCoordinateMode::InverseClip:
496 parsed.value.inverseClipBoundary.push_back(point);
497 break;
498 }
499 break;
500 }
501 case 1040: {
502 double value = 0.0;
503 if (readXDataDouble(variant.get(), 1040, value) != XDataReadResult::Valid)
504 return false;
505 if (scale3) {
506 switch (scalarCount) {
507 case 0:
508 parsed.value.scale.x = value;
509 break;
510 case 1:
511 parsed.value.scale.y = value;
512 break;
513 case 2:
514 parsed.value.scale.z = value;
515 break;
516 case 3:
517 parsed.value.rotation = value;
518 break;
519 default:
520 return false;
521 }
522 } else {
523 switch (scalarCount) {
524 case 0:
525 parsed.value.scale.x = value;
526 break;
527 case 1:
528 parsed.value.scale.y = value;
529 break;
530 case 2:
531 parsed.value.rotation = value;
532 break;
533 default:
534 return false;
535 }
536 }
537 ++scalarCount;
538 break;
539 }
540 case 1070: {
541 std::int64_t value = 0;
542 if (readXDataInteger(variant.get(), 1070, value, false) !=
543 XDataReadResult::Valid ||
544 value < 0 || value > std::numeric_limits<std::uint8_t>::max())
545 return false;
546 if (displayValueCount == 0)
547 parsed.value.flags = static_cast<std::uint8_t>(value);
548 else if (displayValueCount == 1)
549 parsed.value.contrast = static_cast<std::uint8_t>(value);
550 else if (displayValueCount == 2)
551 parsed.value.fade = static_cast<std::uint8_t>(value);
552 else
553 return false;
554 ++displayValueCount;
555 break;
556 }
557 default:
558 break;
559 }
560 }
561
562 const std::size_t expectedScalars = scale3 ? 4u : 3u;
563 if (!sawApplication || stringOrdinal != 2 || !parsed.gotPosition ||
564 scalarCount != expectedScalars || displayValueCount != 3 ||
565 parsed.value.definitionHandle == 0 ||
566 parsed.noClip && parsed.hasOriginalClip ||
567 !std::isfinite(parsed.value.rotation) ||
568 std::abs(parsed.value.scale.x) <= RS_TOLERANCE1.0e-10 ||
569 std::abs(parsed.value.scale.y) <= RS_TOLERANCE1.0e-10)
570 return false;
571
572 result = std::move(parsed);
573 return true;
574}
575
576bool hasDwgUnderlaySidecar(const RS_Entity *entity) {
577 if (entity == nullptr || !entity->hasDrwExtData())
578 return false;
579 for (const auto &variant : entity->getDrwExtData()) {
580 std::string marker;
581 if (readXDataMarker(variant.get(), marker) == XDataReadResult::Valid &&
582 marker == "LibreCAD_UNDERLAY")
583 return true;
584 }
585 return false;
586}
587
588#ifdef DWGSUPPORT1
589std::uint16_t underlayEntityClassNumber(const DRW_Underlay::Kind kind,
590 const dwgRW *writer) {
591 if (writer == nullptr)
592 return 0;
593 switch (kind) {
594 case DRW_Underlay::DGN:
595 return writer->getDwgTypedClassNumber("AcDbDgnReference", "DGNUNDERLAY",
596 DRW_Underlay::kDwgClassNumDgn);
597 case DRW_Underlay::DWF:
598 return writer->getDwgTypedClassNumber("AcDbDwfReference", "DWFUNDERLAY",
599 DRW_Underlay::kDwgClassNumDwf);
600 case DRW_Underlay::PDF:
601 default:
602 return writer->getDwgTypedClassNumber("AcDbPdfReference", "PDFUNDERLAY",
603 DRW_Underlay::kDwgClassNumPdf);
604 }
605}
606#endif
607
608bool isFiniteDwgVector(const RS_Vector &value) {
609 return std::isfinite(value.x) && std::isfinite(value.y) &&
610 std::isfinite(value.z);
611}
612
613DwgMLeaderWriteRoute selectDwgMLeaderWriteRoute(const LC_MLeaderData &data,
614 DRW::Version version) {
615 const bool hasText = data.hasTextContents && !data.textLabel.isEmpty();
616 const bool hasBlock = data.hasBlockContents;
617
618 if ((data.hasTextContents && data.textLabel.isEmpty()) ||
619 (hasBlock && (data.blockName.isEmpty() || !data.blockLocation.isValid())))
620 return DwgMLeaderWriteRoute::Invalid;
621 if (data.roots.size() > DRW_MLeader::kMaxRoots || data.leaderType < 0 ||
622 data.leaderType > 2 || data.contentType < 0 || data.contentType > 3)
623 return DwgMLeaderWriteRoute::Invalid;
624
625 const auto finite = [](double value) { return std::isfinite(value); };
626 if (!finite(data.landingDistance) || !finite(data.arrowSize) ||
627 !finite(data.scaleFactor) || !finite(data.textHeight) ||
628 !finite(data.textRotation) || !finite(data.boundaryWidth) ||
629 !finite(data.boundaryHeight) || !finite(data.blockRotation) ||
630 !isFiniteDwgVector(data.contentBasePoint) ||
631 !isFiniteDwgVector(data.basePoint) ||
632 !isFiniteDwgVector(data.textLocation) ||
633 !isFiniteDwgVector(data.blockLocation) ||
634 !isFiniteDwgVector(data.blockScale))
635 return DwgMLeaderWriteRoute::Invalid;
636
637 for (const LC_MLeaderRoot &root : data.roots) {
638 if (!isFiniteDwgVector(root.connectionPoint) ||
639 !isFiniteDwgVector(root.direction) || !finite(root.landingDistance) ||
640 root.leaderLines.size() > DRW_MLeader::kMaxLeaderLines)
641 return DwgMLeaderWriteRoute::Invalid;
642 for (const LC_MLeaderLine &line : root.leaderLines) {
643 if (line.points.size() > DRW_MLeader::kMaxLeaderPoints ||
644 std::any_of(
645 line.points.cbegin(), line.points.cend(),
646 [](const RS_Vector &point) { return !isFiniteDwgVector(point); }))
647 return DwgMLeaderWriteRoute::Invalid;
648 }
649 }
650
651 if (version >= DRW::AC1024 && hasText && !hasBlock)
652 return DwgMLeaderWriteRoute::Native;
653 return DwgMLeaderWriteRoute::Fallback;
654}
655
656void overlayActivePlotSettings(DRW_PlotSettings &target,
657 LC_PlotSettings &source) {
658 const QString pages = QString("%1x%2")
659 .arg(source.getPagesNumHoriz())
660 .arg(source.getPagesNumVert());
661 target.plotViewName = pages.toStdString();
662 target.marginLeft = source.getMarginLeftMm();
663 target.marginTop = source.getMarginTopMm();
664 target.marginRight = source.getMarginRightMm();
665 target.marginBottom = source.getMarginBottomMm();
666 target.currentStyleSheet = source.getCurrentStyleName().toStdString();
667 target.pageSetupName = source.getPaperSizeName().toStdString();
668 target.paperWidth = source.getPaperWidthMm();
669 target.paperHeight = source.getPaperHeightMm();
670 target.plotOriginX = source.getOriginOffsetXMm();
671 target.plotOriginY = source.getOriginOffsetYMm();
672 target.windowMinX = source.getPlotWindowLowerLeftX();
673 target.windowMinY = source.getPlotWindowLowerLeftY();
674 target.windowMaxX = source.getPlotWindowUpperRightX();
675 target.windowMaxY = source.getPlotWindowUpperRightY();
676 target.realWorldUnits = source.getCustomPrintScalePaperUnitsNumerator();
677 target.drawingUnits = source.getCustomPrintScaleDrawingUnitsDenominator();
678 target.plotLayoutFlags = source.getPlotLayoutFlag();
679 target.paperUnits = source.getPlotPaperUnits();
680 target.plotRotation = source.getPlotRotation();
681 target.plotType = source.getPlotType();
682 target.scaleType = source.getStandardScaleType();
683 target.shadePlotMode = source.getShadePlotMode();
684 target.shadePlotResLevel = source.getShadePlotResolutionMode();
685 target.shadePlotCustomDPI = source.getShadePlotCustomDpi();
686 target.scaleFactor = source.getStandardScaleFactor();
687 target.paperImageOriginX = source.getPaperImageOriginX();
688 target.paperImageOriginY = source.getPaperImageOriginY();
689}
690
691bool restoreTableEntryExtData(
692 DRW_TableEntry &destination,
693 const std::vector<std::shared_ptr<DRW_Variant>> &source) {
694 for (const auto &value : source) {
695 if (!value ||
696 !destination.addExtData(std::make_unique<DRW_Variant>(*value))) {
697 return false;
698 }
699 }
700 return true;
701}
702
703bool restoreTableEntryExtData(DRW_TableEntry &destination,
704 const std::vector<DRW_Variant *> &source) {
705 for (const DRW_Variant *value : source) {
706 if (value == nullptr ||
707 !destination.addExtData(std::make_unique<DRW_Variant>(*value))) {
708 return false;
709 }
710 }
711 return true;
712}
713
714bool sameDxfClassDefinition(const DRW_Class &lhs, const DRW_Class &rhs) {
715 // instanceCount is recomputed from emitted records, while DWG-only fields
716 // do not participate in the DXF CLASS record. Compare only values that can
717 // change the emitted definition.
718 return lhs.className == rhs.className && lhs.appName == rhs.appName &&
719 lhs.proxyFlag == rhs.proxyFlag &&
720 lhs.wasaProxyFlag == rhs.wasaProxyFlag &&
721 lhs.entityFlag == rhs.entityFlag;
722}
723
724template <typename T> bool hasNormalizedAxialExtrusion(const T &entity) {
725 return entity.extPoint.x == 0.0 && entity.extPoint.y == 0.0 &&
726 (entity.extPoint.z == 1.0 || entity.extPoint.z == -1.0);
727}
728
729bool layerExtStringEmpty(const DRW_Variant *v) {
730 if (v == nullptr || v->type() != DRW_Variant::STRING)
731 return false;
732 return v->content.s == nullptr || v->content.s->empty();
733}
734
735QString layerExtAppName(const DRW_Variant *v) {
736 if (v == nullptr || v->code() != 1001 || v->type() != DRW_Variant::STRING ||
737 v->content.s == nullptr) {
738 return {};
739 }
740 return QString::fromUtf8(v->content.s->c_str()).trimmed();
741}
742
743enum class LayerConstructionMarkerStatus { Absent, Valid, Malformed };
744
745// A layer's construction marker is one complete 1001 application-data
746// group. Classify the complete group before applying its meaning so a bad
747// marker cannot be mistaken for an absent marker and emitted a second time.
748LayerConstructionMarkerStatus
749classifyLayerConstructionMarker(const std::vector<DRW_Variant *> &extData) {
750 bool foundLibreCadGroup = false;
751 bool malformedLibreCadGroup = false;
752 std::size_t validLibreCadGroups = 0;
753
754 for (std::size_t start = 0; start < extData.size();) {
755 const DRW_Variant *appId = extData[start];
756 std::size_t end = start + 1;
757 while (end < extData.size() &&
758 (extData[end] == nullptr || extData[end]->code() != 1001)) {
759 ++end;
760 }
761
762 const bool isLibreCad =
763 layerExtAppName(appId).compare(QLatin1String("LibreCad"),
764 Qt::CaseInsensitive) == 0;
765 if (isLibreCad) {
766 foundLibreCadGroup = true;
767 // The layer marker has exactly one payload value: INTEGER 1.
768 // Any extra, missing, null, or differently typed value makes the
769 // recognized group malformed rather than silently ignorable.
770 const bool valid = end == start + 2 && extData[start + 1] != nullptr &&
771 extData[start + 1]->code() == 1070 &&
772 extData[start + 1]->type() == DRW_Variant::INTEGER &&
773 extData[start + 1]->content.i == 1;
774 if (valid)
775 ++validLibreCadGroups;
776 else
777 malformedLibreCadGroup = true;
778 }
779
780 start = end;
781 }
782
783 if (!foundLibreCadGroup)
784 return LayerConstructionMarkerStatus::Absent;
785 if (malformedLibreCadGroup || validLibreCadGroups != 1)
786 return LayerConstructionMarkerStatus::Malformed;
787 return LayerConstructionMarkerStatus::Valid;
788}
789
790// AutoCAD AEC layer-standard marker: APPID + two empty UTF-16 strings, no
791// actionable LibreCAD semantics.
792bool isAcAecLayerStandardMarker(const std::vector<DRW_Variant *> &extData,
793 std::size_t appBlockStart) {
794 if (appBlockStart >= extData.size())
795 return false;
796 const QString app = layerExtAppName(extData[appBlockStart]);
797 if (!app.compare(QLatin1String("AcAecLayerStandard"), Qt::CaseInsensitive))
798 ;
799 else if (app.startsWith(QLatin1String("AcAec"), Qt::CaseInsensitive) &&
800 app.endsWith(QLatin1String("LayerStandard"), Qt::CaseInsensitive))
801 ;
802 else
803 return false;
804
805 std::size_t idx = appBlockStart + 1;
806 int emptyStrings = 0;
807 while (idx < extData.size() && emptyStrings < 2) {
808 const DRW_Variant *v = extData[idx];
809 if (v == nullptr)
810 return false;
811 if (v->code() == 1000) {
812 if (!layerExtStringEmpty(v))
813 return false;
814 ++emptyStrings;
815 ++idx;
816 continue;
817 }
818 if (v->code() == 1002 && v->type() == DRW_Variant::STRING &&
819 v->content.s != nullptr &&
820 (*v->content.s == "{" || *v->content.s == "}")) {
821 ++idx;
822 continue;
823 }
824 break;
825 }
826 return emptyStrings == 2;
827}
828
829bool isIgnorableLayerExtData(const std::vector<DRW_Variant *> &extData) {
830 if (extData.empty())
831 return true;
832 for (std::size_t i = 0; i < extData.size();) {
833 if (extData[i] == nullptr || extData[i]->code() != 1001)
834 return false;
835 if (layerExtAppName(extData[i])
836 .compare(QLatin1String("LibreCad"), Qt::CaseInsensitive) == 0) {
837 return false;
838 }
839 if (isAcAecLayerStandardMarker(extData, i)) {
840 std::size_t idx = i + 1;
841 int emptyStrings = 0;
842 while (idx < extData.size() && emptyStrings < 2) {
843 const DRW_Variant *v = extData[idx];
844 if (v != nullptr && v->code() == 1000) {
845 ++emptyStrings;
846 ++idx;
847 } else if (v != nullptr && v->code() == 1002) {
848 ++idx;
849 } else {
850 break;
851 }
852 }
853 i = idx;
854 continue;
855 }
856 return false;
857 }
858 return true;
859}
860
861bool differsFromUnitWeight(double weight) {
862 return std::fabs(weight - 1.0) > 1e-12;
863}
864
865bool hasRationalSplineWeights(const DRW_Spline *data) {
866 if (data == nullptr)
867 return false;
868 return (data->flags & 0x4) != 0 ||
869 std::any_of(data->weightlist.begin(), data->weightlist.end(),
870 differsFromUnitWeight);
871}
872
873bool buildSplineDataFromDrw(const DRW_Spline *source, RS_SplineData &target) {
874 if (source == nullptr || source->degree < 1 || source->controllist.empty())
875 return false;
876
877 const size_t degree = static_cast<size_t>(source->degree);
878 const size_t controlCount = source->controllist.size();
879 if (controlCount < degree + 1)
880 return false;
881
882 const size_t requiredKnots = controlCount + degree + 1;
883 if (source->knotslist.size() < requiredKnots)
884 return false;
885
886 const bool closed = (source->flags & 0x3) != 0;
887 target = RS_SplineData(source->degree, closed);
888 target.type = closed ? RS_SplineData::SplineType::WrappedClosed
889 : RS_SplineData::SplineType::ClampedOpen;
890
891 target.knotslist.assign(source->knotslist.begin(),
892 source->knotslist.begin() + requiredKnots);
893
894 target.controlPoints.reserve(controlCount);
895 target.weights.reserve(controlCount);
896 const bool rational = hasRationalSplineWeights(source);
897 for (size_t i = 0; i < controlCount; ++i) {
898 const auto &control = source->controllist[i];
899 if (!control)
900 return false;
901 target.controlPoints.push_back({control->x, control->y});
902 double weight = 1.0;
903 if (rational && i < source->weightlist.size())
904 weight = source->weightlist[i];
905 if (weight <= 0.0 || !std::isfinite(weight))
906 return false;
907 target.weights.push_back(weight);
908 }
909
910 return true;
911}
912
913constexpr double kTableFallbackDimension = 1.0;
914constexpr double kTableFallbackMinTextHeight = 0.1;
915
916enum class TableFallbackCellKind {
917 Empty,
918 PlainText,
919 PlaceholderField,
920 PlaceholderBlock,
921 PlaceholderAttribute,
922 PlaceholderUnknown
923};
924
925struct TableFallbackCellDisplay {
926 QString text;
927 TableFallbackCellKind kind = TableFallbackCellKind::Empty;
928};
929
930TableFallbackCellDisplay tableCellDisplay(const DRW_TableCell &cell,
931 bool tableParseComplete) {
932 auto placeholder = [](TableFallbackCellKind kind, const char *text) {
933 TableFallbackCellDisplay display;
934 display.kind = kind;
935 display.text = QString::fromLatin1(text);
936 return display;
937 };
938
939 if (!tableParseComplete || cell.m_geometryFlags != 0 ||
940 cell.m_geometryHandle != 0 || cell.m_overrideFlags != 0 ||
941 cell.m_isMerged) {
942 return placeholder(TableFallbackCellKind::PlaceholderUnknown, "[TABLE]");
943 }
944
945 for (const DRW_TableCellContent &content : cell.m_contents) {
946 if (content.m_type == 4)
947 return placeholder(TableFallbackCellKind::PlaceholderBlock, "[BLOCK]");
948 if (content.m_type == 2 || content.m_handle != 0)
949 return placeholder(TableFallbackCellKind::PlaceholderField, "[FIELD]");
950 if (!content.m_text.empty()) {
951 return {QString::fromUtf8(content.m_text.c_str()),
952 TableFallbackCellKind::PlainText};
953 }
954 if (!content.m_value.m_valueString.empty()) {
955 return {QString::fromUtf8(content.m_value.m_valueString.c_str()),
956 TableFallbackCellKind::PlainText};
957 }
958 if (content.m_type != 0 && content.m_type != 1)
959 return placeholder(TableFallbackCellKind::PlaceholderUnknown, "[TABLE]");
960 }
961 if (cell.m_blockHandle != 0)
962 return placeholder(TableFallbackCellKind::PlaceholderBlock, "[BLOCK]");
963 if (cell.m_valueHandle != 0)
964 return placeholder(TableFallbackCellKind::PlaceholderField, "[FIELD]");
965 for (const DRW_TableCellAttribute &attribute : cell.m_attributes) {
966 if (!attribute.m_text.empty()) {
967 return {QString::fromUtf8(attribute.m_text.c_str()),
968 TableFallbackCellKind::PlaceholderAttribute};
969 }
970 if (attribute.m_attdefHandle != 0)
971 return placeholder(TableFallbackCellKind::PlaceholderAttribute, "[ATTR]");
972 }
973 if (!cell.m_attributes.empty())
974 return placeholder(TableFallbackCellKind::PlaceholderAttribute, "[ATTR]");
975 return {};
976}
977
978bool tableFallbackCellIsPlaceholder(TableFallbackCellKind kind) {
979 return kind != TableFallbackCellKind::PlainText &&
980 kind != TableFallbackCellKind::Empty;
981}
982
983double tableColumnWidth(const DRW_TableContent &content, size_t index,
984 RS_FilterDXFRW::TableFallbackRenderSummary *summary) {
985 if (index < content.m_columns.size() &&
986 std::isfinite(content.m_columns[index].m_width) &&
987 content.m_columns[index].m_width > 0.0) {
988 return content.m_columns[index].m_width;
989 }
990 if (summary != nullptr)
991 ++summary->clampedDimensionCount;
992 return kTableFallbackDimension;
993}
994
995double tableRowHeight(const DRW_TableContent &content, size_t index,
996 RS_FilterDXFRW::TableFallbackRenderSummary *summary) {
997 if (index < content.m_rows.size() &&
998 std::isfinite(content.m_rows[index].m_height) &&
999 content.m_rows[index].m_height > 0.0) {
1000 return content.m_rows[index].m_height;
1001 }
1002 if (summary != nullptr)
1003 ++summary->clampedDimensionCount;
1004 return kTableFallbackDimension;
1005}
1006
1007DRW_UnsupportedObject
1008rawObjectFromMetadata(const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1009 DRW_UnsupportedObject object;
1010 object.m_version = record.version;
1011 object.m_objectType = record.objectType;
1012 object.m_handle = record.handle;
1013 object.m_parentHandle = record.parentHandle;
1014 object.setCommonLinkEvidence(record.commonLinkEvidence);
1015 object.m_reactorHandles = record.reactorHandles;
1016 object.m_xDictHandle = record.xDictHandle;
1017 object.m_numReactors = record.numReactors;
1018 object.m_xDictFlag = record.xDictFlag;
1019 object.m_blockOwnerHandle = record.blockOwnerHandle;
1020 object.m_bodyBitSize = record.bodyBitSize;
1021 object.m_objectOffset = record.objectOffset;
1022 object.m_objectSize = record.objectSize;
1023 object.m_isEntity = record.isEntity;
1024 object.m_isCustomClass = record.isCustomClass;
1025 object.m_hasDataStorage = record.hasDataStorage;
1026 object.m_hasClassDefinition = record.hasClassDefinition;
1027 object.m_classProxyFlag = record.classProxyFlag;
1028 object.m_classAppName = record.classAppName;
1029 object.m_classWasProxy = record.classWasProxy;
1030 object.m_classEntityFlagRaw = record.classEntityFlagRaw;
1031 object.m_classDwgVersion = record.classDwgVersion;
1032 object.m_classMaintenanceVersion = record.classMaintenanceVersion;
1033 object.m_classUnknown1 = record.classUnknown1;
1034 object.m_classUnknown2 = record.classUnknown2;
1035 object.m_recordName = record.recordName;
1036 object.m_className = record.className;
1037 object.m_rawBytes = record.rawBytes;
1038 object.m_typedPayloadValidated = record.typedPayloadValidated;
1039 return object;
1040}
1041
1042bool rawCustomEntityOwnerFieldsAreReplayable(
1043 const DRW_UnsupportedObject &object, DRW::Version targetVersion) {
1044 if (!object.m_isEntity || !object.m_isCustomClass)
1045 return true;
1046 if (object.m_blockOwnerHandle != DRW::NoHandle &&
1047 object.m_parentHandle == DRW::NoHandle &&
1048 !isDwgSpaceBlockOwner(object.m_blockOwnerHandle)) {
1049 // The reverse BLOCK_RECORD edge has no corresponding owner field in
1050 // a user block's opaque body, so it cannot be repaired without a
1051 // typed encoder. Model/paper-space entries are the documented
1052 // ownerless entmode forms and are safe aliases.
1053 return false;
1054 }
1055 if (object.m_blockOwnerHandle != DRW::NoHandle &&
1056 object.m_parentHandle != DRW::NoHandle &&
1057 object.m_blockOwnerHandle != object.m_parentHandle &&
1058 targetVersion < DRW::AC1021) {
1059 // The legacy layouts do not expose a validated owner bit range.
1060 return false;
1061 }
1062 return true;
1063}
1064
1065bool isSunRawObject(const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1066 return record.recordName == "SUN" || record.className == "AcDbSun";
1067}
1068
1069bool isLightRawEntity(const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1070 return record.isEntity &&
1071 (record.recordName == "LIGHT" || record.className == "AcDbLight");
1072}
1073
1074bool isNavisworksModelRawEntity(
1075 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1076 return record.isEntity && (record.recordName == "NAVISWORKSMODEL" ||
1077 record.className == "AcDbNavisworksModel");
1078}
1079
1080bool isCameraRawEntity(const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1081 return record.isEntity &&
1082 (record.recordName == "CAMERA" || record.className == "AcDbCamera");
1083}
1084
1085bool isNativeMetadataEntityRawObject(
1086 const LC_DwgAdvancedMetadata &metadata,
1087 const LC_DwgAdvancedMetadata::RawObjectRecord &raw) {
1088 if (raw.hasDataStorage || raw.handle == 0)
1089 return false;
1090
1091 const auto isRetained = [handle = raw.handle](const auto &record) {
1092 return record.handle == handle &&
1093 record.replayState ==
1094 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed;
1095 };
1096 if (isLightRawEntity(raw)) {
1097 return std::any_of(metadata.lights().cbegin(), metadata.lights().cend(),
1098 isRetained);
1099 }
1100 if (isNavisworksModelRawEntity(raw)) {
1101 return std::any_of(metadata.navisworksModels().cbegin(),
1102 metadata.navisworksModels().cend(), isRetained);
1103 }
1104 if (isCameraRawEntity(raw)) {
1105 return std::any_of(metadata.cameras().cbegin(), metadata.cameras().cend(),
1106 isRetained);
1107 }
1108 const bool isSectionObject =
1109 raw.isEntity &&
1110 (raw.recordName == "SECTIONOBJECT" ||
1111 raw.recordName == "SECTION_OBJECT" || raw.className == "AcDbSection");
1112 return isSectionObject &&
1113 std::any_of(metadata.sectionObjects().cbegin(),
1114 metadata.sectionObjects().cend(), isRetained);
1115}
1116
1117bool isSunStudyRawObject(
1118 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1119 return record.objectType == DRW_SunStudy::kDwgType ||
1120 record.recordName == "SUNSTUDY" ||
1121 record.recordName == "ACDBSUNSTUDY" ||
1122 record.className == "AcDbSunStudy";
1123}
1124
1125bool isMotionPathRawObject(
1126 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1127 return record.objectType == DRW_MotionPath::kDwgClassNum ||
1128 record.objectType == DRW_MotionPath::kDwgType ||
1129 record.recordName == "MOTIONPATH" ||
1130 record.recordName == "ACDBMOTIONPATH" ||
1131 record.className == "AcDbMotionPath";
1132}
1133
1134bool isCurvePathRawObject(
1135 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1136 return record.objectType == DRW_CurvePath::kDwgClassNum ||
1137 record.objectType == DRW_CurvePath::kDwgType ||
1138 record.recordName == "CURVEPATH" ||
1139 record.recordName == "ACDBCURVEPATH" ||
1140 record.className == "AcDbCurvePath";
1141}
1142
1143bool isPointPathRawObject(
1144 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1145 return record.objectType == DRW_PointPath::kDwgClassNum ||
1146 record.objectType == DRW_PointPath::kDwgType ||
1147 record.recordName == "POINTPATH" ||
1148 record.recordName == "ACDBPOINTPATH" ||
1149 record.className == "AcDbPointPath";
1150}
1151
1152bool isObjectPtrRawObject(
1153 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1154 return record.objectType == DRW_ObjectPtr::kDwgClassNum ||
1155 record.objectType == DRW_ObjectPtr::kDwgType ||
1156 record.recordName == "OBJECT_PTR" ||
1157 record.recordName == "OBJECTPTR" ||
1158 record.recordName == "ACDBOBJECTPTR" ||
1159 record.className == "AcDbObjectPtr";
1160}
1161
1162bool isPartialViewingIndexRawObject(
1163 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1164 return record.objectType == DRW_PartialViewingIndex::kDwgClassNum ||
1165 record.objectType == DRW_PartialViewingIndex::kDwgType ||
1166 record.recordName == "PARTIAL_VIEWING_INDEX" ||
1167 record.recordName == "PARTIALVIEWINGINDEX" ||
1168 record.recordName == "ACDBPARTIALVIEWINGINDEX" ||
1169 record.className == "AcDbPartialViewingIndex";
1170}
1171
1172bool isRenderSettingsRawObject(
1173 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1174 return record.recordName == "RENDERENTRY" ||
1175 record.recordName == "RENDERENVIRONMENT" ||
1176 record.recordName == "RENDERGLOBAL" ||
1177 record.recordName == "ACDBRENDERENTRY" ||
1178 record.recordName == "ACDBRENDERENVIRONMENT" ||
1179 record.recordName == "ACDBRENDERGLOBAL" ||
1180 record.recordName == "RAPIDRTRENDERSETTINGS" ||
1181 record.recordName == "ACDBRAPIDRTRENDERSETTINGS" ||
1182 record.recordName == "MENTALRAYRENDERSETTINGS" ||
1183 record.recordName == "ACDBMENTALRAYRENDERSETTINGS" ||
1184 record.className == "AcDbRenderEntry" ||
1185 record.className == "AcDbRenderEnvironment" ||
1186 record.className == "AcDbRenderGlobal" ||
1187 record.className == "AcDbRapidRTRenderSettings" ||
1188 record.className == "AcDbMentalRayRenderSettings";
1189}
1190
1191bool isVisualStyleRawObject(
1192 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1193 return record.objectType == DRW_VisualStyle::kDwgClassNum ||
1194 record.recordName == "VISUALSTYLE" ||
1195 record.recordName == "ACDB_VISUALSTYLE_CLASS" ||
1196 record.className == "AcDbVisualStyle";
1197}
1198
1199bool isTvDevicePropertiesRawObject(
1200 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1201 return record.objectType == DRW_TvDeviceProperties::kDwgClassNum ||
1202 record.recordName == "TVDEVICEPROPERTIES" ||
1203 record.className == "AcDbTvDeviceProperties";
1204}
1205
1206bool isVxControlRawObject(
1207 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1208 return record.objectType == DRW_VxControl::kDwgClassNum ||
1209 record.recordName == "VXCONTROL" ||
1210 record.recordName == "VX_CONTROL" ||
1211 record.className == "AcDbVxControl";
1212}
1213
1214bool isVxTableRecordRawObject(
1215 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1216 return record.objectType == DRW_VxTableRecord::kDwgClassNum ||
1217 record.recordName == "VXTABLERECORD" ||
1218 record.recordName == "VX_TABLE_RECORD" ||
1219 record.className == "AcDbVxTableRecord";
1220}
1221
1222bool isViewportEntityHeaderRawObject(
1223 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1224 return !record.isEntity &&
1225 record.objectType == DRW_ViewportEntityHeader::kDwgType;
1226}
1227
1228bool isViewportEntityHeaderControlRawObject(
1229 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1230 // Fixed type 70 is regenerated by dwgWriter15 only when typed type-71
1231 // records are available for the target AC1015 file. Otherwise the raw
1232 // carrier is the only representation and must remain replayable.
1233 return !record.isEntity &&
1234 record.objectType == DRW_ViewportEntityHeader::kDwgControlType;
1235}
1236
1237bool hasReplayableRawVxControl(const LC_DwgAdvancedMetadata &metadata,
1238 std::uint32_t handle,
1239 DRW::Version targetVersion) {
1240 for (const auto &record : metadata.rawObjects()) {
1241 if (record.handle == handle && record.version == targetVersion &&
1242 record.replayState ==
1243 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
1244 isVxControlRawObject(record))
1245 return true;
1246 }
1247 return false;
1248}
1249
1250bool hasReplayableRawVxTableRecord(const LC_DwgAdvancedMetadata &metadata,
1251 std::uint32_t handle,
1252 DRW::Version targetVersion) {
1253 for (const auto &record : metadata.rawObjects()) {
1254 if (record.handle == handle && record.version == targetVersion &&
1255 record.replayState ==
1256 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
1257 isVxTableRecordRawObject(record))
1258 return true;
1259 }
1260 return false;
1261}
1262
1263bool isAcDbPlaceholderRawObject(
1264 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1265 return record.objectType == 80 || record.recordName == "ACDBPLACEHOLDER" ||
1266 record.className == "AcDbPlaceHolder";
1267}
1268
1269bool isMLeaderStyleRawObject(
1270 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1271 return record.recordName == "MLEADERSTYLE" ||
1272 record.className == "AcDbMLeaderStyle";
1273}
1274
1275bool isTableStyleRawObject(
1276 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1277 return record.objectType == DRW_TableStyle::kDwgClassNum ||
1278 record.recordName == "TABLESTYLE" ||
1279 record.className == "AcDbTableStyle";
1280}
1281
1282bool isOpaqueTableRawObject(
1283 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1284 return isTableStyleRawObject(record) || record.recordName == "CELLSTYLEMAP" ||
1285 record.className == "AcDbCellStyleMap" ||
1286 record.recordName == "ACAD_TABLE" ||
1287 record.recordName == "TABLECONTENT" ||
1288 record.className == "AcDbTable" ||
1289 record.className == "AcDbTableContent";
1290}
1291
1292// R2010 introduced the TABLESTYLE layout that the typed writer deliberately
1293// does not encode. Legacy table carriers retain their established raw fallback
1294// even when the partial parser cannot produce a semantic summary.
1295bool requiresDwgOpaqueTableAdmission(
1296 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1297 return !record.isEntity && record.version >= DRW::AC1024 &&
1298 isOpaqueTableRawObject(record);
1299}
1300
1301using DwgOpaqueTableDigest = std::array<std::uint8_t, 32>;
1302
1303class DwgOpaqueTableHasher final {
1304public:
1305 explicit DwgOpaqueTableHasher(const char *domain)
1306 : m_hash(QCryptographicHash::Sha256) {
1307 appendString(domain);
1308 }
1309
1310 void appendUnsigned(std::uint64_t value) {
1311 std::array<char, sizeof(value)> bytes{};
1312 for (std::size_t index = 0; index < bytes.size(); ++index)
1313 bytes[index] = static_cast<char>((value >> (index * 8u)) & 0xffu);
1314 appendBytes(reinterpret_cast<const std::uint8_t *>(bytes.data()),
1315 bytes.size());
1316 }
1317
1318 void appendSigned(std::int64_t value) {
1319 appendUnsigned(static_cast<std::uint64_t>(value));
1320 }
1321
1322 void appendBoolean(bool value) {
1323 const std::uint8_t byte = value ? 1u : 0u;
1324 appendBytes(&byte, 1);
1325 }
1326
1327 void appendDouble(double value) {
1328 static_assert(sizeof(value) == sizeof(std::uint64_t));
1329 std::uint64_t bits = 0;
1330 std::memcpy(&bits, &value, sizeof(bits));
1331 appendUnsigned(bits);
1332 }
1333
1334 void appendString(const std::string &value) {
1335 appendUnsigned(value.size());
1336 appendBytes(reinterpret_cast<const std::uint8_t *>(value.data()),
1337 value.size());
1338 }
1339
1340 void appendBytes(const std::vector<std::uint8_t> &value) {
1341 appendUnsigned(value.size());
1342 appendBytes(value.data(), value.size());
1343 }
1344
1345 DwgOpaqueTableDigest finish() {
1346 const QByteArray result = m_hash.result();
1347 DwgOpaqueTableDigest digest{};
1348 if (result.size() != static_cast<int>(digest.size()))
1349 return digest;
1350 std::copy_n(reinterpret_cast<const std::uint8_t *>(result.constData()),
1351 digest.size(), digest.begin());
1352 return digest;
1353 }
1354
1355private:
1356 void appendBytes(const std::uint8_t *data, std::size_t size) {
1357 while (size != 0) {
1358 const std::size_t chunk = std::min(
1359 size, static_cast<std::size_t>(std::numeric_limits<int>::max()));
1360#if QT_VERSION((6<<16)|(9<<8)|(0)) >= QT_VERSION_CHECK(6, 4, 0)((6<<16)|(4<<8)|(0))
1361 m_hash.addData(QByteArrayView(reinterpret_cast<const char *>(data),
1362 static_cast<qsizetype>(chunk)));
1363#else
1364 m_hash.addData(reinterpret_cast<const char *>(data),
1365 static_cast<int>(chunk));
1366#endif
1367 data += chunk;
1368 size -= chunk;
1369 }
1370 }
1371
1372 QCryptographicHash m_hash;
1373};
1374
1375void appendDwgOpaqueTableUnsignedList(
1376 DwgOpaqueTableHasher &hash, const std::vector<std::uint32_t> &values) {
1377 hash.appendUnsigned(values.size());
1378 for (const std::uint32_t value : values)
1379 hash.appendUnsigned(value);
1380}
1381
1382DwgOpaqueTableDigest dwgOpaqueTableDictionaryDigest(
1383 const LC_DwgAdvancedMetadata::DictionaryRecord &dictionary) {
1384 DwgOpaqueTableHasher hash("LibreCAD.DwgOpaqueTable.Dictionary.v1");
1385 hash.appendUnsigned(dictionary.handle);
1386 hash.appendUnsigned(dictionary.parentHandle);
1387 hash.appendSigned(dictionary.cloning);
1388 hash.appendSigned(dictionary.hardOwner);
1389 hash.appendString(dictionary.name);
1390 hash.appendUnsigned(dictionary.entries.size());
1391 for (const auto &entry : dictionary.entries) {
1392 hash.appendString(entry.name);
1393 hash.appendUnsigned(entry.handle);
1394 }
1395 hash.appendBoolean(dictionary.countCap.has_value());
1396 hash.appendUnsigned(dictionary.appData.size());
1397 hash.appendUnsigned(dictionary.extData.size());
1398 appendDwgOpaqueTableUnsignedList(hash, dictionary.reactorHandles);
1399 hash.appendUnsigned(dictionary.xDictHandle);
1400 hash.appendSigned(dictionary.numReactors);
1401 hash.appendUnsigned(dictionary.xDictFlag);
1402 hash.appendBoolean(dictionary.hasDsData);
1403 hash.appendUnsigned(static_cast<std::uint64_t>(dictionary.replayState));
1404 return hash.finish();
1405}
1406
1407bool isCanonicalDwgNamedDictionary(
1408 const LC_DwgAdvancedMetadata::DictionaryRecord &dictionary) {
1409 return dictionary.cloning == 1 && dictionary.hardOwner == 0 &&
1410 dictionary.name.empty() && !dictionary.countCap.has_value() &&
1411 dictionary.appData.empty() && dictionary.extData.empty() &&
1412 dictionary.reactorHandles.empty() && dictionary.xDictHandle == 0 &&
1413 dictionary.numReactors == 0 && dictionary.xDictFlag == 0 &&
1414 !dictionary.hasDsData &&
1415 dictionary.replayState ==
1416 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed;
1417}
1418
1419void appendDwgOpaqueTableSignedList(DwgOpaqueTableHasher &hash,
1420 const std::vector<int> &values) {
1421 hash.appendUnsigned(values.size());
1422 for (const int value : values)
1423 hash.appendSigned(value);
1424}
1425
1426void appendDwgOpaqueTableDoubleList(DwgOpaqueTableHasher &hash,
1427 const std::vector<double> &values) {
1428 hash.appendUnsigned(values.size());
1429 for (const double value : values)
1430 hash.appendDouble(value);
1431}
1432
1433void appendDwgOpaqueTableStringList(DwgOpaqueTableHasher &hash,
1434 const std::vector<std::string> &values) {
1435 hash.appendUnsigned(values.size());
1436 for (const std::string &value : values)
1437 hash.appendString(value);
1438}
1439
1440void appendDwgOpaqueTableCell(
1441 DwgOpaqueTableHasher &hash,
1442 const LC_DwgAdvancedMetadata::TableCellRecord &cell) {
1443 hash.appendSigned(cell.row);
1444 hash.appendSigned(cell.column);
1445 hash.appendSigned(cell.flags);
1446 hash.appendSigned(cell.type);
1447 hash.appendSigned(cell.styleId);
1448 hash.appendUnsigned(cell.overrideFlags);
1449 hash.appendUnsigned(cell.valueHandle);
1450 hash.appendUnsigned(cell.textStyleHandle);
1451 hash.appendUnsigned(cell.textStyleOverrideHandle);
1452 hash.appendUnsigned(cell.blockHandle);
1453 hash.appendUnsigned(cell.geometryHandle);
1454 hash.appendBoolean(cell.isMerged);
1455 hash.appendBoolean(cell.autoFit);
1456 hash.appendUnsigned(cell.contentCount);
1457 hash.appendUnsigned(cell.textContentCount);
1458 hash.appendUnsigned(cell.fieldContentCount);
1459 hash.appendUnsigned(cell.blockContentCount);
1460 hash.appendUnsigned(cell.attributeCount);
1461 hash.appendUnsigned(cell.attributeHandleCount);
1462 hash.appendUnsigned(cell.unknownContentCount);
1463 appendDwgOpaqueTableStringList(hash, cell.texts);
1464 appendDwgOpaqueTableStringList(hash, cell.attributeTexts);
1465 appendDwgOpaqueTableUnsignedList(hash, cell.contentHandles);
1466 appendDwgOpaqueTableUnsignedList(hash, cell.attributeHandles);
1467}
1468
1469DwgOpaqueTableDigest
1470dwgOpaqueTableRawDigest(const LC_DwgAdvancedMetadata::RawObjectRecord &raw) {
1471 DwgOpaqueTableHasher hash("LibreCAD.DwgOpaqueTable.Raw.v4");
1472 hash.appendUnsigned(static_cast<std::uint64_t>(raw.version));
1473 hash.appendSigned(raw.objectType);
1474 hash.appendUnsigned(raw.handle);
1475 hash.appendUnsigned(raw.parentHandle);
1476 hash.appendBoolean(drwDwgCommonLinkEvidenceValidated(raw.commonLinkEvidence));
1477 hash.appendUnsigned(static_cast<std::uint64_t>(raw.commonLinkEvidence));
1478 hash.appendUnsigned(raw.reactorHandles.size());
1479 for (const std::uint32_t handle : raw.reactorHandles)
1480 hash.appendUnsigned(handle);
1481 hash.appendUnsigned(raw.xDictHandle);
1482 hash.appendSigned(raw.numReactors);
1483 hash.appendUnsigned(raw.xDictFlag);
1484 hash.appendUnsigned(raw.blockOwnerHandle);
1485 hash.appendUnsigned(raw.bodyBitSize);
1486 hash.appendUnsigned(raw.objectOffset);
1487 hash.appendUnsigned(raw.objectSize);
1488 hash.appendBoolean(raw.isEntity);
1489 hash.appendBoolean(raw.isCustomClass);
1490 hash.appendUnsigned(static_cast<std::uint64_t>(raw.family));
1491 hash.appendString(raw.recordName);
1492 hash.appendString(raw.className);
1493 hash.appendBytes(raw.rawBytes);
1494 hash.appendBoolean(raw.typedPayloadValidated);
1495 hash.appendUnsigned(static_cast<std::uint64_t>(raw.replayState));
1496 hash.appendBoolean(raw.hasDataStorage);
1497 hash.appendBoolean(raw.hasClassDefinition);
1498 hash.appendUnsigned(raw.classProxyFlag);
1499 hash.appendString(raw.classAppName);
1500 hash.appendBoolean(raw.classWasProxy);
1501 hash.appendUnsigned(raw.classEntityFlagRaw);
1502 hash.appendSigned(raw.classDwgVersion);
1503 hash.appendSigned(raw.classMaintenanceVersion);
1504 hash.appendSigned(raw.classUnknown1);
1505 hash.appendSigned(raw.classUnknown2);
1506 return hash.finish();
1507}
1508
1509DwgOpaqueTableDigest
1510dwgOpaqueTableSummaryDigest(const LC_DwgAdvancedMetadata::TableRecord &table) {
1511 DwgOpaqueTableHasher hash("LibreCAD.DwgOpaqueTable.TableSummary.v1");
1512 hash.appendUnsigned(table.handle);
1513 hash.appendUnsigned(table.parentHandle);
1514 hash.appendString(table.recordName);
1515 hash.appendUnsigned(table.tableStyleHandle);
1516 hash.appendSigned(table.rowCount);
1517 hash.appendSigned(table.columnCount);
1518 hash.appendUnsigned(table.cellCount);
1519 hash.appendUnsigned(table.contentCount);
1520 hash.appendUnsigned(table.textContentCount);
1521 hash.appendUnsigned(table.fieldContentCount);
1522 hash.appendUnsigned(table.blockContentCount);
1523 hash.appendUnsigned(table.attributeCount);
1524 hash.appendUnsigned(table.unknownContentCount);
1525 hash.appendUnsigned(table.valueHandleCount);
1526 hash.appendUnsigned(table.blockHandleCount);
1527 hash.appendUnsigned(table.fieldHandleCount);
1528 hash.appendUnsigned(table.attributeHandleCount);
1529 hash.appendUnsigned(table.textStyleHandleCount);
1530 hash.appendUnsigned(table.lineTypeHandleCount);
1531 hash.appendUnsigned(table.mergedRangeCount);
1532 hash.appendUnsigned(table.overrideCellCount);
1533 hash.appendUnsigned(table.geometryCellCount);
1534 hash.appendUnsigned(table.rowStyleCount);
1535 hash.appendUnsigned(table.cellStyleCount);
1536 hash.appendUnsigned(table.borderCount);
1537 hash.appendUnsigned(table.m_tableContentFormatCount);
1538 hash.appendUnsigned(table.m_tableNamedCellStyleCount);
1539 hash.appendUnsigned(table.m_tableVisibleBorderCount);
1540 hash.appendUnsigned(table.m_tableMarginStyleCount);
1541 hash.appendUnsigned(table.m_unknownRangeCount);
1542 hash.appendUnsigned(table.m_incompleteRangeCount);
1543 hash.appendUnsigned(table.m_overrideMaskCount);
1544 hash.appendUnsigned(table.m_breakRangeCount);
1545 hash.appendUnsigned(table.m_tableGeometryTailRangeCount);
1546 hash.appendUnsigned(table.m_fallbackGridEntityCount);
1547 hash.appendUnsigned(table.m_fallbackTextEntityCount);
1548 hash.appendUnsigned(table.m_fallbackPlaceholderEntityCount);
1549 hash.appendUnsigned(table.m_fallbackUnresolvedTextStyleCount);
1550 hash.appendUnsigned(table.m_fallbackClampedDimensionCount);
1551 hash.appendSigned(table.m_tableFlowDirection);
1552 hash.appendSigned(table.m_tableFlags);
1553 hash.appendDouble(table.m_tableHorizontalCellMargin);
1554 hash.appendDouble(table.m_tableVerticalCellMargin);
1555 hash.appendUnsigned(table.m_tableUnknownHandle);
1556 hash.appendBoolean(table.titleSuppressed);
1557 hash.appendBoolean(table.headerSuppressed);
1558 hash.appendBoolean(table.hasTextContent);
1559 hash.appendBoolean(table.hasBlockContent);
1560 hash.appendBoolean(table.rawReplayAvailable);
1561 hash.appendBoolean(table.semanticParsed);
1562 hash.appendBoolean(table.styleResolved);
1563 hash.appendBoolean(table.fallbackRendered);
1564 hash.appendBoolean(table.fallbackInvalidated);
1565 appendDwgOpaqueTableDoubleList(hash, table.columnWidths);
1566 appendDwgOpaqueTableDoubleList(hash, table.rowHeights);
1567 appendDwgOpaqueTableSignedList(hash, table.cellStyleIds);
1568 appendDwgOpaqueTableStringList(hash, table.cellTexts);
1569 appendDwgOpaqueTableStringList(hash, table.attributeTexts);
1570 appendDwgOpaqueTableUnsignedList(hash, table.valueHandles);
1571 appendDwgOpaqueTableUnsignedList(hash, table.blockHandles);
1572 appendDwgOpaqueTableUnsignedList(hash, table.fieldHandles);
1573 appendDwgOpaqueTableUnsignedList(hash, table.attributeHandles);
1574 appendDwgOpaqueTableUnsignedList(hash, table.textStyleHandles);
1575 appendDwgOpaqueTableUnsignedList(hash, table.lineTypeHandles);
1576 appendDwgOpaqueTableUnsignedList(hash, table.geometryHandles);
1577 appendDwgOpaqueTableSignedList(hash, table.m_tableStyleIds);
1578 appendDwgOpaqueTableStringList(hash, table.m_tableStyleNames);
1579 appendDwgOpaqueTableDoubleList(hash, table.m_tableTextHeights);
1580 appendDwgOpaqueTableSignedList(hash, table.m_tableAlignments);
1581 appendDwgOpaqueTableSignedList(hash, table.m_tableColors);
1582 hash.appendBoolean(table.nativeTableStyle.has_value());
1583 hash.appendUnsigned(table.mergedRanges.size());
1584 for (const auto &range : table.mergedRanges) {
1585 hash.appendSigned(range.topRow);
1586 hash.appendSigned(range.leftColumn);
1587 hash.appendSigned(range.bottomRow);
1588 hash.appendSigned(range.rightColumn);
1589 }
1590 hash.appendUnsigned(table.cells.size());
1591 for (const auto &cell : table.cells)
1592 appendDwgOpaqueTableCell(hash, cell);
1593 hash.appendBoolean(table.hasDsData);
1594 hash.appendUnsigned(static_cast<std::uint64_t>(table.replayState));
1595 return hash.finish();
1596}
1597
1598std::size_t
1599dwgOpaqueTableRelationCount(const LC_DwgAdvancedMetadata::TableRecord &table) {
1600 return 2u + table.valueHandles.size() + table.blockHandles.size() +
1601 table.fieldHandles.size() + table.attributeHandles.size() +
1602 table.geometryHandles.size() + table.textStyleHandles.size() +
1603 table.lineTypeHandles.size();
1604}
1605
1606DwgOpaqueTableDigest dwgOpaqueTableSummaryDigest(
1607 const LC_DwgAdvancedMetadata::CellStyleMapRecord &map) {
1608 DwgOpaqueTableHasher hash("LibreCAD.DwgOpaqueTable.CellStyleMapSummary.v1");
1609 hash.appendUnsigned(map.handle);
1610 hash.appendUnsigned(map.parentHandle);
1611 hash.appendUnsigned(map.m_cellStyleCount);
1612 hash.appendUnsigned(map.m_borderCount);
1613 hash.appendUnsigned(map.m_contentFormatCount);
1614 hash.appendUnsigned(map.m_namedCellStyleCount);
1615 hash.appendUnsigned(map.m_visibleBorderCount);
1616 hash.appendUnsigned(map.m_marginStyleCount);
1617 hash.appendUnsigned(map.m_unknownRangeCount);
1618 hash.appendUnsigned(map.m_incompleteRangeCount);
1619 appendDwgOpaqueTableSignedList(hash, map.m_styleIds);
1620 appendDwgOpaqueTableSignedList(hash, map.m_styleClasses);
1621 appendDwgOpaqueTableStringList(hash, map.m_styleNames);
1622 appendDwgOpaqueTableUnsignedList(hash, map.m_textStyleHandles);
1623 appendDwgOpaqueTableUnsignedList(hash, map.m_lineTypeHandles);
1624 appendDwgOpaqueTableDoubleList(hash, map.m_textHeights);
1625 appendDwgOpaqueTableSignedList(hash, map.m_alignments);
1626 appendDwgOpaqueTableSignedList(hash, map.m_colors);
1627 hash.appendUnsigned(static_cast<std::uint64_t>(map.replayState));
1628 return hash.finish();
1629}
1630
1631std::size_t dwgOpaqueTableRelationCount(
1632 const LC_DwgAdvancedMetadata::CellStyleMapRecord &map) {
1633 return map.m_textStyleHandles.size() + map.m_lineTypeHandles.size();
1634}
1635
1636bool isMaterialRawObject(
1637 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1638 return record.recordName == "MATERIAL" || record.className == "AcDbMaterial";
1639}
1640
1641bool isLightListRawObject(
1642 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1643 return record.recordName == "LIGHTLIST" ||
1644 record.recordName == "ACDBLIGHTLIST" ||
1645 record.className == "AcDbLightList";
1646}
1647
1648bool isVbaProjectRawObject(
1649 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1650 return record.recordName == "VBA_PROJECT" ||
1651 record.className == "AcDbVbaProject";
1652}
1653
1654bool isBackgroundRawObject(
1655 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1656 return record.objectType == DRW_Background::kDwgClassNumSolid ||
1657 record.objectType == DRW_Background::kDwgClassNumGradient ||
1658 record.objectType == DRW_Background::kDwgClassNumGroundPlane ||
1659 record.objectType == DRW_Background::kDwgClassNumIbl ||
1660 record.objectType == DRW_Background::kDwgClassNumImage ||
1661 record.objectType == DRW_Background::kDwgClassNumSkylight ||
1662 record.recordName == "SOLIDBACKGROUND" ||
1663 record.recordName == "SOLID_BACKGROUND" ||
1664 record.recordName == "GRADIENTBACKGROUND" ||
1665 record.recordName == "GRADIENT_BACKGROUND" ||
1666 record.recordName == "GROUNDPLANEBACKGROUND" ||
1667 record.recordName == "GROUNDPLANE_BACKGROUND" ||
1668 record.recordName == "IMAGEBACKGROUND" ||
1669 record.recordName == "IMAGE_BACKGROUND" ||
1670 record.recordName == "IBLBACKGROUND" ||
1671 record.recordName == "IBL_BACKGROUND" ||
1672 record.recordName == "RAPIDRTRENDERENVIRONMENT" ||
1673 record.recordName == "SKYLIGHTBACKGROUND" ||
1674 record.recordName == "SKYLIGHT_BACKGROUND" ||
1675 record.className == "AcDbSolidBackground" ||
1676 record.className == "AcDbGradientBackground" ||
1677 record.className == "AcDbGroundPlaneBackground" ||
1678 record.className == "AcDbImageBackground" ||
1679 record.className == "AcDbIBLBackground" ||
1680 record.className == "AcDbSkyBackground";
1681}
1682
1683bool isMLineStyleRawObject(
1684 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1685 return record.objectType == 73 || record.recordName == "MLINESTYLE" ||
1686 record.className == "AcDbMlineStyle";
1687}
1688
1689bool isDbColorRawObject(const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1690 return record.objectType == DRW_DbColor::kDwgType ||
1691 record.recordName == "DBCOLOR" || record.className == "AcDbColor";
1692}
1693
1694bool isPlotSettingsRawObject(
1695 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1696 return record.recordName == "PLOTSETTINGS" ||
1697 record.className == "AcDbPlotSettings";
1698}
1699
1700bool isDimensionAssociationRawObject(
1701 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1702 return record.objectType == DRW_DimensionAssociation::kDwgClassNum ||
1703 record.recordName == "DIMASSOC" || record.className == "AcDbDimAssoc";
1704}
1705
1706bool isEvaluationGraphRawObject(
1707 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1708 return record.objectType == DRW_EvaluationGraph::kDwgClassNum ||
1709 record.recordName == "EVALUATION_GRAPH" ||
1710 record.recordName == "ACAD_EVALUATION_GRAPH" ||
1711 record.className == "AcDbEvalGraph";
1712}
1713
1714bool isBlockRepresentationDataRawObject(
1715 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1716 return record.objectType == DRW_BlockRepresentationData::kDwgClassNum ||
1717 record.recordName == "BLOCKREPRESENTATIONDATA" ||
1718 record.recordName == "ACDB_BLOCKREPRESENTATION_DATA" ||
1719 record.className == "AcDbBlockRepresentationData";
1720}
1721
1722bool hasReplayableRawBlockRepresentationData(
1723 const LC_DwgAdvancedMetadata &metadata, std::uint32_t handle,
1724 DRW::Version targetVersion) {
1725 if (handle == 0)
1726 return false;
1727 return std::any_of(
1728 metadata.rawObjects().cbegin(), metadata.rawObjects().cend(),
1729 [handle, targetVersion](const auto &record) {
1730 return record.handle == handle && record.version == targetVersion &&
1731 isBlockRepresentationDataRawObject(record) &&
1732 !record.rawBytes.empty() &&
1733 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
1734 LC_DwgAdvancedMetadata::ReplayBlocker::None;
1735 });
1736}
1737
1738// DICTIONARY raw-object predicate. Fixed type 42 is the universal ODA
1739// identifier; recordName / className fallbacks cover custom-class-emitted
1740// dictionaries (rare but spec-allowed).
1741bool isDictionaryRawObject(
1742 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1743 return record.objectType == 42 || record.recordName == "DICTIONARY" ||
1744 record.className == "AcDbDictionary";
1745}
1746
1747// XRECORD raw-object predicate. Fixed type 79 (0x4f) is the universal ODA
1748// identifier; the custom-class branch in dwgreader still emits raw bytes
1749// under the AcDbXrecord recName/className for non-fixed XRECORDs.
1750bool isXRecordRawObject(const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1751 return record.objectType == 79 || record.recordName == "XRECORD" ||
1752 record.className == "AcDbXrecord";
1753}
1754
1755// LAYOUT raw-object predicate. Fixed type 82 + recordName/className
1756// fallback for vendor extensions.
1757bool isLayoutRawObject(const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1758 return record.objectType == 82 || record.recordName == "LAYOUT" ||
1759 record.className == "AcDbLayout";
1760}
1761
1762// GROUP raw-object predicate. Fixed type 72 (ODA §20.4.72) + recordName/
1763// className fallback for vendor extensions.
1764bool isGroupRawObject(const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1765 return record.objectType == 72 || record.recordName == "GROUP" ||
1766 record.className == "AcDbGroup";
1767}
1768
1769// RASTERVARIABLES raw-object predicate. Custom-class object (no fixed
1770// ODA type) — keyed on recordName/className per dwgreader's dispatch.
1771bool isRasterVariablesRawObject(
1772 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1773 return record.recordName == "RASTERVARIABLES" ||
1774 record.className == "AcDbRasterVariables";
1775}
1776
1777bool isWipeoutVariablesRawObject(
1778 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1779 return record.recordName == "WIPEOUTVARIABLES" ||
1780 record.className == "AcDbWipeoutVariables";
1781}
1782
1783// GEODATA raw-object predicate. Custom-class (no fixed ODA type).
1784bool isGeoDataRawObject(const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1785 return record.recordName == "GEODATA" || record.className == "AcDbGeoData";
1786}
1787
1788// SPATIAL_FILTER raw-object predicate. Custom-class (no fixed ODA type).
1789bool isSpatialFilterRawObject(
1790 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1791 return record.recordName == "SPATIAL_FILTER" ||
1792 record.className == "AcDbSpatialFilter";
1793}
1794
1795// PR 8d.2a — five small no-storage OBJECTS families. All custom-class
1796// (no fixed ODA type); recordName / className strings must match the
1797// dwgreader.cpp dispatch (case-sensitive).
1798bool isScaleRawObject(const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1799 return record.recordName == "SCALE" || record.className == "AcDbScale";
1800}
1801
1802bool isIDBufferRawObject(
1803 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1804 return record.recordName == "IDBUFFER" || record.className == "AcDbIdBuffer";
1805}
1806
1807bool isLayerIndexRawObject(
1808 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1809 return record.recordName == "LAYER_INDEX" ||
1810 record.className == "AcDbLayerIndex";
1811}
1812
1813bool isSpatialIndexRawObject(
1814 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1815 return record.recordName == "SPATIAL_INDEX" ||
1816 record.className == "AcDbSpatialIndex";
1817}
1818
1819bool isDictionaryVarRawObject(
1820 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1821 return record.recordName == "DICTIONARYVAR" ||
1822 record.className == "AcDbDictionaryVar";
1823}
1824
1825// PR 8d.2b — four larger no-storage OBJECTS families. recordName /
1826// className strings must match dwgreader.cpp dispatch (case-sensitive).
1827bool isDictionaryWithDefaultRawObject(
1828 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1829 return record.recordName == "ACDBDICTIONARYWDFLT" ||
1830 record.recordName == "DICTIONARYWDFLT" ||
1831 record.className == "AcDbDictionaryWithDefault";
1832}
1833
1834bool isSortEntsTableRawObject(
1835 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1836 return record.recordName == "SORTENTSTABLE" ||
1837 record.className == "AcDbSortentsTable";
1838}
1839
1840bool isFixedStructuralDwgHandle(std::uint32_t handle);
1841
1842bool hasReplayableRawSortEntsTable(
1843 const LC_DwgAdvancedMetadata &metadata, std::uint32_t handle,
1844 DRW::Version targetVersion) {
1845 return std::any_of(
1846 metadata.rawObjects().cbegin(), metadata.rawObjects().cend(),
1847 [handle, targetVersion](const auto &record) {
1848 return record.handle == handle && !record.isEntity &&
1849 record.isCustomClass && isSortEntsTableRawObject(record) &&
1850 record.version == targetVersion &&
1851 record.replayState ==
1852 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
1853 !record.rawBytes.empty() &&
1854 !isFixedStructuralDwgHandle(record.handle) &&
1855 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
1856 LC_DwgAdvancedMetadata::ReplayBlocker::None;
1857 });
1858}
1859
1860bool isFieldListRawObject(
1861 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1862 return record.recordName == "FIELDLIST" &&
1863 record.className == "AcDbFieldList";
1864}
1865
1866bool isFieldRawObject(const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1867 return record.recordName == "FIELD" && record.className == "AcDbField";
1868}
1869
1870bool isUnderlayDefinitionRawObject(
1871 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1872 return record.recordName == "PDFDEFINITION" ||
1873 record.recordName == "DGNDEFINITION" ||
1874 record.recordName == "DWFDEFINITION" ||
1875 record.className == "AcDbPdfDefinition" ||
1876 record.className == "AcDbDgnDefinition" ||
1877 record.className == "AcDbDwfDefinition";
1878}
1879
1880bool isPointCloudDefinitionRawObject(
1881 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1882 return record.recordName == "POINTCLOUDDEFINITION" ||
1883 record.recordName == "POINTCLOUDDEFINITIONEX" ||
1884 record.recordName == "POINTCLOUDDEFREACTOR" ||
1885 record.recordName == "POINTCLOUDDEFREACTOREX" ||
1886 record.className == "AcDbPointCloudDef" ||
1887 record.className == "AcDbPointCloudDefEx" ||
1888 record.className == "AcDbPointCloudDefReactor" ||
1889 record.className == "AcDbPointCloudDefReactorEx";
1890}
1891
1892bool isNavisworksModelDefinitionRawObject(
1893 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1894 return record.recordName == "NAVISWORKSMODELDEF" ||
1895 record.className == "AcDbNavisworksModelDef";
1896}
1897
1898bool isPointCloudColorMapRawObject(
1899 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1900 return record.recordName == "POINTCLOUDCOLORMAP" ||
1901 record.className == "AcDbPointCloudColorMap";
1902}
1903
1904bool isSectionRawObject(const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1905 return record.objectType == DRW_Section::kDwgClassNumManager ||
1906 record.objectType == DRW_Section::kDwgClassNumSettings ||
1907 record.recordName == "SECTION_MANAGER" ||
1908 record.recordName == "SECTIONMANAGER" ||
1909 record.recordName == "SECTION_SETTINGS" ||
1910 record.recordName == "SECTIONSETTINGS" ||
1911 record.className == "AcDbSectionManager" ||
1912 record.className == "AcDbSectionSettings";
1913}
1914
1915bool isSectionObjectRawEntity(
1916 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1917 return record.isEntity && (record.recordName == "SECTIONOBJECT" ||
1918 record.recordName == "SECTION_OBJECT" ||
1919 record.className == "AcDbSection");
1920}
1921
1922bool hasReplayableSectionObject(const LC_DwgAdvancedMetadata &metadata,
1923 std::uint32_t handle) {
1924 if (handle == 0)
1925 return false;
1926 return std::any_of(
1927 metadata.sectionObjects().cbegin(), metadata.sectionObjects().cend(),
1928 [handle](const auto &record) {
1929 return record.handle == handle &&
1930 record.replayState ==
1931 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed;
1932 });
1933}
1934
1935const char *pointCloudDefinitionRecordName(int kind) {
1936 switch (static_cast<DRW_PointCloudDef::Kind>(kind)) {
1937 case DRW_PointCloudDef::Definition:
1938 return "POINTCLOUDDEFINITION";
1939 case DRW_PointCloudDef::DefinitionEx:
1940 return "POINTCLOUDDEFINITIONEX";
1941 case DRW_PointCloudDef::Reactor:
1942 return "POINTCLOUDDEFREACTOR";
1943 case DRW_PointCloudDef::ReactorEx:
1944 return "POINTCLOUDDEFREACTOREX";
1945 }
1946 return nullptr;
1947}
1948
1949bool hasReplayableRawMLeaderStyle(const LC_DwgAdvancedMetadata &metadata,
1950 std::uint32_t handle,
1951 DRW::Version targetVersion) {
1952 if (handle == 0)
1953 return false;
1954 for (const auto &record : metadata.rawObjects()) {
1955 if (record.handle == handle && record.version == targetVersion &&
1956 isMLeaderStyleRawObject(record) &&
1957 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
1958 LC_DwgAdvancedMetadata::ReplayBlocker::None) {
1959 return true;
1960 }
1961 }
1962 return false;
1963}
1964
1965bool hasReplayableRawSunStudy(const LC_DwgAdvancedMetadata &metadata,
1966 std::uint32_t handle,
1967 DRW::Version targetVersion) {
1968 if (handle == 0)
1969 return false;
1970 for (const auto &record : metadata.rawObjects()) {
1971 if (record.handle == handle && record.version == targetVersion &&
1972 isSunStudyRawObject(record) &&
1973 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
1974 LC_DwgAdvancedMetadata::ReplayBlocker::None) {
1975 return true;
1976 }
1977 }
1978 return false;
1979}
1980
1981bool hasReplayableRawMotionPath(const LC_DwgAdvancedMetadata &metadata,
1982 std::uint32_t handle,
1983 DRW::Version targetVersion) {
1984 if (handle == 0)
1985 return false;
1986 for (const auto &record : metadata.rawObjects()) {
1987 if (record.handle == handle && record.version == targetVersion &&
1988 isMotionPathRawObject(record) &&
1989 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
1990 LC_DwgAdvancedMetadata::ReplayBlocker::None)
1991 return true;
1992 }
1993 return false;
1994}
1995
1996bool hasReplayableRawCurvePath(const LC_DwgAdvancedMetadata &metadata,
1997 std::uint32_t handle,
1998 DRW::Version targetVersion) {
1999 if (handle == 0)
2000 return false;
2001 for (const auto &record : metadata.rawObjects()) {
2002 if (record.handle == handle && record.version == targetVersion &&
2003 isCurvePathRawObject(record) &&
2004 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
2005 LC_DwgAdvancedMetadata::ReplayBlocker::None)
2006 return true;
2007 }
2008 return false;
2009}
2010
2011bool hasReplayableRawPointPath(const LC_DwgAdvancedMetadata &metadata,
2012 std::uint32_t handle,
2013 DRW::Version targetVersion) {
2014 if (handle == 0)
2015 return false;
2016 for (const auto &record : metadata.rawObjects()) {
2017 if (record.handle == handle && record.version == targetVersion &&
2018 isPointPathRawObject(record) &&
2019 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
2020 LC_DwgAdvancedMetadata::ReplayBlocker::None)
2021 return true;
2022 }
2023 return false;
2024}
2025
2026bool hasReplayableRawObjectPtr(const LC_DwgAdvancedMetadata &metadata,
2027 std::uint32_t handle,
2028 DRW::Version targetVersion) {
2029 if (handle == 0)
2030 return false;
2031 for (const auto &record : metadata.rawObjects()) {
2032 if (record.handle == handle && record.version == targetVersion &&
2033 isObjectPtrRawObject(record) &&
2034 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
2035 LC_DwgAdvancedMetadata::ReplayBlocker::None)
2036 return true;
2037 }
2038 return false;
2039}
2040
2041bool hasReplayableRawPartialViewingIndex(const LC_DwgAdvancedMetadata &metadata,
2042 std::uint32_t handle,
2043 DRW::Version targetVersion) {
2044 if (handle == 0)
2045 return false;
2046 for (const auto &record : metadata.rawObjects()) {
2047 if (record.handle == handle && record.version == targetVersion &&
2048 isPartialViewingIndexRawObject(record) &&
2049 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
2050 LC_DwgAdvancedMetadata::ReplayBlocker::None)
2051 return true;
2052 }
2053 return false;
2054}
2055
2056bool hasReplayableRawRenderSettings(const LC_DwgAdvancedMetadata &metadata,
2057 std::uint32_t handle,
2058 DRW_RenderSettings::Kind kind,
2059 DRW::Version targetVersion) {
2060 if (handle == 0)
2061 return false;
2062 const auto matchesKind =
2063 [kind](const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2064 switch (kind) {
2065 case DRW_RenderSettings::Settings:
2066 return record.recordName == "RENDERSETTINGS" ||
2067 record.recordName == "ACDBRENDERSETTINGS" ||
2068 record.className == "AcDbRenderSettings";
2069 case DRW_RenderSettings::Entry:
2070 return record.recordName == "RENDERENTRY" ||
2071 record.recordName == "ACDBRENDERENTRY" ||
2072 record.className == "AcDbRenderEntry";
2073 case DRW_RenderSettings::Environment:
2074 return record.recordName == "RENDERENVIRONMENT" ||
2075 record.recordName == "ACDBRENDERENVIRONMENT" ||
2076 record.className == "AcDbRenderEnvironment";
2077 case DRW_RenderSettings::Global:
2078 return record.recordName == "RENDERGLOBAL" ||
2079 record.recordName == "ACDBRENDERGLOBAL" ||
2080 record.className == "AcDbRenderGlobal";
2081 case DRW_RenderSettings::RapidRT:
2082 return record.recordName == "RAPIDRTRENDERSETTINGS" ||
2083 record.recordName == "ACDBRAPIDRTRENDERSETTINGS" ||
2084 record.className == "AcDbRapidRTRenderSettings";
2085 case DRW_RenderSettings::MentalRay:
2086 return record.recordName == "MENTALRAYRENDERSETTINGS" ||
2087 record.recordName == "ACDBMENTALRAYRENDERSETTINGS" ||
2088 record.className == "AcDbMentalRayRenderSettings";
2089 default:
2090 return false;
2091 }
2092 };
2093 for (const auto &record : metadata.rawObjects()) {
2094 if (record.handle == handle && record.version == targetVersion &&
2095 matchesKind(record) &&
2096 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
2097 LC_DwgAdvancedMetadata::ReplayBlocker::None)
2098 return true;
2099 }
2100 return false;
2101}
2102
2103bool hasReplayableRawVisualStyle(const LC_DwgAdvancedMetadata &metadata,
2104 std::uint32_t handle,
2105 DRW::Version targetVersion) {
2106 if (handle == 0)
2107 return false;
2108 for (const auto &record : metadata.rawObjects()) {
2109 if (record.handle == handle && record.version == targetVersion &&
2110 isVisualStyleRawObject(record) &&
2111 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
2112 LC_DwgAdvancedMetadata::ReplayBlocker::None)
2113 return true;
2114 }
2115 return false;
2116}
2117
2118bool hasReplayableRawMaterial(const LC_DwgAdvancedMetadata &metadata,
2119 std::uint32_t handle,
2120 DRW::Version targetVersion) {
2121 if (handle == 0)
2122 return false;
2123 for (const auto &record : metadata.rawObjects()) {
2124 if (record.handle == handle && record.version == targetVersion &&
2125 isMaterialRawObject(record) &&
2126 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
2127 LC_DwgAdvancedMetadata::ReplayBlocker::None) {
2128 return true;
2129 }
2130 }
2131 return false;
2132}
2133
2134bool hasReplayableRawSpatialIndex(const LC_DwgAdvancedMetadata &metadata,
2135 std::uint32_t handle,
2136 DRW::Version targetVersion) {
2137 if (handle == 0)
2138 return false;
2139 for (const auto &record : metadata.rawObjects()) {
2140 if (record.handle == handle && record.version == targetVersion &&
2141 isSpatialIndexRawObject(record) &&
2142 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
2143 LC_DwgAdvancedMetadata::ReplayBlocker::None) {
2144 return true;
2145 }
2146 }
2147 return false;
2148}
2149
2150template <typename StableReference>
2151bool hasReplayableRawLightList(const LC_DwgAdvancedMetadata &metadata,
2152 std::uint32_t handle, DRW::Version targetVersion,
2153 StableReference stableReference) {
2154 if (handle == 0)
2155 return false;
2156 const auto lightListCount = std::count_if(
2157 metadata.lightLists().cbegin(), metadata.lightLists().cend(),
2158 [handle](const auto &record) { return record.handle == handle; });
2159 if (lightListCount != 1)
2160 return false;
2161 const auto lightList = std::find_if(
2162 metadata.lightLists().cbegin(), metadata.lightLists().cend(),
2163 [handle](const auto &record) { return record.handle == handle; });
2164 if (lightList == metadata.lightLists().cend())
2165 return false;
2166 const auto rawCarrierCount = std::count_if(
2167 metadata.rawObjects().cbegin(), metadata.rawObjects().cend(),
2168 [handle, targetVersion](const auto &record) {
2169 return !record.isEntity && record.handle == handle &&
2170 record.version == targetVersion &&
2171 isLightListRawObject(record) &&
2172 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
2173 LC_DwgAdvancedMetadata::ReplayBlocker::None;
2174 });
2175 if (rawCarrierCount != 1 || !stableReference(lightList->parentHandle) ||
2176 !stableReference(lightList->xDictHandle) ||
2177 !std::all_of(lightList->reactorHandles.cbegin(),
2178 lightList->reactorHandles.cend(), stableReference)) {
2179 return false;
2180 }
2181
2182 for (const auto &light : lightList->lights) {
2183 if (light.handle == 0 || !stableReference(light.handle))
2184 return false;
2185 const auto lightRecord =
2186 std::find_if(metadata.lights().cbegin(), metadata.lights().cend(),
2187 [&light](const auto &candidate) {
2188 return candidate.handle == light.handle;
2189 });
2190 if (lightRecord == metadata.lights().cend() ||
2191 lightRecord->replayState !=
2192 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed)
2193 return false;
2194 }
2195 return true;
2196}
2197
2198bool hasReplayableRawVbaProject(const LC_DwgAdvancedMetadata &metadata,
2199 std::uint32_t handle,
2200 DRW::Version targetVersion) {
2201 if (handle == 0)
2202 return false;
2203 for (const auto &record : metadata.rawObjects()) {
2204 if (record.handle == handle && record.version == targetVersion &&
2205 isVbaProjectRawObject(record) &&
2206 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
2207 LC_DwgAdvancedMetadata::ReplayBlocker::None) {
2208 return true;
2209 }
2210 }
2211 return false;
2212}
2213
2214bool hasReplayableRawBackground(const LC_DwgAdvancedMetadata &metadata,
2215 std::uint32_t handle,
2216 DRW::Version targetVersion) {
2217 if (handle == 0)
2218 return false;
2219 for (const auto &record : metadata.rawObjects()) {
2220 if (record.handle == handle && record.version == targetVersion &&
2221 isBackgroundRawObject(record) &&
2222 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
2223 LC_DwgAdvancedMetadata::ReplayBlocker::None) {
2224 return true;
2225 }
2226 }
2227 return false;
2228}
2229
2230// Handles the DWG writer emits at FIXED values (HandleAllocator::seedReserved):
2231// control objects 0x01-0x0B, the canonical root dictionaries 0x0C-0x0D,
2232// canonical table records 0x0F-0x18 and the model/paper space BLOCK/ENDBLK
2233// entities 0x1B-0x1E. Real source files
2234// (notably AC1021+) reuse these very numbers for ordinary preserved OBJECTS
2235// (placeholders, dictionaries, ...); emitting both sides duplicates the
2236// object-map entry and writeDwgHandles() aborts the whole save (BAD_OPEN,
2237// zero-byte file). Typed preserved objects are therefore remapped to fresh
2238// handles above the preserved high-water mark (fileExport builds
2239// m_dwgWriteHandleRemap, writeObjects applies it); raw verbatim objects
2240// cannot be rewritten and their replay is blocked instead. Mirrors the DXF
2241// path's kFixedStructural remap in fileExport.
2242bool isFixedStructuralDwgHandle(std::uint32_t h) {
2243 static const std::set<std::uint32_t> kFixed = {
2244 DRW::DwgBlockControlHandle,
2245 DRW::DwgLayerControlHandle,
2246 DRW::DwgStyleControlHandle,
2247 DRW::DwgLtypeControlHandle,
2248 DRW::DwgViewControlHandle,
2249 DRW::DwgUcsControlHandle,
2250 DRW::DwgVportControlHandle,
2251 DRW::DwgAppIdControlHandle,
2252 DRW::DwgDimstyleControlHandle,
2253 DRW::DwgViewportEntityHeaderControlHandle,
2254 DRW::DwgNamedObjectsDictionaryHandle,
2255 DRW::DwgAcadGroupDictionaryHandle,
2256 DRW::DwgLtypeByBlockHandle,
2257 DRW::DwgLtypeByLayerHandle,
2258 DRW::DwgLtypeContinuousHandle,
2259 DRW::DwgLayer0Handle,
2260 DRW::DwgStandardTextStyleHandle,
2261 DRW::DwgAcadAppIdHandle,
2262 DRW::DwgStandardDimstyleHandle,
2263 DRW::DwgActiveVportHandle,
2264 DRW::DwgModelSpaceBlockRecordHandle,
2265 DRW::DwgPaperSpaceBlockRecordHandle,
2266 DRW::DwgModelSpaceBlockEntityHandle,
2267 DRW::DwgModelSpaceEndBlockEntityHandle,
2268 DRW::DwgPaperSpaceBlockEntityHandle,
2269 DRW::DwgPaperSpaceEndBlockEntityHandle};
2270 return kFixed.count(h) != 0;
2271}
2272
2273bool isRawViewportEntityHeaderControl(
2274 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2275 return !record.isEntity &&
2276 record.objectType == DRW_ViewportEntityHeader::kDwgControlType &&
2277 record.handle == DRW_ViewportEntityHeader::kDwgControlHandle;
2278}
2279
2280DRW_AcDbPlaceholder placeholderFromMetadata(
2281 const LC_DwgAdvancedMetadata::PlaceholderRecord &record) {
2282 DRW_AcDbPlaceholder placeholder;
2283 placeholder.handle = record.handle;
2284 placeholder.parentHandle = record.parentHandle;
2285 placeholder.appData = record.appData;
2286 restoreTableEntryExtData(placeholder, record.extData);
2287 placeholder.reactorHandles = record.reactorHandles;
2288 placeholder.xDictHandle = record.xDictHandle;
2289 placeholder.setDwgCommonObjectState(
2290 static_cast<std::int32_t>(record.reactorHandles.size()), record.xDictFlag,
2291 record.hasDsData);
2292 return placeholder;
2293}
2294
2295DRW_Dictionary
2296dictionaryFromMetadata(const LC_DwgAdvancedMetadata::DictionaryRecord &record) {
2297 DRW_Dictionary dictionary;
2298 dictionary.handle = record.handle;
2299 dictionary.parentHandle = record.parentHandle;
2300 dictionary.cloning = record.cloning;
2301 dictionary.hardOwner = record.hardOwner;
2302 dictionary.name = record.name;
2303 dictionary.appData = record.appData;
2304 restoreTableEntryExtData(dictionary, record.extData);
2305 dictionary.reactorHandles = record.reactorHandles;
2306 dictionary.xDictHandle = record.xDictHandle;
2307 dictionary.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2308 record.hasDsData);
2309 dictionary.m_entries.reserve(record.entries.size());
2310 for (const auto &er : record.entries) {
2311 DRW_Dictionary::Entry entry;
2312 entry.m_name = er.name;
2313 entry.m_handle = er.handle;
2314 dictionary.m_entries.push_back(std::move(entry));
2315 }
2316 return dictionary;
2317}
2318
2319DRW_XRecord
2320xrecordFromMetadata(const LC_DwgAdvancedMetadata::XRecordRecord &record) {
2321 DRW_XRecord xrecord;
2322 xrecord.handle = record.handle;
2323 xrecord.parentHandle = record.parentHandle;
2324 xrecord.m_cloning = record.cloning;
2325 xrecord.m_values = record.values;
2326 xrecord.m_handleValues = record.handleValues;
2327 xrecord.m_dataEntries = record.dataEntries;
2328 xrecord.m_rawData = record.rawData;
2329 xrecord.m_rawDataValid = record.rawDataValid;
2330 xrecord.appData = record.appData;
2331 restoreTableEntryExtData(xrecord, record.extData);
2332 xrecord.reactorHandles = record.reactorHandles;
2333 xrecord.xDictHandle = record.xDictHandle;
2334 xrecord.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2335 record.hasDsData);
2336 return xrecord;
2337}
2338
2339DRW_Layout
2340layoutFromMetadata(const LC_DwgAdvancedMetadata::LayoutRecord &record) {
2341 DRW_Layout layout;
2342 layout.handle = record.handle;
2343 layout.parentHandle = record.parentHandle;
2344 layout.appData = record.appData;
2345 restoreTableEntryExtData(layout, record.extData);
2346 layout.reactorHandles = record.reactorHandles;
2347 layout.xDictHandle = record.xDictHandle;
2348 layout.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2349 record.hasDsData);
2350 // PlotSettings prefix.
2351 layout.pageSetupName = record.pageSetupName;
2352 layout.printerConfig = record.printerConfig;
2353 layout.plotLayoutFlags = record.plotLayoutFlags;
2354 layout.marginLeft = record.marginLeft;
2355 layout.marginBottom = record.marginBottom;
2356 layout.marginRight = record.marginRight;
2357 layout.marginTop = record.marginTop;
2358 layout.paperWidth = record.paperWidth;
2359 layout.paperHeight = record.paperHeight;
2360 layout.paperSize = record.paperSize;
2361 layout.plotOriginX = record.plotOriginX;
2362 layout.plotOriginY = record.plotOriginY;
2363 layout.paperUnits = record.paperUnits;
2364 layout.plotRotation = record.plotRotation;
2365 layout.plotType = record.plotType;
2366 layout.windowMinX = record.windowMinX;
2367 layout.windowMinY = record.windowMinY;
2368 layout.windowMaxX = record.windowMaxX;
2369 layout.windowMaxY = record.windowMaxY;
2370 layout.plotViewName = record.plotViewName;
2371 layout.realWorldUnits = record.realWorldUnits;
2372 layout.drawingUnits = record.drawingUnits;
2373 layout.currentStyleSheet = record.currentStyleSheet;
2374 layout.scaleType = record.scaleType;
2375 layout.scaleFactor = record.scaleFactor;
2376 layout.paperImageOriginX = record.paperImageOriginX;
2377 layout.paperImageOriginY = record.paperImageOriginY;
2378 layout.shadePlotMode = record.shadePlotMode;
2379 layout.shadePlotResLevel = record.shadePlotResLevel;
2380 layout.shadePlotCustomDPI = record.shadePlotCustomDPI;
2381 // Layout-specific.
2382 layout.name = record.name;
2383 layout.layoutFlags = record.layoutFlags;
2384 layout.tabOrder = record.tabOrder;
2385 layout.ucsOrigin = record.ucsOrigin;
2386 layout.limMinX = record.limMinX;
2387 layout.limMinY = record.limMinY;
2388 layout.limMaxX = record.limMaxX;
2389 layout.limMaxY = record.limMaxY;
2390 layout.insPoint = record.insPoint;
2391 layout.ucsXAxis = record.ucsXAxis;
2392 layout.ucsYAxis = record.ucsYAxis;
2393 layout.elevation = record.elevation;
2394 layout.orthoViewType = record.orthoViewType;
2395 layout.extMin = record.extMin;
2396 layout.extMax = record.extMax;
2397 layout.viewportCount = record.viewportCount;
2398 layout.plotViewHandle.ref = record.plotViewHandle;
2399 layout.shadePlotHandle.ref = record.shadePlotHandle;
2400 layout.paperSpaceBlockRecordHandle.ref = record.paperSpaceBlockRecordHandle;
2401 layout.lastActiveViewportHandle.ref = record.lastActiveViewportHandle;
2402 layout.baseUcsHandle.ref = record.baseUcsHandle;
2403 layout.namedUcsHandle.ref = record.namedUcsHandle;
2404 layout.viewportHandles = record.viewportHandles;
2405 return layout;
2406}
2407
2408DRW_Group groupFromMetadata(const LC_DwgAdvancedMetadata::GroupRecord &record) {
2409 DRW_Group group;
2410 group.handle = record.handle;
2411 group.parentHandle = record.parentHandle;
2412 group.appData = record.appData;
2413 restoreTableEntryExtData(group, record.extData);
2414 group.reactorHandles = record.reactorHandles;
2415 group.xDictHandle = record.xDictHandle;
2416 group.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2417 record.hasDsData);
2418 group.m_description = record.description;
2419 group.m_isUnnamed = record.isUnnamed;
2420 group.m_selectable = record.selectable;
2421 group.m_entityHandles = record.entityHandles;
2422 return group;
2423}
2424
2425DRW_UnderlayDefinition underlayDefinitionFromMetadata(
2426 const LC_DwgAdvancedMetadata::UnderlayDefinitionRecord &record) {
2427 DRW_UnderlayDefinition definition;
2428 definition.handle = record.handle;
2429 definition.parentHandle = record.parentHandle;
2430 definition.appData = record.appData;
2431 restoreTableEntryExtData(definition, record.extData);
2432 definition.kind = static_cast<DRW_UnderlayDefinition::Kind>(record.kind);
2433 definition.filename = record.path;
2434 definition.sheetName = record.sheetName;
2435 definition.reactorHandles = record.reactorHandles;
2436 definition.xDictHandle = record.xDictHandle;
2437 definition.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2438 record.hasDsData);
2439 return definition;
2440}
2441
2442DRW_ImageDef imageDefinitionFromMetadata(
2443 const LC_DwgAdvancedMetadata::ImageDefinitionRecord &record) {
2444 DRW_ImageDef definition;
2445 definition.handle = record.handle;
2446 definition.parentHandle = record.parentHandle;
2447 definition.appData = record.appData;
2448 restoreTableEntryExtData(definition, record.extData);
2449 definition.name = record.path;
2450 definition.imgVersion = record.classVersion;
2451 definition.u = record.imageSizeU;
2452 definition.v = record.imageSizeV;
2453 definition.up = record.pixelSizeU;
2454 definition.vp = record.pixelSizeV;
2455 definition.loaded = record.loaded;
2456 definition.resolution = record.resolution;
2457 definition.reactorHandles = record.commonReactorHandles;
2458 definition.xDictHandle = record.xDictHandle;
2459 definition.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2460 record.hasDsData);
2461 return definition;
2462}
2463
2464DRW_ImageDefinitionReactor imageDefinitionReactorFromMetadata(
2465 const LC_DwgAdvancedMetadata::ImageDefinitionReactorRecord &record) {
2466 DRW_ImageDefinitionReactor reactor;
2467 reactor.handle = record.handle;
2468 reactor.parentHandle = record.parentHandle;
2469 reactor.appData = record.appData;
2470 restoreTableEntryExtData(reactor, record.extData);
2471 reactor.m_classVersion = record.classVersion;
2472 reactor.reactorHandles = record.reactorHandles;
2473 reactor.xDictHandle = record.xDictHandle;
2474 reactor.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2475 record.hasDsData);
2476 return reactor;
2477}
2478
2479DRW_PointCloudDef pointCloudDefinitionFromMetadata(
2480 const LC_DwgAdvancedMetadata::PointCloudDefinitionRecord &record) {
2481 DRW_PointCloudDef definition;
2482 definition.handle = record.handle;
2483 definition.parentHandle = record.parentHandle;
2484 definition.appData = record.appData;
2485 restoreTableEntryExtData(definition, record.extData);
2486 definition.m_kind = static_cast<DRW_PointCloudDef::Kind>(record.kind);
2487 definition.m_classVersion = record.classVersion;
2488 definition.m_sourceFilename = record.sourceFilename;
2489 definition.m_isLoaded = record.isLoaded;
2490 definition.m_pointCount = record.pointCount;
2491 definition.m_extentsMin = record.extentsMin;
2492 definition.m_extentsMax = record.extentsMax;
2493 definition.reactorHandles = record.reactorHandles;
2494 definition.xDictHandle = record.xDictHandle;
2495 definition.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2496 record.hasDsData);
2497 return definition;
2498}
2499
2500DRW_NavisworksModelDef navisworksModelDefinitionFromMetadata(
2501 const LC_DwgAdvancedMetadata::NavisworksModelDefinitionRecord &record) {
2502 DRW_NavisworksModelDef definition;
2503 definition.handle = record.handle;
2504 definition.parentHandle = record.parentHandle;
2505 definition.appData = record.appData;
2506 restoreTableEntryExtData(definition, record.extData);
2507 definition.m_flags = record.flags;
2508 definition.m_path = record.path;
2509 definition.m_status = record.status;
2510 definition.m_minExtent = record.minExtent;
2511 definition.m_maxExtent = record.maxExtent;
2512 definition.m_hostDrawingVisibility = record.hostDrawingVisibility;
2513 definition.reactorHandles = record.reactorHandles;
2514 definition.xDictHandle = record.xDictHandle;
2515 definition.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2516 record.hasDsData);
2517 return definition;
2518}
2519
2520DRW_NavisworksModel navisworksModelFromMetadata(
2521 const LC_DwgAdvancedMetadata::NavisworksModelRecord &record) {
2522 DRW_NavisworksModel model;
2523 model.handle = record.handle;
2524 model.parentHandle = record.parentHandle;
2525 model.flags = record.flags;
2526 model.definitionHandle = record.definitionHandle;
2527 model.transform = record.transform;
2528 model.unitFactor = record.unitFactor;
2529 return model;
2530}
2531
2532DRW_PointCloudColorMap pointCloudColorMapFromMetadata(
2533 const LC_DwgAdvancedMetadata::PointCloudColorMapRecord &record) {
2534 DRW_PointCloudColorMap colorMap;
2535 colorMap.handle = record.handle;
2536 colorMap.parentHandle = record.parentHandle;
2537 colorMap.appData = record.appData;
2538 restoreTableEntryExtData(colorMap, record.extData);
2539 colorMap.m_classVersion = record.classVersion;
2540 colorMap.m_defaultIntensityColorScheme = record.defaultIntensityColorScheme;
2541 colorMap.m_defaultElevationColorScheme = record.defaultElevationColorScheme;
2542 colorMap.m_defaultClassificationColorScheme =
2543 record.defaultClassificationColorScheme;
2544 colorMap.m_colorRampCount = record.declaredColorRampCount;
2545 colorMap.m_classificationColorRampCount =
2546 record.declaredClassificationColorRampCount;
2547 const auto copyRamps = [](const auto &source, auto &destination) {
2548 destination.reserve(source.size());
2549 for (const auto &ramp : source) {
2550 DRW_PointCloudColorMapRamp copied;
2551 copied.m_classVersion = ramp.classVersion;
2552 copied.m_rampCount = ramp.declaredColorCount;
2553 copied.m_colorSchemes = ramp.colorSchemes;
2554 destination.push_back(std::move(copied));
2555 }
2556 };
2557 copyRamps(record.colorRamps, colorMap.m_colorRamps);
2558 copyRamps(record.classificationColorRamps,
2559 colorMap.m_classificationColorRamps);
2560 colorMap.reactorHandles = record.reactorHandles;
2561 colorMap.xDictHandle = record.xDictHandle;
2562 colorMap.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2563 record.hasDsData);
2564 return colorMap;
2565}
2566
2567DRW_RasterVariables rasterVariablesFromMetadata(
2568 const LC_DwgAdvancedMetadata::RasterVariablesRecord &record) {
2569 DRW_RasterVariables rv;
2570 rv.handle = record.handle;
2571 rv.parentHandle = record.parentHandle;
2572 rv.appData = record.appData;
2573 restoreTableEntryExtData(rv, record.extData);
2574 rv.m_classVersion = record.classVersion;
2575 rv.m_imageFrame = record.imageFrame;
2576 rv.m_imageQuality = record.imageQuality;
2577 rv.m_units = record.units;
2578 rv.reactorHandles = record.reactorHandles;
2579 rv.xDictHandle = record.xDictHandle;
2580 rv.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2581 record.hasDsData);
2582 return rv;
2583}
2584
2585DRW_GeoData
2586geoDataFromMetadata(const LC_DwgAdvancedMetadata::GeoDataRecord &record) {
2587 DRW_GeoData gd;
2588 gd.handle = record.handle;
2589 gd.parentHandle = record.parentHandle;
2590 gd.appData = record.appData;
2591 restoreTableEntryExtData(gd, record.extData);
2592 gd.reactorHandles = record.reactorHandles;
2593 gd.xDictHandle = record.xDictHandle;
2594 gd.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2595 record.hasDsData);
2596 gd.m_hostBlockHandle = record.hostBlockHandle;
2597 gd.m_version = record.version;
2598 gd.m_coordinatesType = record.coordinatesType;
2599 gd.m_horizontalUnits = record.horizontalUnits;
2600 gd.m_verticalUnits = record.verticalUnits;
2601 gd.m_horizontalUnitScale = record.horizontalUnitScale;
2602 gd.m_verticalUnitScale = record.verticalUnitScale;
2603 gd.m_coordinateSystemDefinition = record.coordinateSystemDefinition;
2604 gd.m_geoRssTag = record.geoRssTag;
2605 gd.m_designPoint = record.designPoint;
2606 gd.m_referencePoint = record.referencePoint;
2607 gd.m_upDirection = record.upDirection;
2608 gd.m_northDirection = record.northDirection;
2609 gd.m_scaleEstimationMethod = record.scaleEstimationMethod;
2610 gd.m_userSpecifiedScaleFactor = record.userSpecifiedScaleFactor;
2611 gd.m_enableSeaLevelCorrection = record.enableSeaLevelCorrection;
2612 gd.m_seaLevelElevation = record.seaLevelElevation;
2613 gd.m_coordinateProjectionRadius = record.coordinateProjectionRadius;
2614 gd.m_observationFromTag = record.observationFromTag;
2615 gd.m_observationToTag = record.observationToTag;
2616 gd.m_observationCoverageTag = record.observationCoverageTag;
2617 gd.m_points.reserve(record.meshPoints.size());
2618 for (const auto &mp : record.meshPoints) {
2619 DRW_GeoMeshPoint point;
2620 point.m_source = mp.source;
2621 point.m_destination = mp.destination;
2622 gd.m_points.push_back(point);
2623 }
2624 gd.m_faces.reserve(record.meshFaces.size());
2625 for (const auto &mf : record.meshFaces) {
2626 DRW_GeoMeshFace face;
2627 face.m_index1 = mf.index1;
2628 face.m_index2 = mf.index2;
2629 face.m_index3 = mf.index3;
2630 gd.m_faces.push_back(face);
2631 }
2632 return gd;
2633}
2634
2635DRW_SpatialFilter spatialFilterFromMetadata(
2636 const LC_DwgAdvancedMetadata::SpatialFilterRecord &record) {
2637 DRW_SpatialFilter sf;
2638 sf.handle = record.handle;
2639 sf.parentHandle = record.parentHandle;
2640 sf.appData = record.appData;
2641 restoreTableEntryExtData(sf, record.extData);
2642 sf.reactorHandles = record.reactorHandles;
2643 sf.xDictHandle = record.xDictHandle;
2644 sf.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2645 record.hasDsData);
2646 sf.m_boundaryPoints = record.boundaryPoints;
2647 sf.m_normal = record.normal;
2648 sf.m_origin = record.origin;
2649 sf.m_displayBoundary = record.displayBoundary;
2650 sf.m_clipFrontPlane = record.clipFrontPlane;
2651 sf.m_clipBackPlane = record.clipBackPlane;
2652 sf.m_frontDistance = record.frontDistance;
2653 sf.m_backDistance = record.backDistance;
2654 sf.m_inverseInsertTransform = record.inverseInsertTransform;
2655 sf.m_insertTransform = record.insertTransform;
2656 return sf;
2657}
2658
2659// PR 8d.2a — five small no-storage OBJECTS families. Flat field-copy
2660// builders matching the addX captures above.
2661DRW_Scale scaleFromMetadata(const LC_DwgAdvancedMetadata::ScaleRecord &record) {
2662 DRW_Scale s;
2663 s.handle = record.handle;
2664 s.parentHandle = record.parentHandle;
2665 s.appData = record.appData;
2666 restoreTableEntryExtData(s, record.extData);
2667 s.reactorHandles = record.reactorHandles;
2668 s.xDictHandle = record.xDictHandle;
2669 s.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2670 record.hasDsData);
2671 s.name = record.name;
2672 s.flag = record.flag;
2673 s.paperUnits = record.paperUnits;
2674 s.drawingUnits = record.drawingUnits;
2675 s.isUnitScale = record.isUnitScale;
2676 return s;
2677}
2678
2679DRW_MLineStyle
2680mlineStyleFromMetadata(const LC_DwgAdvancedMetadata::MLineStyleRecord &record) {
2681 DRW_MLineStyle s;
2682 s.handle = record.handle;
2683 s.parentHandle = record.parentHandle;
2684 s.appData = record.appData;
2685 restoreTableEntryExtData(s, record.extData);
2686 s.reactorHandles = record.reactorHandles;
2687 s.xDictHandle = record.xDictHandle;
2688 s.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2689 record.hasDsData);
2690 s.name = record.name;
2691 s.flags = record.flags;
2692 s.description = record.description;
2693 s.fillColor = record.fillColor;
2694 s.startAngle = record.startAngle;
2695 s.endAngle = record.endAngle;
2696 s.elements.reserve(record.elements.size());
2697 for (const auto &er : record.elements) {
2698 DRW_MLineElement e;
2699 e.offset = er.offset;
2700 e.color = er.color;
2701 e.color24 = er.color24;
2702 e.linetypeIndex = er.linetypeIndex;
2703 e.linetypeHandle = er.linetypeHandle;
2704 e.linetype = er.linetype;
2705 s.elements.push_back(std::move(e));
2706 }
2707 return s;
2708}
2709
2710DRW_WipeoutVariables wipeoutVariablesFromMetadata(
2711 const LC_DwgAdvancedMetadata::WipeoutVariablesRecord &record) {
2712 DRW_WipeoutVariables w;
2713 w.handle = record.handle;
2714 w.parentHandle = record.parentHandle;
2715 w.appData = record.appData;
2716 restoreTableEntryExtData(w, record.extData);
2717 w.m_displayFrame = static_cast<std::uint16_t>(record.displayFrame);
2718 w.reactorHandles = record.reactorHandles;
2719 w.xDictHandle = record.xDictHandle;
2720 w.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2721 record.hasDsData);
2722 return w;
2723}
2724
2725DRW_IDBuffer
2726idBufferFromMetadata(const LC_DwgAdvancedMetadata::IDBufferRecord &record) {
2727 DRW_IDBuffer b;
2728 b.handle = record.handle;
2729 b.parentHandle = record.parentHandle;
2730 b.appData = record.appData;
2731 restoreTableEntryExtData(b, record.extData);
2732 b.reactorHandles = record.reactorHandles;
2733 b.xDictHandle = record.xDictHandle;
2734 b.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2735 record.hasDsData);
2736 b.classVersion = record.classVersion;
2737 b.objIds = record.objIds;
2738 return b;
2739}
2740
2741DRW_LayerIndex
2742layerIndexFromMetadata(const LC_DwgAdvancedMetadata::LayerIndexRecord &record) {
2743 DRW_LayerIndex li;
2744 li.handle = record.handle;
2745 li.parentHandle = record.parentHandle;
2746 li.appData = record.appData;
2747 restoreTableEntryExtData(li, record.extData);
2748 li.timestamp1 = record.timestamp1;
2749 li.timestamp2 = record.timestamp2;
2750 li.entries.reserve(record.entries.size());
2751 for (const auto &er : record.entries) {
2752 DRW_LayerIndexEntry e;
2753 e.indexLong = er.indexLong;
2754 e.name = er.name;
2755 e.entryHandle = er.entryHandle;
2756 li.entries.push_back(std::move(e));
2757 }
2758 li.reactorHandles = record.reactorHandles;
2759 li.xDictHandle = record.xDictHandle;
2760 li.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2761 record.hasDsData);
2762 return li;
2763}
2764
2765DRW_SpatialIndex spatialIndexFromMetadata(
2766 const LC_DwgAdvancedMetadata::SpatialIndexRecord &record) {
2767 DRW_SpatialIndex si;
2768 si.handle = record.handle;
2769 si.parentHandle = record.parentHandle;
2770 si.appData = record.appData;
2771 restoreTableEntryExtData(si, record.extData);
2772 si.timestamp1 = record.timestamp1;
2773 si.timestamp2 = record.timestamp2;
2774 si.reactorHandles = record.reactorHandles;
2775 si.xDictHandle = record.xDictHandle;
2776 si.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2777 record.hasDsData);
2778 return si;
2779}
2780
2781DRW_DictionaryVar dictionaryVarFromMetadata(
2782 const LC_DwgAdvancedMetadata::DictionaryVarRecord &record) {
2783 DRW_DictionaryVar dv;
2784 dv.handle = record.handle;
2785 dv.parentHandle = record.parentHandle;
2786 dv.appData = record.appData;
2787 restoreTableEntryExtData(dv, record.extData);
2788 dv.m_schema = record.schema;
2789 dv.m_value = record.value;
2790 dv.reactorHandles = record.reactorHandles;
2791 dv.xDictHandle = record.xDictHandle;
2792 dv.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2793 record.hasDsData);
2794 return dv;
2795}
2796
2797// PR 8d.2b — four larger no-storage OBJECTS families. Flat field-copy
2798// builders matching the addX captures above.
2799DRW_DictionaryWithDefault dictionaryWithDefaultFromMetadata(
2800 const LC_DwgAdvancedMetadata::DictionaryWithDefaultRecord &record) {
2801 DRW_DictionaryWithDefault dwd;
2802 dwd.handle = record.handle;
2803 dwd.parentHandle = record.parentHandle;
2804 dwd.appData = record.appData;
2805 restoreTableEntryExtData(dwd, record.extData);
2806 dwd.cloning = record.cloning;
2807 dwd.hardOwner = record.hardOwner;
2808 dwd.name = record.name;
2809 dwd.m_entries.reserve(record.entries.size());
2810 for (const auto &er : record.entries) {
2811 DRW_Dictionary::Entry entry;
2812 entry.m_name = er.name;
2813 entry.m_handle = er.handle;
2814 dwd.m_entries.push_back(std::move(entry));
2815 }
2816 dwd.m_defaultEntryHandle = record.defaultEntryHandle;
2817 dwd.reactorHandles = record.reactorHandles;
2818 dwd.xDictHandle = record.xDictHandle;
2819 dwd.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2820 record.hasDsData);
2821 return dwd;
2822}
2823
2824bool canWriteDwgDictionaryRecord(
2825 const LC_DwgAdvancedMetadata &metadata,
2826 const LC_DwgAdvancedMetadata::DictionaryRecord &record,
2827 DRW::Version version) {
2828 const DRW_Dictionary dictionary = dictionaryFromMetadata(record);
2829 const auto sourceAuthorization =
2830 metadata.authorizeDwgDictionarySource(record);
2831 return record.typedPayloadComplete &&
2832 !hasDxfTableAppDataUnsupportedByDwg(dictionary) &&
2833 dictionary.isDwgPayloadValid(version) &&
2834 (sourceAuthorization.status ==
2835 LC_DwgAdvancedMetadata::DwgDictionarySourceAuthorizationStatus::
2836 NotDwgSource ||
2837 sourceAuthorization.authorized());
2838}
2839
2840bool canWriteDwgDictionaryWithDefaultRecord(
2841 const LC_DwgAdvancedMetadata &metadata,
2842 const LC_DwgAdvancedMetadata::DictionaryWithDefaultRecord &record,
2843 DRW::Version version) {
2844 const DRW_DictionaryWithDefault dictionary =
2845 dictionaryWithDefaultFromMetadata(record);
2846 const auto sourceAuthorization =
2847 metadata.authorizeDwgDictionaryWithDefaultSource(record);
2848 return record.typedPayloadComplete &&
2849 !hasDxfTableAppDataUnsupportedByDwg(dictionary) &&
2850 dictionary.isDwgPayloadValid(version) &&
2851 (sourceAuthorization.status ==
2852 LC_DwgAdvancedMetadata::
2853 DwgDictionaryWithDefaultSourceAuthorizationStatus::
2854 NotDwgSource ||
2855 sourceAuthorization.authorized());
2856}
2857
2858DRW_SortEntsTable sortEntsTableFromMetadata(
2859 const LC_DwgAdvancedMetadata::SortEntsTableRecord &record) {
2860 DRW_SortEntsTable se;
2861 se.handle = record.handle;
2862 se.parentHandle = record.parentHandle;
2863 se.appData = record.appData;
2864 restoreTableEntryExtData(se, record.extData);
2865 se.m_sortHandles = record.sortHandles;
2866 se.m_blockOwnerHandle = record.blockOwnerHandle;
2867 se.m_entityHandles = record.entityHandles;
2868 se.reactorHandles = record.reactorHandles;
2869 se.xDictHandle = record.xDictHandle;
2870 se.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2871 record.hasDsData);
2872 return se;
2873}
2874
2875DRW_FieldList
2876fieldListFromMetadata(const LC_DwgAdvancedMetadata::FieldListRecord &record) {
2877 DRW_FieldList fl;
2878 fl.handle = record.handle;
2879 fl.parentHandle = record.parentHandle;
2880 fl.m_unknown = record.unknown;
2881 fl.m_fieldHandles = record.fieldHandles;
2882 fl.appData = record.appData;
2883 restoreTableEntryExtData(fl, record.extData);
2884 fl.reactorHandles = record.reactorHandles;
2885 fl.xDictHandle = record.xDictHandle;
2886 fl.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2887 record.hasDsData);
2888 return fl;
2889}
2890
2891bool canWriteDwgFieldListRecord(
2892 const LC_DwgAdvancedMetadata &metadata,
2893 const LC_DwgAdvancedMetadata::FieldListRecord &record,
2894 const DRW_FieldList &fieldList,
2895 DRW::Version version) {
2896 const auto sourceAuthorization = metadata.authorizeDwgFieldListSource(record);
2897 return !hasDxfTableAppDataUnsupportedByDwg(fieldList) &&
2898 fieldList.isDwgPayloadValid(version) &&
2899 (sourceAuthorization.status ==
2900 LC_DwgAdvancedMetadata::DwgFieldListSourceAuthorizationStatus::
2901 NotDwgSource ||
2902 sourceAuthorization.authorized());
2903}
2904
2905DRW_CadValue
2906cadValueFromRecord(const LC_DwgAdvancedMetadata::CadValueRecord &cr) {
2907 DRW_CadValue cv;
2908 cv.m_formatFlags = cr.formatFlags;
2909 cv.m_dataType = cr.dataType;
2910 cv.m_dataSize = cr.dataSize;
2911 cv.m_unitType = cr.unitType;
2912 cv.m_value = cr.value;
2913 cv.m_formatString = cr.formatString;
2914 cv.m_valueString = cr.valueString;
2915 cv.m_handle = cr.handle;
2916 cv.m_rawData = cr.rawData;
2917 return cv;
2918}
2919
2920DRW_Field fieldFromMetadata(const LC_DwgAdvancedMetadata::FieldRecord &record) {
2921 DRW_Field f;
2922 f.handle = record.handle;
2923 f.parentHandle = record.parentHandle;
2924 f.m_evaluatorId = record.evaluatorId;
2925 f.m_fieldCode = record.fieldCode;
2926 f.m_formatString = record.formatString;
2927 f.m_evaluationOptionFlags = record.evaluationOptionFlags;
2928 f.m_filingOptionFlags = record.filingOptionFlags;
2929 f.m_fieldStateFlags = record.fieldStateFlags;
2930 f.m_evaluationStatusFlags = record.evaluationStatusFlags;
2931 f.m_evaluationErrorCode = record.evaluationErrorCode;
2932 f.m_evaluationErrorMessage = record.evaluationErrorMessage;
2933 f.m_value = cadValueFromRecord(record.value);
2934 f.m_valueString = record.valueString;
2935 f.m_valueStringLength = record.valueStringLength;
2936 f.m_childHandles = record.childHandles;
2937 f.m_objectHandles = record.objectHandles;
2938 f.appData = record.appData;
2939 restoreTableEntryExtData(f, record.extData);
2940 f.reactorHandles = record.reactorHandles;
2941 f.xDictHandle = record.xDictHandle;
2942 f.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2943 record.hasDsData);
2944 f.m_childValues.reserve(record.childValues.size());
2945 for (const auto &cv : record.childValues) {
2946 DRW_Field::ChildValue dcv;
2947 dcv.m_key = cv.key;
2948 dcv.m_value = cadValueFromRecord(cv.value);
2949 f.m_childValues.push_back(std::move(dcv));
2950 }
2951 return f;
2952}
2953
2954bool canWriteDwgFieldRecord(const LC_DwgAdvancedMetadata &metadata,
2955 const LC_DwgAdvancedMetadata::FieldRecord &record,
2956 const DRW_Field &field,
2957 DRW::Version version) {
2958 const auto sourceAuthorization = metadata.authorizeDwgFieldSource(record);
2959 return record.typedPayloadComplete &&
2960 !hasDxfTableAppDataUnsupportedByDwg(field) &&
2961 field.isDwgPayloadValid(version) &&
2962 (sourceAuthorization.status ==
2963 LC_DwgAdvancedMetadata::DwgFieldSourceAuthorizationStatus::
2964 NotDwgSource ||
2965 sourceAuthorization.authorized());
2966}
2967
2968DRW_Sun sunFromMetadata(const LC_DwgAdvancedMetadata::SunRecord &record) {
2969 DRW_Sun sun;
2970 sun.handle = record.handle;
2971 sun.parentHandle = record.parentHandle;
2972 sun.appData = record.appData;
2973 restoreTableEntryExtData(sun, record.extData);
2974 sun.m_classVersion = record.classVersion;
2975 sun.m_isOn = record.isOn;
2976 sun.m_color = record.color;
2977 sun.m_intensity = record.intensity;
2978 sun.m_hasShadow = record.hasShadow;
2979 sun.m_julianDay = record.julianDay;
2980 sun.m_milliseconds = record.milliseconds;
2981 sun.m_isDaylightSavings = record.isDaylightSavings;
2982 sun.m_shadowType = record.shadowType;
2983 sun.m_shadowMapSize = record.shadowMapSize;
2984 sun.m_shadowSoftness = record.shadowSoftness;
2985 sun.reactorHandles = record.reactorHandles;
2986 sun.xDictHandle = record.xDictHandle;
2987 sun.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2988 record.hasDsData);
2989 return sun;
2990}
2991
2992DRW_TvDeviceProperties tvDevicePropertiesFromMetadata(
2993 const LC_DwgAdvancedMetadata::TvDevicePropertiesRecord &record) {
2994 DRW_TvDeviceProperties properties;
2995 properties.handle = record.handle;
2996 properties.parentHandle = record.parentHandle;
2997 properties.flags = record.flags;
2998 properties.maxRegenThreads = record.maxRegenThreads;
2999 properties.useLutPalette = record.useLutPalette;
3000 properties.alternateHighlight = record.alternateHighlight;
3001 properties.alternateHighlightColor = record.alternateHighlightColor;
3002 properties.geometryShaderUsage = record.geometryShaderUsage;
3003 properties.blendingMode = record.blendingMode;
3004 properties.antialiasingLevel = record.antialiasingLevel;
3005 properties.valueBd2 = record.valueBd2;
3006 properties.appData = record.appData;
3007 restoreTableEntryExtData(properties, record.extData);
3008 properties.reactorHandles = record.reactorHandles;
3009 properties.xDictHandle = record.xDictHandle;
3010 properties.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3011 record.hasDsData);
3012 return properties;
3013}
3014
3015DRW_VxControl
3016vxControlFromMetadata(const LC_DwgAdvancedMetadata::VxControlRecord &record) {
3017 DRW_VxControl control;
3018 control.handle = record.handle;
3019 control.parentHandle = record.parentHandle;
3020 control.appData = record.appData;
3021 restoreTableEntryExtData(control, record.extData);
3022 control.classVersion = record.classVersion;
3023 control.flags = record.flags;
3024 control.recordHandles = record.recordHandles;
3025 if (record.rawDataValid)
3026 (void)control.setDwgRawData(record.rawData, record.rawDataBitSize,
3027 record.rawDataVersion);
3028 control.reactorHandles = record.reactorHandles;
3029 control.xDictHandle = record.xDictHandle;
3030 control.setDwgCommonObjectState(
3031 static_cast<std::int32_t>(record.reactorHandles.size()), record.xDictFlag,
3032 record.hasDsData);
3033 return control;
3034}
3035
3036DRW_VxTableRecord vxTableRecordFromMetadata(
3037 const LC_DwgAdvancedMetadata::VxTableRecordRecord &record) {
3038 DRW_VxTableRecord tableRecord;
3039 tableRecord.handle = record.handle;
3040 tableRecord.parentHandle = record.parentHandle;
3041 tableRecord.appData = record.appData;
3042 restoreTableEntryExtData(tableRecord, record.extData);
3043 tableRecord.name = record.name;
3044 tableRecord.classVersion = record.classVersion;
3045 tableRecord.flags = record.flags;
3046 if (record.rawDataValid)
3047 (void)tableRecord.setDwgRawData(record.rawData, record.rawDataBitSize,
3048 record.rawDataVersion);
3049 tableRecord.reactorHandles = record.reactorHandles;
3050 tableRecord.xDictHandle = record.xDictHandle;
3051 tableRecord.setDwgCommonObjectState(
3052 static_cast<std::int32_t>(record.reactorHandles.size()), record.xDictFlag,
3053 record.hasDsData);
3054 return tableRecord;
3055}
3056
3057const char *backgroundRecordName(DRW_Background::Kind kind) {
3058 switch (kind) {
3059 case DRW_Background::Gradient:
3060 return "GRADIENTBACKGROUND";
3061 case DRW_Background::GroundPlane:
3062 return "GROUNDPLANEBACKGROUND";
3063 case DRW_Background::Image:
3064 return "IMAGEBACKGROUND";
3065 case DRW_Background::Ibl:
3066 return "IBLBACKGROUND";
3067 case DRW_Background::Skylight:
3068 return "SKYLIGHTBACKGROUND";
3069 case DRW_Background::Solid:
3070 default:
3071 return "SOLIDBACKGROUND";
3072 }
3073}
3074
3075DRW_Background
3076backgroundFromMetadata(const LC_DwgAdvancedMetadata::BackgroundRecord &record) {
3077 DRW_Background background;
3078 background.handle = record.handle;
3079 background.parentHandle = record.parentHandle;
3080 background.appData = record.appData;
3081 restoreTableEntryExtData(background, record.extData);
3082 background.m_kind = record.kind;
3083 background.m_classVersion = record.classVersion;
3084 background.m_solidColor = record.solidColor;
3085 background.m_colorTop = record.colorTop;
3086 background.m_colorMiddle = record.colorMiddle;
3087 background.m_colorBottom = record.colorBottom;
3088 background.m_horizon = record.horizon;
3089 background.m_height = record.height;
3090 background.m_rotation = record.rotation;
3091 background.m_colorSkyZenith = record.colorSkyZenith;
3092 background.m_colorSkyHorizon = record.colorSkyHorizon;
3093 background.m_colorUndergroundHorizon = record.colorUndergroundHorizon;
3094 background.m_colorUndergroundAzimuth = record.colorUndergroundAzimuth;
3095 background.m_colorNear = record.colorNear;
3096 background.m_colorFar = record.colorFar;
3097 background.m_fileName = record.fileName;
3098 background.m_fitToScreen = record.fitToScreen;
3099 background.m_maintainAspect = record.maintainAspect;
3100 background.m_useTiling = record.useTiling;
3101 background.m_offset = record.offset;
3102 background.m_scale = record.scale;
3103 background.m_iblName = record.iblName;
3104 background.m_enabled = record.enabled;
3105 background.m_displayImage = record.displayImage;
3106 background.m_secondaryBackgroundHandle = record.secondaryBackgroundHandle;
3107 background.m_sunHandle = record.sunHandle;
3108 background.reactorHandles = record.reactorHandles;
3109 background.xDictHandle = record.xDictHandle;
3110 background.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3111 record.hasDsData);
3112 return background;
3113}
3114
3115DRW_Material
3116materialFromMetadata(const LC_DwgAdvancedMetadata::MaterialRecord &record) {
3117 DRW_Material material;
3118 material.handle = record.handle;
3119 material.parentHandle = record.parentHandle;
3120 material.m_name = record.name;
3121 material.m_description = record.description;
3122 material.appData = record.appData;
3123 restoreTableEntryExtData(material, record.extData);
3124 material.reactorHandles = record.reactorHandles;
3125 material.xDictHandle = record.xDictHandle;
3126 material.setDwgCommonObjectState(
3127 static_cast<std::int32_t>(record.reactorHandles.size()), record.xDictFlag,
3128 record.hasDsData);
3129 if (record.rawDataValid)
3130 (void)material.setDwgRawData(record.rawData, record.rawDataBitSize,
3131 record.rawDataVersion);
3132 return material;
3133}
3134
3135DRW_LightList
3136lightListFromMetadata(const LC_DwgAdvancedMetadata::LightListRecord &record) {
3137 DRW_LightList lightList;
3138 lightList.handle = record.handle;
3139 lightList.parentHandle = record.parentHandle;
3140 lightList.m_classVersion = record.classVersion;
3141 lightList.m_lightCount = record.lightCount;
3142 lightList.appData = record.appData;
3143 restoreTableEntryExtData(lightList, record.extData);
3144 lightList.reactorHandles = record.reactorHandles;
3145 lightList.xDictHandle = record.xDictHandle;
3146 lightList.setDwgCommonObjectState(
3147 static_cast<std::int32_t>(record.reactorHandles.size()), record.xDictFlag,
3148 record.hasDsData);
3149 lightList.m_lights.reserve(record.lights.size());
3150 for (const auto &source : record.lights) {
3151 DRW_LightListEntry light;
3152 light.m_handle = source.handle;
3153 light.m_name = source.name;
3154 lightList.m_lights.push_back(std::move(light));
3155 }
3156 return lightList;
3157}
3158
3159DRW_Light lightFromMetadata(const LC_DwgAdvancedMetadata::LightRecord &record) {
3160 DRW_Light light;
3161 light.handle = record.handle;
3162 light.parentHandle = record.parentHandle;
3163 light.m_classVersion = record.classVersion;
3164 light.m_name = record.name;
3165 light.m_type = record.type;
3166 light.m_status = record.status;
3167 light.m_color = record.color;
3168 light.m_plotGlyph = record.plotGlyph;
3169 light.m_intensity = record.intensity;
3170 light.m_position = record.position;
3171 light.m_target = record.target;
3172 light.m_attenuationType = record.attenuationType;
3173 light.m_useAttenuationLimits = record.useAttenuationLimits;
3174 light.m_attenuationStartLimit = record.attenuationStartLimit;
3175 light.m_attenuationEndLimit = record.attenuationEndLimit;
3176 light.m_hotspotAngle = record.hotspotAngle;
3177 light.m_falloffAngle = record.falloffAngle;
3178 light.m_castShadows = record.castShadows;
3179 light.m_shadowType = record.shadowType;
3180 light.m_shadowMapSize = record.shadowMapSize;
3181 light.m_shadowMapSoftness = record.shadowMapSoftness;
3182 light.m_hasPhotometricData = record.hasPhotometricData;
3183 light.m_hasWebFile = record.hasWebFile;
3184 light.m_webFile = record.webFile;
3185 light.m_physicalIntensityMethod = record.physicalIntensityMethod;
3186 light.m_physicalIntensity = record.physicalIntensity;
3187 light.m_illuminanceDistance = record.illuminanceDistance;
3188 light.m_lampColorType = record.lampColorType;
3189 light.m_lampColorTemperature = record.lampColorTemperature;
3190 light.m_lampColorPreset = record.lampColorPreset;
3191 light.m_webRotation = record.webRotation;
3192 light.m_extendedLightShape = record.extendedLightShape;
3193 light.m_extendedLightLength = record.extendedLightLength;
3194 light.m_extendedLightWidth = record.extendedLightWidth;
3195 light.m_extendedLightRadius = record.extendedLightRadius;
3196 return light;
3197}
3198
3199DRW_VisualStyle visualStyleFromMetadata(
3200 const LC_DwgAdvancedMetadata::VisualStyleRecord &record) {
3201 DRW_VisualStyle style;
3202 style.handle = record.handle;
3203 style.parentHandle = record.parentHandle;
3204 style.appData = record.appData;
3205 restoreTableEntryExtData(style, record.extData);
3206 style.name = record.name;
3207 style.desc = record.description;
3208 style.type = record.type;
3209 style.m_body = record.body;
3210 style.m_bodyDecoded = record.bodyDecoded;
3211 style.reactorHandles = record.reactorHandles;
3212 style.xDictHandle = record.xDictHandle;
3213 style.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3214 record.hasDsData);
3215 return style;
3216}
3217
3218const char *renderSettingsRecordName(DRW_RenderSettings::Kind kind) {
3219 switch (kind) {
3220 case DRW_RenderSettings::Global:
3221 return "RENDERGLOBAL";
3222 case DRW_RenderSettings::Environment:
3223 return "RENDERENVIRONMENT";
3224 case DRW_RenderSettings::Entry:
3225 return "RENDERENTRY";
3226 case DRW_RenderSettings::RapidRT:
3227 return "RAPIDRTRENDERSETTINGS";
3228 case DRW_RenderSettings::MentalRay:
3229 return "MENTALRAYRENDERSETTINGS";
3230 case DRW_RenderSettings::Settings:
3231 default:
3232 return "RENDERSETTINGS";
3233 }
3234}
3235
3236DRW_RenderSettings renderSettingsFromMetadata(
3237 const LC_DwgAdvancedMetadata::RenderSettingsRecord &record) {
3238 DRW_RenderSettings settings;
3239 settings.handle = record.handle;
3240 settings.parentHandle = record.parentHandle;
3241 settings.appData = record.appData;
3242 restoreTableEntryExtData(settings, record.extData);
3243 settings.m_kind = record.kind;
3244 settings.m_classVersion = record.classVersion;
3245 settings.m_name = record.name;
3246 settings.m_longs = record.longs;
3247 settings.m_strings = record.strings;
3248 settings.m_bools = record.bools;
3249 settings.m_shorts = record.shorts;
3250 settings.m_bytes = record.bytes;
3251 settings.m_doubles = record.doubles;
3252 settings.m_fogEnabled = record.fogEnabled;
3253 settings.m_fogBackgroundEnabled = record.fogBackgroundEnabled;
3254 settings.m_backfacesEnabled = record.backfacesEnabled;
3255 settings.m_environmentImageEnabled = record.environmentImageEnabled;
3256 settings.m_environmentImageFilename = record.environmentImageFilename;
3257 settings.m_description = record.description;
3258 settings.m_displayIndex = record.displayIndex;
3259 settings.m_hasPredefined = record.hasPredefined;
3260 settings.m_hasPredefinedPresent = record.hasPredefinedPresent;
3261 settings.m_fogColorR = record.fogColorR;
3262 settings.m_fogColorG = record.fogColorG;
3263 settings.m_fogColorB = record.fogColorB;
3264 settings.m_fogDensityNear = record.fogDensityNear;
3265 settings.m_fogDensityFar = record.fogDensityFar;
3266 settings.m_fogDistanceNear = record.fogDistanceNear;
3267 settings.m_fogDistanceFar = record.fogDistanceFar;
3268 settings.m_procedure = record.procedure;
3269 settings.m_destination = record.destination;
3270 settings.reactorHandles = record.reactorHandles;
3271 settings.xDictHandle = record.xDictHandle;
3272 settings.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3273 record.hasDsData);
3274 if (record.rawDataValid)
3275 (void)settings.setDwgRawData(record.rawData, record.rawDataBitSize,
3276 record.rawDataVersion);
3277 return settings;
3278}
3279
3280DRW_SunStudy
3281sunStudyFromMetadata(const LC_DwgAdvancedMetadata::SunStudyRecord &record) {
3282 DRW_SunStudy study;
3283 study.handle = record.handle;
3284 study.parentHandle = record.parentHandle;
3285 study.appData = record.appData;
3286 restoreTableEntryExtData(study, record.extData);
3287 study.m_classVersion = record.classVersion;
3288 study.m_setupName = record.setupName;
3289 study.m_description = record.description;
3290 study.m_sheetSetName = record.sheetSetName;
3291 study.m_sheetSubsetName = record.sheetSubsetName;
3292 study.m_outputType = record.outputType;
3293 study.m_useSubset = record.useSubset;
3294 study.m_selectDatesFromCalendar = record.selectDatesFromCalendar;
3295 study.m_selectRangeOfDates = record.selectRangeOfDates;
3296 study.m_lockViewports = record.lockViewports;
3297 study.m_labelViewports = record.labelViewports;
3298 study.m_startTime = record.startTime;
3299 study.m_endTime = record.endTime;
3300 study.m_interval = record.interval;
3301 study.m_shadePlotType = record.shadePlotType;
3302 study.m_viewportCount = record.viewportCount;
3303 study.m_rowCount = record.rowCount;
3304 study.m_columnCount = record.columnCount;
3305 study.m_spacing = record.spacing;
3306 study.m_dates = record.dates;
3307 study.m_hours = record.hours;
3308 study.m_pageSetupWizardHandle = record.pageSetupWizardHandle;
3309 study.m_viewHandle = record.viewHandle;
3310 study.m_visualStyleHandle = record.visualStyleHandle;
3311 study.m_textStyleHandle = record.textStyleHandle;
3312 study.reactorHandles = record.reactorHandles;
3313 study.xDictHandle = record.xDictHandle;
3314 study.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3315 record.hasDsData);
3316 return study;
3317}
3318
3319DRW_MotionPath
3320motionPathFromMetadata(const LC_DwgAdvancedMetadata::MotionPathRecord &record) {
3321 DRW_MotionPath path;
3322 path.handle = record.handle;
3323 path.parentHandle = record.parentHandle;
3324 path.appData = record.appData;
3325 restoreTableEntryExtData(path, record.extData);
3326 path.m_classVersion = record.classVersion;
3327 path.m_cameraPathHandle = record.cameraPathHandle;
3328 path.m_targetPathHandle = record.targetPathHandle;
3329 path.m_viewTableHandle = record.viewTableHandle;
3330 path.m_frames = record.frames;
3331 path.m_frameRate = record.frameRate;
3332 path.m_cornerDeceleration = record.cornerDeceleration;
3333 path.reactorHandles = record.reactorHandles;
3334 path.xDictHandle = record.xDictHandle;
3335 path.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3336 record.hasDsData);
3337 return path;
3338}
3339
3340DRW_CurvePath
3341curvePathFromMetadata(const LC_DwgAdvancedMetadata::CurvePathRecord &record) {
3342 DRW_CurvePath path;
3343 path.handle = record.handle;
3344 path.parentHandle = record.parentHandle;
3345 path.appData = record.appData;
3346 restoreTableEntryExtData(path, record.extData);
3347 path.m_classVersion = record.classVersion;
3348 path.m_entityHandle = record.entityHandle;
3349 path.reactorHandles = record.reactorHandles;
3350 path.xDictHandle = record.xDictHandle;
3351 path.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3352 record.hasDsData);
3353 return path;
3354}
3355
3356DRW_PointPath
3357pointPathFromMetadata(const LC_DwgAdvancedMetadata::PointPathRecord &record) {
3358 DRW_PointPath path;
3359 path.handle = record.handle;
3360 path.parentHandle = record.parentHandle;
3361 path.appData = record.appData;
3362 restoreTableEntryExtData(path, record.extData);
3363 path.m_classVersion = record.classVersion;
3364 path.m_point = record.point;
3365 path.reactorHandles = record.reactorHandles;
3366 path.xDictHandle = record.xDictHandle;
3367 path.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3368 record.hasDsData);
3369 return path;
3370}
3371
3372DRW_ObjectPtr
3373objectPtrFromMetadata(const LC_DwgAdvancedMetadata::ObjectPtrRecord &record) {
3374 DRW_ObjectPtr object;
3375 object.handle = record.handle;
3376 object.parentHandle = record.parentHandle;
3377 object.appData = record.appData;
3378 restoreTableEntryExtData(object, record.extData);
3379 object.reactorHandles = record.reactorHandles;
3380 object.xDictHandle = record.xDictHandle;
3381 object.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3382 record.hasDsData);
3383 return object;
3384}
3385
3386DRW_PartialViewingIndex partialViewingIndexFromMetadata(
3387 const LC_DwgAdvancedMetadata::PartialViewingIndexRecord &record) {
3388 DRW_PartialViewingIndex index;
3389 index.handle = record.handle;
3390 index.parentHandle = record.parentHandle;
3391 index.appData = record.appData;
3392 restoreTableEntryExtData(index, record.extData);
3393 index.m_entryCount = static_cast<std::uint32_t>(record.entries.size());
3394 index.m_hasEntries = record.hasEntries && !record.entries.empty();
3395 index.m_entries = record.entries;
3396 index.reactorHandles = record.reactorHandles;
3397 index.xDictHandle = record.xDictHandle;
3398 index.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3399 record.hasDsData);
3400 return index;
3401}
3402
3403DRW_DbColor
3404dbColorFromMetadata(const LC_DwgAdvancedMetadata::DbColorRecord &record) {
3405 DRW_DbColor color;
3406 color.handle = record.handle;
3407 color.parentHandle = record.parentHandle;
3408 color.appData = record.appData;
3409 restoreTableEntryExtData(color, record.extData);
3410 color.reactorHandles = record.reactorHandles;
3411 color.xDictHandle = record.xDictHandle;
3412 color.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3413 record.hasDsData);
3414 color.name = record.name;
3415 color.rgb = record.rgb;
3416 color.colorIndex = record.colorIndex;
3417 color.colorMethod = record.colorMethod;
3418 color.bookName = record.bookName;
3419 return color;
3420}
3421
3422DRW_DimensionAssociation dimensionAssociationFromMetadata(
3423 const LC_DwgAdvancedMetadata::DimensionAssociationRecord &record) {
3424 DRW_DimensionAssociation association;
3425 association.handle = record.handle;
3426 association.parentHandle = record.parentHandle;
3427 association.appData = record.appData;
3428 restoreTableEntryExtData(association, record.extData);
3429 association.reactorHandles = record.reactorHandles;
3430 association.xDictHandle = record.xDictHandle;
3431 association.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3432 record.hasDsData);
3433 association.m_dimensionHandle = record.dimensionHandle;
3434 association.m_associativityFlags = record.associativityFlags;
3435 association.m_isTransSpace = record.isTransSpace;
3436 association.m_rotatedDimensionType = record.rotatedDimensionType;
3437 association.m_osnapRefs = record.osnapRefs;
3438 association.m_hasUnrepresentableDetail = record.hasUnrepresentableDetail;
3439 return association;
3440}
3441
3442bool canWriteNativeDimensionAssociation(
3443 const DRW_DimensionAssociation &association) {
3444 if ((association.m_associativityFlags & ~0x0Fu) != 0 ||
3445 association.m_osnapRefs.size() > 4 ||
3446 association.m_hasUnrepresentableDetail)
3447 return false;
3448
3449 std::size_t activeSlots = 0;
3450 for (std::uint32_t flag = 1; flag <= 8; flag <<= 1) {
3451 if ((association.m_associativityFlags & flag) != 0)
3452 ++activeSlots;
3453 }
3454 return activeSlots == association.m_osnapRefs.size() &&
3455 std::all_of(association.m_osnapRefs.begin(),
3456 association.m_osnapRefs.end(),
3457 [](const DRW_DimensionAssociationOsnapRef &ref) {
3458 return ref.m_objectOsnapType == 0;
3459 });
3460}
3461
3462DRW_EvaluationGraph evaluationGraphFromMetadata(
3463 const LC_DwgAdvancedMetadata::EvaluationGraphRecord &record) {
3464 DRW_EvaluationGraph graph;
3465 graph.handle = record.handle;
3466 graph.parentHandle = record.parentHandle;
3467 graph.appData = record.appData;
3468 restoreTableEntryExtData(graph, record.extData);
3469 graph.reactorHandles = record.reactorHandles;
3470 graph.xDictHandle = record.xDictHandle;
3471 graph.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3472 record.hasDsData);
3473 graph.m_value96 = record.value96;
3474 graph.m_value97 = record.value97;
3475 graph.m_nodes = record.nodes;
3476 graph.m_edges = record.edges;
3477 return graph;
3478}
3479
3480DRW_BlockRepresentationData blockRepresentationFromMetadata(
3481 const LC_DwgAdvancedMetadata::BlockRepresentationRecord &record) {
3482 DRW_BlockRepresentationData data;
3483 data.handle = record.handle;
3484 data.parentHandle = record.parentHandle;
3485 data.appData = record.appData;
3486 restoreTableEntryExtData(data, record.extData);
3487 data.reactorHandles = record.reactorHandles;
3488 data.xDictHandle = record.xDictHandle;
3489 data.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3490 record.hasDsData);
3491 data.m_flag = record.flag;
3492 data.m_blockHandle = record.blockHandle;
3493 return data;
3494}
3495
3496bool canWriteNativeEvaluationGraph(const DRW_EvaluationGraph &graph) {
3497 return graph.m_nodes.size() <= DRW_EvaluationGraph::kMaxEntries &&
3498 graph.m_edges.size() <= DRW_EvaluationGraph::kMaxEntries;
3499}
3500
3501const char *sectionRecordName(DRW_Section::Kind kind) {
3502 return kind == DRW_Section::Settings ? "SECTIONSETTINGS" : "SECTIONMANAGER";
3503}
3504
3505DRW_Section
3506sectionFromMetadata(const LC_DwgAdvancedMetadata::SectionRecord &record) {
3507 DRW_Section section;
3508 section.handle = record.handle;
3509 section.parentHandle = record.parentHandle;
3510 section.appData = record.appData;
3511 restoreTableEntryExtData(section, record.extData);
3512 section.reactorHandles = record.reactorHandles;
3513 section.xDictHandle = record.xDictHandle;
3514 section.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3515 record.hasDsData);
3516 if (record.rawDataValid)
3517 (void)section.setDwgRawData(record.rawData, record.rawDataBitSize,
3518 record.rawDataVersion);
3519 section.m_kind = record.kind;
3520 section.m_isLive = record.isLive;
3521 section.m_sectionCount = record.sectionCount;
3522 section.m_sectionHandles = record.sectionHandles;
3523 section.m_classVersion = record.classVersion;
3524 section.m_sectionType = record.sectionType;
3525 section.m_generationOptions = record.generationOptions;
3526 section.m_currentType = record.currentType;
3527 section.m_typeCount = record.typeCount;
3528 section.m_types = record.types;
3529 section.m_sourceHandle = record.sourceHandle;
3530 section.m_destinationBlockHandle = record.destinationBlockHandle;
3531 section.m_destinationFileHandle = record.destinationFileHandle;
3532 section.m_destinationFile = record.destinationFile;
3533 return section;
3534}
3535
3536DRW_SectionObject sectionObjectFromMetadata(
3537 const LC_DwgAdvancedMetadata::SectionObjectRecord &record) {
3538 DRW_SectionObject section;
3539 section.handle = record.handle;
3540 section.parentHandle = record.parentHandle;
3541 section.m_state = record.state;
3542 section.m_flags = record.flags;
3543 section.m_name = record.name;
3544 section.m_vertDir = record.vertDir;
3545 section.m_topHeight = record.topHeight;
3546 section.m_bottomHeight = record.bottomHeight;
3547 section.m_indicatorAlpha = record.indicatorAlpha;
3548 section.m_indicatorColor = record.indicatorColor;
3549 section.m_verts = record.verts;
3550 section.m_blVerts = record.blVerts;
3551 section.m_sectionSettingsHandle = record.sectionSettingsHandle;
3552 section.extData = record.extData;
3553 return section;
3554}
3555
3556DRW_MLeaderStyle mleaderStyleFromMetadata(
3557 const LC_DwgAdvancedMetadata::MLeaderStyleRecord &record) {
3558 DRW_MLeaderStyle style;
3559 style.handle = record.handle;
3560 style.parentHandle = record.parentHandle;
3561 style.name = record.name;
3562 style.appData = record.appData;
3563 restoreTableEntryExtData(style, record.extData);
3564 style.reactorHandles = record.reactorHandles;
3565 style.xDictHandle = record.xDictHandle;
3566 style.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3567 record.hasDsData);
3568 style.styleVersion = record.styleVersion != 0 ? record.styleVersion : 2;
3569 style.contentType = record.contentType;
3570 style.drawMLeaderOrder = record.drawMLeaderOrder;
3571 style.drawLeaderOrder = record.drawLeaderOrder;
3572 style.maxLeaderPoints = record.maxLeaderPoints;
3573 style.firstSegmentAngle = record.firstSegmentAngle;
3574 style.secondSegmentAngle = record.secondSegmentAngle;
3575 style.leaderType = record.leaderType;
3576 style.leaderColor = record.leaderColor;
3577 style.leaderLineTypeHandle.ref = record.leaderLineTypeHandle;
3578 style.leaderLineWeight = record.leaderLineWeight;
3579 style.landingEnabled = record.landingEnabled;
3580 style.landingGap = record.landingGap;
3581 style.autoIncludeLanding = record.autoIncludeLanding;
3582 style.landingDistance = record.landingDistance;
3583 style.description = record.description;
3584 style.arrowHeadBlockHandle.ref = record.arrowHeadBlockHandle;
3585 style.arrowHeadSize = record.arrowHeadSize;
3586 style.textDefault = record.textDefault;
3587 style.textStyleHandle.ref = record.textStyleHandle;
3588 style.leftAttachment = record.leftAttachment;
3589 style.rightAttachment = record.rightAttachment;
3590 style.textAngleType = record.textAngleType;
3591 style.textAlignmentType = record.textAlignmentType;
3592 style.textColor = record.textColor;
3593 style.textHeight = record.textHeight;
3594 style.textFrameEnabled = record.textFrameEnabled;
3595 style.alwaysAlignTextLeft = record.alwaysAlignTextLeft;
3596 style.alignSpace = record.alignSpace;
3597 style.blockHandle.ref = record.blockHandle;
3598 style.blockColor = record.blockColor;
3599 style.blockScale = record.blockScale;
3600 style.blockScaleEnabled = record.blockScaleEnabled;
3601 style.blockRotation = record.blockRotation;
3602 style.blockRotationEnabled = record.blockRotationEnabled;
3603 style.blockConnectionType = record.blockConnectionType;
3604 style.scaleFactor = record.scaleFactor;
3605 style.propertyChanged = record.propertyChanged;
3606 style.isAnnotative = record.isAnnotative;
3607 style.breakSize = record.breakSize;
3608 style.attachmentDirection = record.attachmentDirection;
3609 style.topAttachment = record.topAttachment;
3610 style.bottomAttachment = record.bottomAttachment;
3611 style.textExtended = record.textExtended;
3612 return style;
3613}
3614
3615bool validateDwgMLeaderStylePayload(
3616 const LC_DwgAdvancedMetadata::MLeaderStyleRecord &record,
3617 DRW::Version version, std::uint32_t &payloadBitEstimate) {
3618 payloadBitEstimate = 0;
3619 DRW_MLeaderStyle style = mleaderStyleFromMetadata(record);
3620 if (style.extData.size() != record.extData.size() ||
3621 hasDxfTableAppDataUnsupportedByDwg(style) ||
3622 !style.validateDwgPayload(version)) {
3623 return false;
3624 }
3625
3626 dwgBufferW body;
3627 dwgBufferW strings;
3628 dwgBufferW handles;
3629 if (!style.encodeDwg(version, &body, &strings, &handles))
3630 return false;
3631
3632 std::uint64_t payloadBytes = 0;
3633 for (const dwgBufferW *buffer : {&body, &strings, &handles}) {
3634 const std::uint64_t size = static_cast<std::uint64_t>(buffer->size());
3635 if (payloadBytes > std::numeric_limits<std::uint64_t>::max() - size)
3636 return false;
3637 payloadBytes += size;
3638 }
3639 if (payloadBytes > std::numeric_limits<std::uint32_t>::max() / 8u)
3640 return false;
3641 payloadBitEstimate = static_cast<std::uint32_t>(payloadBytes * 8u);
3642 return true;
3643}
3644
3645std::unique_ptr<LC_SplinePoints>
3646approximateDrwSpline(RS_EntityContainer *parent, const DRW_Spline *data) {
3647 RS_SplineData splineData;
3648 if (!buildSplineDataFromDrw(data, splineData))
3649 return nullptr;
3650
3651 const size_t degree = static_cast<size_t>(data->degree);
3652 const size_t controlCount = splineData.controlPoints.size();
3653 const double tmin = splineData.knotslist[degree];
3654 const double tmax = splineData.knotslist[controlCount];
3655 if (!std::isfinite(tmin) || !std::isfinite(tmax) || tmax <= tmin)
3656 return nullptr;
3657
3658 LC_SplinePointsData sampled((data->flags & 0x3) != 0, false);
3659 sampled.useControlPoints = false;
3660 sampled.splinePoints.reserve(kImportedSplineFallbackSamples + 1);
3661 for (int i = 0; i <= kImportedSplineFallbackSamples; ++i) {
3662 const double u = tmin + (tmax - tmin) * (static_cast<double>(i) /
3663 kImportedSplineFallbackSamples);
3664 const RS_Vector point = RS_Spline::evaluateNURBS(splineData, u);
3665 if (!point.valid)
3666 return nullptr;
3667 sampled.splinePoints.push_back(point);
3668 }
3669
3670 if (sampled.closed && sampled.splinePoints.size() > 1 &&
3671 sampled.splinePoints.front().distanceTo(sampled.splinePoints.back()) <=
3672 1e-8) {
3673 sampled.splinePoints.pop_back();
3674 }
3675
3676 if (sampled.splinePoints.size() < 2)
3677 return nullptr;
3678
3679 return std::make_unique<LC_SplinePoints>(parent, std::move(sampled));
3680}
3681// convert DRW_Coord to RS_Vector
3682RS_Vector coordToVector(const std::shared_ptr<DRW_Coord> &c) {
3683 return c ? RS_Vector(c->x, c->y) : RS_Vector(false);
3684}
3685
3686} // namespace
3687
3688/**
3689 * Default constructor.
3690 *
3691 */
3692RS_FilterDXFRW::RS_FilterDXFRW() : RS_FilterInterface(), DRW_Interface() {
3693 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::RS_FilterDXFRW()");
3694
3695 m_currentContainer = nullptr;
3696 m_graphic = nullptr;
3697 // Init hash to change the QCAD "normal" style to the more correct ISO-3059
3698 // or draftsight symbol (AR*.shx) to sy*.lff
3699 m_fontList["arastro"] = "syastro";
3700 m_fontList["armap"] = "symap";
3701 m_fontList["armeteo"] = "symeteo";
3702 m_fontList["armusic"] = "symusic";
3703 m_fontList["math"] = "symath";
3704 m_fontList["normal"] = "iso";
3705 m_fontList["normallatin1"] = "iso";
3706 m_fontList["normallatin2"] = "iso";
3707
3708 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::RS_FilterDXFRW(): OK");
3709}
3710
3711/**
3712 * Destructor.
3713 */
3714RS_FilterDXFRW::~RS_FilterDXFRW() {
3715 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::~RS_FilterDXFRW(): OK");
3716}
3717
3718// Human-readable display for a DRW::Version. Year suffixes anchor users
3719// in time so they can decide whether to convert (pre-R13) or update their
3720// install (post-R2018 future versions).
3721static QString dwgVersionDisplay(DRW::Version v) {
3722 switch (v) {
3723 case DRW::MC00:
3724 return QStringLiteral("R1.1 (1982)")(QString(QtPrivate::qMakeStringPrivate(u"" "R1.1 (1982)")));
3725 case DRW::AC12:
3726 return QStringLiteral("R1.2 (1983)")(QString(QtPrivate::qMakeStringPrivate(u"" "R1.2 (1983)")));
3727 case DRW::AC14:
3728 return QStringLiteral("R1.4 (1983)")(QString(QtPrivate::qMakeStringPrivate(u"" "R1.4 (1983)")));
3729 case DRW::AC150:
3730 return QStringLiteral("R2.0 (1984)")(QString(QtPrivate::qMakeStringPrivate(u"" "R2.0 (1984)")));
3731 case DRW::AC210:
3732 return QStringLiteral("R2.10 (1986)")(QString(QtPrivate::qMakeStringPrivate(u"" "R2.10 (1986)")));
3733 case DRW::AC1002:
3734 return QStringLiteral("R2.5 (1986)")(QString(QtPrivate::qMakeStringPrivate(u"" "R2.5 (1986)")));
3735 case DRW::AC1003:
3736 return QStringLiteral("R2.6 (1987)")(QString(QtPrivate::qMakeStringPrivate(u"" "R2.6 (1987)")));
3737 case DRW::AC1004:
3738 return QStringLiteral("R9 (1987)")(QString(QtPrivate::qMakeStringPrivate(u"" "R9 (1987)")));
3739 case DRW::AC1006:
3740 return QStringLiteral("R10 (1988)")(QString(QtPrivate::qMakeStringPrivate(u"" "R10 (1988)")));
3741 case DRW::AC1009:
3742 return QStringLiteral("R11/R12 (1990)")(QString(QtPrivate::qMakeStringPrivate(u"" "R11/R12 (1990)"))
)
;
3743 case DRW::AC1012:
3744 return QStringLiteral("R13 (1994)")(QString(QtPrivate::qMakeStringPrivate(u"" "R13 (1994)")));
3745 case DRW::AC1014:
3746 return QStringLiteral("R14 (1997)")(QString(QtPrivate::qMakeStringPrivate(u"" "R14 (1997)")));
3747 case DRW::AC1015:
3748 return QStringLiteral("AutoCAD 2000")(QString(QtPrivate::qMakeStringPrivate(u"" "AutoCAD 2000")));
3749 case DRW::AC1018:
3750 return QStringLiteral("AutoCAD 2004")(QString(QtPrivate::qMakeStringPrivate(u"" "AutoCAD 2004")));
3751 case DRW::AC1021:
3752 return QStringLiteral("AutoCAD 2007")(QString(QtPrivate::qMakeStringPrivate(u"" "AutoCAD 2007")));
3753 case DRW::AC1024:
3754 return QStringLiteral("AutoCAD 2010")(QString(QtPrivate::qMakeStringPrivate(u"" "AutoCAD 2010")));
3755 case DRW::AC1027:
3756 return QStringLiteral("AutoCAD 2013")(QString(QtPrivate::qMakeStringPrivate(u"" "AutoCAD 2013")));
3757 case DRW::AC1032:
3758 return QStringLiteral("AutoCAD 2018")(QString(QtPrivate::qMakeStringPrivate(u"" "AutoCAD 2018")));
3759 case DRW::UNKNOWNV:
3760 default:
3761 return QStringLiteral("unknown")(QString(QtPrivate::qMakeStringPrivate(u"" "unknown")));
3762 }
3763}
3764
3765// fixme - sand - non informative error message (mixed file format which is
3766// known)
3767QString RS_FilterDXFRW::lastError() const {
3768 switch (m_errorCode) {
3769 case DRW::BAD_NONE:
3770 return (QObject::tr("no DXF/DWG error", "RS_FilterDXFRW"));
3771 case DRW::BAD_OPEN:
3772 return (QObject::tr("error opening DXF/DWG file", "RS_FilterDXFRW"));
3773 case DRW::BAD_VERSION:
3774 if (m_dwgVersion != DRW::UNKNOWNV) {
3775 return QObject::tr(
3776 "Cannot open DWG: file is %1; LibreCAD supports %2 and newer. "
3777 "Convert with GNU LibreDWG (dwgread / dwg2dxf) or re-save "
3778 "from a recent CAD tool.",
3779 "RS_FilterDXFRW")
3780 .arg(dwgVersionDisplay(m_dwgVersion))
3781 .arg(dwgVersionDisplay(DRW::AC1012));
3782 }
3783 return (QObject::tr("unsupported DXF/DWG file version", "RS_FilterDXFRW"));
3784 case DRW::BAD_READ_METADATA:
3785 return (QObject::tr("error reading DXF/DWG meta data", "RS_FilterDXFRW"));
3786 case DRW::BAD_READ_FILE_HEADER:
3787 return (QObject::tr("error reading DXF/DWG file header", "RS_FilterDXFRW"));
3788 case DRW::BAD_READ_HEADER:
3789 return (QObject::tr("error reading DXF/DWG header data", "RS_FilterDXFRW"));
3790 case DRW::BAD_READ_HANDLES:
3791 return (QObject::tr("error reading DXF/DWG object map", "RS_FilterDXFRW"));
3792 case DRW::BAD_READ_CLASSES:
3793 return (QObject::tr("error reading DXF/DWG classes", "RS_FilterDXFRW"));
3794 case DRW::BAD_READ_TABLES:
3795 return (QObject::tr("error reading DXF/DWG tables", "RS_FilterDXFRW"));
3796 case DRW::BAD_READ_BLOCKS:
3797 return (QObject::tr("error reading DXF/DWG blocks", "RS_FilterDXFRW"));
3798 case DRW::BAD_READ_ENTITIES:
3799 return (QObject::tr("error reading DXF/DWG entities", "RS_FilterDXFRW"));
3800 case DRW::BAD_READ_OBJECTS:
3801 return (QObject::tr("error reading DXF/DWG objects", "RS_FilterDXFRW"));
3802 case DRW::BAD_READ_SECTION:
3803 return (QObject::tr("error reading DXF/DWG sections", "RS_FilterDXFRW"));
3804 case DRW::BAD_CODE_PARSED:
3805 return (QObject::tr("error reading DXF/DWG code", "RS_FilterDXFRW"));
3806 default:
3807 break;
3808 }
3809
3810 return RS_FilterInterface::lastError();
3811}
3812
3813namespace {
3814
3815// Pre-R13 DWG files often omit $ACADVER from the table snapshot delivered to
3816// addHeader(), leaving m_version at the 1021 fallback during entity import.
3817// Sniff the on-disk magic so import-time guards can key off AC1009 et al.
3818int versionIdFromDwgMagic(const QString &filePath) {
3819 QFile file(filePath);
3820 if (!file.open(QIODevice::ReadOnly))
3821 return 0;
3822 const QByteArray magic = file.read(6);
3823 if (magic.size() < 6)
3824 return 0;
3825 if (magic == "AC1009")
3826 return 1009;
3827 if (magic == "AC1012")
3828 return 1012;
3829 if (magic == "AC1014")
3830 return 1014;
3831 if (magic == "AC1015")
3832 return 1015;
3833 if (magic == "AC1018")
3834 return 1018;
3835 if (magic == "AC1021")
3836 return 1021;
3837 if (magic == "AC1024")
3838 return 1024;
3839 if (magic == "AC1027")
3840 return 1027;
3841 if (magic == "AC1032")
3842 return 1032;
3843 return 0;
3844}
3845
3846} // namespace
3847
3848/**
3849 * Implementation of the method used for RS_Import to communicate
3850 * with this filter.
3851 *
3852 * @param g The m_graphic in which the entities from the m_file
3853 * will be created or the graphics from which the entities are
3854 * taken to be stored in a m_file.
3855 */
3856bool RS_FilterDXFRW::fileImport(RS_Graphic &g, const QString &file,
3857 [[maybe_unused]] RS2::FormatType type) {
3858 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::fileImport");
3859 RS_DEBUGRS_Debug::instance()->print("DXFRW Filter: importing file '%s'...",
3860 (const char *)QFile::encodeName(file));
3861
3862 m_graphic = &g;
3863 m_currentContainer = m_graphic;
3864 m_dummyContainer = new RS_EntityContainer(nullptr, true);
3865 // RAII: free the scratch container on every return path (success and the
3866 // BAD_VERSION / parse-failure early returns), not just the success path.
3867 struct DummyContainerGuard {
3868 RS_EntityContainer **p;
3869 ~DummyContainerGuard() {
3870 delete *p;
3871 *p = nullptr;
3872 }
3873 } dummyGuard{&m_dummyContainer};
3874
3875 this->m_file = file;
3876 // Register the file being loaded into the XREF recursion guard so
3877 // A → B → A cycles are detected at depth 2 rather than depth 4.
3878 // Done via QFileInfo::absoluteFilePath() to match resolveXrefPath()
3879 // (which returns absolute paths). RAII-erased on return so failure
3880 // paths (BAD_VERSION, parse failure, etc.) don't leak the entry.
3881 const QString thisFileAbs = QFileInfo(file).absoluteFilePath();
3882 const bool stackInsertedSelf = m_xrefStack.insert(thisFileAbs).second;
3883 struct XrefStackGuard {
3884 std::set<QString> *stack = nullptr;
3885 QString key;
3886 bool owned = false;
3887 ~XrefStackGuard() {
3888 if (stack && owned)
3889 stack->erase(key);
3890 }
3891 } stackGuard{&m_xrefStack, thisFileAbs, stackInsertedSelf};
3892 // add some variables that need to be there for DXF drawings:
3893 m_graphic->addVariable("$DIMSTYLE", "Standard", 2);
3894 m_dimStyle = "Standard";
3895 m_codePage = "ANSI_1252";
3896 m_textStyle = "Standard";
3897 // reset library version
3898 m_isLibDxfRw = false;
3899 m_libDxfRwVersion = 0;
3900 m_unsupportedDwgObjects.clear();
3901 m_graphic->dwgAdvancedMetadata().clear();
3902 // Clear per-import caches so a reused filter instance does not carry stale
3903 // (and, for m_blockHash, potentially dangling) state across imports. XREF
3904 // sub-imports use a separate child filter, so this never wipes parent state.
3905 m_blockHash.clear();
3906 m_importLayerCache.clear();
3907 m_importLayerRawCache.clear();
3908 m_mlineStyleCache.clear();
3909 m_underlayDefMap.clear();
3910 m_xrefBlockNames.clear();
3911
3912#ifdef DWGSUPPORT1
3913 if (type == RS2::FormatDWG || type == RS2::FormatDWG2004 ||
3914 type == RS2::FormatDWG2007 || type == RS2::FormatDWG2010 ||
3915 type == RS2::FormatDWG2013 || type == RS2::FormatDWG2018) {
3916 const int magicVersion = versionIdFromDwgMagic(file);
3917 if (magicVersion != 0)
3918 m_version = magicVersion;
3919
3920 dwgR dwgr(QFile::encodeName(file));
3921 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::fileImport: reading DWG file");
3922 if (RS_DEBUGRS_Debug::instance()->getLevel() == RS_Debug::D_DEBUGGING)
3923 dwgr.setDebug(DRW::DebugLevel::Debug);
3924 const auto dwgReadStart = std::chrono::steady_clock::now();
3925 bool success = dwgr.read(this, true);
3926 if (std::getenv("LC_IMPORT_BENCH") != nullptr) {
3927 const auto dwgReadMs =
3928 std::chrono::duration_cast<std::chrono::milliseconds>(
3929 std::chrono::steady_clock::now() - dwgReadStart)
3930 .count();
3931 std::cerr << "[import-bench] dwg_read_ms=" << dwgReadMs << "\n";
3932 }
3933 // Capture the recognized version BEFORE acting on the result so
3934 // BAD_VERSION error reporting (printDwgError / lastError) can
3935 // name the format the user supplied. dwgR::version is set by
3936 // openFile() even on the BAD_VERSION fork.
3937 m_dwgVersion = dwgr.getVersion();
3938 // Persist the source version on the document so the export filter can
3939 // gate raw-replay (emit + CLASSES registration) on source==target.
3940 m_graphic->dwgAdvancedMetadata().setSourceDwgVersion(m_dwgVersion);
3941 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::fileImport: reading DWG file: OK");
3942 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
3943 QObject::tr("Opened DWG file version %1.")
3944 .arg(printDwgVersion(dwgr.getVersion())));
3945 std::cout << "DWG file version: "
3946 << printDwgVersion(dwgr.getVersion()).toStdString() << "\n";
3947 const size_t parseFailures = dwgr.getEntityParseFailures();
3948 if (parseFailures > 0) {
3949 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
3950 QObject::tr("DWG load: %1 %2 had parse errors and were skipped. "
3951 "Drawing loaded with the rest.")
3952 .arg(parseFailures)
3953 .arg(parseFailures == 1 ? QObject::tr("entity")
3954 : QObject::tr("entities")));
3955 }
3956 // Vendor-extension custom classes (AutoCAD Mechanical AmgStdPart aka
3957 // STDPART2D, AcmBomRow, etc.) whose proprietary geometry libdxfrw
3958 // can't decode. Surface the top breakdown so the user knows what's
3959 // missing rather than silently rendering a partial drawing.
3960 const auto skipped = dwgr.getSkippedCustomClasses();
3961 if (!skipped.empty()) {
3962 size_t totalSkipped = 0;
3963 std::vector<std::pair<QString, size_t>> sorted;
3964 sorted.reserve(skipped.size());
3965 for (const auto &kv : skipped) {
3966 totalSkipped += kv.second;
3967 sorted.emplace_back(QString::fromStdString(kv.first), kv.second);
3968 }
3969 std::sort(sorted.begin(), sorted.end(), [](const auto &a, const auto &b) {
3970 return a.second > b.second;
3971 });
3972 QStringList top;
3973 const size_t showN = std::min<size_t>(3, sorted.size());
3974 for (size_t i = 0; i < showN; ++i)
3975 top << QString("%1×%2").arg(sorted[i].second).arg(sorted[i].first);
3976 QString breakdown = top.join(QLatin1String(", "));
3977 if (sorted.size() > showN)
3978 breakdown += QObject::tr(", and %n more class(es)", "",
3979 static_cast<int>(sorted.size() - showN));
3980 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
3981 QObject::tr(
3982 "DWG load: %1 vendor-extension entities not rendered (%2). "
3983 "These are typically AutoCAD Mechanical or other "
3984 "vertical-product "
3985 "custom classes that libdxfrw cannot decode.")
3986 .arg(totalSkipped)
3987 .arg(breakdown));
3988 }
3989 const auto unsupportedObjects = dwgr.getSkippedUnsupportedObjects();
3990 if (!unsupportedObjects.empty()) {
3991 size_t totalSkipped = 0;
3992 std::vector<std::pair<QString, size_t>> sorted;
3993 sorted.reserve(unsupportedObjects.size());
3994 for (const auto &kv : unsupportedObjects) {
3995 totalSkipped += kv.second;
3996 sorted.emplace_back(QString::fromStdString(kv.first), kv.second);
3997 }
3998 std::sort(sorted.begin(), sorted.end(), [](const auto &a, const auto &b) {
3999 return a.second > b.second;
4000 });
4001 QStringList top;
4002 const size_t showN = std::min<size_t>(3, sorted.size());
4003 for (size_t i = 0; i < showN; ++i)
4004 top << QString("%1×%2").arg(sorted[i].second).arg(sorted[i].first);
4005 QString breakdown = top.join(QLatin1String(", "));
4006 if (sorted.size() > showN)
4007 breakdown += QObject::tr(", and %n more object type(s)", "",
4008 static_cast<int>(sorted.size() - showN));
4009 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
4010 QObject::tr("DWG load: %1 unsupported metadata object(s) skipped "
4011 "(%2). Drawing geometry may still be complete.")
4012 .arg(totalSkipped)
4013 .arg(breakdown));
4014 }
4015 RS_DEBUGRS_Debug::instance()->print("DWG read summary: %d entities, %d blocks, error=%d",
4016 m_graphic ? m_graphic->count() : -1,
4017 m_graphic ? m_graphic->countBlocks() : -1, dwgr.getError());
4018 int lastError = dwgr.getError();
4019 if (false == success) {
4020 printDwgError(lastError);
4021 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING, "Cannot open DWG file '%s'.",
4022 (const char *)QFile::encodeName(file));
4023 m_errorCode = dwgr.getError();
4024 return false;
4025 }
4026 m_graphic->dwgAdvancedMetadata().markVportsImportedFromDwg();
4027 } else {
4028#endif
4029
4030 m_dxfR = new dxfRW(QFile::encodeName(file));
4031
4032 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::fileImport: reading file");
4033 if (RS_Debug::D_DEBUGGING == RS_DEBUGRS_Debug::instance()->getLevel()) {
4034 m_dxfR->setDebug(DRW::DebugLevel::Debug);
4035 }
4036 bool success{false};
4037 if (file.startsWith(":")) {
4038 // load content from resources. It SHOULD be present in resource!
4039 QFile resourceFile(file);
4040 if (resourceFile.open(QIODevice::ReadOnly)) {
4041 QByteArray contentString = resourceFile.readAll();
4042 resourceFile.close();
4043 std::string content = contentString.toStdString();
4044 success = m_dxfR->readAscii(this, true, content);
4045 }
4046 } else {
4047 success = m_dxfR->read(this, true);
4048 }
4049 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::fileImport: reading file: OK");
4050 if (success) {
4051 std::cout << "DXF file version: "
4052 << printDwgVersion(m_dxfR->getVersion()).toStdString() << "\n";
4053 }
4054 // graphic->setAutoUpdateBorders(true);
4055
4056 if (false == success) {
4057 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING, "Cannot open DXF file '%s'.",
4058 (const char *)QFile::encodeName(file));
4059 m_errorCode = m_dxfR->getError();
4060 delete m_dxfR;
4061 return false;
4062 } else {
4063 delete m_dxfR;
4064 }
4065#ifdef DWGSUPPORT1
4066 }
4067#endif
4068
4069 /*set current layer */
4070 auto cl = m_graphic->findLayer(m_graphic->getVariableString("$CLAYER", "0"));
4071 if (cl) {
4072 // require to notify
4073 m_graphic->activateLayer(cl, true);
4074 }
4075 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::fileImport: updating inserts");
4076 const auto updateInsertsStart = std::chrono::steady_clock::now();
4077
4078 // BLOCK_CONTROL order is not a dependency order. Resolve definition-owned
4079 // INSERTs first, from their referenced blocks outward, so model-space
4080 // INSERT expansion never snapshots an unresolved nested definition.
4081 std::set<RS_Block *> resolvedBlocks;
4082 std::set<RS_Block *> resolvingBlocks;
4083 const auto updateBlockInserts = [&](auto &&self, RS_Block *block) -> void {
4084 if (block == nullptr || resolvedBlocks.find(block) != resolvedBlocks.end())
4085 return;
4086 if (!resolvingBlocks.insert(block).second)
4087 return; // RS_Insert::update() will reject the reference cycle.
4088 for (RS_Entity *entity : *block) {
4089 if (entity == nullptr || entity->rtti() != RS2::EntityInsert)
4090 continue;
4091 self(self, static_cast<RS_Insert *>(entity)->getBlockForInsert());
4092 }
4093 block->updateInserts();
4094 resolvingBlocks.erase(block);
4095 resolvedBlocks.insert(block);
4096 };
4097 for (unsigned i = 0; i < m_graphic->countBlocks(); ++i)
4098 updateBlockInserts(updateBlockInserts, m_graphic->blockAt(i));
4099 m_graphic->updateInserts();
4100 if (std::getenv("LC_IMPORT_BENCH") != nullptr) {
4101 const auto updateInsertsMs =
4102 std::chrono::duration_cast<std::chrono::milliseconds>(
4103 std::chrono::steady_clock::now() - updateInsertsStart)
4104 .count();
4105 int modelInserts = 0;
4106 for (RS_Entity *e : *m_graphic) {
4107 if (e != nullptr && e->rtti() == RS2::EntityInsert)
4108 ++modelInserts;
4109 }
4110 std::cerr << "[import-bench] updateInserts_ms=" << updateInsertsMs
4111 << " blocks=" << m_graphic->countBlocks()
4112 << " model_entities=" << m_graphic->count()
4113 << " model_inserts=" << modelInserts << "\n";
4114 }
4115
4116 // Orphan XREF detection. An XREF block whose contents were embedded
4117 // is still invisible in modelspace unless something INSERTs it.
4118 // AutoCAD typically renders these through a paper-space layout
4119 // viewport, which LibreCAD doesn't implement, so the user should
4120 // know why the externally-referenced geometry isn't visible.
4121 // Only run for the outer fileImport (m_xrefStack empty); skip when
4122 // recursively loading an XREF source.
4123 if (m_xrefStack.empty() && !m_xrefBlockNames.empty()) {
4124 std::set<QString> referenced;
4125 for (auto *e : *m_graphic) {
4126 if (e && e->rtti() == RS2::EntityInsert) {
4127 referenced.insert(static_cast<RS_Insert *>(e)->getName());
4128 }
4129 }
4130 QStringList orphans;
4131 for (const QString &xrefName : m_xrefBlockNames) {
4132 if (!referenced.count(xrefName))
4133 orphans << xrefName;
4134 }
4135 if (!orphans.isEmpty()) {
4136 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
4137 QObject::tr("DWG/DXF load: %1 XREF block(s) (%2) loaded but not "
4138 "INSERTed into modelspace. Their externally-referenced "
4139 "geometry won't be visible — AutoCAD typically renders "
4140 "these through a paper-space layout viewport, which "
4141 "LibreCAD doesn't render.")
4142 .arg(orphans.size())
4143 .arg(orphans.join(QStringLiteral(", ")(QString(QtPrivate::qMakeStringPrivate(u"" ", "))))));
4144 }
4145 }
4146
4147 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::fileImport OK");
4148 return true;
4149}
4150
4151/**
4152 * Implementation of the method which handles layers.
4153 */
4154RS_Layer *
4155RS_FilterDXFRW::importLayerForEntity(const QString &layName,
4156 const std::string &rawLayerName) {
4157 // Fast path: most entities repeat one of a handful of distinct layer
4158 // names, so a raw-string hit here skips the NFC-normalization pass below
4159 // entirely. A raw-key hit can only return a layer that was itself
4160 // resolved (and validated) via the normalized-key path below for this
4161 // exact byte-identical string, so there is no correctness difference
4162 // from a miss -- just a skipped normalization.
4163 auto rawCached = m_importLayerRawCache.constFind(layName);
4164 if (rawCached != m_importLayerRawCache.constEnd())
4165 return rawCached.value();
4166
4167 const QString key = layName.normalized(QString::NormalizationForm_C);
4168 auto cached = m_importLayerCache.constFind(key);
4169 if (cached != m_importLayerCache.constEnd()) {
4170 m_importLayerRawCache.insert(layName, cached.value());
4171 return cached.value();
4172 }
4173
4174 RS_Layer *layer = m_graphic->findLayer(layName);
4175 if (layer == nullptr) {
4176 DRW_Layer lay;
4177 lay.name = rawLayerName;
4178 addLayer(lay);
4179 layer = m_graphic->findLayer(layName);
4180 }
4181 if (layer != nullptr) {
4182 m_importLayerCache.insert(key, layer);
4183 m_importLayerRawCache.insert(layName, layer);
4184 }
4185 return layer;
4186}
4187
4188void RS_FilterDXFRW::addLayer(const DRW_Layer &data) {
4189 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLayer");
4190 RS_DEBUGRS_Debug::instance()->print(" adding layer: %s", data.name.c_str());
4191
4192 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLayer: creating layer");
4193
4194 QString name = QString::fromUtf8(data.name.c_str());
4195 const QString key = name.normalized(QString::NormalizationForm_C);
4196 if (name != "0") {
4197 auto cached = m_importLayerCache.constFind(key);
4198 if (cached != m_importLayerCache.constEnd())
4199 return;
4200 RS_Layer *existing = m_graphic->findLayer(name);
4201 if (existing != nullptr) {
4202 m_importLayerCache.insert(key, existing);
4203 return;
4204 }
4205 }
4206 auto *layer = new RS_Layer(name);
4207 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLayer: set pen");
4208 layer->setPen(attributesToPen(&data));
4209
4210 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLayer: flags");
4211 if (data.flags & 0x01) {
4212 layer->freeze(true);
4213 }
4214 if (data.flags & 0x04) {
4215 layer->lock(true);
4216 }
4217 layer->setPrint(data.plotF);
4218
4219 // parse extended data to read construction flag
4220 if (!data.extData.empty()) {
4221 if (!isIgnorableLayerExtData(data.extData)) {
4222 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_DEBUGGING,
4223 "RS_FilterDXFRW::addLayer: layer %s has extended data",
4224 layer->getName().toStdString().c_str());
4225 }
4226 if (classifyLayerConstructionMarker(data.extData) ==
4227 LayerConstructionMarkerStatus::Valid) {
4228 layer->setConstruction(true);
4229 }
4230 }
4231 // pre dxfrw 0.5.13 plot flag are used to store construction layer
4232 if (m_isLibDxfRw && m_libDxfRwVersion < LIBDXFRW_VERSION(0, 5, 13)(((0) << 16) | ((5) << 8) | (13))) {
4233 layer->setConstruction(!data.plotF);
4234 }
4235
4236 if (layer->isConstruction()) {
4237 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
4238 "RS_FilterDXF::addLayer: layer %s is construction layer",
4239 layer->getName().toStdString().c_str());
4240 }
4241
4242 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLayer: add layer to graphic");
4243 m_graphic->addLayer(layer);
4244 // RS_LayerList::add() deletes `layer` and updates the existing layer in
4245 // place instead when one with this name already exists (documented on
4246 // that function) - which happens for "0" on every import, since
4247 // RS_Graphic always bootstraps a default "0" layer before this runs.
4248 // Re-resolve rather than trusting `layer`, which may already be freed.
4249 layer = m_graphic->findLayer(name);
4250 m_graphic->dwgAdvancedMetadata().addLayerTableEntry(data);
4251 m_importLayerCache.insert(key, layer);
4252 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLayer: OK");
4253}
4254
4255void RS_FilterDXFRW::addLType(const DRW_LType &data) {
4256 if (m_graphic != nullptr)
4257 m_graphic->dwgAdvancedMetadata().addLineTypeName(data);
4258}
4259
4260void RS_FilterDXFRW::addTextStyle(const DRW_Textstyle &data) {
4261 if (m_graphic != nullptr)
4262 m_graphic->dwgAdvancedMetadata().addTextStyleName(data);
4263}
4264
4265void RS_FilterDXFRW::addAppId(const DRW_AppId &data) {
4266 if (m_graphic != nullptr)
4267 m_graphic->dwgAdvancedMetadata().addAppIdTableEntry(data);
4268}
4269
4270/**
4271 * Implementation of the method which handles dimension styles.
4272 */
4273void RS_FilterDXFRW::addDimStyle(const DRW_Dimstyle &data) {
4274 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addLayer");
4275 m_graphic->dwgAdvancedMetadata().addDimStyleTableEntry(data);
4276 QString dimStyleName = m_graphic->getVariableString("$DIMSTYLE", "standard");
4277
4278 if (QString::compare(data.name.c_str(), dimStyleName, Qt::CaseInsensitive) ==
4279 0) {
4280 if (m_isLibDxfRw && m_libDxfRwVersion < LIBDXFRW_VERSION(0, 6, 2)(((0) << 16) | ((6) << 8) | (2))) {
4281 m_graphic->addVariable(
4282 "$DIMDEC",
4283 m_graphic->getVariableInt("$DIMDEC",
4284 m_graphic->getVariableInt("$LUPREC", 4)),
4285 70);
4286 m_graphic->addVariable(
4287 "$DIMADEC",
4288 m_graphic->getVariableInt("$DIMADEC",
4289 m_graphic->getVariableInt("$AUPREC", 2)),
4290 70);
4291 // do nothing;
4292 } else {
4293 m_graphic->addVariable("$DIMDEC", data.dimdec, 70);
4294 m_graphic->addVariable("$DIMADEC", data.dimadec, 70);
4295 }
4296 }
4297
4298 LC_DimStyle *dimStyle = createDimStyle(data);
4299 m_graphic->addDimStyle(dimStyle);
4300}
4301
4302// todo - sand - ucs - rework to direct setup of LC_GraphicViewport instead of
4303// RS_GraphicView But this modification requires cleanup of the overall file
4304// open flow, so leave it as it is for now
4305/**
4306 * Implementation of the method which handles vports.
4307 */
4308void RS_FilterDXFRW::addVport(const DRW_Vport &data) {
4309 if (m_graphic != nullptr)
4310 m_graphic->dwgAdvancedMetadata().addVport(data);
4311 QString name = QString::fromStdString(data.name);
4312 if (name.toLower() == "*active") {
4313 data.grid == 1 ? m_graphic->setGridOn(true) : m_graphic->setGridOn(false);
4314 m_graphic->setIsometricGrid(data.snapStyle);
4315 m_graphic->setIsoView(static_cast<RS2::IsoGridViewType>(data.snapIsopair));
4316 const RS_GraphicView *gv =
4317 m_graphic->getGraphicView(); // fixme - sand - review this dependency
4318 if (gv != nullptr) {
4319 const double width = data.height * data.ratio;
4320 // todo - sand - ucs - investigate support/usage of different x and y
4321 // factors.
4322 double factorX = gv->getWidth() / width;
4323 const double factorY = gv->getHeight() / data.height;
4324 if (factorX > factorY) {
4325 factorX = factorY;
4326 }
4327 const int ox = gv->getWidth() - data.center.x * 2 * factorX;
4328 const int oy = gv->getHeight() - data.center.y * 2 * factorX;
4329 gv->getViewPort()->justSetOffsetAndFactor(ox, oy, factorX);
4330 }
4331 }
4332}
4333
4334void RS_FilterDXFRW::addUCS(const DRW_UCS &data) {
4335 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addUCS");
4336 RS_DEBUGRS_Debug::instance()->print(" adding ucs: %s", data.name.c_str());
4337 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addUCS: creating ucs");
4338
4339 const QString name = QString::fromUtf8(data.name.c_str());
4340 if (m_graphic != nullptr)
4341 m_graphic->dwgAdvancedMetadata().addUcs(data);
4342 if (!name.isEmpty() && m_graphic->findNamedUCS(name) != nullptr) {
4343 const int existingIndex = m_graphic->getUCSList()->getIndex(name);
4344 m_graphic->dwgAdvancedMetadata().mapUcsToDocumentItem(
4345 data.handle, data.name, existingIndex);
4346 return;
4347 }
4348
4349 auto *ucs = new LC_UCS(name);
4350 const auto origin = RS_Vector(data.origin.x, data.origin.y, data.origin.z);
4351 ucs->setOrigin(origin);
4352
4353 ucs->setElevation(data.elevation);
4354 ucs->setOrthoType(data.orthoType);
4355
4356 const auto orthoOrigin =
4357 RS_Vector(data.orthoOrigin.x, data.orthoOrigin.y, data.orthoOrigin.z);
4358 ucs->setOrthoOrigin(orthoOrigin);
4359
4360 const auto xAxis = RS_Vector(data.xAxisDirection.x, data.xAxisDirection.y,
4361 data.xAxisDirection.z);
4362 ucs->setXAxis(xAxis);
4363
4364 const auto yAxis = RS_Vector(data.yAxisDirection.x, data.yAxisDirection.y,
4365 data.yAxisDirection.z);
4366 ucs->setYAxis(yAxis);
4367
4368 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addUCS: add ucs to graphic");
4369 m_graphic->addUCS(ucs);
4370 const int documentItemIndex = m_graphic->getUCSList()->getIndex(name);
4371 m_graphic->dwgAdvancedMetadata().mapUcsToDocumentItem(data.handle, data.name,
4372 documentItemIndex);
4373 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addUCS: OK");
4374}
4375
4376void RS_FilterDXFRW::addView(const DRW_View &data) {
4377 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addView");
4378 RS_DEBUGRS_Debug::instance()->print(" adding view: %s", data.name.c_str());
4379 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addView: creating view");
4380
4381 if (m_graphic != nullptr) {
4382 m_graphic->dwgAdvancedMetadata().addView(data);
4383 }
4384 QString name = QString::fromUtf8(data.name.c_str());
4385 if (!name.isEmpty() && m_graphic->findNamedView(name) != nullptr) {
4386 const int existingIndex = m_graphic->getViewList()->getIndex(name);
4387 m_graphic->dwgAdvancedMetadata().mapViewToDocumentItem(
4388 data.handle, data.name, existingIndex);
4389 return;
4390 }
4391 auto *view = new LC_View(name);
4392 const auto center = RS_Vector(data.center.x, data.center.y, data.center.z);
4393 view->setCenter(center);
4394
4395 const auto size = RS_Vector(data.size.x, data.size.y, data.size.z);
4396 view->setSize(size);
4397
4398 const auto targetPoint =
4399 RS_Vector(data.targetPoint.x, data.targetPoint.y, data.targetPoint.z);
4400 view->setTargetPoint(targetPoint);
4401
4402 const auto viewDirection =
4403 RS_Vector(data.viewDirectionFromTarget.x, data.viewDirectionFromTarget.y,
4404 data.viewDirectionFromTarget.z);
4405 view->setViewDirection(viewDirection);
4406
4407 view->setLensLen(data.lensLen);
4408 view->setCameraPlottable(data.cameraPlottable);
4409
4410 view->setRenderMode(data.renderMode);
4411 view->setBackClippingPlaneOffset(data.backClippingPlaneOffset);
4412 view->setFrontClippingPlaneOffset(data.frontClippingPlaneOffset);
4413 view->setTwistAngle(data.twistAngle);
4414 view->setFlags(data.flags); // todo - review, use differ properties?
4415 view->setViewMode(
4416 data.viewMode); // todo - probably it might be simpler than long?
4417
4418 if (data.hasUCS) {
4419 const auto ucs = new LC_UCS();
4420
4421 const auto ucsOrigin =
4422 RS_Vector(data.ucsOrigin.x, data.ucsOrigin.y, data.ucsOrigin.z);
4423 const auto ucsXAxis =
4424 RS_Vector(data.ucsXAxis.x, data.ucsXAxis.y, data.ucsXAxis.z);
4425 const auto ucsYAxis =
4426 RS_Vector(data.ucsYAxis.x, data.ucsYAxis.y, data.ucsYAxis.z);
4427 ucs->setOrthoOrigin(RS_Vector(false));
4428 ucs->setOrigin(ucsOrigin);
4429 ucs->setXAxis(ucsXAxis);
4430 ucs->setYAxis(ucsYAxis);
4431 ucs->setElevation(data.ucsElevation);
4432 ucs->setOrthoType(data.ucsOrthoType);
4433 view->setUCS(ucs);
4434 }
4435
4436 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addView: set pen");
4437
4438 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addView: add view to graphic");
4439 m_graphic->addNamedView(view);
4440 const int documentItemIndex = m_graphic->getViewList()->getIndex(name);
4441 m_graphic->dwgAdvancedMetadata().mapViewToDocumentItem(data.handle, data.name,
4442 documentItemIndex);
4443 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addView: OK");
4444}
4445
4446void RS_FilterDXFRW::addVisualStyle(const DRW_VisualStyle &data) {
4447 if (m_graphic != nullptr)
4448 m_graphic->dwgAdvancedMetadata().addVisualStyle(data);
4449}
4450
4451/**
4452 * Implementation of the method which handles blocks.
4453 *
4454 * @todo Adding blocks to blocks (stack for m_currentContainer)
4455 */
4456void RS_FilterDXFRW::addBlock(const DRW_Block &data) {
4457 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addBlock");
4458 RS_DEBUGRS_Debug::instance()->print(" adding block: %s", data.name.c_str());
4459 /*TODO correct handle of model-space*/
4460
4461 const QString name = QString::fromUtf8(data.name.c_str());
4462 const QString mid = name.mid(1, 11);
4463 // Prevent special blocks (paper_space, model_space) from being added:
4464 if (mid.toLower() != "paper_space" && mid.toLower() != "model_space") {
4465 RS_Vector bp(data.basePoint.x, data.basePoint.y);
4466 RS_BlockData blockData(name, bp, false);
4467 blockData.previewData = data.previewData;
4468 auto block = new RS_Block(m_graphic, blockData);
4469 block->setInsertionUnits(data.insUnits);
4470 if (data.parentHandle != DRW::NoHandle)
4471 block->setSourceHandle(static_cast<quint32>(data.parentHandle));
4472 // block->setFlags(flags);
4473
4474 if (m_graphic->addBlock(block)) {
4475 m_currentContainer = block;
4476 m_blockHash.insert(data.parentHandle, m_currentContainer);
4477
4478 // Bound/unbound XREF: load the external file and embed its
4479 // modelspace contents into this block. Layers are namespaced
4480 // `<blockName>|<layerName>` per AutoCAD BIND convention.
4481 // Bit 0x04 = XREF, bit 0x08 = XREF overlay.
4482 const bool isXref = (data.flags & 0x0c) != 0;
4483 if (isXref) {
4484 m_xrefBlockNames.insert(name);
4485 if (!data.xrefPath.empty()) {
4486 embedXref(block, QString::fromUtf8(data.xrefPath.c_str()), name);
4487 }
4488 }
4489 } else {
4490 // RS_BlockList::add returns false on name collision and
4491 // deletes the block. Without updating m_currentContainer,
4492 // subsequent addEntity calls would leak into whatever the
4493 // previous endBlock left behind (typically m_graphic). Route
4494 // them to m_dummyContainer so the failure is contained.
4495 m_blockHash.insert(data.parentHandle, m_dummyContainer);
4496 m_currentContainer = m_dummyContainer;
4497 }
4498 } else {
4499 if (mid.toLower() == "model_space") {
4500 m_blockHash.insert(data.parentHandle, m_graphic);
4501 } else {
4502 m_blockHash.insert(data.parentHandle, m_dummyContainer);
4503 }
4504 }
4505}
4506
4507QString RS_FilterDXFRW::resolveXrefPath(const QString &xrefPath) const {
4508 if (xrefPath.isEmpty())
4509 return {};
4510
4511 // Normalize separators: XREFs authored on Windows store backslashes
4512 // that QFileInfo doesn't recognize on Unix.
4513 QString normalized = xrefPath;
4514 normalized.replace('\\', '/');
4515
4516 // 1. Try the stored path verbatim (could be absolute on this host).
4517 if (QFileInfo::exists(normalized))
4518 return QFileInfo(normalized).absoluteFilePath();
4519
4520 QFileInfo host(m_file);
4521 const QString hostDir = host.absolutePath();
4522
4523 // 2. Treat stored path as relative to host file's directory.
4524 {
4525 const QString f = hostDir + "/" + normalized;
4526 if (QFileInfo::exists(f))
4527 return QFileInfo(f).absoluteFilePath();
4528 }
4529
4530 // 3. host-dir + basename (most common: XREF was authored on a
4531 // different machine but file shipped alongside the host).
4532 const QString basename = QFileInfo(normalized).fileName();
4533 {
4534 const QString f = hostDir + "/" + basename;
4535 if (QFileInfo::exists(f))
4536 return QFileInfo(f).absoluteFilePath();
4537 }
4538
4539 // 4-5. Tolerant match in host-dir: case-insensitive + space-to-underscore
4540 // normalization on the stem; accept .dwg or .dxf extension.
4541 const QString basenameNorm = basename.toLower().replace(' ', '_');
4542 QFileInfo basenameInfo(basenameNorm);
4543 const QString stemNorm = basenameInfo.completeBaseName();
4544 QDir dir(hostDir);
4545 const QStringList entries =
4546 dir.entryList({QStringLiteral("*.dwg")(QString(QtPrivate::qMakeStringPrivate(u"" "*.dwg"))), QStringLiteral("*.DWG")(QString(QtPrivate::qMakeStringPrivate(u"" "*.DWG"))),
4547 QStringLiteral("*.dxf")(QString(QtPrivate::qMakeStringPrivate(u"" "*.dxf"))), QStringLiteral("*.DXF")(QString(QtPrivate::qMakeStringPrivate(u"" "*.DXF")))},
4548 QDir::Files);
4549 for (const QString &entry : entries) {
4550 const QString entryNorm = entry.toLower().replace(' ', '_');
4551 if (entryNorm == basenameNorm) {
4552 return dir.absoluteFilePath(entry);
4553 }
4554 QFileInfo entryInfo(entry);
4555 const QString entryStemNorm =
4556 entryInfo.completeBaseName().toLower().replace(' ', '_');
4557 if (entryStemNorm == stemNorm) {
4558 return dir.absoluteFilePath(entry);
4559 }
4560 }
4561
4562 return {};
4563}
4564
4565bool RS_FilterDXFRW::embedXref(RS_Block *block, const QString &xrefPath,
4566 const QString &blockName) {
4567 if (!block)
4568 return false;
4569 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::embedXref: %s -> %s", qPrintable(blockName)QtPrivate::asString(blockName).toLocal8Bit().constData(),
4570 qPrintable(xrefPath)QtPrivate::asString(xrefPath).toLocal8Bit().constData());
4571
4572 const QString resolved = resolveXrefPath(xrefPath);
4573 if (resolved.isEmpty()) {
4574 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
4575 QObject::tr("XREF not resolved for block \"%1\": %2 (file not found in "
4576 "host directory). The block will render as empty.")
4577 .arg(blockName, xrefPath));
4578 return false;
4579 }
4580
4581 if (m_xrefStack.count(resolved) > 0) {
4582 RS_DEBUGRS_Debug::instance()->print(" XREF cycle detected, skipping: %s",
4583 qPrintable(resolved)QtPrivate::asString(resolved).toLocal8Bit().constData());
4584 return false;
4585 }
4586 if (m_xrefStack.size() >= 8) {
4587 RS_DEBUGRS_Debug::instance()->print(" XREF depth limit reached at %s", qPrintable(resolved)QtPrivate::asString(resolved).toLocal8Bit().constData());
4588 return false;
4589 }
4590 m_xrefStack.insert(resolved);
4591
4592 RS_Graphic ext;
4593 RS_FilterDXFRW xrefFilter;
4594 xrefFilter.m_xrefStack = m_xrefStack; // propagate cycle guard
4595 const bool isDwg =
4596 resolved.endsWith(QStringLiteral(".dwg")(QString(QtPrivate::qMakeStringPrivate(u"" ".dwg"))), Qt::CaseInsensitive);
4597 const RS2::FormatType fmt = isDwg ? RS2::FormatDWG : RS2::FormatDXFRW;
4598 const bool ok = xrefFilter.fileImport(ext, resolved, fmt);
4599
4600 m_xrefStack.erase(resolved);
4601
4602 if (!ok) {
4603 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
4604 QObject::tr("XREF load failed for block \"%1\": %2")
4605 .arg(blockName, resolved));
4606 return false;
4607 }
4608
4609 // Layer namespacing: copy the XREF's layers into the host with prefix.
4610 RS_LayerList *extLayers = ext.getLayerList();
4611 RS_LayerList *hostLayers = m_graphic ? m_graphic->getLayerList() : nullptr;
4612 if (extLayers && hostLayers) {
4613 for (unsigned i = 0; i < extLayers->count(); ++i) {
4614 RS_Layer *extLyr = extLayers->at(i);
4615 if (!extLyr)
4616 continue;
4617 const QString nsName = blockName + "|" + extLyr->getName();
4618 if (!hostLayers->find(nsName)) {
4619 RS_Layer *clone = extLyr->clone();
4620 clone->setName(nsName);
4621 hostLayers->add(clone);
4622 }
4623 }
4624 }
4625
4626 // Per-entity clone helper: clone @p src, redirect layer pointer to the
4627 // host's namespaced layer, and (if it's an INSERT) rewrite its block
4628 // name reference to the namespaced form. Returns the cloned entity
4629 // owned by no parent yet; caller adds it to the target container.
4630 auto cloneAndRedirect = [&](const RS_Entity *src,
4631 RS_EntityContainer *target) -> RS_Entity * {
4632 if (!src)
4633 return nullptr;
4634 RS_Entity *cloned = src->clone();
4635 if (!cloned)
4636 return nullptr;
4637 cloned->setParent(target);
4638 if (hostLayers && cloned->getLayer()) {
4639 const QString lyrNs = blockName + "|" + cloned->getLayer()->getName();
4640 if (auto *hostLyr = hostLayers->find(lyrNs)) {
4641 cloned->setLayer(hostLyr);
4642 }
4643 }
4644 if (cloned->rtti() == RS2::EntityInsert) {
4645 auto *ins = static_cast<RS_Insert *>(cloned);
4646 const QString ref = ins->getName();
4647 // Don't rewrite if the reference is already namespaced (defensive
4648 // — shouldn't happen for a freshly-loaded XREF source, but harmless).
4649 if (!ref.startsWith(blockName + "|")) {
4650 ins->setName(blockName + "|" + ref);
4651 }
4652 }
4653 return cloned;
4654 };
4655
4656 // Block-def propagation: clone every block definition from the XREF
4657 // source into the host's blockList with namespaced name. Without this,
4658 // any cloned RS_Insert (whether at modelspace top-level or inside a
4659 // cloned block def) would reference a block name that doesn't exist
4660 // in the host, leaving a dangling reference.
4661 RS_BlockList *extBlocks = ext.getBlockList();
4662 RS_BlockList *hostBlocks = m_graphic ? m_graphic->getBlockList() : nullptr;
4663 int blockDefsCopied = 0;
4664 if (extBlocks && hostBlocks) {
4665 for (int i = 0; i < extBlocks->count(); ++i) {
4666 RS_Block *extBk = extBlocks->at(i);
4667 if (!extBk)
4668 continue;
4669 const QString origName = extBk->getName();
4670 // Skip special blocks — *MODEL_SPACE/*PAPER_SPACE aren't in
4671 // blockList anyway, but be defensive.
4672 if (origName.startsWith(QLatin1String("*Model_Space"),
4673 Qt::CaseInsensitive) ||
4674 origName.startsWith(QLatin1String("*Paper_Space"),
4675 Qt::CaseInsensitive)) {
4676 continue;
4677 }
4678 const QString nsName = blockName + "|" + origName;
4679 if (hostBlocks->find(nsName))
4680 continue; // already present
4681
4682 auto nsBlock = std::make_unique<RS_Block>(
4683 m_graphic, RS_BlockData(nsName, extBk->getBasePoint(), false));
4684 for (auto *e : *extBk) {
4685 if (auto *cloned = cloneAndRedirect(e, nsBlock.get())) {
4686 nsBlock->addEntity(cloned);
4687 }
4688 }
4689 // RS_BlockList::add takes ownership on success and deletes on failure,
4690 // so either way the raw pointer is consumed once released here.
4691 if (!hostBlocks->add(nsBlock.release())) {
4692 // Name collision against an existing host block — already deleted.
4693 } else {
4694 ++blockDefsCopied;
4695 }
4696 }
4697 }
4698
4699 // Move modelspace entities (top-level) into the local block, with
4700 // their layer pointers + INSERT block-name references redirected.
4701 int moved = 0;
4702 QList<RS_Entity *> snapshot;
4703 for (auto *e : ext)
4704 snapshot.push_back(e);
4705 for (RS_Entity *e : snapshot) {
4706 if (auto *cloned = cloneAndRedirect(e, block)) {
4707 block->addEntity(cloned);
4708 ++moved;
4709 }
4710 }
4711
4712 RS_DEBUGRS_Debug::instance()->print(" XREF embedded: %d entities + %d block defs into "
4713 "block \"%s\"",
4714 moved, blockDefsCopied, qPrintable(blockName)QtPrivate::asString(blockName).toLocal8Bit().constData());
4715 return true;
4716}
4717
4718void RS_FilterDXFRW::setBlock(const int handle) {
4719 if (m_blockHash.contains(handle)) {
4720 m_currentContainer = m_blockHash.value(handle);
4721 } else {
4722 m_currentContainer = m_graphic;
4723 }
4724 // Never leave a null container: subsequent addEntity would SIGSEGV.
4725 // Fall back to the import sink used for paper-space / name collisions.
4726 if (m_currentContainer == nullptr) {
4727 m_currentContainer =
4728 m_dummyContainer != nullptr ? m_dummyContainer : m_graphic;
4729 }
4730}
4731
4732/**
4733 * Implementation of the method which closes blocks.
4734 */
4735void RS_FilterDXFRW::endBlock() {
4736 if (m_currentContainer != nullptr &&
4737 m_currentContainer->rtti() == RS2::EntityBlock) {
4738 auto bk = static_cast<RS_Block *>(m_currentContainer);
4739 // remove unnamed blocks *D only if version != R12
4740 if (m_version != 1009 && m_graphic != nullptr) {
4741 if (bk->getName().startsWith("*D")) {
4742 m_graphic->removeBlock(bk);
4743 }
4744 }
4745 }
4746 m_currentContainer = m_graphic;
4747}
4748
4749namespace {
4750// F2 type-fidelity sidecar markers. POINT and LINE coordinates are WCS, but
4751// LibreCAD's 2D entities cannot retain their Z values, thickness, or arbitrary
4752// extrusion direction. Keep those fields separately for native re-emission.
4753constexpr const char *kPointExtrusionMarker = "LibreCAD_POINT_EXTRUSION";
4754constexpr const char *kLineExtrusionMarker = "LibreCAD_LINE_EXTRUSION";
4755// CIRCLE centers are OCS data. Preserve plane metadata so editable planar
4756// circles can be transformed back to their source OCS on export.
4757constexpr const char *kCircleOcsMarker = "LibreCAD_CIRCLE_OCS";
4758// ARC center and angles are OCS data. Keep its plane metadata separately so
4759// the editable 2D arc can be transformed back to the source OCS on export.
4760constexpr const char *kArcOcsMarker = "LibreCAD_ARC_OCS";
4761// 3DLINE is distinct from LINE even when its normal and thickness are default.
4762constexpr const char *k3DLineMarker = "LibreCAD_3DLINE";
4763// HATCH boundary coordinates remain raw OCS in libdxfrw. Preserve the
4764// accompanying elevation/normal so the writer does not relabel them as WCS.
4765constexpr const char *kHatchExtrusionMarker = "LibreCAD_HATCH_EXTRUSION";
4766// IMAGE has no extrusion normal. Its insertion and pixel U/V vectors are WCS,
4767// while RS_Image exposes a 2D-editable frame, so retain their source Z values
4768// separately for native output.
4769constexpr const char *kImageFrameMarker = "LibreCAD_IMAGE_FRAME";
4770constexpr const char *kTextOcsMarker = "LibreCAD_TEXT_OCS";
4771constexpr const char *kMTextOcsMarker = "LibreCAD_MTEXT_OCS";
4772constexpr const char *kRayMarker = "LibreCAD_RAY";
4773constexpr const char *kXlineMarker = "LibreCAD_XLINE";
4774constexpr const char *kTraceMarker = "LibreCAD_TRACE";
4775constexpr const char *kSolidMarker = "LibreCAD_SOLID";
4776constexpr const char *k3dFaceMarker = "LibreCAD_3DFACE";
4777// RS_Insert expands its block into derived children and therefore cannot own
4778// imported ATTRIB entities directly. Keep the rendered child tagged with its
4779// INSERT identity so the writer can rebuild the native child sequence.
4780constexpr const char *kInsertAttribMarker = "LibreCAD_INSERT_ATTRIB";
4781constexpr const char *kInsertAttribMTextMarker = "LibreCAD_INSERT_ATTRIB_MTEXT";
4782constexpr const char *kInsertAttribMTextXDataMarker =
4783 "LibreCAD_INSERT_ATTRIB_MTEXT_XDATA";
4784constexpr const char *kInsertAttribMTextCommonMarker =
4785 "LibreCAD_INSERT_ATTRIB_MTEXT_COMMON";
4786constexpr int kInsertAttribMTextXDataSchema = 1;
4787constexpr int kInsertAttribMTextCommonSchema = 2;
4788constexpr int kMaxInsertAttribMTextXDataValues = 4096;
4789constexpr int kMaxInsertAttribMTextReactors = 4096;
4790
4791enum class DwgTypedConversionFixedRoute : std::uint8_t {
4792 PointExtrusion,
4793 LineExtrusion,
4794 Circle,
4795 Arc,
4796 Ray,
4797 Xline,
4798 Trace,
4799 Solid,
4800 Face3d
4801};
4802
4803struct DwgTypedConversionFixedDescriptor {
4804 DwgTypedConversionFixedRoute route;
4805 std::uint16_t wireType;
4806};
4807
4808enum class TypedConversionSidecarKind : std::uint8_t {
4809 PointExtrusion,
4810 LineExtrusion,
4811 Circle,
4812 Arc,
4813 ThreeDLine,
4814 Ray,
4815 Xline,
4816 Trace,
4817 Solid,
4818 Face3d
4819};
4820
4821struct TypedConversionSidecar {
4822 TypedConversionSidecarKind kind;
4823 QString marker;
4824 std::array<DRW_Coord, 4> coords{};
4825 double thickness{0.0};
4826 double pointXAxisAngle{0.0};
4827 int flag{0};
4828};
4829
4830std::optional<TypedConversionSidecarKind>
4831typedConversionSidecarKind(const std::string &marker) {
4832 if (marker == kPointExtrusionMarker)
4833 return TypedConversionSidecarKind::PointExtrusion;
4834 if (marker == kLineExtrusionMarker)
4835 return TypedConversionSidecarKind::LineExtrusion;
4836 if (marker == kCircleOcsMarker)
4837 return TypedConversionSidecarKind::Circle;
4838 if (marker == kArcOcsMarker)
4839 return TypedConversionSidecarKind::Arc;
4840 if (marker == k3DLineMarker)
4841 return TypedConversionSidecarKind::ThreeDLine;
4842 if (marker == kRayMarker)
4843 return TypedConversionSidecarKind::Ray;
4844 if (marker == kXlineMarker)
4845 return TypedConversionSidecarKind::Xline;
4846 if (marker == kTraceMarker)
4847 return TypedConversionSidecarKind::Trace;
4848 if (marker == kSolidMarker)
4849 return TypedConversionSidecarKind::Solid;
4850 if (marker == k3dFaceMarker)
4851 return TypedConversionSidecarKind::Face3d;
4852 return std::nullopt;
4853}
4854
4855std::size_t typedConversionCoordinateCount(TypedConversionSidecarKind kind) {
4856 switch (kind) {
4857 case TypedConversionSidecarKind::PointExtrusion:
4858 case TypedConversionSidecarKind::Circle:
4859 case TypedConversionSidecarKind::Arc:
4860 case TypedConversionSidecarKind::Ray:
4861 case TypedConversionSidecarKind::Xline:
4862 return 2;
4863 case TypedConversionSidecarKind::LineExtrusion:
4864 case TypedConversionSidecarKind::ThreeDLine:
4865 return 3;
4866 case TypedConversionSidecarKind::Trace:
4867 case TypedConversionSidecarKind::Solid:
4868 case TypedConversionSidecarKind::Face3d:
4869 return 4;
4870 }
4871 return 0;
4872}
4873
4874bool typedConversionRequiresThickness(TypedConversionSidecarKind kind) {
4875 return kind == TypedConversionSidecarKind::PointExtrusion ||
4876 kind == TypedConversionSidecarKind::LineExtrusion ||
4877 kind == TypedConversionSidecarKind::ThreeDLine ||
4878 kind == TypedConversionSidecarKind::Circle ||
4879 kind == TypedConversionSidecarKind::Arc ||
4880 kind == TypedConversionSidecarKind::Trace ||
4881 kind == TypedConversionSidecarKind::Solid;
4882}
4883
4884bool typedConversionMatchesEntity(TypedConversionSidecarKind kind,
4885 const RS_Entity *entity) {
4886 if (entity == nullptr)
4887 return false;
4888 switch (kind) {
4889 case TypedConversionSidecarKind::PointExtrusion:
4890 return entity->rtti() == RS2::EntityPoint;
4891 case TypedConversionSidecarKind::LineExtrusion:
4892 case TypedConversionSidecarKind::ThreeDLine:
4893 case TypedConversionSidecarKind::Ray:
4894 case TypedConversionSidecarKind::Xline:
4895 return entity->rtti() == RS2::EntityLine;
4896 case TypedConversionSidecarKind::Circle:
4897 return entity->rtti() == RS2::EntityCircle;
4898 case TypedConversionSidecarKind::Arc:
4899 return entity->rtti() == RS2::EntityArc;
4900 case TypedConversionSidecarKind::Trace:
4901 case TypedConversionSidecarKind::Solid:
4902 return entity->rtti() == RS2::EntitySolid;
4903 case TypedConversionSidecarKind::Face3d:
4904 return entity->rtti() == RS2::EntityPolyline;
4905 }
4906 return false;
4907}
4908
4909// The sidecar is internal, versioned type-fidelity data. Treat it as a
4910// compact record rather than permissive application XDATA: every recognized
4911// marker has one exact schema and a matching editable entity type.
4912std::optional<TypedConversionSidecar>
4913parseTypedConversionSidecar(const RS_Entity *entity) {
4914 if (entity == nullptr || !entity->hasDrwExtData())
4915 return std::nullopt;
4916
4917 TypedConversionSidecar parsed{};
4918 bool found = false;
4919 bool inGroup = false;
4920 std::array<bool, 4> gotCoords{};
4921 bool gotThickness = false;
4922 bool gotPointXAxisAngle = false;
4923 bool gotFlag = false;
4924
4925 for (const auto &value : entity->getDrwExtData()) {
4926 if (value == nullptr) {
4927 if (inGroup)
4928 return std::nullopt;
4929 continue;
4930 }
4931 const int code = value->code();
4932 if (code == 1001) {
4933 std::string marker;
4934 const XDataReadResult markerStatus = readXDataMarker(value.get(), marker);
4935 if (markerStatus != XDataReadResult::Valid) {
4936 if (inGroup)
4937 return std::nullopt;
4938 continue;
4939 }
4940 const auto kind = typedConversionSidecarKind(marker);
4941 if (!kind) {
4942 inGroup = false;
4943 continue;
4944 }
4945 if (found)
4946 return std::nullopt;
4947 found = true;
4948 inGroup = true;
4949 parsed.kind = *kind;
4950 parsed.marker = QString::fromStdString(marker);
4951 continue;
4952 }
4953 if (!inGroup)
4954 continue;
4955
4956 if (code >= 1010 && code <= 1013) {
4957 const std::size_t index = static_cast<std::size_t>(code - 1010);
4958 DRW_Coord coordinate;
4959 const XDataReadResult coordinateStatus =
4960 readXDataCoord(value.get(), code, coordinate);
4961 if (index >= typedConversionCoordinateCount(parsed.kind) ||
4962 gotCoords[index] || coordinateStatus != XDataReadResult::Valid) {
4963 return std::nullopt;
4964 }
4965 parsed.coords[index] = coordinate;
4966 gotCoords[index] = true;
4967 continue;
4968 }
4969
4970 if (code == 1040 && typedConversionRequiresThickness(parsed.kind) &&
4971 !gotThickness) {
4972 double thickness = 0.0;
4973 if (readXDataDouble(value.get(), 1040, thickness) !=
4974 XDataReadResult::Valid)
4975 return std::nullopt;
4976 parsed.thickness = thickness;
4977 gotThickness = true;
4978 continue;
4979 }
4980 if (code == 1041 &&
4981 parsed.kind == TypedConversionSidecarKind::PointExtrusion &&
4982 !gotPointXAxisAngle) {
4983 double xAxisAngle = 0.0;
4984 if (readXDataDouble(value.get(), 1041, xAxisAngle) !=
4985 XDataReadResult::Valid)
4986 return std::nullopt;
4987 parsed.pointXAxisAngle = xAxisAngle;
4988 gotPointXAxisAngle = true;
4989 continue;
4990 }
4991 if (code == 1070 && parsed.kind == TypedConversionSidecarKind::Face3d &&
4992 !gotFlag) {
4993 std::int64_t flag = 0;
4994 if (readXDataInteger(value.get(), 1070, flag, false) !=
4995 XDataReadResult::Valid ||
4996 !fitsXDataInt32(flag))
4997 return std::nullopt;
4998 parsed.flag = static_cast<int>(flag);
4999 gotFlag = true;
5000 continue;
5001 }
5002 return std::nullopt;
5003 }
5004
5005 if (!found || !typedConversionMatchesEntity(parsed.kind, entity))
5006 return std::nullopt;
5007 const std::size_t coordinateCount =
5008 typedConversionCoordinateCount(parsed.kind);
5009 for (std::size_t index = 0; index < coordinateCount; ++index) {
5010 if (!gotCoords[index])
5011 return std::nullopt;
5012 }
5013 if ((typedConversionRequiresThickness(parsed.kind) && !gotThickness) ||
5014 (parsed.kind == TypedConversionSidecarKind::PointExtrusion &&
5015 !gotPointXAxisAngle) ||
5016 (parsed.kind == TypedConversionSidecarKind::Face3d && !gotFlag)) {
5017 return std::nullopt;
5018 }
5019 if ((parsed.kind == TypedConversionSidecarKind::Circle ||
5020 parsed.kind == TypedConversionSidecarKind::Arc) &&
5021 (parsed.coords[1].x != 0.0 || parsed.coords[1].y != 0.0 ||
5022 (parsed.coords[1].z != 1.0 && parsed.coords[1].z != -1.0))) {
5023 return std::nullopt;
5024 }
5025 return parsed;
5026}
5027
5028// Predict only the sidecars reconstructed as one fixed-wire entity. A
5029// malformed or multi-group sidecar must not reserve a receipt for the
5030// ordinary entity fallback; 3DLINE is deliberately handled by its class
5031// descriptor instead.
5032std::optional<DwgTypedConversionFixedDescriptor>
5033findDwgTypedConversionFixedDescriptor(const RS_Entity *entity) {
5034 const auto sidecar = parseTypedConversionSidecar(entity);
5035 if (!sidecar)
5036 return std::nullopt;
5037 switch (sidecar->kind) {
5038 case TypedConversionSidecarKind::PointExtrusion:
5039 return DwgTypedConversionFixedDescriptor{
5040 DwgTypedConversionFixedRoute::PointExtrusion, dwgType::POINT};
5041 case TypedConversionSidecarKind::LineExtrusion:
5042 return DwgTypedConversionFixedDescriptor{
5043 DwgTypedConversionFixedRoute::LineExtrusion, dwgType::LINE};
5044 case TypedConversionSidecarKind::Circle:
5045 return DwgTypedConversionFixedDescriptor{
5046 DwgTypedConversionFixedRoute::Circle, dwgType::CIRCLE};
5047 case TypedConversionSidecarKind::Arc:
5048 return DwgTypedConversionFixedDescriptor{DwgTypedConversionFixedRoute::Arc,
5049 dwgType::ARC};
5050 case TypedConversionSidecarKind::Ray:
5051 return DwgTypedConversionFixedDescriptor{DwgTypedConversionFixedRoute::Ray,
5052 dwgType::RAY};
5053 case TypedConversionSidecarKind::Xline:
5054 return DwgTypedConversionFixedDescriptor{
5055 DwgTypedConversionFixedRoute::Xline, dwgType::XLINE};
5056 case TypedConversionSidecarKind::Trace:
5057 return DwgTypedConversionFixedDescriptor{
5058 DwgTypedConversionFixedRoute::Trace, dwgType::TRACE};
5059 case TypedConversionSidecarKind::Solid:
5060 return DwgTypedConversionFixedDescriptor{
5061 DwgTypedConversionFixedRoute::Solid, dwgType::SOLID};
5062 case TypedConversionSidecarKind::Face3d:
5063 return DwgTypedConversionFixedDescriptor{
5064 DwgTypedConversionFixedRoute::Face3d, dwgType::FACE3D};
5065 case TypedConversionSidecarKind::ThreeDLine:
5066 return std::nullopt;
5067 }
5068 return std::nullopt;
5069}
5070
5071bool hasDwgTypedConversionSidecar(const RS_Entity *entity) {
5072 if (entity == nullptr || !entity->hasDrwExtData())
5073 return false;
5074 for (const auto &value : entity->getDrwExtData()) {
5075 std::string marker;
5076 if (readXDataMarker(value.get(), marker) == XDataReadResult::Valid &&
5077 typedConversionSidecarKind(marker)) {
5078 return true;
5079 }
5080 }
5081 return false;
5082}
5083
5084bool hasValidDwg3DLineSidecar(const RS_Entity *entity) {
5085 const auto sidecar = parseTypedConversionSidecar(entity);
5086 return sidecar && sidecar->kind == TypedConversionSidecarKind::ThreeDLine;
5087}
5088
5089bool needsWcsThicknessSidecar(const DRW_Point &entity) {
5090 return entity.basePoint.z != 0.0 || entity.thickness != 0.0 ||
5091 entity.extPoint.x != 0.0 || entity.extPoint.y != 0.0 ||
5092 entity.extPoint.z != 1.0 || entity.xAxisAngle != 0.0;
5093}
5094
5095bool needsWcsThicknessSidecar(const DRW_Line &entity) {
5096 return needsWcsThicknessSidecar(static_cast<const DRW_Point &>(entity)) ||
5097 entity.secPoint.z != 0.0;
5098}
5099
5100bool needsArcOcsSidecar(const DRW_Arc &entity) {
5101 return entity.basePoint.z != 0.0 || entity.thickness != 0.0 ||
5102 entity.extPoint.x != 0.0 || entity.extPoint.y != 0.0 ||
5103 entity.extPoint.z != 1.0;
5104}
5105
5106bool needsCircleOcsSidecar(const DRW_Circle &entity) {
5107 return entity.basePoint.z != 0.0 || entity.thickness != 0.0 ||
5108 entity.extPoint.x != 0.0 || entity.extPoint.y != 0.0 ||
5109 entity.extPoint.z != 1.0;
5110}
5111
5112DRW_Coord sourceAxialOcsCenter(const DRW_Circle &entity) {
5113 DRW_Coord center = entity.basePoint;
5114 if (entity.extPoint.z < 0.0) {
5115 center.x = -center.x;
5116 center.z = -center.z;
5117 }
5118 return center;
5119}
5120
5121void appendTypeSidecar(RS_Entity *entity, const char *marker,
5122 std::vector<std::shared_ptr<DRW_Variant>> payload);
5123
5124struct InsertAttribSidecar {
5125 unsigned long long ownerId = 0;
5126 int ordinal = -1;
5127 int flags = 0;
5128 int fieldLength = 0;
5129 int attVersion = 0;
5130 int attributeType = 1;
5131 bool lockPosition = false;
5132 std::string tag;
5133 std::string value;
5134 bool hasValue = false;
5135 struct MText {
5136 int schemaVersion = 0;
5137 std::string text;
5138 bool hasText = false;
5139 int textgen = 1;
5140 int alignH = 1;
5141 int alignV = 1;
5142 int linespacingStyle = 1;
5143 double interlin = 1.0;
5144 int backgroundFlags = 0;
5145 double backgroundScale = 0.0;
5146 int backgroundColor = 0;
5147 int backgroundTransparency = 0;
5148 bool r2018IsNotAnnotative = false;
5149 int r2018Version = 0;
5150 bool r2018DefaultFlag = false;
5151 std::uint32_t r2018AppIdHandle = 0;
5152 int r2018Attachment = 0;
5153 DRW_Coord r2018XAxisDir;
5154 DRW_Coord r2018InsertionPoint;
5155 double r2018RectWidth = 0.0;
5156 double r2018RectHeight = 0.0;
5157 double r2018ExtentsHeight = 0.0;
5158 double r2018ExtentsWidth = 0.0;
5159 int r2018ColumnType = 0;
5160 int r2018ColumnCount = 0;
5161 double r2018ColumnWidth = 0.0;
5162 double r2018ColumnGutter = 0.0;
5163 bool r2018ColumnAutoHeight = false;
5164 bool r2018ColumnFlowReversed = false;
5165 std::vector<double> r2018ColumnHeights;
5166 std::vector<std::uint8_t> annotativeData;
5167 int annotativeUnknown = 0;
5168 std::uint32_t annotativeAppHandle = 0;
5169 bool reallyLocked = false;
5170 std::uint32_t styleHandle = 0;
5171 bool hasDataStorageBinaryData = false;
5172 std::vector<std::uint32_t> reactorHandles;
5173 std::uint32_t xDictHandle = 0;
5174 int color = DRW::ColorByLayer;
5175 int color24 = -1;
5176 std::string colorName;
5177 int transparency = DRW::Opaque;
5178 bool hasAcDbColorHandle = false;
5179 std::uint32_t acDbColorHandle = 0;
5180 double ltypeScale = 1.0;
5181 std::string lineType = "BYLAYER";
5182 bool visible = true;
5183 int lineWeight = static_cast<int>(DRW_LW_Conv::widthByLayer);
5184 std::uint32_t material = DRW::MaterialByLayer;
5185 int shadow = static_cast<int>(DRW::CastAndReceieveShadows);
5186 std::uint32_t shadowHandle = 0;
5187 std::uint32_t plotStyle = DRW::DefaultPlotStyle;
5188 std::uint32_t fullVisualStyleHandle = 0;
5189 std::uint32_t faceVisualStyleHandle = 0;
5190 std::uint32_t edgeVisualStyleHandle = 0;
5191 std::uint32_t layerHandle = 0;
5192 std::uint32_t linetypeHandle = 0;
5193 std::vector<std::shared_ptr<DRW_Variant>> xdata;
5194 DRW_Coord basePoint;
5195 DRW_Coord secPoint;
5196 DRW_Coord extPoint{0.0, 0.0, 1.0};
5197 bool hasBasePoint = false;
5198 bool hasSecPoint = false;
5199 bool hasExtPoint = false;
5200 } mtext;
5201 bool hasMText = false;
5202 DRW_Coord basePoint;
5203 DRW_Coord secPoint;
5204 DRW_Coord extPoint{0.0, 0.0, 1.0};
5205 double thickness = 0.0;
5206 bool hasBasePoint = false;
5207 bool hasSecPoint = false;
5208 bool hasExtPoint = false;
5209 bool hasThickness = false;
5210};
5211} // namespace
5212
5213/**
5214 * Implementation of the method which handles point entities.
5215 */
5216void RS_FilterDXFRW::addPoint(const DRW_Point &data) {
5217 const RS_Vector v(data.basePoint.x, data.basePoint.y);
5218 const auto entity = new RS_Point(m_currentContainer, RS_PointData(v));
5219 setEntityAttributes(entity, &data);
5220 if (needsWcsThicknessSidecar(data)) {
5221 std::vector<std::shared_ptr<DRW_Variant>> payload;
5222 payload.push_back(std::make_shared<DRW_Variant>(
5223 1010, DRW_Coord(data.basePoint.x, data.basePoint.y, data.basePoint.z)));
5224 payload.push_back(std::make_shared<DRW_Variant>(
5225 1011, DRW_Coord(data.extPoint.x, data.extPoint.y, data.extPoint.z)));
5226 payload.push_back(std::make_shared<DRW_Variant>(1040, data.thickness));
5227 payload.push_back(std::make_shared<DRW_Variant>(1041, data.xAxisAngle));
5228 appendTypeSidecar(entity, kPointExtrusionMarker, std::move(payload));
5229 }
5230 m_currentContainer->addEntity(entity);
5231}
5232
5233/**
5234 * Implementation of the method which handles line entities.
5235 */
5236void RS_FilterDXFRW::addLine(const DRW_Line &data) {
5237 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLine");
5238
5239 RS_Vector v1(data.basePoint.x, data.basePoint.y);
5240 RS_Vector v2(data.secPoint.x, data.secPoint.y);
5241
5242 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLine: create line");
5243
5244 if (!m_currentContainer) {
5245 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLine: currentContainer is nullptr");
5246 }
5247
5248 const auto entity = new RS_Line{m_currentContainer, {v1, v2}};
5249 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLine: set attributes");
5250 setEntityAttributes(entity, &data);
5251 if (needsWcsThicknessSidecar(data)) {
5252 std::vector<std::shared_ptr<DRW_Variant>> payload;
5253 payload.push_back(std::make_shared<DRW_Variant>(
5254 1010, DRW_Coord(data.basePoint.x, data.basePoint.y, data.basePoint.z)));
5255 payload.push_back(std::make_shared<DRW_Variant>(
5256 1011, DRW_Coord(data.secPoint.x, data.secPoint.y, data.secPoint.z)));
5257 payload.push_back(std::make_shared<DRW_Variant>(
5258 1012, DRW_Coord(data.extPoint.x, data.extPoint.y, data.extPoint.z)));
5259 payload.push_back(std::make_shared<DRW_Variant>(1040, data.thickness));
5260 appendTypeSidecar(entity, kLineExtrusionMarker, std::move(payload));
5261 }
5262
5263 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLine: add entity");
5264
5265 if (m_currentContainer) {
5266 m_currentContainer->addEntity(entity);
5267 }
5268
5269 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLine: OK");
5270}
5271
5272void RS_FilterDXFRW::add3DLine(const DRW_3DLine &data) {
5273 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::add3DLine");
5274
5275 const auto entity =
5276 new RS_Line{m_currentContainer,
5277 {RS_Vector(data.basePoint.x, data.basePoint.y),
5278 RS_Vector(data.secPoint.x, data.secPoint.y)}};
5279 setEntityAttributes(entity, &data);
5280
5281 // Preserve the entity kind and all fields because RS_Line cannot retain
5282 // 3D coordinates or distinguish a modern 3DLINE from an ordinary LINE.
5283 std::vector<std::shared_ptr<DRW_Variant>> payload;
5284 payload.push_back(std::make_shared<DRW_Variant>(
5285 1010, DRW_Coord(data.basePoint.x, data.basePoint.y, data.basePoint.z)));
5286 payload.push_back(std::make_shared<DRW_Variant>(
5287 1011, DRW_Coord(data.secPoint.x, data.secPoint.y, data.secPoint.z)));
5288 payload.push_back(std::make_shared<DRW_Variant>(
5289 1012, DRW_Coord(data.extPoint.x, data.extPoint.y, data.extPoint.z)));
5290 payload.push_back(std::make_shared<DRW_Variant>(1040, data.thickness));
5291 appendTypeSidecar(entity, k3DLineMarker, std::move(payload));
5292 m_currentContainer->addEntity(entity);
5293}
5294
5295namespace {
5296// Append a type-fidelity sidecar (marker + payload variants) to whatever
5297// XDATA the entity already carries from setEntityAttributes().
5298void appendTypeSidecar(RS_Entity *entity, const char *marker,
5299 std::vector<std::shared_ptr<DRW_Variant>> payload) {
5300 std::vector<std::shared_ptr<DRW_Variant>> ext;
5301 if (entity->hasDrwExtData())
5302 ext = entity->getDrwExtData();
5303 ext.push_back(std::make_shared<DRW_Variant>(1001, std::string(marker)));
5304 for (auto &p : payload)
5305 ext.push_back(std::move(p));
5306 entity->setDrwExtData(std::move(ext));
5307}
5308
5309void appendInsertAttribSidecar(RS_Entity *entity, const DRW_Attrib &attrib,
5310 unsigned long long ownerId, int ordinal) {
5311 const std::uint64_t id = static_cast<std::uint64_t>(ownerId);
5312 std::vector<std::shared_ptr<DRW_Variant>> payload;
5313 payload.push_back(std::make_shared<DRW_Variant>(
5314 1071, static_cast<std::int32_t>(id & 0xFFFFFFFFu)));
5315 payload.push_back(
5316 std::make_shared<DRW_Variant>(1071, static_cast<std::int32_t>(id >> 32)));
5317 payload.push_back(std::make_shared<DRW_Variant>(1070, std::int32_t{ordinal}));
5318 payload.push_back(
5319 std::make_shared<DRW_Variant>(1070, std::int32_t{attrib.attribFlags}));
5320 payload.push_back(
5321 std::make_shared<DRW_Variant>(1070, std::int32_t{attrib.m_fieldLength}));
5322 payload.push_back(
5323 std::make_shared<DRW_Variant>(1070, std::int32_t{attrib.attVersion}));
5324 payload.push_back(std::make_shared<DRW_Variant>(
5325 1070, std::int32_t{attrib.m_attributeType}));
5326 payload.push_back(std::make_shared<DRW_Variant>(
5327 1070, std::int32_t{attrib.lockPosition ? 1 : 0}));
5328 payload.push_back(
5329 std::make_shared<DRW_Variant>(1000, std::string{attrib.tag}));
5330 payload.push_back(
5331 std::make_shared<DRW_Variant>(1000, std::string{attrib.text}));
5332 payload.push_back(std::make_shared<DRW_Variant>(1010, attrib.basePoint));
5333 payload.push_back(std::make_shared<DRW_Variant>(1011, attrib.secPoint));
5334 payload.push_back(std::make_shared<DRW_Variant>(1012, attrib.extPoint));
5335 payload.push_back(std::make_shared<DRW_Variant>(1040, attrib.thickness));
5336 appendTypeSidecar(entity, kInsertAttribMarker, std::move(payload));
5337
5338 if (!attrib.mtext)
5339 return;
5340
5341 const DRW_MText &mtext = *attrib.mtext;
5342 if (mtext.m_r2018ColumnHeights.size() >
5343 static_cast<std::size_t>(std::numeric_limits<std::int32_t>::max()) ||
5344 mtext.m_r2018AnnotativeData.size() >
5345 static_cast<std::size_t>(std::numeric_limits<std::int32_t>::max())) {
5346 return;
5347 }
5348 std::vector<std::shared_ptr<DRW_Variant>> mtextPayload;
5349 // Schema 2 adds the embedded-MTEXT annotative payload and lock bit.
5350 mtextPayload.push_back(std::make_shared<DRW_Variant>(1070, 2));
5351 const std::size_t maxChunkBytes = 240;
5352 const std::size_t chunkCount =
5353 mtext.text.empty()
5354 ? 0
5355 : (mtext.text.size() + maxChunkBytes - 1) / maxChunkBytes;
5356 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5357 1070, static_cast<std::int32_t>(chunkCount)));
5358 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5359 1070, static_cast<std::int32_t>(mtext.textgen)));
5360 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5361 1070, static_cast<std::int32_t>(mtext.alignH)));
5362 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5363 1070, static_cast<std::int32_t>(mtext.alignV)));
5364 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5365 1070, static_cast<std::int32_t>(mtext.linespacingStyle)));
5366 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5367 1070, static_cast<std::int32_t>(mtext.m_backgroundFlags)));
5368 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5369 1070, static_cast<std::int32_t>(mtext.m_r2018IsNotAnnotative ? 1 : 0)));
5370 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5371 1070, static_cast<std::int32_t>(mtext.m_r2018Version)));
5372 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5373 1070, static_cast<std::int32_t>(mtext.m_r2018DefaultFlag ? 1 : 0)));
5374 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5375 1070, static_cast<std::int32_t>(mtext.m_r2018Attachment)));
5376 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5377 1070, static_cast<std::int32_t>(mtext.m_r2018ColumnType)));
5378 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5379 1070, static_cast<std::int32_t>(mtext.m_r2018ColumnAutoHeight ? 1 : 0)));
5380 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5381 1070,
5382 static_cast<std::int32_t>(mtext.m_r2018ColumnFlowReversed ? 1 : 0)));
5383 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5384 1070, static_cast<std::int32_t>(mtext.m_r2018ColumnHeights.size())));
5385 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5386 1070, static_cast<std::int32_t>(mtext.m_r2018AnnotativeData.size())));
5387 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5388 1070, static_cast<std::int32_t>(mtext.m_r2018AnnotativeUnknown)));
5389 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5390 1070, static_cast<std::int32_t>(mtext.m_r2018ReallyLocked ? 1 : 0)));
5391 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5392 1071, static_cast<std::int32_t>(mtext.m_backgroundColor)));
5393 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5394 1071, static_cast<std::int32_t>(mtext.m_backgroundTransparency)));
5395 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5396 1071, static_cast<std::int32_t>(mtext.m_r2018ColumnCount)));
5397 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5398 1071, static_cast<std::int32_t>(mtext.m_r2018AppIdHandle)));
5399 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5400 1071, static_cast<std::int32_t>(mtext.styleH.ref)));
5401 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5402 1071, static_cast<std::int32_t>(mtext.m_r2018AnnotativeAppHandle)));
5403 mtextPayload.push_back(std::make_shared<DRW_Variant>(1040, mtext.interlin));
5404 mtextPayload.push_back(
5405 std::make_shared<DRW_Variant>(1040, mtext.m_backgroundScale));
5406 mtextPayload.push_back(
5407 std::make_shared<DRW_Variant>(1040, mtext.m_r2018RectWidth));
5408 mtextPayload.push_back(
5409 std::make_shared<DRW_Variant>(1040, mtext.m_r2018RectHeight));
5410 mtextPayload.push_back(
5411 std::make_shared<DRW_Variant>(1040, mtext.m_r2018ExtentsHeight));
5412 mtextPayload.push_back(
5413 std::make_shared<DRW_Variant>(1040, mtext.m_r2018ExtentsWidth));
5414 mtextPayload.push_back(
5415 std::make_shared<DRW_Variant>(1040, mtext.m_r2018ColumnWidth));
5416 mtextPayload.push_back(
5417 std::make_shared<DRW_Variant>(1040, mtext.m_r2018ColumnGutter));
5418 for (double height : mtext.m_r2018ColumnHeights)
5419 mtextPayload.push_back(std::make_shared<DRW_Variant>(1040, height));
5420 for (std::uint8_t byte : mtext.m_r2018AnnotativeData)
5421 mtextPayload.push_back(
5422 std::make_shared<DRW_Variant>(1070, static_cast<std::int32_t>(byte)));
5423 mtextPayload.push_back(std::make_shared<DRW_Variant>(1010, mtext.basePoint));
5424 mtextPayload.push_back(std::make_shared<DRW_Variant>(1011, mtext.secPoint));
5425 mtextPayload.push_back(std::make_shared<DRW_Variant>(1012, mtext.extPoint));
5426 mtextPayload.push_back(
5427 std::make_shared<DRW_Variant>(1010, mtext.m_r2018XAxisDir));
5428 mtextPayload.push_back(
5429 std::make_shared<DRW_Variant>(1011, mtext.m_r2018InsertionPoint));
5430 for (std::size_t offset = 0; offset < mtext.text.size();) {
5431 std::size_t length = std::min(maxChunkBytes, mtext.text.size() - offset);
5432 while (length > 0 && offset + length < mtext.text.size() &&
5433 (static_cast<unsigned char>(mtext.text[offset + length]) & 0xC0) ==
5434 0x80) {
5435 --length;
5436 }
5437 if (length == 0)
5438 length = std::min(maxChunkBytes, mtext.text.size() - offset);
5439 mtextPayload.push_back(
5440 std::make_shared<DRW_Variant>(1000, mtext.text.substr(offset, length)));
5441 offset += length;
5442 }
5443 appendTypeSidecar(entity, kInsertAttribMTextMarker, std::move(mtextPayload));
5444
5445 const bool hasExtendedCommonMetadata =
5446 mtext.hasDataStorageBinaryData() || !mtext.reactorHandles.empty() ||
5447 mtext.xDictHandle != 0 || mtext.color != DRW::ColorByLayer ||
5448 mtext.color24 >= 0 || !mtext.colorName.empty() ||
5449 mtext.transparency != DRW::Opaque || mtext.hasDwgAcDbColorHandle() ||
5450 mtext.ltypeScale != 1.0 || mtext.lineType != "BYLAYER" ||
5451 !mtext.visible || mtext.lWeight != DRW_LW_Conv::widthByLayer ||
5452 mtext.material != DRW::MaterialByLayer ||
5453 mtext.shadow != DRW::CastAndReceieveShadows || mtext.shadowHandle != 0 ||
5454 mtext.plotStyle != DRW::DefaultPlotStyle ||
5455 mtext.fullVisualStyleHandle != 0 || mtext.faceVisualStyleHandle != 0 ||
5456 mtext.edgeVisualStyleHandle != 0 || mtext.dwgLayerHandle() != 0 ||
5457 mtext.dwgLinetypeHandle() != 0;
5458 if (hasExtendedCommonMetadata) {
5459 if (mtext.reactorHandles.size() >
5460 static_cast<std::size_t>(kMaxInsertAttribMTextReactors)) {
5461 return;
5462 }
5463 std::vector<std::shared_ptr<DRW_Variant>> commonPayload;
5464 commonPayload.push_back(
5465 std::make_shared<DRW_Variant>(1070, kInsertAttribMTextCommonSchema));
5466 commonPayload.push_back(std::make_shared<DRW_Variant>(
5467 1070, mtext.hasDataStorageBinaryData() ? 1 : 0));
5468 commonPayload.push_back(std::make_shared<DRW_Variant>(
5469 1070, static_cast<std::int32_t>(mtext.reactorHandles.size())));
5470 const auto appendHandle = [&commonPayload](std::uint32_t handle) {
5471 commonPayload.push_back(std::make_shared<DRW_Variant>(
5472 1071, static_cast<std::int32_t>(handle & 0xFFFFFFFFu)));
5473 commonPayload.push_back(
5474 std::make_shared<DRW_Variant>(1071, std::int32_t{0}));
5475 };
5476 for (const std::uint32_t handle : mtext.reactorHandles)
5477 appendHandle(handle);
5478 appendHandle(mtext.xDictHandle);
5479 commonPayload.push_back(std::make_shared<DRW_Variant>(1070, mtext.color));
5480 commonPayload.push_back(std::make_shared<DRW_Variant>(1071, mtext.color24));
5481 commonPayload.push_back(
5482 std::make_shared<DRW_Variant>(1000, mtext.colorName));
5483 commonPayload.push_back(
5484 std::make_shared<DRW_Variant>(1071, mtext.transparency));
5485 commonPayload.push_back(std::make_shared<DRW_Variant>(
5486 1070, mtext.hasDwgAcDbColorHandle() ? 1 : 0));
5487 appendHandle(mtext.dwgAcDbColorHandle());
5488 commonPayload.push_back(
5489 std::make_shared<DRW_Variant>(1040, mtext.ltypeScale));
5490 commonPayload.push_back(
5491 std::make_shared<DRW_Variant>(1000, mtext.lineType));
5492 commonPayload.push_back(
5493 std::make_shared<DRW_Variant>(1070, mtext.visible ? 1 : 0));
5494 commonPayload.push_back(
5495 std::make_shared<DRW_Variant>(1070, static_cast<int>(mtext.lWeight)));
5496 commonPayload.push_back(
5497 std::make_shared<DRW_Variant>(1071, mtext.material));
5498 commonPayload.push_back(
5499 std::make_shared<DRW_Variant>(1070, static_cast<int>(mtext.shadow)));
5500 appendHandle(mtext.shadowHandle);
5501 commonPayload.push_back(
5502 std::make_shared<DRW_Variant>(1071, mtext.plotStyle));
5503 appendHandle(mtext.fullVisualStyleHandle);
5504 appendHandle(mtext.faceVisualStyleHandle);
5505 appendHandle(mtext.edgeVisualStyleHandle);
5506 appendHandle(mtext.dwgLayerHandle());
5507 appendHandle(mtext.dwgLinetypeHandle());
5508 appendTypeSidecar(entity, kInsertAttribMTextCommonMarker,
5509 std::move(commonPayload));
5510 }
5511
5512 if (mtext.extData.empty() ||
5513 mtext.extData.size() > kMaxInsertAttribMTextXDataValues)
5514 return;
5515
5516 std::vector<std::shared_ptr<DRW_Variant>> xdataPayload;
5517 xdataPayload.push_back(
5518 std::make_shared<DRW_Variant>(1070, kInsertAttribMTextXDataSchema));
5519 xdataPayload.push_back(std::make_shared<DRW_Variant>(
5520 1070, static_cast<std::int32_t>(mtext.extData.size())));
5521 for (const auto &value : mtext.extData) {
5522 if (!value || value->type() == DRW_Variant::INVALID)
5523 return;
5524 const auto type = static_cast<std::int32_t>(value->type());
5525 xdataPayload.push_back(std::make_shared<DRW_Variant>(1070, type));
5526 xdataPayload.push_back(std::make_shared<DRW_Variant>(1070, value->code()));
5527 switch (value->type()) {
5528 case DRW_Variant::STRING:
5529 xdataPayload.push_back(
5530 std::make_shared<DRW_Variant>(1070, value->isLayerRef() ? 1 : 0));
5531 xdataPayload.push_back(
5532 std::make_shared<DRW_Variant>(1000, value->c_str()));
5533 break;
5534 case DRW_Variant::INTEGER:
5535 xdataPayload.push_back(
5536 std::make_shared<DRW_Variant>(1071, value->i_val()));
5537 break;
5538 case DRW_Variant::INTEGER64: {
5539 const std::uint64_t bits = static_cast<std::uint64_t>(value->i64_val());
5540 xdataPayload.push_back(std::make_shared<DRW_Variant>(
5541 1071, static_cast<std::int32_t>(static_cast<std::uint32_t>(bits))));
5542 xdataPayload.push_back(std::make_shared<DRW_Variant>(
5543 1071,
5544 static_cast<std::int32_t>(static_cast<std::uint32_t>(bits >> 32u))));
5545 break;
5546 }
5547 case DRW_Variant::DOUBLE:
5548 xdataPayload.push_back(
5549 std::make_shared<DRW_Variant>(1040, value->d_val()));
5550 break;
5551 case DRW_Variant::COORD:
5552 if (value->coord() == nullptr)
5553 return;
5554 xdataPayload.push_back(
5555 std::make_shared<DRW_Variant>(1010, *value->coord()));
5556 break;
5557 case DRW_Variant::BINARY: {
5558 const auto *bytes = value->binary();
5559 if (bytes == nullptr)
5560 return;
5561 static constexpr char hex[] = "0123456789ABCDEF";
5562 std::string encoded;
5563 encoded.reserve(bytes->size() * 2);
5564 for (const std::uint8_t byte : *bytes) {
5565 encoded.push_back(hex[(byte >> 4u) & 0x0Fu]);
5566 encoded.push_back(hex[byte & 0x0Fu]);
5567 }
5568 xdataPayload.push_back(
5569 std::make_shared<DRW_Variant>(1004, std::move(encoded)));
5570 break;
5571 }
5572 case DRW_Variant::INVALID:
5573 return;
5574 }
5575 }
5576 appendTypeSidecar(entity, kInsertAttribMTextXDataMarker,
5577 std::move(xdataPayload));
5578}
5579
5580bool decodeInsertAttribMTextXData(
5581 const std::vector<std::shared_ptr<DRW_Variant>> &encoded,
5582 std::vector<std::shared_ptr<DRW_Variant>> &decoded) {
5583 auto nextInteger = [&encoded](std::size_t &index, int &value) {
5584 if (index >= encoded.size() || !encoded[index] ||
5585 encoded[index]->type() != DRW_Variant::INTEGER)
5586 return false;
5587 value = encoded[index++]->i_val();
5588 return true;
5589 };
5590 auto nextValue = [&encoded](std::size_t &index, int code,
5591 DRW_Variant::TYPE type,
5592 std::shared_ptr<DRW_Variant> &value) {
5593 if (index >= encoded.size() || !encoded[index] ||
5594 encoded[index]->type() != type)
5595 return false;
5596 const auto &source = encoded[index++];
5597 switch (type) {
5598 case DRW_Variant::STRING:
5599 if (source->content.s == nullptr)
5600 return false;
5601 value = std::make_shared<DRW_Variant>(code, *source->content.s);
5602 break;
5603 case DRW_Variant::INTEGER:
5604 value = std::make_shared<DRW_Variant>(code, source->i_val());
5605 break;
5606 case DRW_Variant::DOUBLE:
5607 if (!std::isfinite(source->d_val()))
5608 return false;
5609 value = std::make_shared<DRW_Variant>(code, source->d_val());
5610 break;
5611 case DRW_Variant::COORD:
5612 if (source->coord() == nullptr || !std::isfinite(source->coord()->x) ||
5613 !std::isfinite(source->coord()->y) ||
5614 !std::isfinite(source->coord()->z))
5615 return false;
5616 value = std::make_shared<DRW_Variant>(code, *source->coord());
5617 break;
5618 case DRW_Variant::INTEGER64:
5619 case DRW_Variant::BINARY:
5620 case DRW_Variant::INVALID:
5621 return false;
5622 }
5623 return true;
5624 };
5625
5626 std::size_t index = 0;
5627 int schema = 0;
5628 int count = 0;
5629 if (!nextInteger(index, schema) || schema != kInsertAttribMTextXDataSchema ||
5630 !nextInteger(index, count) || count < 0 ||
5631 count > kMaxInsertAttribMTextXDataValues)
5632 return false;
5633 decoded.clear();
5634 decoded.reserve(static_cast<std::size_t>(count));
5635 for (int item = 0; item < count; ++item) {
5636 int typeValue = 0;
5637 int code = 0;
5638 if (!nextInteger(index, typeValue) || !nextInteger(index, code) ||
5639 typeValue < DRW_Variant::STRING || typeValue > DRW_Variant::BINARY ||
5640 code < 1000 || code > 1071)
5641 return false;
5642 const auto type = static_cast<DRW_Variant::TYPE>(typeValue);
5643 std::shared_ptr<DRW_Variant> value;
5644 if (type == DRW_Variant::STRING) {
5645 int layerRef = 0;
5646 if (!nextInteger(index, layerRef) || (layerRef != 0 && layerRef != 1) ||
5647 index >= encoded.size() || !encoded[index] ||
5648 encoded[index]->type() != DRW_Variant::STRING ||
5649 encoded[index]->content.s == nullptr)
5650 return false;
5651 value = std::make_shared<DRW_Variant>(code, *encoded[index]->content.s,
5652 layerRef != 0);
5653 ++index;
5654 } else if (type == DRW_Variant::INTEGER64) {
5655 if (index + 1 >= encoded.size() || !encoded[index] ||
5656 !encoded[index + 1] ||
5657 encoded[index]->type() != DRW_Variant::INTEGER ||
5658 encoded[index + 1]->type() != DRW_Variant::INTEGER)
5659 return false;
5660 const std::uint64_t low =
5661 static_cast<std::uint32_t>(encoded[index++]->i_val());
5662 const std::uint64_t high =
5663 static_cast<std::uint32_t>(encoded[index++]->i_val());
5664 const std::uint64_t bits = low | (high << 32u);
5665 std::int64_t restored = 0;
5666 std::memcpy(&restored, &bits, sizeof(restored));
5667 value = std::make_shared<DRW_Variant>(code, restored);
5668 } else if (type == DRW_Variant::BINARY) {
5669 if (index >= encoded.size() || !encoded[index] ||
5670 encoded[index]->type() != DRW_Variant::STRING ||
5671 encoded[index]->content.s == nullptr)
5672 return false;
5673 const std::string hex = *encoded[index++]->content.s;
5674 if ((hex.size() & 1u) != 0)
5675 return false;
5676 std::vector<std::uint8_t> bytes;
5677 bytes.reserve(hex.size() / 2);
5678 auto hexValue = [](char c) -> int {
5679 if (c >= '0' && c <= '9')
5680 return c - '0';
5681 if (c >= 'A' && c <= 'F')
5682 return c - 'A' + 10;
5683 if (c >= 'a' && c <= 'f')
5684 return c - 'a' + 10;
5685 return -1;
5686 };
5687 for (std::size_t offset = 0; offset < hex.size(); offset += 2) {
5688 const int high = hexValue(hex[offset]);
5689 const int low = hexValue(hex[offset + 1]);
5690 if (high < 0 || low < 0)
5691 return false;
5692 bytes.push_back(static_cast<std::uint8_t>((high << 4) | low));
5693 }
5694 value = std::make_shared<DRW_Variant>(code, std::move(bytes));
5695 } else {
5696 const DRW_Variant::TYPE storageType = type;
5697 if (!nextValue(index, code, storageType, value))
5698 return false;
5699 }
5700 decoded.push_back(std::move(value));
5701 }
5702 return index == encoded.size();
5703}
5704
5705bool decodeInsertAttribMTextCommon(
5706 const std::vector<std::shared_ptr<DRW_Variant>> &encoded,
5707 InsertAttribSidecar::MText &decoded) {
5708 std::size_t index = 0;
5709 const auto nextInteger = [&encoded](std::size_t &position, int code,
5710 int &value) {
5711 if (position >= encoded.size() || !encoded[position] ||
5712 encoded[position]->code() != code ||
5713 encoded[position]->type() != DRW_Variant::INTEGER)
5714 return false;
5715 value = encoded[position++]->i_val();
5716 return true;
5717 };
5718 const auto nextHandle = [&encoded](std::size_t &position,
5719 std::uint32_t &handle) {
5720 if (position + 1 >= encoded.size() || !encoded[position] ||
5721 !encoded[position + 1] || encoded[position]->code() != 1071 ||
5722 encoded[position + 1]->code() != 1071 ||
5723 encoded[position]->type() != DRW_Variant::INTEGER ||
5724 encoded[position + 1]->type() != DRW_Variant::INTEGER)
5725 return false;
5726 const std::uint32_t low =
5727 static_cast<std::uint32_t>(encoded[position++]->i_val());
5728 const std::uint32_t high =
5729 static_cast<std::uint32_t>(encoded[position++]->i_val());
5730 if (high != 0)
5731 return false;
5732 handle = low;
5733 return true;
5734 };
5735 const auto nextDouble = [&encoded](std::size_t &position, double &value) {
5736 if (position >= encoded.size() || !encoded[position] ||
5737 encoded[position]->code() != 1040 ||
5738 encoded[position]->type() != DRW_Variant::DOUBLE ||
5739 !std::isfinite(encoded[position]->d_val()))
5740 return false;
5741 value = encoded[position++]->d_val();
5742 return true;
5743 };
5744 const auto nextString = [&encoded](std::size_t &position,
5745 std::string &value) {
5746 if (position >= encoded.size() || !encoded[position] ||
5747 encoded[position]->code() != 1000 ||
5748 encoded[position]->type() != DRW_Variant::STRING ||
5749 encoded[position]->content.s == nullptr)
5750 return false;
5751 value = *encoded[position++]->content.s;
5752 return true;
5753 };
5754
5755 int schema = 0;
5756 int hasDataStorage = 0;
5757 int reactorCount = 0;
5758 if (!nextInteger(index, 1070, schema) ||
5759 (schema != 1 && schema != kInsertAttribMTextCommonSchema) ||
5760 !nextInteger(index, 1070, hasDataStorage) ||
5761 (hasDataStorage != 0 && hasDataStorage != 1) ||
5762 !nextInteger(index, 1070, reactorCount) || reactorCount < 0 ||
5763 reactorCount > kMaxInsertAttribMTextReactors)
5764 return false;
5765
5766 decoded.hasDataStorageBinaryData = hasDataStorage != 0;
5767 decoded.reactorHandles.clear();
5768 decoded.reactorHandles.reserve(static_cast<std::size_t>(reactorCount));
5769 for (int i = 0; i < reactorCount; ++i) {
5770 std::uint32_t handle = 0;
5771 if (!nextHandle(index, handle))
5772 return false;
5773 if (handle != 0)
5774 decoded.reactorHandles.push_back(handle);
5775 }
5776 if (!nextHandle(index, decoded.xDictHandle))
5777 return false;
5778 if (schema == 1)
5779 return index == encoded.size();
5780
5781 int hasAcDbColor = 0;
5782 int visible = 0;
5783 if (!nextInteger(index, 1070, decoded.color) || index >= encoded.size() ||
5784 !encoded[index] || encoded[index]->code() != 1071 ||
5785 encoded[index]->type() != DRW_Variant::INTEGER)
5786 return false;
5787 decoded.color24 = encoded[index++]->i_val();
5788 if (!nextString(index, decoded.colorName) || index >= encoded.size() ||
5789 !encoded[index] || encoded[index]->code() != 1071 ||
5790 encoded[index]->type() != DRW_Variant::INTEGER)
5791 return false;
5792 decoded.transparency = encoded[index++]->i_val();
5793 if (!nextInteger(index, 1070, hasAcDbColor) ||
5794 (hasAcDbColor != 0 && hasAcDbColor != 1) ||
5795 !nextHandle(index, decoded.acDbColorHandle) ||
5796 !nextDouble(index, decoded.ltypeScale) ||
5797 !nextString(index, decoded.lineType) ||
5798 !nextInteger(index, 1070, visible) || (visible != 0 && visible != 1) ||
5799 !nextInteger(index, 1070, decoded.lineWeight) ||
5800 index >= encoded.size() || !encoded[index] ||
5801 encoded[index]->code() != 1071 ||
5802 encoded[index]->type() != DRW_Variant::INTEGER)
5803 return false;
5804 decoded.hasAcDbColorHandle = hasAcDbColor != 0;
5805 decoded.visible = visible != 0;
5806 decoded.material = static_cast<std::uint32_t>(encoded[index++]->i_val());
5807 if (!nextInteger(index, 1070, decoded.shadow) ||
5808 !nextHandle(index, decoded.shadowHandle) || index >= encoded.size() ||
5809 !encoded[index] || encoded[index]->code() != 1071 ||
5810 encoded[index]->type() != DRW_Variant::INTEGER)
5811 return false;
5812 decoded.plotStyle = static_cast<std::uint32_t>(encoded[index++]->i_val());
5813 if (!nextHandle(index, decoded.fullVisualStyleHandle) ||
5814 !nextHandle(index, decoded.faceVisualStyleHandle) ||
5815 !nextHandle(index, decoded.edgeVisualStyleHandle) ||
5816 !nextHandle(index, decoded.layerHandle) ||
5817 !nextHandle(index, decoded.linetypeHandle))
5818 return false;
5819 return index == encoded.size();
5820}
5821
5822std::optional<InsertAttribSidecar>
5823extractInsertAttribSidecar(const RS_Entity *entity) {
5824 if (entity == nullptr || !entity->hasDrwExtData())
5825 return std::nullopt;
5826
5827 InsertAttribSidecar sidecar;
5828 bool inGroup = false;
5829 bool inMTextGroup = false;
5830 bool inMTextCommonGroup = false;
5831 int seenOwnerParts = 0;
5832 int seenValues = 0;
5833 int seenStrings = 0;
5834 int seenMText1070 = 0;
5835 int seenMText1071 = 0;
5836 int seenMText1040 = 0;
5837 int seenMText1010 = 0;
5838 std::vector<std::shared_ptr<DRW_Variant>> encodedMTextXData;
5839 int expectedColumnHeights = -1;
5840 int expectedAnnotativeBytes = -1;
5841 int seenAnnotativeBytes = 0;
5842 std::uint32_t ownerLow = 0;
5843 std::uint32_t ownerHigh = 0;
5844 bool foundMarker = false;
5845 bool foundMTextMarker = false;
5846 bool foundMTextXDataMarker = false;
5847 bool foundMTextCommonMarker = false;
5848 bool inMTextXDataGroup = false;
5849 std::vector<std::shared_ptr<DRW_Variant>> encodedMTextCommon;
5850 for (const auto &value : entity->getDrwExtData()) {
5851 if (!value)
5852 continue;
5853 if (value->code() == 1001) {
5854 std::string marker;
5855 if (readXDataString(value.get(), 1001, marker) !=
5856 XDataReadResult::Valid) {
5857 inGroup = false;
5858 inMTextGroup = false;
5859 inMTextXDataGroup = false;
5860 inMTextCommonGroup = false;
5861 continue;
5862 }
5863 inGroup = marker == kInsertAttribMarker;
5864 inMTextGroup = marker == kInsertAttribMTextMarker;
5865 inMTextXDataGroup = marker == kInsertAttribMTextXDataMarker;
5866 inMTextCommonGroup = marker == kInsertAttribMTextCommonMarker;
5867 foundMarker = foundMarker || inGroup;
5868 foundMTextMarker = foundMTextMarker || inMTextGroup;
5869 foundMTextXDataMarker = foundMTextXDataMarker || inMTextXDataGroup;
5870 foundMTextCommonMarker = foundMTextCommonMarker || inMTextCommonGroup;
5871 continue;
5872 }
5873 if (inMTextCommonGroup) {
5874 encodedMTextCommon.push_back(std::make_shared<DRW_Variant>(*value));
5875 continue;
5876 }
5877 if (inMTextXDataGroup) {
5878 encodedMTextXData.push_back(std::make_shared<DRW_Variant>(*value));
5879 continue;
5880 }
5881 if (inMTextGroup) {
5882 if (value->code() == 1070) {
5883 std::int64_t integerValue = 0;
5884 if (readXDataInteger(value.get(), 1070, integerValue, false) !=
5885 XDataReadResult::Valid ||
5886 !fitsXDataInt32(integerValue)) {
5887 return std::nullopt;
5888 }
5889 const int number = static_cast<int>(integerValue);
5890 switch (seenMText1070++) {
5891 case 0:
5892 sidecar.mtext.schemaVersion = number;
5893 break;
5894 case 1:
5895 sidecar.mtext.hasText = number > 0;
5896 break;
5897 case 2:
5898 sidecar.mtext.textgen = number;
5899 break;
5900 case 3:
5901 sidecar.mtext.alignH = number;
5902 break;
5903 case 4:
5904 sidecar.mtext.alignV = number;
5905 break;
5906 case 5:
5907 sidecar.mtext.linespacingStyle = number;
5908 break;
5909 case 6:
5910 sidecar.mtext.backgroundFlags = number;
5911 break;
5912 case 7:
5913 sidecar.mtext.r2018IsNotAnnotative = number != 0;
5914 break;
5915 case 8:
5916 sidecar.mtext.r2018Version = number;
5917 break;
5918 case 9:
5919 sidecar.mtext.r2018DefaultFlag = number != 0;
5920 break;
5921 case 10:
5922 sidecar.mtext.r2018Attachment = number;
5923 break;
5924 case 11:
5925 sidecar.mtext.r2018ColumnType = number;
5926 break;
5927 case 12:
5928 sidecar.mtext.r2018ColumnAutoHeight = number != 0;
5929 break;
5930 case 13:
5931 sidecar.mtext.r2018ColumnFlowReversed = number != 0;
5932 break;
5933 case 14:
5934 expectedColumnHeights = number;
5935 if (number >= 0 && number <= 10000) {
5936 sidecar.mtext.r2018ColumnHeights.reserve(
5937 static_cast<std::size_t>(number));
5938 }
5939 break;
5940 case 15:
5941 expectedAnnotativeBytes = number;
5942 break;
5943 case 16:
5944 sidecar.mtext.annotativeUnknown = number;
5945 break;
5946 case 17:
5947 sidecar.mtext.reallyLocked = number != 0;
5948 break;
5949 default:
5950 if (expectedAnnotativeBytes >= 0 &&
5951 seenAnnotativeBytes < expectedAnnotativeBytes && number >= 0 &&
5952 number <= 255) {
5953 sidecar.mtext.annotativeData.push_back(
5954 static_cast<std::uint8_t>(number));
5955 ++seenAnnotativeBytes;
5956 }
5957 break;
5958 }
5959 } else if (value->code() == 1071) {
5960 std::int64_t integerValue = 0;
5961 if (readXDataInteger(value.get(), 1071, integerValue) !=
5962 XDataReadResult::Valid ||
5963 integerValue < 0 ||
5964 static_cast<std::uint64_t>(integerValue) >
5965 std::numeric_limits<std::uint32_t>::max()) {
5966 return std::nullopt;
5967 }
5968 const std::uint32_t number = static_cast<std::uint32_t>(integerValue);
5969 switch (seenMText1071++) {
5970 case 0:
5971 sidecar.mtext.backgroundColor = static_cast<int>(number);
5972 break;
5973 case 1:
5974 sidecar.mtext.backgroundTransparency = static_cast<int>(number);
5975 break;
5976 case 2:
5977 sidecar.mtext.r2018ColumnCount = static_cast<int>(number);
5978 break;
5979 case 3:
5980 sidecar.mtext.r2018AppIdHandle = number;
5981 break;
5982 case 4:
5983 sidecar.mtext.styleHandle = number;
5984 break;
5985 case 5:
5986 sidecar.mtext.annotativeAppHandle = number;
5987 break;
5988 default:
5989 break;
5990 }
5991 } else if (value->code() == 1040) {
5992 double number = 0.0;
5993 if (readXDataDouble(value.get(), 1040, number) !=
5994 XDataReadResult::Valid) {
5995 return std::nullopt;
5996 }
5997 switch (seenMText1040++) {
5998 case 0:
5999 sidecar.mtext.interlin = number;
6000 break;
6001 case 1:
6002 sidecar.mtext.backgroundScale = number;
6003 break;
6004 case 2:
6005 sidecar.mtext.r2018RectWidth = number;
6006 break;
6007 case 3:
6008 sidecar.mtext.r2018RectHeight = number;
6009 break;
6010 case 4:
6011 sidecar.mtext.r2018ExtentsHeight = number;
6012 break;
6013 case 5:
6014 sidecar.mtext.r2018ExtentsWidth = number;
6015 break;
6016 case 6:
6017 sidecar.mtext.r2018ColumnWidth = number;
6018 break;
6019 case 7:
6020 sidecar.mtext.r2018ColumnGutter = number;
6021 break;
6022 default:
6023 sidecar.mtext.r2018ColumnHeights.push_back(number);
6024 break;
6025 }
6026 } else if (value->code() == 1010 || value->code() == 1011 ||
6027 value->code() == 1012) {
6028 DRW_Coord point;
6029 if (readXDataCoord(value.get(), value->code(), point) !=
6030 XDataReadResult::Valid) {
6031 return std::nullopt;
6032 }
6033 switch (seenMText1010++) {
6034 case 0:
6035 sidecar.mtext.basePoint = point;
6036 sidecar.mtext.hasBasePoint = true;
6037 break;
6038 case 1:
6039 sidecar.mtext.secPoint = point;
6040 sidecar.mtext.hasSecPoint = true;
6041 break;
6042 case 2:
6043 sidecar.mtext.extPoint = point;
6044 sidecar.mtext.hasExtPoint = true;
6045 break;
6046 case 3:
6047 sidecar.mtext.r2018XAxisDir = point;
6048 break;
6049 case 4:
6050 sidecar.mtext.r2018InsertionPoint = point;
6051 break;
6052 default:
6053 break;
6054 }
6055 } else if (value->code() == 1000) {
6056 std::string text;
6057 if (readXDataString(value.get(), 1000, text) !=
6058 XDataReadResult::Valid) {
6059 return std::nullopt;
6060 }
6061 sidecar.mtext.text += text;
6062 }
6063 sidecar.hasMText =
6064 sidecar.mtext.schemaVersion == 1 || sidecar.mtext.schemaVersion == 2;
6065 continue;
6066 }
6067 if (!inGroup)
6068 continue;
6069 if (value->code() == 1071) {
6070 std::int64_t integerValue = 0;
6071 if (readXDataInteger(value.get(), 1071, integerValue) !=
6072 XDataReadResult::Valid ||
6073 integerValue < 0 ||
6074 static_cast<std::uint64_t>(integerValue) >
6075 std::numeric_limits<std::uint32_t>::max()) {
6076 return std::nullopt;
6077 }
6078 const auto part = static_cast<std::uint32_t>(integerValue);
6079 if (seenOwnerParts++ == 0)
6080 ownerLow = part;
6081 else if (seenOwnerParts == 2)
6082 ownerHigh = part;
6083 } else if (value->code() == 1070) {
6084 std::int64_t integerValue = 0;
6085 if (readXDataInteger(value.get(), 1070, integerValue, false) !=
6086 XDataReadResult::Valid ||
6087 !fitsXDataInt32(integerValue)) {
6088 return std::nullopt;
6089 }
6090 const int number = static_cast<int>(integerValue);
6091 switch (seenValues++) {
6092 case 0:
6093 sidecar.ordinal = number;
6094 break;
6095 case 1:
6096 sidecar.flags = number;
6097 break;
6098 case 2:
6099 sidecar.fieldLength = number;
6100 break;
6101 case 3:
6102 sidecar.attVersion = number;
6103 break;
6104 case 4:
6105 sidecar.attributeType = number;
6106 break;
6107 case 5:
6108 sidecar.lockPosition = number != 0;
6109 break;
6110 default:
6111 break;
6112 }
6113 } else if (value->code() == 1000) {
6114 std::string text;
6115 if (readXDataString(value.get(), 1000, text) != XDataReadResult::Valid) {
6116 return std::nullopt;
6117 }
6118 if (seenStrings++ == 0)
6119 sidecar.tag = text;
6120 else if (seenStrings == 2) {
6121 sidecar.value = text;
6122 sidecar.hasValue = true;
6123 }
6124 } else if (value->code() >= 1010 && value->code() <= 1012) {
6125 DRW_Coord point;
6126 if (readXDataCoord(value.get(), value->code(), point) !=
6127 XDataReadResult::Valid) {
6128 return std::nullopt;
6129 }
6130 if (value->code() == 1010) {
6131 sidecar.basePoint = point;
6132 sidecar.hasBasePoint = true;
6133 } else if (value->code() == 1011) {
6134 sidecar.secPoint = point;
6135 sidecar.hasSecPoint = true;
6136 } else {
6137 sidecar.extPoint = point;
6138 sidecar.hasExtPoint = true;
6139 }
6140 } else if (value->code() == 1040) {
6141 double thickness = 0.0;
6142 if (readXDataDouble(value.get(), 1040, thickness) !=
6143 XDataReadResult::Valid) {
6144 return std::nullopt;
6145 }
6146 sidecar.thickness = thickness;
6147 sidecar.hasThickness = true;
6148 }
6149 }
6150
6151 sidecar.ownerId =
6152 (static_cast<unsigned long long>(ownerHigh) << 32) | ownerLow;
6153 if (sidecar.hasMText) {
6154 sidecar.hasMText =
6155 expectedColumnHeights >= 0 &&
6156 static_cast<int>(sidecar.mtext.r2018ColumnHeights.size()) ==
6157 expectedColumnHeights;
6158 if (sidecar.mtext.schemaVersion >= 2) {
6159 sidecar.hasMText =
6160 sidecar.hasMText && expectedAnnotativeBytes >= 0 &&
6161 static_cast<int>(sidecar.mtext.annotativeData.size()) ==
6162 expectedAnnotativeBytes;
6163 }
6164 }
6165 if (foundMTextMarker && !sidecar.hasMText)
6166 return std::nullopt;
6167 if (foundMTextXDataMarker &&
6168 !decodeInsertAttribMTextXData(encodedMTextXData, sidecar.mtext.xdata))
6169 return std::nullopt;
6170 if (foundMTextCommonMarker &&
6171 !decodeInsertAttribMTextCommon(encodedMTextCommon, sidecar.mtext))
6172 return std::nullopt;
6173 if (!foundMarker || seenOwnerParts != 2 || sidecar.ownerId == 0 ||
6174 sidecar.ordinal < 0) {
6175 return std::nullopt;
6176 }
6177 return sidecar;
6178}
6179
6180void removeInsertAttribSidecar(DRW_Entity &entity) {
6181 std::vector<std::shared_ptr<DRW_Variant>> filtered;
6182 bool inGroup = false;
6183 for (const auto &value : entity.extData) {
6184 if (value && value->code() == 1001 &&
6185 value->type() == DRW_Variant::STRING && value->content.s != nullptr) {
6186 const std::string marker = value->c_str();
6187 inGroup = marker == kInsertAttribMarker ||
6188 marker == kInsertAttribMTextMarker ||
6189 marker == kInsertAttribMTextXDataMarker ||
6190 marker == kInsertAttribMTextCommonMarker;
6191 if (inGroup)
6192 continue;
6193 }
6194 if (!inGroup)
6195 filtered.push_back(value);
6196 }
6197 entity.extData = std::move(filtered);
6198}
6199
6200bool isGeneratedInsertAttrib(const RS_Entity *entity) {
6201 if (entity == nullptr || !entity->hasDrwExtData())
6202 return false;
6203 for (const auto &value : entity->getDrwExtData()) {
6204 if (!value || value->code() != 1001 ||
6205 value->type() != DRW_Variant::STRING || value->content.s == nullptr)
6206 continue;
6207 const std::string marker = value->c_str();
6208 if (marker == kInsertAttribMarker || marker == kInsertAttribMTextMarker)
6209 return true;
6210 }
6211 return false;
6212}
6213} // namespace
6214
6215/**
6216 * Implementation of the method which handles ray entities.
6217 */
6218void RS_FilterDXFRW::addRay(const DRW_Ray &data) {
6219 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addRay");
6220
6221 RS_Vector v1{data.basePoint.x, data.basePoint.y};
6222 RS_Vector v2{data.basePoint.x + data.secPoint.x,
6223 data.basePoint.y + data.secPoint.y};
6224
6225 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addRay: create line");
6226
6227 if (!m_currentContainer) {
6228 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addRay: currentContainer is nullptr");
6229 }
6230
6231 const auto entity = new RS_Line{m_currentContainer, {v1, v2}};
6232 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addRay: set attributes");
6233 setEntityAttributes(entity, &data);
6234
6235 // F2 sidecar: base point + direction (preserve Z for full fidelity).
6236 std::vector<std::shared_ptr<DRW_Variant>> payload;
6237 payload.push_back(std::make_shared<DRW_Variant>(
6238 1010, DRW_Coord(data.basePoint.x, data.basePoint.y, data.basePoint.z)));
6239 payload.push_back(std::make_shared<DRW_Variant>(
6240 1011, DRW_Coord(data.secPoint.x, data.secPoint.y, data.secPoint.z)));
6241 appendTypeSidecar(entity, kRayMarker, std::move(payload));
6242
6243 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addRay: add entity");
6244
6245 if (m_currentContainer) {
6246 m_currentContainer->addEntity(entity);
6247 }
6248
6249 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addRay: OK");
6250}
6251
6252/**
6253 * Implementation of the method which handles line entities.
6254 */
6255void RS_FilterDXFRW::addXline(const DRW_Xline &data) {
6256 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addXline");
6257
6258 RS_Vector v1(data.basePoint.x, data.basePoint.y);
6259 RS_Vector v2(data.basePoint.x + data.secPoint.x,
6260 data.basePoint.y + data.secPoint.y);
6261
6262 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addXline: create line");
6263
6264 if (!m_currentContainer) {
6265 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addXline: currentContainer is nullptr");
6266 }
6267
6268 const auto entity = new RS_Line{m_currentContainer, {v1, v2}};
6269 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addXline: set attributes");
6270 setEntityAttributes(entity, &data);
6271
6272 // F2 sidecar: base point + direction (preserve Z for full fidelity).
6273 std::vector<std::shared_ptr<DRW_Variant>> payload;
6274 payload.push_back(std::make_shared<DRW_Variant>(
6275 1010, DRW_Coord(data.basePoint.x, data.basePoint.y, data.basePoint.z)));
6276 payload.push_back(std::make_shared<DRW_Variant>(
6277 1011, DRW_Coord(data.secPoint.x, data.secPoint.y, data.secPoint.z)));
6278 appendTypeSidecar(entity, kXlineMarker, std::move(payload));
6279
6280 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addXline: add entity");
6281
6282 if (m_currentContainer) {
6283 m_currentContainer->addEntity(entity);
6284 }
6285
6286 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addXline: OK");
6287}
6288
6289/**
6290 * Implementation of the method which handles circle entities.
6291 */
6292namespace {
6293template <typename T>
6294bool canCreatePlanarEntity(const T &entity, const char *typeName) {
6295 if (hasNormalizedAxialExtrusion(entity))
6296 return true;
6297 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
6298 "RS_FilterDXFRW::%s: skipped non-axial extrusion", typeName);
6299 return false;
6300}
6301} // namespace
6302
6303void RS_FilterDXFRW::addCircle(const DRW_Circle &data) {
6304 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addCircle");
6305 if (!canCreatePlanarEntity(data, "addCircle"))
6306 return;
6307
6308 RS_Vector v{data.basePoint.x, data.basePoint.y};
6309 const auto entity = new RS_Circle(m_currentContainer, {v, data.radious});
6310 setEntityAttributes(entity, &data);
6311 if (needsCircleOcsSidecar(data)) {
6312 std::vector<std::shared_ptr<DRW_Variant>> payload;
6313 payload.push_back(
6314 std::make_shared<DRW_Variant>(1010, sourceAxialOcsCenter(data)));
6315 payload.push_back(std::make_shared<DRW_Variant>(1011, data.extPoint));
6316 payload.push_back(std::make_shared<DRW_Variant>(1040, data.thickness));
6317 appendTypeSidecar(entity, kCircleOcsMarker, std::move(payload));
6318 }
6319 m_currentContainer->addEntity(entity);
6320}
6321
6322/**
6323 * Implementation of the method which handles arc entities.
6324 *
6325 * @param data
6326 */
6327void RS_FilterDXFRW::addArc(const DRW_Arc &data) {
6328 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addArc");
6329 if (!canCreatePlanarEntity(data, "addArc"))
6330 return;
6331 const RS_Vector v(data.basePoint.x, data.basePoint.y);
6332 const RS_ArcData d(v, data.radious, data.staangle, data.endangle, false);
6333 const auto entity = new RS_Arc(m_currentContainer, d);
6334 setEntityAttributes(entity, &data);
6335 if (needsArcOcsSidecar(data)) {
6336 std::vector<std::shared_ptr<DRW_Variant>> payload;
6337 payload.push_back(
6338 std::make_shared<DRW_Variant>(1010, sourceAxialOcsCenter(data)));
6339 payload.push_back(std::make_shared<DRW_Variant>(1011, data.extPoint));
6340 payload.push_back(std::make_shared<DRW_Variant>(1040, data.thickness));
6341 appendTypeSidecar(entity, kArcOcsMarker, std::move(payload));
6342 }
6343 m_currentContainer->addEntity(entity);
6344}
6345
6346/**
6347 * Implementation of the method which handles ellipse entities.
6348 *
6349 * @param data
6350 */
6351void RS_FilterDXFRW::addEllipse(const DRW_Ellipse &data) {
6352 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addEllipse");
6353 if (!canCreatePlanarEntity(data, "addEllipse"))
6354 return;
6355
6356 const RS_Vector v1(data.basePoint.x, data.basePoint.y);
6357 const RS_Vector v2(data.secPoint.x, data.secPoint.y);
6358 double ang2 = data.endparam;
6359 if (fabs(ang2 - 2. * M_PI3.14159265358979323846) < RS_TOLERANCE1.0e-10 &&
6360 fabs(data.staparam) < RS_TOLERANCE1.0e-10) {
6361 ang2 = 0.;
6362 }
6363 const auto entity = new RS_Ellipse{
6364 m_currentContainer, {v1, v2, data.ratio, data.staparam, ang2, false}};
6365 setEntityAttributes(entity, &data);
6366 m_currentContainer->addEntity(entity);
6367}
6368
6369/**
6370 * Implementation of the method which handles trace entities.
6371 */
6372void RS_FilterDXFRW::addTrace(const DRW_Trace &data) {
6373 // Pre-R13 (AC1009) typed LINEAR/ALIGNED dimensions rebuild arrows in
6374 // RS_DimLinear/RS_DimAligned. ENTITIES-section SOLID/TRACE records are
6375 // duplicate *D-block graphics (ACEB10 and peers); importing them inflates
6376 // the model-space bbox with misplaced arrow triangles.
6377 if (m_version == 1009 && m_currentContainer == m_graphic) {
6378 return;
6379 }
6380 if (!hasNormalizedAxialExtrusion(data)) {
6381 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
6382 "RS_FilterDXFRW::addTrace: skipped non-axial extrusion");
6383 return;
6384 }
6385
6386 RS_Solid *entity;
6387 const RS_Vector v1{data.basePoint.x, data.basePoint.y};
6388 const RS_Vector v2{data.secPoint.x, data.secPoint.y};
6389 const RS_Vector v3{data.thirdPoint.x, data.thirdPoint.y};
6390 const RS_Vector v4{data.fourPoint.x, data.fourPoint.y};
6391 if (v3 == v4) {
6392 entity = new RS_Solid(m_currentContainer, RS_SolidData(v1, v2, v3));
6393 } else {
6394 entity = new RS_Solid(m_currentContainer, RS_SolidData(v1, v2, v3, v4));
6395 }
6396
6397 setEntityAttributes(entity, &data);
6398
6399 // F2 sidecar: preserve the exact native TRACE/SOLID type and all four WCS
6400 // corners. RS_Solid is 2D-lossy, so the write pre-pass must rebuild the
6401 // source type with canonicalized axial thickness.
6402 const char *marker = data.eType == DRW::DXF_TRACE ? kTraceMarker
6403 : data.eType == DRW::SOLID ? kSolidMarker
6404 : nullptr;
6405 if (marker != nullptr) {
6406 const bool reflectedOcs = data.extPoint.z < 0.0;
6407 std::vector<std::shared_ptr<DRW_Variant>> payload;
6408 payload.push_back(std::make_shared<DRW_Variant>(
6409 1010, DRW_Coord(data.basePoint.x, data.basePoint.y, data.basePoint.z)));
6410 payload.push_back(std::make_shared<DRW_Variant>(
6411 1011, DRW_Coord(data.secPoint.x, data.secPoint.y, data.secPoint.z)));
6412 payload.push_back(std::make_shared<DRW_Variant>(
6413 1012,
6414 DRW_Coord(data.thirdPoint.x, data.thirdPoint.y, data.thirdPoint.z)));
6415 payload.push_back(std::make_shared<DRW_Variant>(
6416 1013, DRW_Coord(data.fourPoint.x, data.fourPoint.y, data.fourPoint.z)));
6417 payload.push_back(std::make_shared<DRW_Variant>(
6418 1040, reflectedOcs ? -data.thickness : data.thickness));
6419 appendTypeSidecar(entity, marker, std::move(payload));
6420 }
6421
6422 m_currentContainer->addEntity(entity);
6423}
6424
6425void RS_FilterDXFRW::addTolerance(const DRW_Tolerance &data) {
6426 const RS_Vector insertionPoint{data.insertionPoint.x, data.insertionPoint.y};
6427 const RS_Vector axisDirectionVector{data.xAxisDirectionVector.x,
6428 data.xAxisDirectionVector.y};
6429
6430 const QString text = toNativeString(QString::fromUtf8(data.text.c_str()));
6431
6432 QString sty = QString::fromUtf8(data.dimStyleName.c_str());
6433 if (sty.isEmpty()) {
6434 sty = m_dimStyle;
6435 }
6436
6437 const auto tolData =
6438 LC_ToleranceData(insertionPoint, axisDirectionVector, text, sty);
6439
6440 const auto entity = new LC_Tolerance{m_currentContainer, tolData};
6441 setEntityAttributes(entity, &data);
6442 entity->update();
6443 m_currentContainer->addEntity(entity);
6444}
6445
6446/**
6447 * Implementation of the method which handles solid entities.
6448 */
6449void RS_FilterDXFRW::addSolid(const DRW_Solid &data) { addTrace(data); }
6450
6451// Convert a decoded ACIS wireframe (drw_acis.h) into 2D RS_* entities,
6452// mirroring addMesh: LibreCAD is 2D, so every point is projected by dropping Z.
6453// The result is added to `container`; the created entities are returned so a
6454// caller can apply setEntityAttributes afterwards. Static + free of m_graphic
6455// so it can be unit-tested directly against a bare RS_EntityContainer.
6456std::vector<RS_Entity *>
6457RS_FilterDXFRW::acisWireframeToEntities(const DRW_AcisBrep &brep,
6458 RS_EntityContainer *container) {
6459 std::vector<RS_Entity *> created;
6460 if (container == nullptr)
6461 return created;
6462
6463 auto xy = [](const DRW_Coord &c) { return RS_Vector(c.x, c.y); };
6464 auto pushEntity = [&](RS_Entity *e) {
6465 container->addEntity(e);
6466 created.push_back(e);
6467 };
6468 auto addLineSeg = [&](const DRW_AcisEdge &edge) {
6469 if (edge.hasStart && edge.hasEnd)
6470 pushEntity(new RS_Line(container, {xy(edge.start), xy(edge.end)}));
6471 };
6472
6473 // Parametric (eccentric) angle of a 2D point on an ellipse defined by its
6474 // center, major-axis vector and minor/major ratio.
6475 auto ellipseParam = [](const RS_Vector &center, const RS_Vector &majorP,
6476 double ratio, const RS_Vector &p) -> double {
6477 double majLen = majorP.magnitude();
6478 if (majLen < RS_TOLERANCE1.0e-10)
6479 return 0.0;
6480 RS_Vector d = p - center;
6481 RS_Vector majHat = majorP / majLen;
6482 RS_Vector minHat(-majHat.y, majHat.x); // major rotated +90 deg
6483 double cosT = d.dotP(majHat) / majLen;
6484 double sinT = d.dotP(minHat);
6485 if (ratio > RS_TOLERANCE1.0e-10)
6486 sinT /= (majLen * ratio);
6487 return RS_Math::correctAngle(std::atan2(sinT, cosT));
6488 };
6489
6490 // Track edge endpoints so isolated vertices can be rendered as points.
6491 std::vector<RS_Vector> endpoints;
6492 auto noteEndpoints = [&](const DRW_AcisEdge &edge) {
6493 if (edge.hasStart)
6494 endpoints.push_back(xy(edge.start));
6495 if (edge.hasEnd)
6496 endpoints.push_back(xy(edge.end));
6497 };
6498
6499 for (const DRW_AcisEdge &edge : brep.edges) {
6500 noteEndpoints(edge);
6501 switch (edge.curveType) {
6502 case DRW_AcisCurve::Straight:
6503 addLineSeg(edge);
6504 break;
6505 case DRW_AcisCurve::Ellipse: {
6506 RS_Vector center = xy(edge.p0);
6507 RS_Vector majorP = xy(edge.p2);
6508 double ratio = edge.ratio;
6509 if (majorP.magnitude() < RS_TOLERANCE1.0e-10 || ratio <= 0.0) {
6510 addLineSeg(edge); // insufficient/degenerate ellipse params
6511 break;
6512 }
6513 double a1 = 0.0;
6514 double a2 = 2.0 * M_PI3.14159265358979323846;
6515 if (edge.hasStart && edge.hasEnd) {
6516 a1 = ellipseParam(center, majorP, ratio, xy(edge.start));
6517 a2 = ellipseParam(center, majorP, ratio, xy(edge.end));
6518 }
6519 pushEntity(
6520 new RS_Ellipse(container, {center, majorP, ratio, a1, a2, false}));
6521 break;
6522 }
6523 case DRW_AcisCurve::Intcurve: {
6524 if (edge.controlPoints.size() >= 2) {
6525 size_t n = edge.controlPoints.size();
6526 int degree = (n >= 4) ? 3 : static_cast<int>(n - 1);
6527 RS_SplineData d(degree, /*closed=*/false);
6528 auto *spline = new RS_Spline(container, d);
6529 for (const DRW_Coord &cp : edge.controlPoints)
6530 spline->addControlPointRaw(RS_Vector(cp.x, cp.y), 1.0);
6531 pushEntity(spline);
6532 } else {
6533 addLineSeg(edge); // no control polygon -> straight fallback
6534 }
6535 break;
6536 }
6537 case DRW_AcisCurve::Unknown:
6538 default:
6539 addLineSeg(edge);
6540 break;
6541 }
6542 }
6543
6544 // Isolated vertices (no incident edge endpoint) -> RS_Point.
6545 for (const DRW_AcisVertex &v : brep.vertices) {
6546 if (!v.valid)
6547 continue;
6548 RS_Vector p = xy(v.point);
6549 bool incident = false;
6550 for (const RS_Vector &e : endpoints) {
6551 if (std::fabs(e.x - p.x) < RS_TOLERANCE1.0e-10 &&
6552 std::fabs(e.y - p.y) < RS_TOLERANCE1.0e-10) {
6553 incident = true;
6554 break;
6555 }
6556 }
6557 if (!incident)
6558 pushEntity(new RS_Point(container, RS_PointData(p)));
6559 }
6560
6561 return created;
6562}
6563
6564void RS_FilterDXFRW::addModelerGeometry(const DRW_ModelerGeometry &data) {
6565 // Preserve the raw modeler payload as advanced-metadata sidecar so a native
6566 // ACIS body is never silently lost on round-trip.
6567 if (m_graphic != nullptr) {
6568 m_graphic->dwgAdvancedMetadata().addModelerGeometry(data);
6569 }
6570 // Additionally render the decoded SAB wireframe as 2D geometry (mirrors
6571 // addMesh). Guard: only touch the container when the decode yields edges, so
6572 // undecodable bodies keep the previous metadata-only behavior (no
6573 // regression).
6574 DRW_ModelerGeometry &mut = const_cast<DRW_ModelerGeometry &>(data);
6575 if (mut.decodeWireframe() && !mut.m_wireframe.edges.empty()) {
6576 std::vector<RS_Entity *> ents =
6577 acisWireframeToEntities(mut.m_wireframe, m_currentContainer);
6578 for (RS_Entity *e : ents) {
6579 setEntityAttributes(e, &data);
6580 // Wireframe entities are derived display geometry, not additional
6581 // instances of the opaque modeler object.
6582 e->setSourceHandle(0);
6583 }
6584 RS_DEBUGRS_Debug::instance()->print(
6585 "RS_FilterDXFRW::addModelerGeometry: rendered %zu wireframe entities",
6586 ents.size());
6587 }
6588 RS_DEBUGRS_Debug::instance()->print(
6589 "RS_FilterDXFRW::addModelerGeometry: type %d handle %d history %d",
6590 static_cast<int>(data.eType), static_cast<int>(data.handle),
6591 static_cast<int>(data.m_historyHandle));
6592}
6593
6594void RS_FilterDXFRW::addSurface(const DRW_Surface *data) {
6595 if (data == nullptr)
6596 return;
6597 if (m_graphic != nullptr) {
6598 m_graphic->dwgAdvancedMetadata().addSurface(*data);
6599 }
6600 // Decode the lazily-cached SAB wireframe (idempotent; never throws), then
6601 // render its edges as 2D geometry via the shared helper. Guard on edges to
6602 // avoid emitting anything for bodies we cannot decode (no regression over the
6603 // base no-op).
6604 DRW_Surface *surf = const_cast<DRW_Surface *>(data);
6605 if (surf->decodeWireframe() && !surf->m_wireframe.edges.empty()) {
6606 std::vector<RS_Entity *> ents =
6607 acisWireframeToEntities(surf->m_wireframe, m_currentContainer);
6608 for (RS_Entity *e : ents) {
6609 setEntityAttributes(e, data);
6610 // Keep the source handle reserved for the opaque surface body.
6611 e->setSourceHandle(0);
6612 }
6613 RS_DEBUGRS_Debug::instance()->print(
6614 "RS_FilterDXFRW::addSurface: rendered %zu wireframe entities",
6615 ents.size());
6616 }
6617}
6618
6619void RS_FilterDXFRW::addLight(const DRW_Light &data) {
6620 if (m_graphic != nullptr) {
6621 m_graphic->dwgAdvancedMetadata().addLight(data);
6622 }
6623 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addLight: %s handle %d",
6624 data.m_name.empty() ? "(unnamed)" : data.m_name.c_str(),
6625 static_cast<int>(data.handle));
6626}
6627
6628void RS_FilterDXFRW::addLightList(const DRW_LightList &data) {
6629 if (m_graphic != nullptr)
6630 m_graphic->dwgAdvancedMetadata().addLightList(data);
6631 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addLightList: handle %d lights %d",
6632 static_cast<int>(data.handle),
6633 static_cast<int>(data.m_lights.size()));
6634}
6635
6636void RS_FilterDXFRW::addLayerFilter(const DRW_LayerFilter &data) {
6637 if (m_graphic != nullptr)
6638 m_graphic->dwgAdvancedMetadata().addLayerFilter(data);
6639 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addLayerFilter: handle %d names %d",
6640 static_cast<int>(data.handle),
6641 static_cast<int>(data.m_names.size()));
6642}
6643
6644void RS_FilterDXFRW::addDataLink(const DRW_DataLink &data) {
6645 if (m_graphic != nullptr)
6646 m_graphic->dwgAdvancedMetadata().addDataLink(data);
6647 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDataLink: handle %d custom data %d",
6648 static_cast<int>(data.handle),
6649 static_cast<int>(data.m_customData.size()));
6650}
6651
6652void RS_FilterDXFRW::addGeoMapImage(const DRW_GeoMapImage &data) {
6653 if (m_graphic != nullptr)
6654 m_graphic->dwgAdvancedMetadata().addGeoMapImage(data);
6655 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addGeoMapImage: handle %d",
6656 static_cast<int>(data.handle));
6657}
6658
6659void RS_FilterDXFRW::addCamera(const DRW_Camera &data) {
6660 if (m_graphic != nullptr)
6661 m_graphic->dwgAdvancedMetadata().addCamera(data);
6662 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addCamera: handle %d view %d",
6663 static_cast<int>(data.handle),
6664 static_cast<int>(data.m_viewHandle));
6665}
6666
6667void RS_FilterDXFRW::addGeoPositionMarker(const DRW_GeoPositionMarker &data) {
6668 if (m_graphic != nullptr)
6669 m_graphic->dwgAdvancedMetadata().addGeoPositionMarker(data);
6670 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addGeoPositionMarker: handle %d",
6671 static_cast<int>(data.handle));
6672}
6673
6674void RS_FilterDXFRW::addTvDeviceProperties(const DRW_TvDeviceProperties &data) {
6675 if (m_graphic != nullptr)
6676 m_graphic->dwgAdvancedMetadata().addTvDeviceProperties(data);
6677 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addTvDeviceProperties: handle %d",
6678 static_cast<int>(data.handle));
6679}
6680
6681void RS_FilterDXFRW::addViewportEntityHeader(
6682 const DRW_ViewportEntityHeader &data) {
6683 if (m_graphic != nullptr)
6684 m_graphic->dwgAdvancedMetadata().addViewportEntityHeader(data);
6685 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addViewportEntityHeader: handle %d",
6686 static_cast<int>(data.handle));
6687}
6688
6689void RS_FilterDXFRW::addVxControl(const DRW_VxControl &data) {
6690 if (m_graphic != nullptr)
6691 m_graphic->dwgAdvancedMetadata().addVxControl(data);
6692 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addVxControl: handle %d",
6693 static_cast<int>(data.handle));
6694}
6695
6696void RS_FilterDXFRW::addVxTableRecord(const DRW_VxTableRecord &data) {
6697 if (m_graphic != nullptr)
6698 m_graphic->dwgAdvancedMetadata().addVxTableRecord(data);
6699 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addVxTableRecord: handle %d",
6700 static_cast<int>(data.handle));
6701}
6702
6703/**
6704 * Implementation of the method which handles lightweight polyline entities.
6705 */
6706void RS_FilterDXFRW::addLWPolyline(const DRW_LWPolyline &data) {
6707 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addLWPolyline");
6708 if (data.vertlist.empty()) {
6709 return;
6710 }
6711
6712 // libdxfrw has already applied the OCS frame before this callback. A
6713 // LibreCAD polyline is strictly 2D, so only the two normalized axial
6714 // frames can be represented without retaining a second coordinate space.
6715 if (!hasNormalizedAxialExtrusion(data)) {
6716 RS_DEBUGRS_Debug::instance()->print(
6717 RS_Debug::D_WARNING,
6718 "RS_FilterDXFRW::addLWPolyline: skipped non-axial extrusion");
6719 return;
6720 }
6721 const bool reflectedOcs = data.extPoint.z < 0.0;
6722 RS_PolylineData d(RS_Vector{}, RS_Vector{}, data.flags & 0x1);
6723 auto polyline = std::make_unique<RS_Polyline>(m_currentContainer, d);
6724 setEntityAttributes(polyline.get(), &data);
6725
6726 std::vector<std::pair<RS_Vector, double>> verList;
6727 for (const auto &v : data.vertlist) {
6728 verList.emplace_back(std::make_pair(RS_Vector{v->x, v->y},
6729 reflectedOcs ? -v->bulge : v->bulge));
6730 }
6731
6732 polyline->appendVertexs(verList);
6733 const bool defaultExtrusion = !reflectedOcs;
6734 bool hasVertexMetadata = false;
6735 for (const auto &v : data.vertlist) {
6736 if (v && (v->stawidth != 0.0 || v->endwidth != 0.0 || v->identifier != 0)) {
6737 hasVertexMetadata = true;
6738 break;
6739 }
6740 }
6741 if (data.width != 0.0 || data.elevation != 0.0 || data.thickness != 0.0 ||
6742 !defaultExtrusion || hasVertexMetadata) {
6743 std::vector<std::shared_ptr<DRW_Variant>> ext;
6744 ext.push_back(std::make_shared<DRW_Variant>(
6745 1001, std::string("LibreCAD_LWPOLYLINE")));
6746 ext.push_back(std::make_shared<DRW_Variant>(1040, data.width));
6747 ext.push_back(std::make_shared<DRW_Variant>(
6748 1040, reflectedOcs ? -data.elevation : data.elevation));
6749 ext.push_back(std::make_shared<DRW_Variant>(
6750 1040, reflectedOcs ? -data.thickness : data.thickness));
6751 ext.push_back(
6752 std::make_shared<DRW_Variant>(1010, DRW_Coord{0.0, 0.0, 1.0}));
6753 ext.push_back(std::make_shared<DRW_Variant>(
6754 1070, std::int32_t{static_cast<int>(data.vertlist.size())}));
6755 for (const auto &v : data.vertlist) {
6756 ext.push_back(std::make_shared<DRW_Variant>(1040, v ? v->stawidth : 0.0));
6757 ext.push_back(std::make_shared<DRW_Variant>(1040, v ? v->endwidth : 0.0));
6758 ext.push_back(std::make_shared<DRW_Variant>(
6759 1071, std::int32_t{v ? v->identifier : 0}));
6760 }
6761 polyline->setDrwExtData(std::move(ext));
6762 }
6763 m_currentContainer->addEntity(polyline.release());
6764}
6765
6766/**
6767 * Implementation of the method which handles MLINESTYLE table entries.
6768 * Cache by name so a later addMLine can resolve element offsets/colors.
6769 */
6770void RS_FilterDXFRW::addMLineStyle(const DRW_MLineStyle &data) {
6771 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addMLineStyle: %s (%zu elements)",
6772 data.name.c_str(), data.elements.size());
6773 QString key = QString::fromUtf8(data.name.c_str());
6774 if (!key.isEmpty()) {
6775 m_mlineStyleCache[key] = data;
6776 }
6777 // Durable metadata (in ADDITION to the transient cache addMLine uses to
6778 // decompose). The DWG reader populates ONLY this; the DXF->DXF path keeps
6779 // MLINESTYLE in the raw net, so writeObjects dedups by handle to avoid a
6780 // double-emit.
6781 if (m_graphic != nullptr) {
6782 m_graphic->dwgAdvancedMetadata().addMLineStyle(data);
6783 }
6784}
6785
6786void RS_FilterDXFRW::addWipeoutVariables(const DRW_WipeoutVariables &data) {
6787 // DWG read populates only this typed metadata; DXF read also keeps it in the
6788 // raw net (writeObjects dedups by handle). Enables DWG->DXF typed re-emit.
6789 if (m_graphic != nullptr) {
6790 m_graphic->dwgAdvancedMetadata().addWipeoutVariables(data);
6791 }
6792}
6793
6794/**
6795 * Implementation of the method which handles MLINE entities.
6796 *
6797 * LibreCAD has no native multiline. Decompose each MLINE into N parallel
6798 * RS_Polylines (one per element in the referenced MLINESTYLE), with
6799 * round-trip metadata stored on each polyline's drwExtData so the export
6800 * path can reconstruct an MLINE on save.
6801 */
6802void RS_FilterDXFRW::addMLine(const DRW_MLine *data) {
6803 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addMLine");
6804 if (!data || data->vertlist.empty() || data->numLines == 0)
6805 return;
6806
6807 const int N = data->numLines;
6808 QString styleName = QString::fromUtf8(data->styleName.c_str());
6809 auto styleIt = m_mlineStyleCache.find(styleName);
6810 const DRW_MLineStyle *style =
6811 (styleIt != m_mlineStyleCache.end()) ? &styleIt->second : nullptr;
6812
6813 // Compute effective per-element offsets after justification + scale.
6814 // Justification: 0=top, 1=zero/middle, 2=bottom. The reference offset
6815 // is removed from each element's offset so the chosen line aligns
6816 // with the baseline polyline path.
6817 std::vector<double> effOffsets(N, 0.0);
6818 if (style && static_cast<int>(style->elements.size()) == N) {
6819 double ref = 0.0;
6820 if (data->justification == 0) { // top
6821 for (auto &e : style->elements)
6822 ref = std::max(ref, e.offset);
6823 } else if (data->justification == 2) { // bottom
6824 for (auto &e : style->elements)
6825 ref = std::min(ref, e.offset);
6826 }
6827 for (int i = 0; i < N; ++i) {
6828 effOffsets[i] = (style->elements[i].offset - ref) * data->scale;
6829 }
6830 } else {
6831 // Fallback: evenly spaced [-(N-1)/2 .. +(N-1)/2] * scale.
6832 for (int i = 0; i < N; ++i) {
6833 effOffsets[i] = (i - 0.5 * (N - 1)) * data->scale;
6834 }
6835 }
6836
6837 const QString mlineId = QStringLiteral("mline_%1")(QString(QtPrivate::qMakeStringPrivate(u"" "mline_%1"))).arg(data->handle);
6838 const bool closed = (data->openClosed & 0x1) != 0;
6839
6840 for (int i = 0; i < N; ++i) {
6841 RS_PolylineData pd(RS_Vector{}, RS_Vector{}, closed);
6842 auto polyline = std::make_unique<RS_Polyline>(m_currentContainer, pd);
6843 setEntityAttributes(polyline.get(), data);
6844
6845 for (const auto &v : data->vertlist) {
6846 RS_Vector miter(v.miterDir.x, v.miterDir.y);
6847 const double mlen = miter.magnitude();
6848 RS_Vector pos;
6849 if (mlen < RS_TOLERANCE1.0e-10) {
6850 pos = RS_Vector{v.position.x, v.position.y};
6851 } else {
6852 miter /= mlen;
6853 pos = RS_Vector{v.position.x + miter.x * effOffsets[i],
6854 v.position.y + miter.y * effOffsets[i]};
6855 }
6856 polyline->addVertex(pos, 0.0, false);
6857 }
6858
6859 // Round-trip metadata as XDATA. Schema per the implementation plan:
6860 // 1001 "LibreCAD_MLINE", 1000 mlineId, 1000 styleName,
6861 // 1071 styleHandle, 1040 scale, 1070 justification, 1070 elementCount,
6862 // 1070 elementIndex, 1040 offset, 1070 flags.
6863 // Anchor (i==0) additionally stores per-vertex baseline + miter
6864 // so the export side can reconstruct without averaging.
6865 std::vector<std::shared_ptr<DRW_Variant>> ext;
6866 ext.push_back(
6867 std::make_shared<DRW_Variant>(1001, std::string("LibreCAD_MLINE")));
6868 ext.push_back(std::make_shared<DRW_Variant>(1000, mlineId.toStdString()));
6869 ext.push_back(std::make_shared<DRW_Variant>(1000, data->styleName));
6870 ext.push_back(std::make_shared<DRW_Variant>(
6871 1071, static_cast<std::int32_t>(data->styleHandle)));
6872 ext.push_back(std::make_shared<DRW_Variant>(1040, data->scale));
6873 ext.push_back(
6874 std::make_shared<DRW_Variant>(1070, std::int32_t{data->justification}));
6875 ext.push_back(std::make_shared<DRW_Variant>(1070, std::int32_t{N}));
6876 ext.push_back(std::make_shared<DRW_Variant>(1070, std::int32_t{i}));
6877 ext.push_back(std::make_shared<DRW_Variant>(1040, effOffsets[i]));
6878 ext.push_back(
6879 std::make_shared<DRW_Variant>(1070, std::int32_t{data->openClosed}));
6880 if (i == 0) {
6881 // Anchor carries baseline + miter for each vertex.
6882 for (const auto &v : data->vertlist) {
6883 ext.push_back(std::make_shared<DRW_Variant>(
6884 1011, DRW_Coord(v.position.x, v.position.y, v.position.z)));
6885 ext.push_back(std::make_shared<DRW_Variant>(
6886 1013, DRW_Coord(v.miterDir.x, v.miterDir.y, v.miterDir.z)));
6887 }
6888 }
6889 polyline->setDrwExtData(std::move(ext));
6890
6891 m_currentContainer->addEntity(polyline.release());
6892 }
6893}
6894
6895/**
6896 * Implementation of the UNDERLAYDEFINITION callback.
6897 * Caches by handle so addUnderlay (entities arrive earlier) and the
6898 * export reconstruction can resolve filename + sheet on demand.
6899 */
6900void RS_FilterDXFRW::linkUnderlay(const DRW_UnderlayDefinition *d) {
6901 if (!d)
6902 return;
6903 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::linkUnderlay: %s", d->filename.c_str());
6904 m_underlayDefMap[d->handle] = *d;
6905 if (m_graphic != nullptr)
6906 m_graphic->dwgAdvancedMetadata().addUnderlayDefinition(*d);
6907}
6908
6909void RS_FilterDXFRW::addShape(const DRW_Shape &data) {
6910 if (m_graphic != nullptr)
6911 m_graphic->dwgAdvancedMetadata().addShape(data);
6912 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addShape: index %d style %d",
6913 static_cast<int>(data.m_shapeIndex),
6914 static_cast<int>(data.m_shapeFileHandle));
6915}
6916
6917void RS_FilterDXFRW::addOle2Frame(const DRW_Ole2Frame &data) {
6918 if (m_graphic != nullptr)
6919 m_graphic->dwgAdvancedMetadata().addOle2Frame(data);
6920 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addOle2Frame: bytes %d declared %d",
6921 static_cast<int>(data.m_payloadByteCount),
6922 static_cast<int>(data.m_declaredPayloadLength));
6923}
6924
6925void RS_FilterDXFRW::addOleFrame(const DRW_OleFrame &data) {
6926 if (m_graphic != nullptr)
6927 m_graphic->dwgAdvancedMetadata().addOleFrame(data);
6928 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addOleFrame: bytes %d declared %d",
6929 static_cast<int>(data.m_payloadByteCount),
6930 static_cast<int>(data.m_declaredPayloadLength));
6931}
6932
6933/**
6934 * Implementation of the UNDERLAY entity callback (PDFUNDERLAY/DGNUNDERLAY/
6935 * DWFUNDERLAY). Decomposes the underlay into a single closed RS_Polyline
6936 * showing the clip-boundary placeholder. The filename + kind ride along
6937 * in XDATA so the export side can reconstruct the original UNDERLAY entity.
6938 *
6939 * Filename resolution: OBJECTS are parsed AFTER ENTITIES in libdxfrw, so
6940 * m_underlayDefMap may be empty here. We store the definitionHandle in
6941 * XDATA only; on-demand lookups (UI tooltip, export reconstruction) use
6942 * the cache once it's populated by linkUnderlay.
6943 */
6944void RS_FilterDXFRW::addUnderlay(const DRW_Underlay *data) {
6945 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addUnderlay");
6946 if (!data)
6947 return;
6948
6949 // Build clip polygon in WCS. clipBoundary is in OCS-2D; for a typical
6950 // 2D extrusion (extPoint == (0,0,1)), OCS == WCS modulo position +
6951 // scale + rotation. LibreCAD is 2D so we project z-up regardless.
6952 std::vector<RS_Vector> verts;
6953 const bool fallbackPreviewGenerated = true;
6954 if (data->clipBoundary.size() >= 3) {
6955 verts.reserve(data->clipBoundary.size());
6956 for (const auto &v : data->clipBoundary) {
6957 verts.emplace_back(v.x, v.y);
6958 }
6959 } else if (data->clipBoundary.size() == 2) {
6960 // Two corners of an axis-aligned rectangle (in OCS).
6961 const auto &a = data->clipBoundary[0];
6962 const auto &b = data->clipBoundary[1];
6963 verts.emplace_back(a.x, a.y);
6964 verts.emplace_back(b.x, a.y);
6965 verts.emplace_back(b.x, b.y);
6966 verts.emplace_back(a.x, b.y);
6967 } else {
6968 // No clip — synthesize a unit-square placeholder centered on origin.
6969 // Real underlay sizes need PDF page dims we don't have access to.
6970 verts.emplace_back(-0.5, -0.5);
6971 verts.emplace_back(0.5, -0.5);
6972 verts.emplace_back(0.5, 0.5);
6973 verts.emplace_back(-0.5, 0.5);
6974 }
6975
6976 // Apply scale, rotation, translation (rotation is in radians per DWG).
6977 const double cs = std::cos(data->rotation);
6978 const double sn = std::sin(data->rotation);
6979 for (auto &p : verts) {
6980 const double sx = p.x * data->scale.x;
6981 const double sy = p.y * data->scale.y;
6982 const double rx = sx * cs - sy * sn;
6983 const double ry = sx * sn + sy * cs;
6984 p = RS_Vector{rx + data->position.x, ry + data->position.y};
6985 }
6986
6987 RS_PolylineData pd(RS_Vector{}, RS_Vector{}, /*closed=*/true);
6988 auto polyline = new RS_Polyline(m_currentContainer, pd);
6989 setEntityAttributes(polyline, data);
6990 for (const auto &p : verts)
6991 polyline->addVertex(p, 0.0, false);
6992
6993 // Round-trip metadata as XDATA. App marker LibreCAD_UNDERLAY.
6994 const QString underlayId = QStringLiteral("underlay_%1")(QString(QtPrivate::qMakeStringPrivate(u"" "underlay_%1"))).arg(data->handle);
6995 const char *kindStr = (data->kind == DRW_Underlay::DGN) ? "DGN"
6996 : (data->kind == DRW_Underlay::DWF) ? "DWF"
6997 : "PDF";
6998 std::vector<std::shared_ptr<DRW_Variant>> ext;
6999 ext.push_back(
7000 std::make_shared<DRW_Variant>(1001, std::string("LibreCAD_UNDERLAY")));
7001 ext.push_back(std::make_shared<DRW_Variant>(1000, underlayId.toStdString()));
7002 ext.push_back(std::make_shared<DRW_Variant>(1000, std::string(kindStr)));
7003 ext.push_back(std::make_shared<DRW_Variant>(
7004 1071, std::int32_t{static_cast<int>(data->definitionHandle)}));
7005 ext.push_back(std::make_shared<DRW_Variant>(
7006 1010, DRW_Coord(data->position.x, data->position.y, data->position.z)));
7007 ext.push_back(std::make_shared<DRW_Variant>(1000, std::string("EXTRUSION")));
7008 ext.push_back(std::make_shared<DRW_Variant>(
7009 1010, DRW_Coord(data->extPoint.x, data->extPoint.y, data->extPoint.z)));
7010 ext.push_back(std::make_shared<DRW_Variant>(1000, std::string("SCALE3")));
7011 ext.push_back(std::make_shared<DRW_Variant>(1040, data->scale.x));
7012 ext.push_back(std::make_shared<DRW_Variant>(1040, data->scale.y));
7013 ext.push_back(std::make_shared<DRW_Variant>(1040, data->scale.z));
7014 ext.push_back(std::make_shared<DRW_Variant>(1040, data->rotation));
7015 ext.push_back(std::make_shared<DRW_Variant>(1070, std::int32_t{data->flags}));
7016 ext.push_back(
7017 std::make_shared<DRW_Variant>(1070, std::int32_t{data->contrast}));
7018 ext.push_back(std::make_shared<DRW_Variant>(1070, std::int32_t{data->fade}));
7019 if (data->clipBoundary.empty()) {
7020 ext.push_back(std::make_shared<DRW_Variant>(1000, std::string("NO_CLIP")));
7021 } else {
7022 ext.push_back(
7023 std::make_shared<DRW_Variant>(1000, std::string("CLIP_ORIGINAL")));
7024 for (const DRW_Coord &point : data->clipBoundary) {
7025 ext.push_back(std::make_shared<DRW_Variant>(
7026 1010, DRW_Coord(point.x, point.y, point.z)));
7027 }
7028 }
7029 if (!data->inverseClipBoundary.empty() || (data->flags & 0x10) != 0) {
7030 ext.push_back(
7031 std::make_shared<DRW_Variant>(1000, std::string("INVERSE_CLIP")));
7032 for (const DRW_Coord &point : data->inverseClipBoundary) {
7033 ext.push_back(std::make_shared<DRW_Variant>(
7034 1010, DRW_Coord(point.x, point.y, point.z)));
7035 }
7036 }
7037 polyline->setDrwExtData(std::move(ext));
7038
7039 if (m_graphic != nullptr)
7040 m_graphic->dwgAdvancedMetadata().addUnderlay(*data,
7041 fallbackPreviewGenerated);
7042 m_currentContainer->addEntity(polyline);
7043}
7044
7045/**
7046 * Implementation of the method which handles polyline entities.
7047 */
7048void RS_FilterDXFRW::addPolyline(const DRW_Polyline &data) {
7049 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addPolyline");
7050 if (data.flags & 0x10) {
7051 // the polyline is a polygon mesh
7052 int M = data.vertexcount;
7053 int N = data.facecount;
7054 const bool canRenderFallback =
7055 M > 0 && N > 0 && data.vertlist.size() == static_cast<size_t>(M * N) &&
7056 data.curvetype == 0;
7057 if (m_graphic != nullptr)
7058 m_graphic->dwgAdvancedMetadata().addMeshPolyline(data, canRenderFallback);
7059 if (M <= 0 || N <= 0 ||
7060 data.vertlist.size() != static_cast<size_t>(M * N)) {
7061 return; // invalid mesh
7062 }
7063 if (data.curvetype != 0) {
7064 return; // smooth surfaces not handled
7065 }
7066 bool closedM = (data.flags & 0x1); // closed in M direction
7067 bool closedN = (data.flags & 0x20); // closed in N direction
7068 const std::string meshId =
7069 std::string("polyline_mesh_") + std::to_string(data.handle);
7070 const int meshElementCount = M + N;
7071 auto makeMeshExtData = [&](int elementIndex, const std::string &role,
7072 int roleIndex, bool anchor) {
7073 std::vector<std::shared_ptr<DRW_Variant>> ext;
7074 ext.push_back(std::make_shared<DRW_Variant>(
7075 1001, std::string("LibreCAD_POLYLINE_MESH")));
7076 ext.push_back(std::make_shared<DRW_Variant>(1000, meshId));
7077 ext.push_back(std::make_shared<DRW_Variant>(1000, role));
7078 ext.push_back(
7079 std::make_shared<DRW_Variant>(1070, std::int32_t{elementIndex}));
7080 ext.push_back(
7081 std::make_shared<DRW_Variant>(1070, std::int32_t{meshElementCount}));
7082 ext.push_back(
7083 std::make_shared<DRW_Variant>(1070, std::int32_t{roleIndex}));
7084 ext.push_back(
7085 std::make_shared<DRW_Variant>(1070, std::int32_t{data.flags}));
7086 ext.push_back(std::make_shared<DRW_Variant>(1070, std::int32_t{M}));
7087 ext.push_back(std::make_shared<DRW_Variant>(1070, std::int32_t{N}));
7088 ext.push_back(
7089 std::make_shared<DRW_Variant>(1070, std::int32_t{data.smoothM}));
7090 ext.push_back(
7091 std::make_shared<DRW_Variant>(1070, std::int32_t{data.smoothN}));
7092 ext.push_back(
7093 std::make_shared<DRW_Variant>(1070, std::int32_t{data.curvetype}));
7094 if (anchor) {
7095 for (const auto &vertex : data.vertlist) {
7096 if (!vertex) {
7097 continue;
7098 }
7099 ext.push_back(std::make_shared<DRW_Variant>(
7100 1010, DRW_Coord{vertex->basePoint.x, vertex->basePoint.y,
7101 vertex->basePoint.z}));
7102 }
7103 }
7104 return ext;
7105 };
7106 auto addMeshSidecarMetadata = [&](const RS_Entity *entity, int elementIndex,
7107 const std::string &role, int roleIndex,
7108 bool anchor) {
7109 if (m_graphic == nullptr || entity == nullptr)
7110 return;
7111 LC_DwgAdvancedMetadata::MeshSidecarRecord record;
7112 record.sourceHandle = data.handle;
7113 record.fallbackEntityId = entity->getId();
7114 record.meshId = meshId;
7115 record.role = role;
7116 record.elementIndex = elementIndex;
7117 record.elementCount = meshElementCount;
7118 record.roleIndex = roleIndex;
7119 record.flags = data.flags;
7120 record.mCount = M;
7121 record.nCount = N;
7122 record.smoothM = data.smoothM;
7123 record.smoothN = data.smoothN;
7124 record.curveType = data.curvetype;
7125 record.sourceVertexCount = anchor ? data.vertlist.size() : 0u;
7126 record.anchor = anchor;
7127 m_graphic->dwgAdvancedMetadata().addMeshSidecar(std::move(record));
7128 };
7129
7130 // Add row polylines (along N direction)
7131 for (int i = 0; i < M; i++) {
7132 RS_PolylineData pd(RS_Vector(), RS_Vector(), closedN);
7133 auto pl = std::make_unique<RS_Polyline>(m_currentContainer, pd);
7134 setEntityAttributes(pl.get(), &data);
7135 for (int j = 0; j < N; j++) {
7136 auto v = data.vertlist.at(i * N + j);
7137 auto basePoint = v->basePoint;
7138 RS_Vector pos(basePoint.x, basePoint.y);
7139 pl->addVertex(pos, 0.0, false);
7140 }
7141 pl->setDrwExtData(makeMeshExtData(i, "row", i, i == 0));
7142 addMeshSidecarMetadata(pl.get(), i, "row", i, i == 0);
7143 m_currentContainer->addEntity(pl.release());
7144 }
7145
7146 // Add column polylines (along M direction)
7147 for (int j = 0; j < N; j++) {
7148 RS_PolylineData pd(RS_Vector(), RS_Vector(), closedM);
7149 auto pl = std::make_unique<RS_Polyline>(m_currentContainer, pd);
7150 setEntityAttributes(pl.get(), &data);
7151 for (int i = 0; i < M; i++) {
7152 auto v = data.vertlist.at(i * N + j);
7153 RS_Vector pos(v->basePoint.x, v->basePoint.y);
7154 pl->addVertex(pos, 0.0, false);
7155 }
7156 pl->setDrwExtData(makeMeshExtData(M + j, "column", j, false));
7157 addMeshSidecarMetadata(pl.get(), M + j, "column", j, false);
7158 m_currentContainer->addEntity(pl.release());
7159 }
7160 return;
7161 }
7162
7163 if (data.flags & 0x40) {
7164 // the polyline is a polyface mesh
7165 std::vector<RS_Vector> vertices;
7166 std::vector<DRW_Coord> sourceVertices;
7167 std::vector<std::shared_ptr<DRW_Vertex>> faceRecords;
7168 for (const std::shared_ptr<DRW_Vertex> &v : data.vertlist) {
7169 if (!v) {
7170 continue;
7171 }
7172 const bool coordinateVertex =
7173 v->dwgSubtype() == DRW_Vertex::DwgSubtype::Polyface ||
7174 ((v->flags & 0x40) != 0 && (v->flags & 0x80) != 0);
7175 const bool faceRecord =
7176 v->dwgSubtype() == DRW_Vertex::DwgSubtype::PolyfaceFace ||
7177 ((v->flags & 0x80) != 0 && (v->flags & 0x40) == 0);
7178 if (coordinateVertex) {
7179 vertices.emplace_back(v->basePoint.x, v->basePoint.y);
7180 sourceVertices.emplace_back(v->basePoint.x, v->basePoint.y,
7181 v->basePoint.z);
7182 } else if (faceRecord) {
7183 faceRecords.push_back(v);
7184 }
7185 }
7186 const std::string polyfaceId =
7187 std::string("polyline_pface_") + std::to_string(data.handle);
7188 auto makePolyfaceExtData = [&](const DRW_Vertex &face, int faceIndex,
7189 bool anchor) {
7190 std::vector<std::shared_ptr<DRW_Variant>> ext;
7191 ext.push_back(std::make_shared<DRW_Variant>(
7192 1001, std::string("LibreCAD_POLYLINE_PFACE")));
7193 ext.push_back(std::make_shared<DRW_Variant>(1000, polyfaceId));
7194 ext.push_back(
7195 std::make_shared<DRW_Variant>(1070, std::int32_t{faceIndex}));
7196 ext.push_back(std::make_shared<DRW_Variant>(
7197 1070, std::int32_t{static_cast<int>(faceRecords.size())}));
7198 ext.push_back(
7199 std::make_shared<DRW_Variant>(1070, std::int32_t{data.flags}));
7200 ext.push_back(
7201 std::make_shared<DRW_Variant>(1070, std::int32_t{data.vertexcount}));
7202 ext.push_back(
7203 std::make_shared<DRW_Variant>(1070, std::int32_t{data.facecount}));
7204 ext.push_back(
7205 std::make_shared<DRW_Variant>(1070, std::int32_t{face.vindex1}));
7206 ext.push_back(
7207 std::make_shared<DRW_Variant>(1070, std::int32_t{face.vindex2}));
7208 ext.push_back(
7209 std::make_shared<DRW_Variant>(1070, std::int32_t{face.vindex3}));
7210 ext.push_back(
7211 std::make_shared<DRW_Variant>(1070, std::int32_t{face.vindex4}));
7212 if (anchor) {
7213 for (const auto &coord : sourceVertices) {
7214 ext.push_back(std::make_shared<DRW_Variant>(1010, coord));
7215 }
7216 }
7217 return ext;
7218 };
7219 // add faces as closed polylines
7220 for (size_t faceIndex = 0; faceIndex < faceRecords.size(); ++faceIndex) {
7221 const auto &f = faceRecords[faceIndex];
7222 std::vector<int> indices = {
7223 {f->vindex1, f->vindex2, f->vindex3, f->vindex4}};
7224 int num_points = (f->vindex4 == 0) ? 3 : 4;
7225 RS_PolylineData pd(RS_Vector(), RS_Vector(), true); // closed
7226 auto pl = std::make_unique<RS_Polyline>(m_currentContainer, pd);
7227 setEntityAttributes(pl.get(), &data);
7228 bool valid = true;
7229 for (int k = 0; k < num_points; ++k) {
7230 int idx = std::abs(indices[k]);
7231 if (idx < 1 || idx > static_cast<int>(vertices.size())) {
7232 valid = false;
7233 break;
7234 }
7235 pl->addVertex(vertices[idx - 1], 0.0);
7236 }
7237 if (valid) {
7238 pl->setDrwExtData(makePolyfaceExtData(*f, static_cast<int>(faceIndex),
7239 faceIndex == 0));
7240 m_currentContainer->addEntity(pl.release());
7241 }
7242 }
7243 return;
7244 }
7245
7246 RS_PolylineData pd(RS_Vector{}, RS_Vector{}, data.flags & 0x1);
7247 const bool is2dPolyline = (data.flags & (8 | 16 | 64)) == 0;
7248 if (is2dPolyline && !hasNormalizedAxialExtrusion(data)) {
7249 RS_DEBUGRS_Debug::instance()->print(
7250 RS_Debug::D_WARNING,
7251 "RS_FilterDXFRW::addPolyline: skipped non-axial 2D extrusion");
7252 return;
7253 }
7254 const bool reflectedOcs = is2dPolyline && data.extPoint.z < 0.0;
7255 auto polyline = std::make_unique<RS_Polyline>(m_currentContainer, pd);
7256 setEntityAttributes(polyline.get(), &data);
7257
7258 if (data.vertlist.empty()) {
7259 m_currentContainer->addEntity(polyline.release());
7260 return;
7261 }
7262
7263 auto vert0 = data.vertlist[0];
7264 RS_Vector first_pos(reflectedOcs ? -vert0->basePoint.x : vert0->basePoint.x,
7265 vert0->basePoint.y);
7266 const double firstBulge = reflectedOcs ? -vert0->bulge : vert0->bulge;
7267 polyline->addVertex(first_pos, firstBulge, false);
7268 RS_Vector prev_pos = first_pos;
7269
7270 bool closed = (data.flags & 0x1) != 0;
7271 size_t num_segments =
7272 closed ? data.vertlist.size() : data.vertlist.size() - 1;
7273
7274 for (size_t i = 0; i < num_segments; ++i) {
7275 size_t vert_idx = (i + 1) % data.vertlist.size();
7276 auto vert = data.vertlist[vert_idx];
7277 RS_Vector curr_pos(reflectedOcs ? -vert->basePoint.x : vert->basePoint.x,
7278 vert->basePoint.y);
7279
7280 size_t bulge_idx = i % data.vertlist.size();
7281 double segmentBulge = data.vertlist[bulge_idx]->bulge;
7282 if (reflectedOcs)
7283 segmentBulge = -segmentBulge;
7284 double nextBulge = vert->bulge;
7285 if (reflectedOcs)
7286 nextBulge = -nextBulge;
7287 const auto &extData = data.vertlist[bulge_idx]->extData;
7288
7289 addPolylineSegment(*polyline, prev_pos, curr_pos, segmentBulge, nextBulge,
7290 extData);
7291
7292 prev_pos = curr_pos;
7293 }
7294
7295 if (closed) {
7296 polyline->setFlag(RS2::FlagClosed);
7297 polyline->getData().endpoint = polyline->getData().startpoint;
7298 } else {
7299 polyline->endPolyline();
7300 }
7301
7302 bool hasVertexMetadata = data.defstawidth != 0.0 || data.defendwidth != 0.0;
7303 for (const auto &vertex : data.vertlist) {
7304 if (vertex && (vertex->stawidth != 0.0 || vertex->endwidth != 0.0 ||
7305 vertex->identifier != 0)) {
7306 hasVertexMetadata = true;
7307 break;
7308 }
7309 }
7310 if (is2dPolyline && (data.basePoint.z != 0.0 || data.thickness != 0.0 ||
7311 reflectedOcs || hasVertexMetadata)) {
7312 std::vector<std::shared_ptr<DRW_Variant>> ext;
7313 ext.push_back(std::make_shared<DRW_Variant>(
7314 1001, std::string("LibreCAD_LWPOLYLINE")));
7315 ext.push_back(std::make_shared<DRW_Variant>(1040, 0.0));
7316 ext.push_back(std::make_shared<DRW_Variant>(
7317 1040, reflectedOcs ? -data.basePoint.z : data.basePoint.z));
7318 ext.push_back(std::make_shared<DRW_Variant>(
7319 1040, reflectedOcs ? -data.thickness : data.thickness));
7320 ext.push_back(
7321 std::make_shared<DRW_Variant>(1010, DRW_Coord{0.0, 0.0, 1.0}));
7322 ext.push_back(std::make_shared<DRW_Variant>(
7323 1070, std::int32_t{static_cast<int>(data.vertlist.size())}));
7324 for (const auto &vertex : data.vertlist) {
7325 const double startWidth = vertex && vertex->stawidth != 0.0
7326 ? vertex->stawidth
7327 : data.defstawidth;
7328 const double endWidth = vertex && vertex->endwidth != 0.0
7329 ? vertex->endwidth
7330 : data.defendwidth;
7331 ext.push_back(std::make_shared<DRW_Variant>(1040, startWidth));
7332 ext.push_back(std::make_shared<DRW_Variant>(1040, endWidth));
7333 ext.push_back(std::make_shared<DRW_Variant>(
7334 1071, std::int32_t{vertex ? vertex->identifier : 0}));
7335 }
7336 polyline->setDrwExtData(std::move(ext));
7337 }
7338
7339 m_currentContainer->addEntity(polyline.release());
7340}
7341
7342bool RS_FilterDXFRW::handleQuadraticConicSpline(const DRW_Spline *data) {
7343 if (data->degree != 2 || data->controllist.size() != 3) {
7344 return false;
7345 }
7346
7347 // Three explicit branches by middle weight: hyperbola (w>1), parabola
7348 // (weights absent or w=1), or fall-through for ellipse arc (w<1) and
7349 // anything else the dispatcher can't classify cleanly.
7350 std::unique_ptr<RS_Entity> en =
7351 LC_HyperbolaSpline::splineToHyperbola(*data, m_currentContainer);
7352 if (en == nullptr) {
7353 en = LC_ParabolaSpline::splineToParabola(*data, m_currentContainer);
7354 }
7355 if (en == nullptr) {
7356 // Not a hyperbola or parabola — likely an ellipse arc (w<1), a
7357 // collinear/degenerate spline, or a non-canonical knot vector. Let
7358 // the caller fall through to the generic addSpline path so the
7359 // entity isn't silently mis-classified as a parabola.
7360 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::handleQuadraticConicSpline: "
7361 "degree-2/3-control spline not classified as "
7362 "parabola or hyperbola; falling through to addSpline");
7363 return false;
7364 }
7365 setEntityAttributes(en.get(), data);
7366 en->update();
7367 m_currentContainer->addEntity(en.release());
7368
7369 return true;
7370}
7371
7372/**
7373 * Implementation of the method which handles splines.
7374 */
7375void RS_FilterDXFRW::addSpline(const DRW_Spline *data) {
7376 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addSpline: degree: %d", data->degree);
7377
7378 // Special case: rational quadratic conic (hyperbola or parabola)
7379 if (handleQuadraticConicSpline(data)) {
7380 return; // Conic handled successfully
7381 }
7382
7383 // Spline points case. Weighted degree-2 splines are exact rational conics
7384 // or NURBS segments; keep them on the RS_Spline path so weights survive.
7385 if (data->degree == 2 && !hasRationalSplineWeights(data)) {
7386 bool closed = (data->flags & 0x1) == 0x1;
7387 bool controlOnly = data->nfit == 0;
7388
7389 const LC_SplinePointsData d(closed, controlOnly);
7390 const auto splinePoints = new LC_SplinePoints(m_currentContainer, d);
7391 setEntityAttributes(splinePoints, data);
7392
7393 for (const auto &vert : data->controllist) {
7394 if (vert) {
7395 splinePoints->addControlPoint({vert->x, vert->y});
7396 }
7397 }
7398 for (const auto &vert : data->fitlist) {
7399 if (vert) {
7400 splinePoints->addPoint({vert->x, vert->y});
7401 }
7402 }
7403
7404 splinePoints->update();
7405 m_currentContainer->addEntity(splinePoints);
7406 return;
7407 }
7408
7409 // General spline (degree 1-3)
7410 if (data->degree < 1) {
7411 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
7412 "RS_FilterDXFRW::addSpline: unsupported spline degree %d",
7413 data->degree);
7414 return;
7415 }
7416 if (data->degree > 3) {
7417 std::unique_ptr<LC_SplinePoints> sampled =
7418 approximateDrwSpline(m_currentContainer, data);
7419 if (sampled) {
7420 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
7421 "RS_FilterDXFRW::addSpline: approximating unsupported "
7422 "spline degree %d",
7423 data->degree);
7424 setEntityAttributes(sampled.get(), data);
7425 sampled->update();
7426 m_currentContainer->addEntity(sampled.release());
7427 return;
7428 }
7429
7430 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
7431 "RS_FilterDXFRW::addSpline: unsupported spline degree %d",
7432 data->degree);
7433 return;
7434 }
7435
7436 const bool isClosed = (data->flags & 0x1) == 0x1;
7437
7438 RS_SplineData d(data->degree, isClosed);
7439 if (!data->knotslist.empty()) {
7440 const double tolknot = (data->tolknot > 0.0) ? data->tolknot : 1e-7;
7441 for (const double k : data->knotslist) {
7442 d.knotslist.push_back(RS_Math::round(k, tolknot));
7443 }
7444 }
7445
7446 d.type = isClosed ? RS_SplineData::SplineType::Standard
7447 : RS_SplineData::SplineType::ClampedOpen;
7448
7449 const auto spline = new RS_Spline(m_currentContainer, d);
7450 setEntityAttributes(spline, data);
7451 m_currentContainer->addEntity(spline);
7452
7453 // Control points and weights. Non-rational B-splines have no weight array
7454 // (weight=1.0 implied); only warn for rational splines (flag bit 2).
7455 size_t numCtrl = data->controllist.size();
7456 bool isRational = (data->flags & 0x4) != 0;
7457 if (isRational && numCtrl != data->weightlist.size()) {
7458 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
7459 "RS_FilterDXFRW::addSpline: rational spline control "
7460 "points (%zu) != weights (%zu)",
7461 numCtrl, data->weightlist.size());
7462 }
7463
7464 for (size_t i = 0; i < numCtrl; ++i) {
7465 const auto &vert = data->controllist[i];
7466 const double weight =
7467 (i < data->weightlist.size()) ? data->weightlist[i] : 1.0;
7468 if (vert) {
7469 spline->addControlPointRaw({vert->x, vert->y}, weight);
7470 }
7471 }
7472
7473 // Fit points fallback
7474 if (numCtrl == 0 && data->degree != 2) {
7475 std::vector<RS_Vector> fitPoints;
7476 std::transform(data->fitlist.begin(), data->fitlist.end(),
7477 std::back_inserter(fitPoints),
7478 [](const std::shared_ptr<DRW_Coord> &coord) {
7479 return RS_Vector{coord->x, coord->y};
7480 });
7481 spline->setFitPoints(fitPoints);
7482 }
7483
7484 if (isClosed) {
7485 spline->setClosed(true);
7486 }
7487
7488 spline->update();
7489}
7490
7491/**
7492 * Handles a HELIX entity. LibreCAD has no native helix; the entity is mapped
7493 * to its spline approximation via addSpline. The AcDbHelix axis/turns metadata
7494 * (radius, turns, turnHeight, axisVector, ...) is preserved across a DWG
7495 * round-trip but is not represented in the RS entity model, so it is dropped
7496 * on import.
7497 */
7498void RS_FilterDXFRW::addHelix(const DRW_Helix *data) {
7499 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addHelix: mapping HELIX to spline "
7500 "approximation (axis/turns metadata dropped)");
7501 addSpline(data);
7502}
7503
7504/**
7505 * Implementation of the method which handles inserts.
7506 */
7507void RS_FilterDXFRW::addInsert(const DRW_Insert &data) {
7508 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addInsert");
7509
7510 RS_Vector ip(data.basePoint.x, data.basePoint.y, data.basePoint.z);
7511 RS_Vector sc(data.xscale, data.yscale, data.zscale);
7512 RS_Vector sp(data.colspace, data.rowspace);
7513
7514 // cout << "Insert: " << name << " " << ip << " " << cols << "/" << rows <<
7515 // endl;
7516
7517 RS_InsertData d(QString::fromUtf8(data.name.c_str()), ip, sc, data.angle,
7518 data.colcount, data.rowcount, sp, nullptr, RS2::NoUpdate);
7519 d.extrusion = RS_Vector(data.extPoint.x, data.extPoint.y, data.extPoint.z);
7520 RS_Insert *entity = new RS_Insert(m_currentContainer, d);
7521 setEntityAttributes(entity, &data);
7522 RS_DEBUGRS_Debug::instance()->Log() << " id: " << entity->getId();
7523 // entity->update();
7524 m_currentContainer->addEntity(entity);
7525
7526 // Render block attributes (ATTRIB) attached to this INSERT. Invisible
7527 // records are retained as hidden text so their tag/owner/order metadata
7528 // can be reconstructed on the next save.
7529 // ATTRIB coordinates are in world space (per AutoCAD convention), so
7530 // each becomes an independent RS_Text in the current container.
7531 int ordinal = 0;
7532 for (const auto &att : data.attlist) {
7533 if (!att)
7534 continue;
7535
7536 // Multi-line ATTRIB (R2018+, ODA spec §20.4.4 Attribute type 2/4):
7537 // libdxfrw saw the `100 / Embedded Object` DXF subclass marker on the
7538 // ATTRIB and populated att->mtext from the embedded MTEXT body.
7539 // Render it as RS_MText so the multi-line content + formatting codes
7540 // survive; the plain `text` field is the single-line fallback.
7541 if (att->mtext) {
7542 auto *mt = mtextEntityFromDRW(*att->mtext);
7543 mt->setParent(m_currentContainer);
7544 setEntityAttributes(mt, att.get());
7545 mt->setVisible((att->attribFlags & 0x1) == 0);
7546 appendInsertAttribSidecar(mt, *att, entity->getId(), ordinal++);
7547 mt->update();
7548 m_currentContainer->addEntity(mt);
7549 continue;
7550 }
7551
7552 RS_Vector refPoint(att->basePoint.x, att->basePoint.y);
7553 RS_Vector secPoint(att->secPoint.x, att->secPoint.y);
7554 if (att->alignV != 0 || att->alignH != 0 ||
7555 att->alignH == DRW_Text::HMiddle) {
7556 if (att->alignH != DRW_Text::HAligned && att->alignH != DRW_Text::HFit) {
7557 secPoint = RS_Vector(att->basePoint.x, att->basePoint.y);
7558 refPoint = RS_Vector(att->secPoint.x, att->secPoint.y);
7559 }
7560 }
7561
7562 RS_TextData::VAlign valign = (RS_TextData::VAlign)att->alignV;
7563 RS_TextData::HAlign halign = (RS_TextData::HAlign)att->alignH;
7564 RS_TextData::TextGeneration dir;
7565 if (att->textgen == 2)
7566 dir = RS_TextData::Backward;
7567 else if (att->textgen == 4)
7568 dir = RS_TextData::UpsideDown;
7569 else
7570 dir = RS_TextData::None;
7571
7572 QString sty = QString::fromUtf8(att->style.c_str());
7573 prepareTextStyleName(sty);
7574 QString text = toNativeString(QString::fromUtf8(att->text.c_str()));
7575
7576 RS_TextData td(refPoint, secPoint, att->height, att->widthscale, valign,
7577 halign, dir, text, sty, att->angle * M_PI3.14159265358979323846 / 180,
7578 RS2::NoUpdate);
7579 auto textEntity = std::make_unique<RS_Text>(m_currentContainer, td);
7580 setEntityAttributes(textEntity.get(), att.get());
7581 textEntity->setVisible((att->attribFlags & 0x1) == 0);
7582 appendInsertAttribSidecar(textEntity.get(), *att, entity->getId(),
7583 ordinal++);
7584 textEntity->update();
7585 m_currentContainer->addEntity(textEntity.release());
7586 }
7587}
7588
7589void RS_FilterDXFRW::addTable(const DRW_Table &data) {
7590 TableFallbackRenderSummary fallbackSummary;
7591 const bool fallbackRendered = addTableFallback(data, &fallbackSummary);
7592 if (m_graphic != nullptr) {
7593 m_graphic->dwgAdvancedMetadata().addTable(data, fallbackRendered);
7594 LC_DwgAdvancedMetadata::TableFallbackRenderSummary metadataSummary;
7595 metadataSummary.tableHandle = data.handle;
7596 metadataSummary.gridEntityCount = fallbackSummary.gridEntityCount;
7597 metadataSummary.textEntityCount = fallbackSummary.textEntityCount;
7598 metadataSummary.placeholderEntityCount =
7599 fallbackSummary.placeholderEntityCount;
7600 metadataSummary.unresolvedTextStyleCount =
7601 fallbackSummary.unresolvedTextStyleCount;
7602 metadataSummary.clampedDimensionCount =
7603 fallbackSummary.clampedDimensionCount;
7604 m_graphic->dwgAdvancedMetadata().updateTableFallbackRenderSummary(
7605 metadataSummary);
7606 }
7607 addInsert(data);
7608}
7609
7610bool RS_FilterDXFRW::addTableFallback(const DRW_Table &data,
7611 TableFallbackRenderSummary *summary) {
7612 if (!data.m_hasSemanticContent || data.m_content.m_rows.empty() ||
7613 data.m_content.m_columns.empty() || m_currentContainer == nullptr) {
7614 return false;
7615 }
7616
7617 const DRW_TableContent &content = data.m_content;
7618 const RS_Vector origin(data.basePoint.x, data.basePoint.y, data.basePoint.z);
7619 RS_Vector xAxis(data.m_horizontalDirection.x, data.m_horizontalDirection.y,
7620 data.m_horizontalDirection.z);
7621 if (!xAxis.valid || xAxis.magnitude() <= RS_TOLERANCE1.0e-10) {
7622 xAxis = RS_Vector::polar(1.0, data.angle);
7623 } else {
7624 xAxis.set(xAxis.x / xAxis.magnitude(), xAxis.y / xAxis.magnitude(),
7625 xAxis.z / xAxis.magnitude());
7626 }
7627 const RS_Vector yAxis = RS_Vector(-xAxis.y, xAxis.x, 0.0);
7628
7629 std::vector<double> columnOffsets;
7630 columnOffsets.reserve(content.m_columns.size() + 1);
7631 columnOffsets.push_back(0.0);
7632 for (size_t column = 0; column < content.m_columns.size(); ++column) {
7633 columnOffsets.push_back(columnOffsets.back() +
7634 tableColumnWidth(content, column, summary));
7635 }
7636
7637 std::vector<double> rowOffsets;
7638 rowOffsets.reserve(content.m_rows.size() + 1);
7639 rowOffsets.push_back(0.0);
7640 for (size_t row = 0; row < content.m_rows.size(); ++row) {
7641 rowOffsets.push_back(rowOffsets.back() +
7642 tableRowHeight(content, row, summary));
7643 }
7644
7645 auto tablePoint = [&](double x, double y) {
7646 return origin + xAxis * x - yAxis * y;
7647 };
7648 auto addFallbackRecord = [&](RS_Entity *entity, int row, int column,
7649 LC_DwgAdvancedMetadata::TableFallbackRole role) {
7650 if (m_graphic == nullptr || entity == nullptr)
7651 return;
7652 LC_DwgAdvancedMetadata::TableFallbackEntityRecord record;
7653 record.tableHandle = data.handle;
7654 record.sourceHandle = data.handle;
7655 record.row = row;
7656 record.column = column;
7657 record.role = role;
7658 record.entityId = entity->getId();
7659 m_graphic->dwgAdvancedMetadata().addTableFallbackEntity(record);
7660 };
7661 auto addBorder = [&](const RS_Vector &a, const RS_Vector &b, int row,
7662 int column) {
7663 auto *line = new RS_Line{m_currentContainer, {a, b}};
7664 setEntityAttributes(line, &data);
7665 line->update();
7666 addFallbackRecord(line, row, column,
7667 LC_DwgAdvancedMetadata::TableFallbackRole::GridLine);
7668 if (summary != nullptr)
7669 ++summary->gridEntityCount;
7670 m_currentContainer->addEntity(line);
7671 };
7672
7673 for (size_t column = 0; column < columnOffsets.size(); ++column) {
7674 const double x = columnOffsets[column];
7675 addBorder(tablePoint(x, 0.0), tablePoint(x, rowOffsets.back()), -1,
7676 static_cast<int>(column));
7677 }
7678 for (size_t row = 0; row < rowOffsets.size(); ++row) {
7679 const double y = rowOffsets[row];
7680 addBorder(tablePoint(0.0, y), tablePoint(columnOffsets.back(), y),
7681 static_cast<int>(row), -1);
7682 }
7683
7684 bool renderedText = false;
7685 QString style = m_textStyle;
7686 prepareTextStyleName(style);
7687 const double angle = std::atan2(xAxis.y, xAxis.x);
7688 for (size_t row = 0; row < content.m_rows.size(); ++row) {
7689 const auto &tableRow = content.m_rows[row];
7690 const size_t cellCount =
7691 std::min(tableRow.m_cells.size(), content.m_columns.size());
7692 for (size_t column = 0; column < cellCount; ++column) {
7693 const DRW_TableCell &tableCell = tableRow.m_cells[column];
7694 TableFallbackCellDisplay display =
7695 tableCellDisplay(tableCell, data.m_semanticContentComplete);
7696 if (display.text.isEmpty())
7697 continue;
7698 const bool placeholder = tableFallbackCellIsPlaceholder(display.kind);
7699 QString text = toNativeString(display.text);
7700 const double left = columnOffsets[column];
7701 const double right = columnOffsets[column + 1];
7702 const double top = rowOffsets[row];
7703 const double bottom = rowOffsets[row + 1];
7704 const double cellHeight = std::max(0.1, bottom - top);
7705 const double cellWidth = std::max(0.1, right - left);
7706 const RS_Vector insertion =
7707 tablePoint(left + cellWidth * 0.08, top + cellHeight * 0.25);
7708 double textHeight = cellHeight * 0.35;
7709 if (tableCell.m_contentHeight > 0.0 &&
7710 std::isfinite(tableCell.m_contentHeight)) {
7711 textHeight = tableCell.m_contentHeight;
7712 } else if (tableCell.m_height > 0.0 &&
7713 std::isfinite(tableCell.m_height)) {
7714 textHeight = tableCell.m_height * 0.35;
7715 }
7716 textHeight = std::max(kTableFallbackMinTextHeight, textHeight);
7717 if (summary != nullptr && (tableCell.m_textStyleHandle != 0 ||
7718 tableCell.m_textStyleOverrideHandle != 0)) {
7719 ++summary->unresolvedTextStyleCount;
7720 }
7721 RS_MTextData textData(insertion, textHeight, cellWidth * 0.84,
7722 RS_MTextData::VATop, RS_MTextData::HALeft,
7723 RS_MTextData::LeftToRight, RS_MTextData::AtLeast,
7724 1.0, text, style, angle, RS2::NoUpdate);
7725 auto *mtext = new RS_MText(m_currentContainer, textData);
7726 setEntityAttributes(mtext, &data);
7727 mtext->update();
7728 addFallbackRecord(
7729 mtext, static_cast<int>(row), static_cast<int>(column),
7730 placeholder ? LC_DwgAdvancedMetadata::TableFallbackRole::Placeholder
7731 : LC_DwgAdvancedMetadata::TableFallbackRole::CellText);
7732 if (summary != nullptr) {
7733 ++summary->textEntityCount;
7734 if (placeholder)
7735 ++summary->placeholderEntityCount;
7736 }
7737 m_currentContainer->addEntity(mtext);
7738 renderedText = true;
7739 }
7740 }
7741
7742 return renderedText || columnOffsets.size() > 1 || rowOffsets.size() > 1;
7743}
7744
7745void RS_FilterDXFRW::prepareTextStyleName(QString &sty) const {
7746 // use default style for the drawing:
7747 if (sty.isEmpty()) {
7748 // japanese, cyrillic:
7749 if (m_codePage == "ANSI_932" || m_codePage == "ANSI_1251") {
7750 sty = "Unicode";
7751 } else {
7752 sty = m_textStyle;
7753 }
7754 } else {
7755 sty = m_fontList.value(sty, sty);
7756 }
7757}
7758
7759/**
7760 * Builds an RS_MText entity from a DRW_MText payload, applying the same
7761 * alignment/drawing-direction/line-spacing/legacy-correction logic that
7762 * addMText() uses. Shared between addMText() (standalone MTEXT entities) and
7763 * addInsert() (block attributes whose `100 / Embedded Object` DXF subclass
7764 * produced an att->mtext payload). Returns a freshly-constructed entity not
7765 * yet attached to any container; the caller is responsible for
7766 * setEntityAttributes() and appending.
7767 */
7768RS_MText *RS_FilterDXFRW::mtextEntityFromDRW(const DRW_MText &data) {
7769 RS_MTextData::VAlign valign;
7770 RS_MTextData::HAlign halign;
7771 RS_MTextData::MTextDrawingDirection dir;
7772 RS_MTextData::MTextLineSpacingStyle lss;
7773
7774 if (data.textgen <= 3) {
7775 valign = RS_MTextData::VATop;
7776 } else if (data.textgen <= 6) {
7777 valign = RS_MTextData::VAMiddle;
7778 } else {
7779 valign = RS_MTextData::VABottom;
7780 }
7781
7782 if (data.textgen % 3 == 1) {
7783 halign = RS_MTextData::HALeft;
7784 } else if (data.textgen % 3 == 2) {
7785 halign = RS_MTextData::HACenter;
7786 } else {
7787 halign = RS_MTextData::HARight;
7788 }
7789
7790 if (data.alignH == 1) {
7791 dir = RS_MTextData::LeftToRight;
7792 } else if (data.alignH == 3) {
7793 dir = RS_MTextData::TopToBottom;
7794 } else {
7795 dir = RS_MTextData::ByStyle;
7796 }
7797
7798 if (data.alignV == 1) {
7799 lss = RS_MTextData::AtLeast;
7800 } else {
7801 lss = RS_MTextData::Exact;
7802 }
7803 QString mtext = toNativeString(QString::fromUtf8(data.text.c_str()));
7804 QString sty = QString::fromUtf8(data.style.c_str());
7805 prepareTextStyleName(sty);
7806
7807 double interlin = data.interlin;
7808 double angle = data.angle * M_PI3.14159265358979323846 / 180.;
7809 RS_Vector ip = RS_Vector(data.basePoint.x, data.basePoint.y);
7810
7811 if (m_oldMText) {
7812 interlin = data.interlin * 0.96;
7813 if (valign == RS_MTextData::VABottom) {
7814#if QT_VERSION((6<<16)|(9<<8)|(0)) >= QT_VERSION_CHECK(5, 15, 0)((5<<16)|(15<<8)|(0))
7815 const QStringList tl = mtext.split('\n', Qt::SkipEmptyParts);
7816#else
7817 QStringList tl = mtext.split('\n', QString::SkipEmptyParts);
7818#endif
7819 if (!tl.isEmpty()) {
7820 QString txt = tl.at(tl.size() - 1);
7821 RS_TextData d(RS_Vector(0., 0., 0.), RS_Vector(0., 0., 0.), data.height,
7822 1, RS_TextData::VABaseline, RS_TextData::HALeft,
7823 RS_TextData::None, txt, sty, 0, RS2::Update);
7824 auto entity = new RS_Text(nullptr, d);
7825 double textTail = entity->getMin().y;
7826 delete entity;
7827 const auto ot = RS_Vector(0.0, textTail).rotate(angle);
7828 ip.move(ot);
7829 }
7830 }
7831 }
7832
7833 // drawingDirection was resolved above (1→LeftToRight, 3→TopToBottom,
7834 // else→ByStyle), matching the DXF MTEXT group 72 spec. There is no DXF
7835 // value for RightToLeft; that flag round-trips via XDATA below — when
7836 // an MTEXT carries a "LibreCad" + 1071 marker, restore the RTL setting.
7837 bool wantRTL = false;
7838 for (size_t k = 0; k + 1 < data.extData.size(); ++k) {
7839 const auto &appTag = data.extData[k];
7840 if (!appTag || appTag->code() != 1001 ||
7841 appTag->type() != DRW_Variant::STRING || appTag->content.s == nullptr ||
7842 *appTag->content.s != "LibreCad")
7843 continue;
7844 // Walk subsequent entries until the next 1001 (next app block).
7845 for (size_t j = k + 1; j < data.extData.size(); ++j) {
7846 const auto &v = data.extData[j];
7847 if (!v)
7848 continue;
7849 if (v->code() == 1001)
7850 break;
7851 if (v->code() == 1071 && v->type() == DRW_Variant::INTEGER &&
7852 v->content.i != 0) {
7853 wantRTL = true;
7854 }
7855 }
7856 if (wantRTL)
7857 break;
7858 }
7859 if (wantRTL)
7860 dir = RS_MTextData::RightToLeft;
7861
7862 RS_MTextData d(ip, data.height, data.widthscale, valign, halign, dir, lss,
7863 interlin, mtext, sty, angle, RS2::NoUpdate);
7864 return new RS_MText(nullptr, d);
7865}
7866
7867/**
7868 * Implementation of the method which handles
7869 * multi texts (MTEXT).
7870 */
7871void RS_FilterDXFRW::addMText(const DRW_MText &data) {
7872 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addMText: %s", data.text.c_str());
7873 auto *entity = mtextEntityFromDRW(data);
7874 entity->setParent(m_currentContainer);
7875 setEntityAttributes(entity, &data);
7876 if (data.basePoint.z != 0.0 || data.secPoint.x != 0.0 ||
7877 data.secPoint.y != 0.0 || data.secPoint.z != 0.0 ||
7878 data.extPoint.x != 0.0 || data.extPoint.y != 0.0 ||
7879 data.extPoint.z != 1.0) {
7880 std::vector<std::shared_ptr<DRW_Variant>> payload;
7881 payload.push_back(std::make_shared<DRW_Variant>(1010, data.basePoint));
7882 payload.push_back(std::make_shared<DRW_Variant>(1011, data.secPoint));
7883 payload.push_back(std::make_shared<DRW_Variant>(1012, data.extPoint));
7884 appendTypeSidecar(entity, kMTextOcsMarker, std::move(payload));
7885 }
7886 entity->update();
7887 m_currentContainer->addEntity(entity);
7888}
7889
7890/**
7891 * Implementation of the method which handles
7892 * texts (TEXT).
7893 */
7894void RS_FilterDXFRW::addText(const DRW_Text &data) {
7895 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addText");
7896 auto refPoint = RS_Vector(data.basePoint.x, data.basePoint.y);
7897 auto secPoint = RS_Vector(data.secPoint.x, data.secPoint.y);
7898 const double angle = data.angle;
7899
7900 if (data.alignV != 0 || data.alignH != 0 ||
7901 data.alignH == DRW_Text::HMiddle) {
7902 // fixme - sand - review this condition
7903 if (data.alignH != DRW_Text::HAligned && data.alignH != DRW_Text::HFit) {
7904 secPoint = RS_Vector(data.basePoint.x, data.basePoint.y);
7905 refPoint = RS_Vector(data.secPoint.x, data.secPoint.y);
7906 }
7907 }
7908
7909 const auto valign = static_cast<RS_TextData::VAlign>(data.alignV);
7910 const auto halign = static_cast<RS_TextData::HAlign>(data.alignH);
7911 RS_TextData::TextGeneration dir;
7912 QString sty = QString::fromUtf8(data.style.c_str());
7913
7914 if (data.textgen == 2) {
7915 dir = RS_TextData::Backward;
7916 } else if (data.textgen == 4) {
7917 dir = RS_TextData::UpsideDown;
7918 } else {
7919 dir = RS_TextData::None;
7920 }
7921
7922 const QString text = toNativeString(QString::fromUtf8(data.text.c_str()));
7923
7924 prepareTextStyleName(sty);
7925
7926 RS_DEBUGRS_Debug::instance()->print("Text as unicode:");
7927 RS_DEBUGRS_Debug::instance()->printUnicode(text);
7928
7929 const RS_TextData d(refPoint, secPoint, data.height, data.widthscale, valign,
7930 halign, dir, text, sty, angle * M_PI3.14159265358979323846 / 180,
7931 RS2::NoUpdate);
7932 auto *entity = new RS_Text(m_currentContainer, d);
7933
7934 setEntityAttributes(entity, &data);
7935 if (data.basePoint.z != 0.0 || data.secPoint.z != 0.0 ||
7936 data.thickness != 0.0 || data.extPoint.x != 0.0 ||
7937 data.extPoint.y != 0.0 || data.extPoint.z != 1.0) {
7938 std::vector<std::shared_ptr<DRW_Variant>> payload;
7939 payload.push_back(std::make_shared<DRW_Variant>(1010, data.basePoint));
7940 payload.push_back(std::make_shared<DRW_Variant>(1011, data.secPoint));
7941 payload.push_back(std::make_shared<DRW_Variant>(1012, data.extPoint));
7942 payload.push_back(std::make_shared<DRW_Variant>(1040, data.thickness));
7943 appendTypeSidecar(entity, kTextOcsMarker, std::move(payload));
7944 }
7945 entity->update();
7946 m_currentContainer->addEntity(entity);
7947}
7948
7949void RS_FilterDXFRW::addAttDef(const DRW_Attdef &data) {
7950 // LibreCAD has no standalone attribute-definition model. Preserve the
7951 // visible BLOCK definition as text instead of silently dropping it; the
7952 // original DXF/DWG attribute fields remain available to libdxfrw callers.
7953 addText(data);
7954}
7955
7956/**
7957 * Implementation of the method which handles
7958 * dimensions (DIMENSION).
7959 */
7960RS_DimensionData RS_FilterDXFRW::convDimensionData(const DRW_Dimension *data) {
7961 DRW_Coord crd = data->getDefPoint();
7962 const RS_Vector defP(crd.x, crd.y);
7963 crd = data->getTextPoint();
7964 RS_Vector midP(crd.x, crd.y);
7965 RS_MTextData::VAlign valign;
7966 RS_MTextData::HAlign halign;
7967 RS_MTextData::MTextLineSpacingStyle lss;
7968 QString sty = QString::fromUtf8(data->getStyle().c_str());
7969
7970 QString t; //= data.text;
7971
7972 // middlepoint of text can be 0/0 which is considered to be invalid (!):
7973 // 0/0 because older QCad versions save the middle of the text as 0/0
7974 // although they didn't support saving of the middle of the text.
7975 if (fabs(crd.x) < 1.0e-6 && fabs(crd.y) < 1.0e-6) {
7976 midP = RS_Vector(false);
7977 }
7978
7979 if (data->getAlign() <= 3) {
7980 valign = RS_MTextData::VATop;
7981 } else if (data->getAlign() <= 6) {
7982 valign = RS_MTextData::VAMiddle;
7983 } else {
7984 valign = RS_MTextData::VABottom;
7985 }
7986
7987 if (data->getAlign() % 3 == 1) {
7988 halign = RS_MTextData::HALeft;
7989 } else if (data->getAlign() % 3 == 2) {
7990 halign = RS_MTextData::HACenter;
7991 } else {
7992 halign = RS_MTextData::HARight;
7993 }
7994
7995 if (data->getTextLineStyle() == 1) {
7996 lss = RS_MTextData::AtLeast;
7997 } else {
7998 lss = RS_MTextData::Exact;
7999 }
8000
8001 t = toNativeString(QString::fromUtf8(data->getText().c_str()));
8002
8003 if (sty.isEmpty()) {
8004 sty = m_dimStyle;
8005 }
8006
8007 RS_DEBUGRS_Debug::instance()->print("Text as unicode:");
8008 RS_DEBUGRS_Debug::instance()->printUnicode(t);
8009
8010 const bool customTextLocation = data->type >= 128;
8011
8012 std::unique_ptr<LC_DimStyle> parsedDimStyleOverride;
8013
8014 if (!data->extData.empty()) {
8015 std::unique_ptr<LC_ExtEntityData> extData(
8016 extractEntityExtData(data->extData));
8017 parseDimStyleOverride(extData.get(), parsedDimStyleOverride);
8018 }
8019
8020 // data needed to add the actual dimension entity
8021 return RS_DimensionData(defP, midP, valign, halign, lss,
8022 data->getTextLineFactor(), t, sty, data->getDir(),
8023 data->getHDir(), !customTextLocation,
8024 parsedDimStyleOverride.release(),
8025 data->getFlipArrow1(), data->getFlipArrow2());
8026}
8027
8028void RS_FilterDXFRW::fillEntityExtData(
8029 std::vector<std::shared_ptr<DRW_Variant>> &extData,
8030 LC_ExtEntityData *entityData) {
8031 const auto appDatas = entityData->getAppData();
8032 const auto appendAtomic = [&extData](LC_ExtDataTag *tag) {
8033 if (tag == nullptr || !tag->isAtomic())
8034 return;
8035 const auto *variable = tag->var();
8036 if (variable == nullptr)
8037 return;
8038
8039 const int code = variable->getCode();
8040 extData.push_back(std::make_shared<DRW_Variant>(1070, code));
8041 if (tag->isBinary()) {
8042 const QByteArray &bytes = tag->bytes();
8043 std::vector<std::uint8_t> raw(bytes.size());
8044 for (int i = 0; i < bytes.size(); ++i)
8045 raw[i] = static_cast<std::uint8_t>(bytes[i]);
8046 extData.push_back(std::make_shared<DRW_Variant>(1004, std::move(raw)));
8047 return;
8048 }
8049
8050 switch (variable->getType()) {
8051 case RS2::VariableInt:
8052 extData.push_back(
8053 std::make_shared<DRW_Variant>(1070, variable->getInt()));
8054 break;
8055 case RS2::VariableDouble:
8056 extData.push_back(
8057 std::make_shared<DRW_Variant>(1040, variable->getDouble()));
8058 break;
8059 case RS2::VariableString:
8060 if (tag->isLayerRef()) {
8061 extData.push_back(std::make_shared<DRW_Variant>(
8062 1003, variable->getString().toStdString(), true));
8063 } else {
8064 extData.push_back(std::make_shared<DRW_Variant>(
8065 tag->isRef() ? 1005 : 1003, variable->getString().toStdString()));
8066 }
8067 break;
8068 case RS2::VariableVector: {
8069 const RS_Vector vec = variable->getVector();
8070 extData.push_back(std::make_shared<DRW_Variant>(1010, vec.x));
8071 extData.push_back(std::make_shared<DRW_Variant>(1011, vec.y));
8072 extData.push_back(std::make_shared<DRW_Variant>(1012, vec.z));
8073 break;
8074 }
8075 case RS2::VariableVoid:
8076 break;
8077 }
8078 };
8079 const auto appendTags =
8080 [&appendAtomic, &extData](
8081 auto &&self, const std::vector<LC_ExtDataTag *> &tags) -> void {
8082 for (auto *tag : tags) {
8083 if (tag == nullptr)
8084 continue;
8085 if (tag->isAtomic()) {
8086 appendAtomic(tag);
8087 } else {
8088 extData.push_back(std::make_shared<DRW_Variant>(1002, "{"));
8089 self(self, *tag->list());
8090 extData.push_back(std::make_shared<DRW_Variant>(1002, "}"));
8091 }
8092 }
8093 };
8094
8095 for (const auto appData : *appDatas) {
8096 extData.push_back(std::make_shared<DRW_Variant>(
8097 1001, appData->getName().toStdString())); // application name
8098 const auto groups = appData->getGroups();
8099 for (const auto group : *groups) {
8100 extData.push_back(std::make_shared<DRW_Variant>(
8101 1000, group->getName().toStdString())); // group name
8102 extData.push_back(std::make_shared<DRW_Variant>(1002, "{")); // start
8103 appendTags(appendTags, *group->getTagsList());
8104 extData.push_back(std::make_shared<DRW_Variant>(1002, "}")); // end
8105 }
8106 }
8107}
8108
8109// This method is quite generic and may be used for parsing any entity's EED.
8110// 1002 braces are represented as nested LC_ExtDataTag lists so the structure
8111// survives a LibreCAD-side clone and a later DXF/DWG write.
8112LC_ExtEntityData *RS_FilterDXFRW::extractEntityExtData(
8113 const std::vector<std::shared_ptr<DRW_Variant>> &extData) {
8114 auto *result = new LC_ExtEntityData();
8115 LC_ExtDataAppData *currentAppData = nullptr;
8116 LC_ExtDataGroup *currentGroup = nullptr;
8117
8118 int currentValType = -1;
8119 bool expectType = false;
8120 LC_ExtDataTag *currentList = nullptr;
8121 std::vector<LC_ExtDataTag *> listParents;
8122 int controlDepth = 0;
8123 bool malformedGroup = false;
8124 const auto discardCurrentGroup = [&]() {
8125 if (currentAppData == nullptr || currentGroup == nullptr)
8126 return;
8127 auto *groups = currentAppData->getGroups();
8128 const auto it = std::find(groups->begin(), groups->end(), currentGroup);
8129 if (it != groups->end()) {
8130 delete *it;
8131 groups->erase(it);
8132 }
8133 };
8134 const auto resetGroup = [&]() {
8135 if (malformedGroup || controlDepth != 0)
8136 discardCurrentGroup();
8137 currentGroup = nullptr;
8138 currentList = nullptr;
8139 listParents.clear();
8140 currentValType = -1;
8141 expectType = false;
8142 controlDepth = 0;
8143 malformedGroup = false;
8144 };
8145 const auto appendValue = [&currentList, &currentGroup, &expectType,
8146 &malformedGroup](LC_ExtDataTag *value) {
8147 if (value == nullptr)
8148 return;
8149 if (currentList != nullptr) {
8150 currentList->add(value);
8151 expectType = true;
8152 } else {
8153 if (currentGroup != nullptr)
8154 malformedGroup = true;
8155 delete value;
8156 }
8157 };
8158
8159 for (const auto &v : extData) {
8160 if (v == nullptr) {
8161 resetGroup();
8162 currentAppData = nullptr;
8163 continue;
8164 }
8165 const int code = v->code();
8166 switch (code) {
8167 case 1001: {
8168 std::string applicationName;
8169 if (readXDataString(v.get(), 1001, applicationName) !=
8170 XDataReadResult::Valid) {
8171 resetGroup();
8172 currentAppData = nullptr;
8173 break;
8174 }
8175 resetGroup();
8176 currentAppData =
8177 result->addAppData(QString::fromStdString(applicationName));
8178 break;
8179 }
8180 case 1000: {
8181 std::string groupName;
8182 if (readXDataString(v.get(), 1000, groupName) != XDataReadResult::Valid) {
8183 resetGroup();
8184 break;
8185 }
8186 if (currentAppData != nullptr) {
8187 resetGroup();
8188 currentGroup =
8189 currentAppData->addGroup(QString::fromStdString(groupName));
8190 currentList = currentGroup->getData();
8191 listParents.clear();
8192 currentValType = -1;
8193 expectType = false;
8194 controlDepth = 0;
8195 malformedGroup = false;
8196 }
8197 break;
8198 }
8199 case 1002: {
8200 std::string control;
8201 if (readXDataString(v.get(), 1002, control) != XDataReadResult::Valid ||
8202 (control != "{" && control != "}")) {
8203 if (currentGroup != nullptr)
8204 malformedGroup = true;
8205 break;
8206 }
8207 if (control == "{") {
8208 if (currentGroup == nullptr || currentList == nullptr) {
8209 if (currentGroup != nullptr)
8210 malformedGroup = true;
8211 break;
8212 }
8213 const bool isGroupContainer = controlDepth == 0 &&
8214 listParents.empty() &&
8215 currentList == currentGroup->getData();
8216 if (isGroupContainer) {
8217 controlDepth = 1;
8218 } else if (controlDepth > 0) {
8219 auto *nested = new LC_ExtDataTag();
8220 nested->makeList();
8221 currentList->add(nested);
8222 listParents.push_back(currentList);
8223 currentList = nested;
8224 ++controlDepth;
8225 } else {
8226 malformedGroup = true;
8227 break;
8228 }
8229 expectType = true;
8230 } else {
8231 if (currentGroup == nullptr || controlDepth <= 0) {
8232 if (currentGroup != nullptr)
8233 malformedGroup = true;
8234 break;
8235 }
8236 if (!listParents.empty()) {
8237 currentList = listParents.back();
8238 listParents.pop_back();
8239 --controlDepth;
8240 } else {
8241 currentList = nullptr;
8242 controlDepth = 0;
8243 }
8244 expectType = false;
8245 }
8246 break;
8247 }
8248 case 1003: {
8249 std::string stringValue;
8250 if (readXDataString(v.get(), 1003, stringValue) !=
8251 XDataReadResult::Valid) {
8252 if (currentGroup != nullptr)
8253 malformedGroup = true;
8254 break;
8255 }
8256 const QString value = QString::fromStdString(stringValue);
8257 if (v->isLayerRef()) {
8258 appendValue(new LC_ExtDataTag(currentValType, value, false, true));
8259 } else {
8260 appendValue(new LC_ExtDataTag(currentValType, value));
8261 }
8262 break;
8263 }
8264 case 1004: {
8265 const std::vector<std::uint8_t> *bytes = nullptr;
8266 if (v->type() == DRW_Variant::BINARY &&
8267 readXDataBinary(v.get(), 1004, bytes) == XDataReadResult::Valid) {
8268 const QByteArray value(reinterpret_cast<const char *>(bytes->data()),
8269 static_cast<int>(bytes->size()));
8270 appendValue(new LC_ExtDataTag(currentValType, value));
8271 } else if (v->type() == DRW_Variant::STRING) {
8272 QByteArray value;
8273 if (readXDataHexString(v.get(), 1004, value) == XDataReadResult::Valid)
8274 appendValue(new LC_ExtDataTag(currentValType, value));
8275 else if (currentGroup != nullptr)
8276 malformedGroup = true;
8277 } else if (currentGroup != nullptr) {
8278 malformedGroup = true;
8279 }
8280 break;
8281 }
8282 case 1005: {
8283 std::string stringValue;
8284 if (readXDataString(v.get(), 1005, stringValue) !=
8285 XDataReadResult::Valid) {
8286 if (currentGroup != nullptr)
8287 malformedGroup = true;
8288 break;
8289 }
8290 appendValue(new LC_ExtDataTag(currentValType,
8291 QString::fromStdString(stringValue), true));
8292 break;
8293 }
8294 case 1010:
8295 case 1011:
8296 case 1012:
8297 case 1013: {
8298 DRW_Coord coord;
8299 if (readXDataCoord(v.get(), code, coord) == XDataReadResult::Valid) {
8300 appendValue(new LC_ExtDataTag(currentValType,
8301 RS_Vector(coord.x, coord.y, coord.z)));
8302 } else if (currentGroup != nullptr)
8303 malformedGroup = true;
8304 break;
8305 }
8306 case 1070:
8307 case 1071: {
8308 std::int64_t integerValue = 0;
8309 if (readXDataInteger(v.get(), code, integerValue) !=
8310 XDataReadResult::Valid ||
8311 !fitsXDataInt32(integerValue)) {
8312 if (currentGroup != nullptr)
8313 malformedGroup = true;
8314 break;
8315 }
8316 const int value = static_cast<int>(integerValue);
8317 if (expectType) {
8318 currentValType = value;
8319 expectType = false;
8320 } else {
8321 appendValue(new LC_ExtDataTag(currentValType, value));
8322 }
8323 break;
8324 }
8325 case 1040:
8326 case 1041:
8327 case 1042: {
8328 double value = 0.0;
8329 if (readXDataDouble(v.get(), code, value) == XDataReadResult::Valid) {
8330 appendValue(new LC_ExtDataTag(currentValType, value));
8331 } else if (currentGroup != nullptr)
8332 malformedGroup = true;
8333 break;
8334 }
8335 default:
8336 break;
8337 }
8338 }
8339 resetGroup();
8340 return result;
8341}
8342
8343bool RS_FilterDXFRW::shouldGenerateExtEntityData(const RS_Dimension *entity) {
8344 // todo - so far, we support only dimension style override as extension data.
8345 // however, that's logic may be expanded later and store, for example,
8346 // something like entity-specific meta information or so.
8347 return entity->getDimStyleOverride() != nullptr;
8348}
8349
8350QString RS_FilterDXFRW::toHexStr(const int n) {
8351 return QString::number(n, 16).toUpper();
8352}
8353
8354void RS_FilterDXFRW::addDimStyleOverrideToExtendedData(
8355 LC_ExtEntityData *extEntityData, LC_DimStyle *styleOverride) {
8356 if (styleOverride == nullptr) {
8357 return;
8358 }
8359 auto appData = extEntityData->addAppData("ACAD");
8360 auto group = appData->addGroup("DSTYLE");
8361
8362 auto arrowhead = styleOverride->arrowhead();
8363 auto linearFormat = styleOverride->linearFormat();
8364 auto extensionLine = styleOverride->extensionLine();
8365 auto dimensionLine = styleOverride->dimensionLine();
8366 auto text = styleOverride->text();
8367 auto tolerance = styleOverride->latteralTolerance();
8368 auto zerosSuppression = styleOverride->zerosSuppression();
8369 auto scaling = styleOverride->scaling();
8370 auto roundoff = styleOverride->roundOff();
8371 auto radial = styleOverride->radial();
8372 auto angularFormat = styleOverride->angularFormat();
8373 auto fractions = styleOverride->fractions();
8374 auto leader = styleOverride->leader();
8375 auto arc = styleOverride->arc();
8376
8377 auto savedModificationMode = linearFormat->getModifyCheckMode();
8378 // here we're interested only in actually modified fields
8379 styleOverride->setModifyCheckMode(LC_DimStyle::ModificationAware::SET);
8380
8381 if (linearFormat->checkModifyState(LC_DimStyle::LinearFormat::$DIMPOST)) {
8382 // $DIMPOST
8383 group->add(3, linearFormat->prefixOrSuffix());
8384 }
8385 if (linearFormat->checkModifyState(LC_DimStyle::LinearFormat::$DIMAPOST)) {
8386 // $DIMAPOST
8387 group->add(4, linearFormat->altPrefixOrSuffix());
8388 }
8389 if (arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMBLK)) {
8390 // $DIMBLK
8391 // fixme - restore after test!
8392 // group->add(5, arrowhead->sameBlockName());
8393 }
8394 if (arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMBLK1)) {
8395 // $DIMBLK1
8396 // fixme - restore after test!
8397 // group->add(6, arrowhead->arrowHeadBlockNameFirst());
8398 }
8399 if (arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMBLK2)) {
8400 // $DIMBLK2
8401 // fixme - restore after test!
8402 // group->add(7, arrowhead->arrowHeadBlockNameSecond());
8403 }
8404 if (scaling->checkModifyState(LC_DimStyle::Scaling::$DIMSCALE)) {
8405 // $DIMSCALE
8406 group->add(40, scaling->scale());
8407 }
8408 if (arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMASZ)) {
8409 // $DIMASZ
8410 group->add(41, arrowhead->size());
8411 }
8412 if (extensionLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMEXO)) {
8413 // $DIMEXO
8414 group->add(42, extensionLine->distanceFromOriginPoint());
8415 }
8416 if (dimensionLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMDLI)) {
8417 // $DIMDLI
8418 group->add(43, dimensionLine->baseLineDimLinesSpacing());
8419 }
8420 if (extensionLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMEXE)) {
8421 // $DIMEXE
8422 group->add(44, extensionLine->distanceBeyondDimLine());
8423 }
8424 if (roundoff->checkModifyState(LC_DimStyle::LinearRoundOff::$DIMRND)) {
8425 // $DIMRND
8426 group->add(45, roundoff->roundTo());
8427 }
8428 if (dimensionLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMDLE)) {
8429 // $DIMDLE
8430 group->add(46, dimensionLine->distanceBeyondExtLinesForObliqueStroke());
8431 }
8432 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTP)) {
8433 // $DIMDTP
8434 group->add(47, tolerance->upperToleranceLimit());
8435 }
8436 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTM)) {
8437 // $DIMDTM
8438 group->add(48, tolerance->lowerToleranceLimit());
8439 }
8440 if (extensionLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMFXL)) {
8441 // $DIMFXL
8442 group->add(49, extensionLine->fixedLength());
8443 }
8444 if (text->checkModifyState(LC_DimStyle::Text::$DIMTXT)) {
8445 // $DIMTXT
8446 group->add(140, text->height());
8447 }
8448 if (radial->checkModifyState(LC_DimStyle::Radial::$DIMCEN)) {
8449 // $DIMCEN
8450 group->add(141, radial->centerCenterMarkOrLineSize());
8451 }
8452 if (arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMTSZ)) {
8453 // $DIMTSZ
8454 group->add(142, arrowhead->tickSize());
8455 }
8456 if (linearFormat->checkModifyState(LC_DimStyle::LinearFormat::$DIMALTF)) {
8457 // $DIMALTF
8458 group->add(143, linearFormat->altUnitsMultiplier());
8459 }
8460 if (scaling->checkModifyState(LC_DimStyle::Scaling::$DIMLFAC)) {
8461 // $DIMLFAC
8462 group->add(144, scaling->linearFactor());
8463 }
8464 if (text->checkModifyState(LC_DimStyle::Text::$DIMTVP)) {
8465 // $DIMTVP
8466 group->add(145, text->verticalDistanceToDimLine());
8467 }
8468 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTFAC)) {
8469 // $DIMTFAC
8470 group->add(146, tolerance->heightScaleFactorToDimText());
8471 }
8472 if (dimensionLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMGAP)) {
8473 // $DIMGAP
8474 group->add(147, dimensionLine->lineGap());
8475 }
8476 if (roundoff->checkModifyState(LC_DimStyle::LinearRoundOff::$DIMALTRND)) {
8477 // $DIMALTRND
8478 group->add(148, roundoff->altRoundTo());
8479 }
8480 if (text->checkModifyState(LC_DimStyle::Text::$DIMTFILL)) {
8481 // $DIMTFILL
8482 group->add(69, text->backgroundFillMode());
8483 }
8484 if (text->checkModifyState(LC_DimStyle::Text::$DIMTFILLCLR)) {
8485 // $DIMTFILLCLR
8486 int colorRgb;
8487 int colorNumber =
8488 colorToNumber(text->explicitBackgroundFillColor(), &colorRgb);
8489 group->add(70, colorNumber);
8490 }
8491 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTOL)) {
8492 // $DIMTOL
8493 group->add(71, tolerance->isAppendTolerancesToDimText() ? 1 : 0);
8494 }
8495 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMLIM)) {
8496 // $DIMLIM
8497 group->add(72, tolerance->isLimitsGeneratedAsDefaultText() ? 1 : 0);
8498 }
8499 if (text->checkModifyState(LC_DimStyle::Text::$DIMTIH)) {
8500 // $DIMTIH
8501 group->add(73, text->orientationInside());
8502 }
8503 if (text->checkModifyState(LC_DimStyle::Text::$DIMTOH)) {
8504 // $DIMTOH
8505 group->add(74, text->orientationOutside());
8506 }
8507 if (extensionLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMSE1)) {
8508 // $DIMSE1
8509 group->add(75, extensionLine->suppressFirstLine());
8510 }
8511 if (extensionLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMSE2)) {
8512 // $DIMSE2
8513 group->add(76, extensionLine->suppressSecondLine());
8514 }
8515 if (text->checkModifyState(LC_DimStyle::Text::$DIMTAD)) {
8516 // $DIMTAD
8517 group->add(77, text->verticalPositioning());
8518 }
8519 if (zerosSuppression->checkModifyState(
8520 LC_DimStyle::ZerosSuppression::$DIMZIN)) {
8521 // $DIMZIN
8522 group->add(78, zerosSuppression->linearRaw());
8523 }
8524 if (zerosSuppression->checkModifyState(
8525 LC_DimStyle::ZerosSuppression::$DIMAZIN)) {
8526 // $DIMAZIN
8527 group->add(79, zerosSuppression->angularRaw());
8528 }
8529 if (arc->checkModifyState(LC_DimStyle::Arc::$DIMARCSYM)) {
8530 // $DIMARCSYM
8531 group->add(90, arc->arcSymbolPosition());
8532 }
8533 if (linearFormat->checkModifyState(LC_DimStyle::LinearFormat::$DIMALT)) {
8534 // $DIMALT
8535 group->add(170, linearFormat->alternateUnits());
8536 }
8537 if (linearFormat->checkModifyState(LC_DimStyle::LinearFormat::$DIMALTD)) {
8538 // $DIMALTD
8539 group->add(171, linearFormat->altDecimalPlaces());
8540 }
8541 if (dimensionLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMTOFL)) {
8542 // $DIMTOFL
8543 group->add(172, dimensionLine->drawPolicyForOutsideText());
8544 }
8545 if (arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMSAH)) {
8546 // $DIMSAH
8547 group->add(173,
8548 arrowhead->isUseSeparateArrowHeads()); // fixme - check value
8549 }
8550 if (text->checkModifyState(LC_DimStyle::Text::$DIMTIX)) {
8551 // $DIMTIX
8552 group->add(174, text->extLinesRelativePlacement());
8553 }
8554 if (arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMSOXD)) {
8555 // $DIMSOXD
8556 group->add(175, arrowhead->suppression()); // fixme - check value
8557 }
8558 if (dimensionLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMCLRD)) {
8559 // $DIMCLRD
8560 int colorRgb;
8561 int colorNumber = colorToNumber(dimensionLine->color(), &colorRgb);
8562 group->add(176, colorNumber);
8563 }
8564 if (extensionLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMCLRE)) {
8565 // $DIMCLRE
8566 int colorRgb;
8567 int color = colorToNumber(extensionLine->color(), &colorRgb);
8568 group->add(177, color);
8569 }
8570 if (text->checkModifyState(LC_DimStyle::Text::$DIMCLRT)) {
8571 // $DIMCLRT
8572 int colorRgb;
8573 int colorNumber = colorToNumber(text->color(), &colorRgb);
8574 group->add(178, colorNumber);
8575 }
8576 if (angularFormat->checkModifyState(LC_DimStyle::AngularFormat::$DIMADEC)) {
8577 // $DIMADEC
8578 group->add(179, angularFormat->decimalPlaces());
8579 }
8580 // case 270: // fixme - sand - obsolete DIMUNIT
8581 // result->linearFormat()->setUDecimalPlaces(var->getInt());
8582 // dimunit = reader->getInt32();
8583 // add("$DIMUNIT", code, dimunit);
8584 // break;
8585 if (linearFormat->checkModifyState(LC_DimStyle::LinearFormat::$DIMDEC)) {
8586 // $DIMDEC
8587 group->add(271, linearFormat->decimalPlaces());
8588 }
8589 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTDEC)) {
8590 // $DIMTDEC
8591 group->add(272, tolerance->decimalPlaces());
8592 }
8593 if (linearFormat->checkModifyState(LC_DimStyle::LinearFormat::$DIMALTU)) {
8594 // $DIMALTU
8595 group->add(273, linearFormat->altFormat());
8596 }
8597 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMALTTD)) {
8598 // $DIMALTTD
8599 group->add(274, tolerance->decimalPlacesAltDim());
8600 }
8601 if (angularFormat->checkModifyState(LC_DimStyle::AngularFormat::$DIMAUNIT)) {
8602 // $DIMAUNIT
8603 group->add(275, angularFormat->format());
8604 }
8605 if (fractions->checkModifyState(LC_DimStyle::Fractions::$DIMFRAC)) {
8606 // $DIMFRAC
8607 group->add(276, fractions->style());
8608 }
8609 if (linearFormat->checkModifyState(LC_DimStyle::LinearFormat::$DIMLUNIT)) {
8610 // $DIMLUNIT
8611 group->add(277, linearFormat->formatRaw());
8612 }
8613 if (linearFormat->checkModifyState(LC_DimStyle::LinearFormat::$DIMSEP)) {
8614 // $DIMDSEP
8615 group->add(278, linearFormat->decimalFormatSeparatorChar());
8616 }
8617 if (text->checkModifyState(LC_DimStyle::Text::$DIMTMOVE)) {
8618 // $DIMTMOVE
8619 group->add(279, text->positionMovementPolicy());
8620 }
8621 if (text->checkModifyState(LC_DimStyle::Text::$DIMJUST)) {
8622 // $DIMJUST
8623 group->add(280, text->horizontalPositioning());
8624 }
8625 if (dimensionLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMSD1)) {
8626 // $DIMSD1
8627 group->add(281, dimensionLine->suppressFirstLine());
8628 }
8629 if (dimensionLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMSD2)) {
8630 // $DIMSD2
8631 group->add(282, dimensionLine->suppressSecondLine());
8632 }
8633 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTOLJ)) {
8634 // $DIMTOLJ
8635 group->add(283, tolerance->verticalJustification());
8636 }
8637 if (zerosSuppression->checkModifyState(
8638 LC_DimStyle::ZerosSuppression::$DIMTZIN)) {
8639 // $DIMTZIN
8640 group->add(284, zerosSuppression->toleranceRaw());
8641 }
8642 if (zerosSuppression->checkModifyState(
8643 LC_DimStyle::ZerosSuppression::$DIMALTZ)) {
8644 // $DIMALTZ
8645 group->add(285, zerosSuppression->altLinearRaw());
8646 }
8647 if (zerosSuppression->checkModifyState(
8648 LC_DimStyle::ZerosSuppression::$DIMALTTZ)) {
8649 // $DIMALTTZ
8650 group->add(286, zerosSuppression->altToleranceRaw());
8651 }
8652 // case 287: // fixme - DIMFIT
8653 // dimfit = reader->getInt32();
8654 // add("$DIMFIT", code, dimfit);
8655 if (text->checkModifyState(LC_DimStyle::Text::$DIMUPT)) {
8656 // $DIMUPT
8657 group->add(288, text->cursorControlPolicy());
8658 }
8659 if (text->checkModifyState(LC_DimStyle::Text::$DIMATFIT)) {
8660 // $DIMATFIT
8661 group->add(289, text->unsufficientSpacePolicy());
8662 }
8663 if (extensionLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMFXLON)) {
8664 // $DIMFXLON
8665 group->add(290, extensionLine->hasFixedLength() ? 1 : 0); // fixme - check
8666 }
8667 if (dimStyleTargetVersion() >= DRW::AC1024 &&
8668 text->checkModifyState(LC_DimStyle::Text::$DIMTXTDIRECTION)) {
8669 // $DIMTXTDIRECTION
8670 group->add(295, text->readingDirection());
8671 }
8672 if (text->checkModifyState(LC_DimStyle::Text::$DIMTXSTY)) {
8673 // $DIMTXSTY
8674 group->add(340, text->style()); // fixme - ref to style?
8675 }
8676 if (leader->checkModifyState(LC_DimStyle::Leader::$DIMLDRBLK)) {
8677 // DIMLDRBLK
8678 auto blockName = leader->arrowBlockName();
8679 if (!blockName.isEmpty()) {
8680 auto blkName = blockName.toStdString();
8681 std::uint32_t blkHandle =
8682 m_dxfW != nullptr ? m_dxfW->getBlockRecordHandleToWrite(blkName)
8683 : DRW::NoHandle;
8684 if (blkHandle != DRW::NoHandle) {
8685 group->addRef(341, toHexStr(blkHandle));
8686 }
8687 }
8688 }
8689 if (arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMBLK)) {
8690 //$DIMBLK
8691 auto blockName = arrowhead->sameBlockName();
8692 if (!blockName.isEmpty()) {
8693 auto blkName = blockName.toStdString();
8694 std::uint32_t blkHandle =
8695 m_dxfW != nullptr ? m_dxfW->getBlockRecordHandleToWrite(blkName)
8696 : DRW::NoHandle;
8697 if (blkHandle != DRW::NoHandle) {
8698 group->addRef(342, toHexStr(blkHandle));
8699 }
8700 }
8701 }
8702 if (arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMBLK1)) {
8703 //$DIMBLK1
8704 auto blockName = arrowhead->arrowHeadBlockNameFirst();
8705 if (!blockName.isEmpty()) {
8706 auto blkName = blockName.toStdString();
8707 std::uint32_t blkHandle =
8708 m_dxfW != nullptr ? m_dxfW->getBlockRecordHandleToWrite(blkName)
8709 : DRW::NoHandle;
8710 if (blkHandle != DRW::NoHandle) {
8711 group->addRef(343, toHexStr(blkHandle));
8712 }
8713 }
8714 }
8715
8716 if (arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMBLK2)) {
8717 //$DIMBLK2
8718 auto blockName = arrowhead->arrowHeadBlockNameSecond();
8719 if (!blockName.isEmpty()) {
8720 auto blkName = blockName.toStdString();
8721 std::uint32_t blkHandle =
8722 m_dxfW != nullptr ? m_dxfW->getBlockRecordHandleToWrite(blkName)
8723 : DRW::NoHandle;
8724 if (blkHandle != DRW::NoHandle) {
8725 group->addRef(344, toHexStr(blkHandle));
8726 }
8727 }
8728 }
8729 /*
8730 // case 345: // codes///
8731 // fixme - may this code be used for DIMLDRBLK?
8732 // dimblk2 = reader->getUtf8String();
8733 // add("$DIMBLK2", code, dimblk2);
8734 // break;
8735 */
8736
8737 if (dimensionLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMLTYPE)) {
8738 // $DIMLTYPE
8739 std::uint32_t lineTypeHandle =
8740 findLineTypeHandleToWrite(dimensionLine->lineTypeName());
8741 if (lineTypeHandle != DRW::NoHandle) {
8742 auto handleStr = toHexStr(lineTypeHandle);
8743 group->addRef(345, handleStr);
8744 }
8745 }
8746 if (extensionLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMLTEX1)) {
8747 // $DIMLTEX1
8748 std::uint32_t lineTypeHandle =
8749 findLineTypeHandleToWrite(extensionLine->lineTypeFirstRaw());
8750 if (lineTypeHandle != DRW::NoHandle) {
8751 auto handleStr = toHexStr(lineTypeHandle);
8752 group->addRef(347, handleStr);
8753 }
8754 }
8755 if (extensionLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMLTEX2)) {
8756 // $DIMLTEX2
8757 std::uint32_t lineTypeHandle =
8758 findLineTypeHandleToWrite(extensionLine->lineTypeSecondRaw());
8759 if (lineTypeHandle != DRW::NoHandle) {
8760 auto handleStr = toHexStr(lineTypeHandle);
8761 group->addRef(348, handleStr);
8762 }
8763 }
8764 if (dimensionLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMLWD)) {
8765 // $DIMLWD
8766 auto lineWidth = dimensionLine->lineWidth();
8767 int lw = RS2::lineWidth2dxfInt(lineWidth);
8768 group->add(371, lw);
8769 }
8770 if (extensionLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMLWE)) {
8771 // $DIMLWE
8772 auto lineWidth = extensionLine->lineWidth();
8773 int lw = RS2::lineWidth2dxfInt(lineWidth);
8774 group->add(372, lw);
8775 }
8776
8777 styleOverride->setModifyCheckMode(savedModificationMode);
8778}
8779
8780RS_FilterDXFRW::DimStyleOverrideParseResult
8781RS_FilterDXFRW::parseDimStyleOverride(
8782 const LC_ExtEntityData *extEntityData,
8783 std::unique_ptr<LC_DimStyle> &parsedOverride) const {
8784 parsedOverride.reset();
8785 if (extEntityData == nullptr)
8786 return DimStyleOverrideParseResult::Absent;
8787
8788 auto *dimStyleGroup = extEntityData->getGroupByName("ACAD", "DSTYLE");
8789 if (dimStyleGroup == nullptr)
8790 return DimStyleOverrideParseResult::Absent;
8791
8792 auto *dimStyleVariables = dimStyleGroup->getTagsList();
8793 if (dimStyleVariables == nullptr || dimStyleVariables->empty())
8794 return DimStyleOverrideParseResult::Absent;
8795
8796 auto result = std::make_unique<LC_DimStyle>();
8797 auto arrowhead = result->arrowhead();
8798 auto linearFormat = result->linearFormat();
8799 auto extensionLine = result->extensionLine();
8800 auto dimensionLine = result->dimensionLine();
8801 auto text = result->text();
8802 auto tolerance = result->latteralTolerance();
8803 auto zerosSuppression = result->zerosSuppression();
8804 auto arc = result->arc();
8805
8806 result->setModifyCheckMode(LC_DimStyle::ModificationAware::ALL);
8807 bool directionSeen = false;
8808 int directionValue = 0;
8809 const auto count = dimStyleVariables->size();
8810 for (size_t i = 0; i < count; i++) {
8811 LC_ExtDataTag *tag = dimStyleVariables->at(i);
8812 if (tag == nullptr || !tag->isAtomic() || tag->var() == nullptr)
8813 return DimStyleOverrideParseResult::Malformed;
8814 RS_Variable *var = tag->var();
8815 int code = var->getCode();
8816
8817 switch (code) {
8818 case 105:
8819 // handle = reader->getHandleString();
8820 break;
8821 case 3: // "$DIMPOST"
8822 linearFormat->setPrefixOrSuffix(var->getString());
8823 break;
8824 case 4: // "$DIMAPOST"
8825 linearFormat->setAltPrefixOrSuffix(var->getString());
8826 break;
8827 case 5: // $DIMBLK
8828 arrowhead->setSameBlockName(var->getString());
8829 break;
8830 case 6: // "$DIMBLK1"
8831 arrowhead->setArrowHeadBlockNameFirst(var->getString());
8832 break;
8833 case 7: // "$DIMBLK2"
8834 arrowhead->setArrowHeadBlockNameSecond(var->getString());
8835 break;
8836 case 40: // "$DIMSCALE"
8837 result->scaling()->setScale(var->getDouble());
8838 break;
8839 case 41: // "$DIMASZ"
8840 arrowhead->setSize(var->getDouble());
8841 break;
8842 case 42: // "$DIMEXO"
8843 extensionLine->setDistanceFromOriginPoint(var->getDouble());
8844 break;
8845 case 43: //"$DIMDLI"
8846 dimensionLine->setBaselineDimLinesSpacing(var->getDouble());
8847 break;
8848 case 44: //"$DIMEXE"
8849 extensionLine->setDistanceBeyondDimLine(var->getDouble());
8850 break;
8851 case 45: //$DIMRND
8852 result->roundOff()->setRoundToValue(var->getDouble());
8853 break;
8854 case 46: //$DIMDLE
8855 dimensionLine->setDistanceBeyondExtLinesForObliqueStroke(
8856 var->getDouble());
8857 break;
8858 case 47: //"$DIMTP"
8859 tolerance->setUpperToleranceLimit(var->getDouble());
8860 break;
8861 case 48: //"$DIMTM"
8862 tolerance->setLowerToleranceLimit(var->getDouble());
8863 break;
8864 case 49: //"$DIMFXL"
8865 extensionLine->setFixedLength(var->getDouble());
8866 break;
8867 case 140: // "$DIMTXT"
8868 text->setHeight(var->getDouble());
8869 break;
8870 case 141: // "$DIMCEN"
8871 result->radial()->setCenterMarkOrLineSize(var->getDouble());
8872 break;
8873 case 142: //"$DIMTSZ"
8874 arrowhead->setTickSize(var->getDouble());
8875 break;
8876 case 143: //"$DIMALTF"
8877 linearFormat->setAltUnitsMultiplier(var->getDouble());
8878 break;
8879 case 144: //"$DIMLFAC"
8880 result->scaling()->setLinearFactor(var->getDouble());
8881 break;
8882 case 145: //"$DIMTVP"
8883 text->setVerticalDistanceToDimLine(var->getDouble());
8884 break;
8885 case 146: //"$DIMTFAC"
8886 tolerance->setHeightScaleFactorToDimText(var->getDouble());
8887 break;
8888 case 147: //"$DIMGAP"
8889 dimensionLine->setLineGap(var->getDouble());
8890 break;
8891 case 148: //"$DIMALTRND"
8892 result->roundOff()->setAltRoundToValue(var->getDouble());
8893 break;
8894 case 69: // "$DIMTFILL"
8895 text->setBackgroundFillModeRaw(var->getInt());
8896 break;
8897 case 70: {
8898 //"$DIMTFILLCLR"
8899 RS_Color fillClr = numberToColor(var->getInt());
8900 text->setExplicitBackgroundFillColor(fillClr);
8901 break;
8902 }
8903 case 71: // "$DIMTOL"
8904 tolerance->setAppendTolerancesToDimText(var->getInt() ==
8905 1); // fixme - review
8906 break;
8907 case 72: //"$DIMLIM"
8908 tolerance->setLimitsAreGeneratedAsDefaultText(var->getInt() ==
8909 1); // fixme - review
8910 break;
8911 case 73: //"$DIMTIH"
8912 text->setOrientationInsideRaw(var->getInt());
8913 break;
8914 case 74: //"$DIMTOH"
8915 text->setOrientationOutsideRaw(var->getInt());
8916 break;
8917 case 75: //"$DIMSE1"
8918 extensionLine->setSuppressFirstRaw(var->getInt());
8919 break;
8920 case 76: //"$DIMSE2"
8921 extensionLine->setSuppressSecondRaw(var->getInt());
8922 break;
8923 case 77: //"$DIMTAD"
8924 text->setVerticalPositioningRaw(var->getInt());
8925 break;
8926 case 78: // "$DIMZIN"
8927 zerosSuppression->setLinearRaw(var->getInt());
8928 break;
8929 case 79: //"$DIMAZIN"
8930 zerosSuppression->setAngularRaw(var->getInt());
8931 break;
8932 case 170: //"$DIMALT"
8933 linearFormat->setAlternateUnitsRaw(var->getInt());
8934 break;
8935 case 171: //"$DIMALTD"
8936 linearFormat->setAltDecimalPlaces(var->getInt());
8937 break;
8938 case 172: //"$DIMTOFL"
8939 dimensionLine->setDrawPolicyForOutsideTextRaw(var->getInt());
8940 break;
8941 case 173: //"$DIMSAH"
8942 arrowhead->setUseSeparateArrowHeads(var->getInt()); // fixme - check value
8943 break;
8944 case 174: // "$DIMTIX"
8945 text->setExtLinesRelativePlacementRaw(var->getInt());
8946 break;
8947 case 175: //"$DIMSOXD"
8948 arrowhead->setSuppressionsRaw(var->getInt()); // fixme - check value
8949 break;
8950 case 176: {
8951 //"$DIMCLRD"
8952 RS_Color color = numberToColor(var->getInt());
8953 dimensionLine->setColor(color);
8954 break;
8955 }
8956 case 177: {
8957 //"$DIMCLRE"
8958 RS_Color color = numberToColor(var->getInt());
8959 extensionLine->setColor(color);
8960 break;
8961 }
8962 case 178: {
8963 //"$DIMCLRT"
8964 RS_Color color = numberToColor(var->getInt());
8965 text->setColor(color);
8966 break;
8967 }
8968 case 179: //"$DIMADEC"
8969 result->angularFormat()->setDecimalPlaces(var->getInt());
8970 break;
8971 case 270: // fixme - sand - obsolete DIMUNIT
8972 // result->linearFormat()->setUDecimalPlaces(var->getInt());
8973 // dimunit = reader->getInt32();
8974 // add("$DIMUNIT", code, dimunit);
8975 break;
8976 case 271: //"$DIMDEC"
8977 linearFormat->setDecimalPlaces(var->getInt());
8978 break;
8979 case 272: //"$DIMTDEC"
8980 tolerance->setDecimalPlaces(var->getInt());
8981 break;
8982 case 273: //"$DIMALTU"
8983 linearFormat->setAltFormatRaw(var->getInt());
8984 break;
8985 case 274: // "$DIMALTTD"
8986 tolerance->setDecimalPlacesAltDim(var->getInt());
8987 break;
8988 case 275: //"$DIMAUNIT"
8989 result->angularFormat()->setFormatRaw(var->getInt());
8990 break;
8991 case 276: // "$DIMFRAC"
8992 result->fractions()->setStyleRaw(var->getInt());
8993 break;
8994 case 277: // "$DIMLUNIT"
8995 linearFormat->setFormatRaw(var->getInt());
8996 break;
8997 case 278: //"$DIMDSEP"
8998 linearFormat->setDecimalFormatSeparatorChar(var->getInt());
8999 break;
9000 case 279: // "$DIMTMOVE"
9001 text->setPositionMovementPolicyRaw(var->getInt());
9002 break;
9003 case 280: // "$DIMJUST"
9004 text->setHorizontalPositioningRaw(var->getInt());
9005 break;
9006 case 281: // "$DIMSD1"
9007 dimensionLine->setSuppressFirstLineRaw(var->getInt());
9008 break;
9009 case 282: // "$DIMSD2"
9010 dimensionLine->setSuppressSecondLineRaw(var->getInt());
9011 break;
9012 case 283: // "$DIMTOLJ"
9013 tolerance->setVerticalJustificationRaw(var->getInt());
9014 break;
9015 case 284: // "$DIMTZIN"
9016 zerosSuppression->setToleranceRaw(var->getInt());
9017 break;
9018 case 285: // "$DIMALTZ"
9019 zerosSuppression->setAltLinearRaw(var->getInt());
9020 break;
9021 case 286: //"$DIMALTTZ"
9022 zerosSuppression->setAltToleranceRaw(var->getInt());
9023 break;
9024 case 287: // fixme - DIMFIT
9025 // dimfit = reader->getInt32();
9026 // add("$DIMFIT", code, dimfit);
9027 break;
9028 case 288: // "$DIMUPT"
9029 text->setCursorControlPolicyRaw(var->getInt());
9030 break;
9031 case 289: //"$DIMATFIT"
9032 text->setUnsufficientSpacePolicyRaw(var->getInt());
9033 break;
9034 case 290: // "$DIMFXLON"
9035 extensionLine->setHasFixedLength(var->getInt()); // fixme - check
9036 break;
9037 case 292: // Legacy LibreCAD compatibility spelling.
9038 case 294: // LibreDWG compatibility spelling.
9039 case 295: { // "$DIMTXTDIRECTION"
9040 if (var->getType() != RS2::VariableInt)
9041 return DimStyleOverrideParseResult::Malformed;
9042 const int value = var->getInt();
9043 if (value < 0 || value > 1 || (directionSeen && directionValue != value))
9044 return DimStyleOverrideParseResult::Malformed;
9045 directionSeen = true;
9046 directionValue = value;
9047 text->setReadingDirection(
9048 static_cast<LC_DimStyle::Text::TextDirection>(value));
9049 break;
9050 }
9051 case 340: // "$DIMTXSTY"
9052 {
9053 auto dimtxsty = var->getString();
9054 prepareTextStyleName(dimtxsty);
9055 text->setStyle(dimtxsty); // fixme - ref to style?
9056 // fixme - ref to style?
9057 break;
9058 }
9059 case 341: {
9060 // "_$DIMLDRBLK"
9061 QString refHandleStr = var->getString();
9062 bool ok;
9063 int refHandle = refHandleStr.toInt(&ok, 16);
9064 if (ok) {
9065 QString blockName;
9066 ok = resolveBlockNameByHandle(refHandle, blockName);
9067 if (ok) {
9068 result->leader()->setArrowBlockName(blockName);
9069 }
9070 } else {
9071 // the string is not handle, but a direct name of the block.
9072 // fixme - DIMLDRBLK reading!
9073 // This is workaround for referring leader by name, not by block ref...
9074 // however, it may be not ACad compatible..
9075 // if (LC_DimArrowRegistry::isStandardBlockName(refHandleStr)) {
9076 // result->leader()->setArrowBlockName(refHandleStr);
9077 // }
9078 }
9079 break;
9080 }
9081 case 342: {
9082 // "_$DIMBLK"
9083 QString refHandleStr = var->getString();
9084 bool ok;
9085 int refHandle = refHandleStr.toInt(&ok, 16);
9086 if (ok) {
9087 QString blockName;
9088 ok = resolveBlockNameByHandle(refHandle, blockName);
9089 if (ok) {
9090 arrowhead->setSameBlockName(blockName);
9091 }
9092 }
9093 break;
9094 }
9095 case 343: {
9096 // "_$DIMBLK1"
9097 QString refHandleStr = var->getString();
9098 bool ok;
9099 int refHandle = refHandleStr.toInt(&ok, 16);
9100 if (ok) {
9101 QString blockName;
9102 ok = resolveBlockNameByHandle(refHandle, blockName);
9103 if (ok) {
9104 arrowhead->setArrowHeadBlockNameFirst(blockName);
9105 }
9106 }
9107 break;
9108 }
9109 case 344: {
9110 // "_$DIMBLK2"
9111 QString refHandleStr = var->getString();
9112 bool ok;
9113 int refHandle = refHandleStr.toInt(&ok, 16);
9114 if (ok) {
9115 QString blockName;
9116 ok = resolveBlockNameByHandle(refHandle, blockName);
9117 if (ok) {
9118 arrowhead->setArrowHeadBlockNameSecond(blockName);
9119 }
9120 }
9121 break;
9122 }
9123 // case 345: // codes///
9124 // fixme - may this code be used for DIMLDRBLK?
9125 // dimblk2 = reader->getUtf8String();
9126 // add("$DIMBLK2", code, dimblk2);
9127 // break;
9128 case 345: {
9129 // "$DIMLTYPE"
9130 auto refHandleStr = var->getString();
9131 bool ok;
9132 int refHandle = refHandleStr.toInt(&ok, 16);
9133 auto lineTypeName =
9134 m_dxfR->getReadingContext()->resolveLineTypeName(refHandle);
9135 if (!lineTypeName.empty()) {
9136 QString name = QString::fromStdString(lineTypeName);
9137 dimensionLine->setLineType(name);
9138 }
9139 break;
9140 }
9141 case 347: {
9142 // "$DIMLTEX1"
9143 auto refHandleStr = var->getString();
9144 bool ok;
9145 int refHandle = refHandleStr.toInt(&ok, 16);
9146 auto lineTypeName =
9147 m_dxfR->getReadingContext()->resolveLineTypeName(refHandle);
9148 if (!lineTypeName.empty()) {
9149 QString name = QString::fromStdString(lineTypeName);
9150 extensionLine->setLineTypeFirst(name);
9151 }
9152 break;
9153 }
9154 case 348: {
9155 //"$DIMLTEX2"
9156 auto refHandleStr = var->getString();
9157 bool ok;
9158 int refHandle = refHandleStr.toInt(&ok, 16);
9159 auto lineTypeName =
9160 m_dxfR->getReadingContext()->resolveLineTypeName(refHandle);
9161 if (!lineTypeName.empty()) {
9162 QString name = QString::fromStdString(lineTypeName);
9163 extensionLine->setLineTypeSecond(name);
9164 }
9165 break;
9166 }
9167 case 371: // "$DIMLWD"
9168 dimensionLine->setLineWidthRaw(var->getInt());
9169 break;
9170 case 372: //"$DIMLWE"
9171 extensionLine->setLineWidthRaw(var->getInt());
9172 break;
9173 case 90: //"$DIMARCSYM"
9174 arc->setArcSymbolPositionRaw(var->getInt());
9175 break;
9176 default:
9177 break;
9178 }
9179 }
9180
9181 // workaround for AutoCAD - it seems that later versions ignores DIMSAH and
9182 // consider blocks for arrowhead to be different if individual arrows are
9183 // different. So we set the flag manually fixme - check whether given
9184 // condition is enough for properly setting the flag
9185 result->setModifyCheckMode(LC_DimStyle::ModificationAware::SET);
9186 if (!arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMSAH)) {
9187 if (arrowhead->sameBlockName().isEmpty() &&
9188 (arrowhead->arrowHeadBlockNameFirst() !=
9189 arrowhead->arrowHeadBlockNameSecond())) {
9190 arrowhead->setUseSeparateArrowHeads(true);
9191 }
9192 }
9193
9194 parsedOverride = std::move(result);
9195 return DimStyleOverrideParseResult::Valid;
9196}
9197
9198/**
9199 * Implementation of the method which handles
9200 * aligned dimensions (DIMENSION).
9201 */
9202void RS_FilterDXFRW::addDimAlign(const DRW_DimAligned *data) {
9203 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDimAligned");
9204 if (data == nullptr || m_currentContainer == nullptr) {
9205 return;
9206 }
9207
9208 const RS_DimensionData dimensionData = convDimensionData(data);
9209
9210 const RS_Vector ext1(data->getDef1Point().x, data->getDef1Point().y);
9211 const RS_Vector ext2(data->getDef2Point().x, data->getDef2Point().y);
9212
9213 const RS_DimAlignedData d(ext1, ext2);
9214 auto *entity = new RS_DimAligned(m_currentContainer, dimensionData, d);
9215 setEntityAttributes(entity, data);
9216 entity->updateDimPoint();
9217 entity->update();
9218 m_currentContainer->addEntity(entity);
9219}
9220
9221/**
9222 * Implementation of the method which handles
9223 * linear dimensions (DIMENSION).
9224 */
9225void RS_FilterDXFRW::addDimLinear(const DRW_DimLinear *data) {
9226 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDimLinear");
9227 if (data == nullptr || m_currentContainer == nullptr) {
9228 return;
9229 }
9230
9231 const RS_DimensionData dimensionData = convDimensionData(data);
9232
9233 const RS_Vector dxt1(data->getDef1Point().x, data->getDef1Point().y);
9234 const RS_Vector dxt2(data->getDef2Point().x, data->getDef2Point().y);
9235
9236 const RS_DimLinearData d(dxt1, dxt2, RS_Math::deg2rad(data->getAngle()),
9237 RS_Math::deg2rad(data->getOblique()));
9238
9239 const auto entity = new RS_DimLinear(m_currentContainer, dimensionData, d);
9240 setEntityAttributes(entity, data);
9241 entity->update();
9242 m_currentContainer->addEntity(entity);
9243}
9244
9245/**
9246 * Implementation of the method which handles
9247 * radial dimensions (DIMENSION).
9248 */
9249void RS_FilterDXFRW::addDimRadial(const DRW_DimRadial *data) {
9250 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDimRadial");
9251 if (data == nullptr || m_currentContainer == nullptr) {
9252 return;
9253 }
9254
9255 const RS_DimensionData dimensionData = convDimensionData(data);
9256 const RS_Vector dp(data->getDiameterPoint().x, data->getDiameterPoint().y);
9257
9258 const RS_DimRadialData d(dp, data->getLeaderLength());
9259 const auto entity = new RS_DimRadial(m_currentContainer, dimensionData, d);
9260
9261 setEntityAttributes(entity, data);
9262 entity->update();
9263 m_currentContainer->addEntity(entity);
9264}
9265
9266/**
9267 * Implementation of the method which handles
9268 * diametric dimensions (DIMENSION).
9269 */
9270void RS_FilterDXFRW::addDimDiametric(const DRW_DimDiametric *data) {
9271 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDimDiametric");
9272 if (data == nullptr || m_currentContainer == nullptr) {
9273 return;
9274 }
9275
9276 const RS_DimensionData dimensionData = convDimensionData(data);
9277 const RS_Vector dp(data->getDiameter1Point().x, data->getDiameter1Point().y);
9278
9279 const RS_DimDiametricData d(dp, data->getLeaderLength());
9280 const auto entity = new RS_DimDiametric(m_currentContainer, dimensionData, d);
9281
9282 setEntityAttributes(entity, data);
9283 entity->update();
9284 m_currentContainer->addEntity(entity);
9285}
9286
9287/**
9288 * Implementation of the method which handles
9289 * angular dimensions (DIMENSION).
9290 */
9291void RS_FilterDXFRW::addDimAngular(const DRW_DimAngular *data) {
9292 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDimAngular");
9293 if (data == nullptr || m_currentContainer == nullptr) {
9294 return;
9295 }
9296
9297 const RS_DimensionData dimensionData = convDimensionData(data);
9298 const RS_Vector dp1(data->getFirstLine1().x, data->getFirstLine1().y);
9299 const RS_Vector dp2(data->getFirstLine2().x, data->getFirstLine2().y);
9300 const RS_Vector dp3(data->getSecondLine1().x, data->getSecondLine1().y);
9301 const RS_Vector dp4(data->getDimPoint().x, data->getDimPoint().y);
9302
9303 const RS_DimAngularData d(dp1, dp2, dp3, dp4);
9304
9305 const auto entity = new RS_DimAngular(m_currentContainer, dimensionData, d);
9306
9307 setEntityAttributes(entity, data);
9308 entity->update();
9309 m_currentContainer->addEntity(entity);
9310}
9311
9312/**
9313 * Implementation of the method which handles
9314 * angular dimensions (DIMENSION).
9315 */
9316void RS_FilterDXFRW::addDimAngular3P(const DRW_DimAngular3p *data) {
9317 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDimAngular3P");
9318 if (data == nullptr || m_currentContainer == nullptr) {
9319 return;
9320 }
9321
9322 const auto &vertexPoint = data->getVertexPoint();
9323
9324 RS_DimensionData dimensionData = convDimensionData(data);
9325 const RS_Vector dp1(data->getFirstLine().x, data->getFirstLine().y);
9326 const RS_Vector dp2(data->getSecondLine().x, data->getSecondLine().y);
9327 const RS_Vector dp3(vertexPoint.x, vertexPoint.y);
9328 const RS_Vector dp4 = dimensionData.definitionPoint;
9329 dimensionData.definitionPoint = RS_Vector(vertexPoint.x, vertexPoint.y);
9330
9331 const RS_DimAngularData d(dp1, dp2, dp3, dp4);
9332
9333 const auto entity = new RS_DimAngular(m_currentContainer, dimensionData, d);
9334
9335 setEntityAttributes(entity, data);
9336 entity->update();
9337 m_currentContainer->addEntity(entity);
9338}
9339
9340void RS_FilterDXFRW::addDimArc(const DRW_DimArc *data) {
9341 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDimArc");
9342 if (data == nullptr || m_currentContainer == nullptr) {
9343 return;
9344 }
9345 RS_DimensionData dd = convDimensionData(data);
9346 RS_Vector centre(data->getArcCenter().x, data->getArcCenter().y);
9347 double radius = dd.definitionPoint.distanceTo(centre);
9348 LC_DimArcData arcData(
9349 radius, radius * std::abs(data->arcEndAngle - data->arcStartAngle),
9350 centre, RS_Vector::polar(1.0, data->arcEndAngle),
9351 RS_Vector::polar(1.0, data->arcStartAngle));
9352 arcData.arcSymbol = data->arcSymbol;
9353 arcData.isPartial = data->isPartial;
9354 arcData.hasLeader = data->hasLeader;
9355 arcData.leaderPt1 = RS_Vector(data->getLeaderPt1().x, data->getLeaderPt1().y);
9356 arcData.leaderPt2 = RS_Vector(data->leaderPt2.x, data->leaderPt2.y);
9357 auto dimEntity = new LC_DimArc(m_currentContainer, dd, arcData);
9358 setEntityAttributes(dimEntity, data);
9359 dimEntity->update();
9360 m_currentContainer->addEntity(dimEntity);
9361}
9362
9363void RS_FilterDXFRW::addDimOrdinate(const DRW_DimOrdinate *data) {
9364 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDimOrdinate(const DL_DimensionData&, "
9365 "const DL_DimOrdinateData&) not yet implemented");
9366 const RS_DimensionData dimensionData = convDimensionData(data);
9367
9368 const RS_Vector featurePoint{data->getFirstLine().x, data->getFirstLine().y};
9369 const RS_Vector leaderEndPoint{data->getSecondLine().x,
9370 data->getSecondLine().y};
9371
9372 bool ordinateTypeForX = false;
9373 const int type = data->type;
9374 if (type & 64) {
9375 ordinateTypeForX = true;
9376 }
9377 const LC_DimOrdinateData d(featurePoint, leaderEndPoint, ordinateTypeForX);
9378 auto *entity = new LC_DimOrdinate(m_currentContainer, dimensionData, d);
9379 setEntityAttributes(entity, data);
9380 entity->update();
9381 m_currentContainer->addEntity(entity);
9382}
9383
9384/**
9385 * Implementation of the method which handles leader entities.
9386 */
9387void RS_FilterDXFRW::addLeader(const DRW_Leader *data) {
9388 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDimLeader");
9389 const RS_LeaderData d(data->arrow != 0,
9390 QString::fromUtf8(data->style.c_str()));
9391 const auto leader = new RS_Leader(m_currentContainer, d);
9392 setEntityAttributes(leader, data);
9393
9394 for (const auto &vert : data->vertexlist) {
9395 leader->addVertex({vert->x, vert->y});
9396 }
9397
9398 leader->update();
9399 m_currentContainer->addEntity(leader);
9400}
9401
9402namespace {
9403// Sample count for the cubic-NURBS → quadratic spline-points sampling branch
9404// in buildHatchSplineEdge. See plan §C.2.
9405constexpr int kHatchSplineSamples = 64;
9406} // namespace
9407
9408/**
9409 * Build an LC_SplinePoints from a DRW_Spline hatch-boundary edge.
9410 * All degrees converge to a quadratic spline-points representation:
9411 * degree==2 fit-points-only → pass-through as splinePoints
9412 * degree==2 control-net → pass-through as controlPoints
9413 * degree==1 or degree==3 → sample 64 points via a throwaway
9414 * RS_Spline evaluator
9415 * Returns nullptr on degenerate input. See plan §C.1-§C.3.
9416 */
9417LC_SplinePoints *
9418RS_FilterDXFRW::buildHatchSplineEdge(RS_EntityContainer *hatchLoop,
9419 const DRW_Spline *s) {
9420 if (s == nullptr)
9421 return nullptr;
9422 if (s->degree < 1 || s->degree > 3) {
9423 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
9424 "buildHatchSplineEdge: unsupported degree %d", s->degree);
9425 return nullptr;
9426 }
9427 if (s->controllist.empty() && s->fitlist.empty()) {
9428 RS_DEBUGRS_Debug::instance()->print(
9429 RS_Debug::D_WARNING,
9430 "buildHatchSplineEdge: spline edge has neither control nor fit points");
9431 return nullptr;
9432 }
9433
9434 // DXF entity-level splines store "closed" in bit 0; DWG hatch-boundary
9435 // streams store "periodic" in bit 1 and never set bit 0 (see
9436 // libraries/libdxfrw/src/drw_entities.cpp parseDwg at lines 2799-2800).
9437 // Either bit implies a closed curve.
9438 const bool closed = (s->flags & 0x3) != 0;
9439 LC_SplinePointsData sd(closed, /*cut*/ false);
9440
9441 if (s->degree == 2 && s->controllist.empty() && !s->fitlist.empty()) {
9442 // Branch 1: degree==2 with fit points only — pass through.
9443 sd.useControlPoints = false;
9444 for (const auto &fp : s->fitlist) {
9445 if (fp)
9446 sd.splinePoints.push_back(RS_Vector{fp->x, fp->y});
9447 }
9448 } else if (s->degree == 2 && !s->controllist.empty()) {
9449 // Branch 2: degree==2 with control net — pass through.
9450 sd.useControlPoints = true;
9451 for (const auto &cp : s->controllist) {
9452 if (cp)
9453 sd.controlPoints.push_back(RS_Vector{cp->x, cp->y});
9454 }
9455 } else {
9456 // Branch 3: degree==1 or degree==3. Sample the NURBS at 64 points
9457 // via a throwaway RS_Spline; the samples become fit points.
9458 // Fall back to treating control points as a polyline if the knot
9459 // vector is missing or short.
9460 const size_t needKnots = s->controllist.size() + size_t(s->degree) + 1;
9461 if (s->knotslist.size() < needKnots) {
9462 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
9463 "buildHatchSplineEdge: short knot vector (%zu < %zu); "
9464 "treating control points as polyline",
9465 s->knotslist.size(), needKnots);
9466 sd.useControlPoints = false;
9467 for (const auto &cp : s->controllist) {
9468 if (cp)
9469 sd.splinePoints.push_back(RS_Vector{cp->x, cp->y});
9470 }
9471 } else {
9472 RS_SplineData td(s->degree, closed);
9473 td.type = closed ? RS_SplineData::SplineType::Standard
9474 : RS_SplineData::SplineType::ClampedOpen;
9475 const double tolknot = (s->tolknot > 0.0) ? s->tolknot : 1e-7;
9476 for (double k : s->knotslist) {
9477 td.knotslist.push_back(RS_Math::round(k, tolknot));
9478 }
9479
9480 auto tmp = std::make_unique<RS_Spline>(nullptr, td);
9481 const bool isRational = (s->flags & 0x4) != 0;
9482 for (size_t i = 0; i < s->controllist.size(); ++i) {
9483 const auto &cp = s->controllist[i];
9484 if (!cp)
9485 continue;
9486 // DXF stores rational weights in weightlist; DWG hatch-
9487 // boundary stream stores them on controllist[i]->z. Check
9488 // both. See plan §C.3.
9489 double w = 1.0;
9490 if (isRational) {
9491 if (i < s->weightlist.size())
9492 w = s->weightlist[i];
9493 else
9494 w = cp->z;
9495 }
9496 tmp->addControlPointRaw({cp->x, cp->y}, w);
9497 }
9498 if (closed)
9499 tmp->setClosed(true);
9500 tmp->update();
9501
9502 sd.useControlPoints = false;
9503 sd.splinePoints.reserve(kHatchSplineSamples);
9504 tmp->fillStrokePoints(kHatchSplineSamples - 1, sd.splinePoints);
9505 }
9506 }
9507
9508 if (sd.splinePoints.size() < 2 && sd.controlPoints.size() < 2) {
9509 RS_DEBUGRS_Debug::instance()->print(
9510 RS_Debug::D_WARNING,
9511 "buildHatchSplineEdge: degenerate spline edge (too few points)");
9512 return nullptr;
9513 }
9514
9515 // Some DWG hatch boundaries encode a geometrically-closed spline with
9516 // both flag bits unset (and an accompanying 0-length line as a closure
9517 // marker). Detect endpoint coincidence in the populated point list and
9518 // flip the closed flag so LoopExtractor treats it as a single closed
9519 // loop instead of an open edge whose start==end. Closed LC_SplinePoints
9520 // expects a periodic point list without an explicit closing repeat —
9521 // drop the duplicate tail when present.
9522 if (!sd.closed) {
9523 if (!sd.splinePoints.empty() &&
9524 sd.splinePoints.front().distanceTo(sd.splinePoints.back()) <= 1e-8) {
9525 sd.closed = true;
9526 if (sd.splinePoints.size() > 2)
9527 sd.splinePoints.pop_back();
9528 } else if (!sd.controlPoints.empty() &&
9529 sd.controlPoints.front().distanceTo(sd.controlPoints.back()) <=
9530 1e-8) {
9531 sd.closed = true;
9532 if (sd.controlPoints.size() > 2)
9533 sd.controlPoints.pop_back();
9534 }
9535 }
9536
9537 return new LC_SplinePoints(hatchLoop, std::move(sd));
9538}
9539
9540/**
9541 * Snap LC_SplinePoints endpoints inside hatchLoop to neighboring line-like
9542 * edge endpoints when the gap is within 10× ENDPOINT_TOLERANCE (1e-7) but
9543 * non-zero. Boundary-stream float drift can leave ~1e-9 to ~1e-8 gaps that
9544 * break LoopExtractor's endpoint-chaining. The 10× window is small enough
9545 * that geometrically distinct edges never stitch.
9546 */
9547void RS_FilterDXFRW::snapSplineEdgeEndpoints(RS_EntityContainer *hatchLoop) {
9548 if (hatchLoop == nullptr || hatchLoop->count() < 2)
9549 return;
9550 constexpr double kSnapWindow = 1e-7; // 10× ENDPOINT_TOLERANCE
9551 const unsigned int n = hatchLoop->count();
9552
9553 auto setSplineEnd = [](LC_SplinePoints *sp, bool atFront,
9554 const RS_Vector &v) {
9555 auto &d = sp->getData();
9556 if (d.useControlPoints && !d.controlPoints.empty()) {
9557 if (atFront)
9558 d.controlPoints.front() = v;
9559 else
9560 d.controlPoints.back() = v;
9561 } else if (!d.splinePoints.empty()) {
9562 if (atFront)
9563 d.splinePoints.front() = v;
9564 else
9565 d.splinePoints.back() = v;
9566 }
9567 sp->update();
9568 };
9569
9570 for (unsigned int i = 0; i < n; ++i) {
9571 RS_Entity *cur = hatchLoop->entityAt(i);
9572 RS_Entity *nxt = hatchLoop->entityAt((i + 1) % n);
9573 if (cur == nullptr || nxt == nullptr)
9574 continue;
9575
9576 const bool curSpline = cur->rtti() == RS2::EntitySplinePoints;
9577 const bool nxtSpline = nxt->rtti() == RS2::EntitySplinePoints;
9578 if (curSpline == nxtSpline)
9579 continue; // line-line or spline-spline
9580
9581 const RS_Vector curEnd = cur->getEndpoint();
9582 const RS_Vector nxtBeg = nxt->getStartpoint();
9583 const double gap = curEnd.distanceTo(nxtBeg);
9584 if (gap <= 0.0 || gap >= kSnapWindow)
9585 continue;
9586
9587 if (curSpline) {
9588 setSplineEnd(static_cast<LC_SplinePoints *>(cur), /*atFront*/ false,
9589 nxtBeg);
9590 } else {
9591 setSplineEnd(static_cast<LC_SplinePoints *>(nxt), /*atFront*/ true,
9592 curEnd);
9593 }
9594 }
9595}
9596
9597/**
9598 * Implementation of the method which handles hatch entities.
9599 */
9600void RS_FilterDXFRW::addHatch(const DRW_Hatch *data) {
9601 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addHatch()");
9602 const auto hatch = new RS_Hatch(
9603 m_currentContainer, RS_HatchData(data->solid, data->scale, data->angle,
9604 QString::fromUtf8(data->name.c_str())));
9605 setEntityAttributes(hatch, data);
9606 if (data->basePoint.z != 0.0 || data->extPoint.x != 0.0 ||
9607 data->extPoint.y != 0.0 || data->extPoint.z != 1.0) {
9608 std::vector<std::shared_ptr<DRW_Variant>> payload;
9609 payload.push_back(std::make_shared<DRW_Variant>(
9610 1010, DRW_Coord(0.0, 0.0, data->basePoint.z)));
9611 payload.push_back(std::make_shared<DRW_Variant>(
9612 1011, DRW_Coord(data->extPoint.x, data->extPoint.y, data->extPoint.z)));
9613 appendTypeSidecar(hatch, kHatchExtrusionMarker, std::move(payload));
9614 }
9615 m_currentContainer->appendEntity(hatch);
9616
9617 for (size_t i = 0; i < data->looplist.size(); i++) {
9618 auto &loop = data->looplist.at(i);
9619 if ((loop->type & 32) == 32) {
9620 continue;
9621 }
9622 const auto hatchLoop = new RS_EntityContainer(hatch);
9623 hatchLoop->setLayer(nullptr);
9624 hatch->addEntity(hatchLoop);
9625
9626 RS_Entity *e = nullptr;
9627 if ((loop->type & 2) == 2) { // polyline, convert to lines & arcs
9628 if (loop->objlist.empty())
9629 continue;
9630 DRW_LWPolyline *pline =
9631 static_cast<DRW_LWPolyline *>(loop->objlist.at(0).get());
9632 RS_Polyline polyline{
9633 nullptr,
9634 RS_PolylineData(RS_Vector(false), RS_Vector(false), pline->flags)};
9635 for (auto const &vert : pline->vertlist) {
9636 polyline.addVertex(RS_Vector{vert->x, vert->y}, vert->bulge);
9637 }
9638
9639 for (const RS_Entity *e :
9640 lc::LC_ContainerTraverser{polyline, RS2::ResolveNone}.entities()) {
9641 // for (RS_Entity* e=polyline.firstEntity(); e;
9642 // e=polyline.nextEntity()) {
9643 RS_Entity *tmp = e->clone();
9644 tmp->reparent(hatchLoop);
9645 tmp->setLayer(nullptr);
9646 hatchLoop->addEntity(tmp);
9647 }
9648 } else {
9649 RS_Entity *e = nullptr;
9650 for (unsigned int j = 0; j < loop->objlist.size(); j++) {
9651 e = nullptr;
9652 auto &ent = loop->objlist.at(j);
9653 switch (ent->eType) {
9654 case DRW::LINE: {
9655 const auto e2 = static_cast<DRW_Line *>(ent.get());
9656 e = new RS_Line{hatchLoop,
9657 {{e2->basePoint.x, e2->basePoint.y},
9658 {e2->secPoint.x, e2->secPoint.y}}};
9659 break;
9660 }
9661 case DRW::ARC: {
9662 const auto e2 = static_cast<DRW_Arc *>(ent.get());
9663 if (e2->isccw && e2->staangle < 1.0e-6 &&
9664 e2->endangle > RS_Math::deg2rad(360) - 1.0e-6) {
9665 e = new RS_Circle(
9666 hatchLoop, {{e2->basePoint.x, e2->basePoint.y}, e2->radious});
9667 } else {
9668 if (e2->isccw) {
9669 e = new RS_Arc(
9670 hatchLoop,
9671 RS_ArcData(RS_Vector(e2->basePoint.x, e2->basePoint.y),
9672 e2->radious, RS_Math::correctAngle(e2->staangle),
9673 RS_Math::correctAngle(e2->endangle), false));
9674 } else {
9675 e = new RS_Arc(
9676 hatchLoop,
9677 RS_ArcData(
9678 RS_Vector(e2->basePoint.x, e2->basePoint.y), e2->radious,
9679 RS_Math::correctAngle(2 * M_PI3.14159265358979323846 - e2->staangle),
9680 RS_Math::correctAngle(2 * M_PI3.14159265358979323846 - e2->endangle), true));
9681 }
9682 }
9683 break;
9684 }
9685 case DRW::ELLIPSE: {
9686 const auto e2 = static_cast<DRW_Ellipse *>(ent.get());
9687 double ang1 = e2->staparam;
9688 double ang2 = e2->endparam;
9689 if (fabs(ang2 - 2. * M_PI3.14159265358979323846) < 1.0e-10 && fabs(ang1) < 1.0e-10) {
9690 ang2 = 0.0;
9691 } else {
9692 // convert angle to parameter
9693 ang1 = atan(tan(ang1) / e2->ratio);
9694 ang2 = atan(tan(ang2) / e2->ratio);
9695 if (ang1 < 0) {
9696 // quadrant 2 & 4
9697 ang1 += M_PI3.14159265358979323846;
9698 if (e2->staparam > M_PI3.14159265358979323846) {
9699 // quadrant 4
9700 ang1 += M_PI3.14159265358979323846;
9701 }
9702 } else if (e2->staparam > M_PI3.14159265358979323846) {
9703 // 3 quadrant
9704 ang1 += M_PI3.14159265358979323846;
9705 }
9706 if (ang2 < 0) {
9707 // quadrant 2 & 4
9708 ang2 += M_PI3.14159265358979323846;
9709 if (e2->endparam > M_PI3.14159265358979323846) {
9710 // quadrant 4
9711 ang2 += M_PI3.14159265358979323846;
9712 }
9713 } else if (e2->endparam > M_PI3.14159265358979323846) {
9714 // 3 quadrant
9715 ang2 += M_PI3.14159265358979323846;
9716 }
9717 }
9718 e = new RS_Ellipse{hatchLoop,
9719 {{e2->basePoint.x, e2->basePoint.y},
9720 {e2->secPoint.x, e2->secPoint.y},
9721 e2->ratio,
9722 ang1,
9723 ang2,
9724 !e2->isccw}};
9725 break;
9726 }
9727 case DRW::SPLINE: {
9728 e = buildHatchSplineEdge(hatchLoop,
9729 static_cast<DRW_Spline *>(ent.get()));
9730 break;
9731 }
9732 default:
9733 break;
9734 }
9735 if (e != nullptr) {
9736 e->setLayer(nullptr);
9737 hatchLoop->addEntity(e);
9738 }
9739 }
9740 // After all explicit-segment edges are attached, snap spline
9741 // endpoints to neighboring line endpoints if a tiny float gap
9742 // is the only thing standing between LoopExtractor and a closed
9743 // chain. See plan §C.1.
9744 snapSplineEdgeEndpoints(hatchLoop);
9745 }
9746 }
9747
9748 RS_DEBUGRS_Debug::instance()->print("hatch->update()");
9749 if (hatch->validate()) {
9750 hatch->update();
9751 } else {
9752 m_graphic->removeEntity(hatch);
9753 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_ERROR, "RS_FilterDXFRW::endEntity(): updating "
9754 "hatch failed: invalid hatch area");
9755 }
9756}
9757
9758/**
9759 * Implementation of the method which handles image entities.
9760 */
9761void RS_FilterDXFRW::addImage(const DRW_Image *data) {
9762 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addImage");
9763 if (m_graphic != nullptr && data != nullptr)
9764 m_graphic->dwgAdvancedMetadata().addRasterImage(*data, false);
9765
9766 if (data == nullptr)
9767 return;
9768
9769 const RS_Vector ip(data->basePoint.x, data->basePoint.y);
9770 const RS_Vector uv(data->secPoint.x, data->secPoint.y);
9771 const RS_Vector vv(data->vVector.x, data->vVector.y);
9772 const RS_Vector size(data->sizeu, data->sizev);
9773
9774 const auto image =
9775 new RS_Image(m_currentContainer,
9776 RS_ImageData(data->ref, ip, uv, vv, size, QString(),
9777 data->brightness, data->contrast, data->fade));
9778
9779 setEntityAttributes(image, data);
9780 if (data->basePoint.z != 0.0 || data->secPoint.z != 0.0 ||
9781 data->vVector.z != 0.0) {
9782 std::vector<std::shared_ptr<DRW_Variant>> payload;
9783 payload.push_back(std::make_shared<DRW_Variant>(
9784 1010,
9785 DRW_Coord(data->basePoint.x, data->basePoint.y, data->basePoint.z)));
9786 payload.push_back(std::make_shared<DRW_Variant>(
9787 1011, DRW_Coord(data->secPoint.x, data->secPoint.y, data->secPoint.z)));
9788 payload.push_back(std::make_shared<DRW_Variant>(
9789 1012, DRW_Coord(data->vVector.x, data->vVector.y, data->vVector.z)));
9790 appendTypeSidecar(image, kImageFrameMarker, std::move(payload));
9791 }
9792 m_currentContainer->appendEntity(image);
9793}
9794
9795/**
9796 * Implementation of the method which handles WIPEOUT entities.
9797 *
9798 * WIPEOUT shares the IMAGE binary layout but only the polygon (clipPath) and
9799 * the IMAGE's frame parameters (basePoint, secPoint=uVector, vVector,
9800 * sizeu/sizev) are meaningful — there's no raster file behind it.
9801 *
9802 * Group 11/12 are single-pixel U/V vectors. With the half-pixel origin
9803 * offset, the WCS transform is:
9804 * P_wcs = basePoint + (px + 0.5) * uVector + (py + 0.5) * vVector.
9805 * The clip coordinates already express the image size; sizeu/sizev must not
9806 * multiply the vectors again.
9807 */
9808void RS_FilterDXFRW::addWipeout(const DRW_Wipeout *data) {
9809 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addWipeout");
9810 if (m_graphic != nullptr && data != nullptr)
9811 m_graphic->dwgAdvancedMetadata().addRasterImage(*data, true);
9812 if (data == nullptr || data->clipPath.empty()) {
9813 return;
9814 }
9815
9816 LC_WipeoutData wipeoutData;
9817 wipeoutData.hasNativeFrame = true;
9818 wipeoutData.insertionPoint =
9819 RS_Vector(data->basePoint.x, data->basePoint.y, data->basePoint.z);
9820 wipeoutData.uPixel =
9821 RS_Vector(data->secPoint.x, data->secPoint.y, data->secPoint.z);
9822 wipeoutData.vPixel =
9823 RS_Vector(data->vVector.x, data->vVector.y, data->vVector.z);
9824 wipeoutData.sizeU = data->sizeu;
9825 wipeoutData.sizeV = data->sizev;
9826 wipeoutData.displayProps = data->m_displayProps;
9827 wipeoutData.imageDefHandle = data->ref;
9828 wipeoutData.imageDefReactorHandle = data->m_imageDefReactorHandle;
9829 wipeoutData.clip = data->clip;
9830 wipeoutData.brightness = data->brightness;
9831 wipeoutData.contrast = data->contrast;
9832 wipeoutData.fade = data->fade;
9833 wipeoutData.clipBoundaryType = data->m_clipBoundaryType;
9834 wipeoutData.clipMode = data->clipMode;
9835 wipeoutData.clipPath.reserve(data->clipPath.size());
9836 for (const DRW_Coord &clipPoint : data->clipPath)
9837 wipeoutData.clipPath.emplace_back(clipPoint.x, clipPoint.y, clipPoint.z);
9838
9839 auto w =
9840 std::make_unique<LC_Wipeout>(m_currentContainer, std::move(wipeoutData));
9841 setEntityAttributes(w.get(), data);
9842 m_currentContainer->appendEntity(w.release());
9843}
9844
9845/**
9846 * Build an LC_MLeader from a parsed DRW_MLeader (ODA spec §20.4.48).
9847 *
9848 * MLEADER carries a multi-root callout structure plus either text or block
9849 * content. This conversion captures the geometric structure (roots →
9850 * leader lines → points) and content reference. Style-handle resolution
9851 * (against LC_MLeaderStyleList) is still metadata-only, but we preserve the
9852 * DWG handle references needed for native re-export.
9853 */
9854void RS_FilterDXFRW::addMLeader(const DRW_MLeader *data) {
9855 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addMLeader");
9856 if (data == nullptr)
9857 return;
9858 if (m_graphic != nullptr) {
9859 m_graphic->dwgAdvancedMetadata().addMLeader(*data);
9860 }
9861
9862 LC_MLeaderData md;
9863 md.roots.reserve(data->context.roots.size());
9864 for (const auto &r : data->context.roots) {
9865 LC_MLeaderRoot root;
9866 root.connectionPoint = RS_Vector(r.connectionPoint.x, r.connectionPoint.y);
9867 root.direction = RS_Vector(r.direction.x, r.direction.y);
9868 root.landingDistance = r.landingDistance;
9869 root.attachmentDirection = r.attachmentDirection;
9870 root.leaderLines.reserve(r.leaderLines.size());
9871 for (const auto &ll : r.leaderLines) {
9872 LC_MLeaderLine line;
9873 line.leaderLineIndex = ll.leaderLineIndex;
9874 line.points.reserve(ll.points.size());
9875 for (const auto &p : ll.points) {
9876 line.points.emplace_back(p.x, p.y);
9877 }
9878 root.leaderLines.push_back(std::move(line));
9879 }
9880 md.roots.push_back(std::move(root));
9881 }
9882
9883 md.contentBasePoint = RS_Vector(data->context.contentBasePoint.x,
9884 data->context.contentBasePoint.y);
9885 md.basePoint =
9886 RS_Vector(data->context.basePoint.x, data->context.basePoint.y);
9887
9888 md.hasTextContents = data->context.hasTextContents;
9889 md.hasBlockContents =
9890 !data->context.hasTextContents && data->context.hasContentsBlock;
9891 if (md.hasTextContents) {
9892 md.textLabel =
9893 toNativeString(QString::fromUtf8(data->context.textLabel.c_str()));
9894 md.textLocation =
9895 RS_Vector(data->context.textLocation.x, data->context.textLocation.y);
9896 md.textHeight = data->context.textHeight;
9897 md.textRotation = data->context.textRotation;
9898 md.boundaryWidth = data->context.boundaryWidth;
9899 md.boundaryHeight = data->context.boundaryHeight;
9900 md.textColor = data->context.textColor;
9901 }
9902 if (md.hasBlockContents) {
9903 md.blockLocation =
9904 RS_Vector(data->context.blockLocation.x, data->context.blockLocation.y);
9905 md.blockScale =
9906 RS_Vector(data->context.blockScale.x, data->context.blockScale.y,
9907 data->context.blockScale.z);
9908 md.blockRotation = data->context.blockRotation;
9909 // Resolve the content block handle -> block name so LC_MLeader can render
9910 // the block symbol the leader points at. The context's
9911 // blockTableRecordHandle is authoritative; fall back to the entity-level
9912 // style block handle.
9913 std::uint32_t blockHandle = data->context.blockTableRecordHandle.ref != 0
9914 ? data->context.blockTableRecordHandle.ref
9915 : data->styleBlockHandle.ref;
9916 if (blockHandle != 0)
9917 resolveBlockNameByHandle(blockHandle, md.blockName);
9918 }
9919
9920 md.leaderType = data->leaderType;
9921 md.leaderColor = data->leaderColor;
9922 md.landingDistance = data->landingDistance;
9923 md.arrowSize = data->defaultArrowHeadSize;
9924 md.landingEnabled = data->landingEnabled;
9925 md.doglegEnabled = data->doglegEnabled;
9926 md.contentType = data->styleContentType;
9927 md.scaleFactor = data->scaleFactor;
9928 md.dwgStyleHandle = data->styleHandle.ref;
9929 md.dwgLeaderLineTypeHandle = data->leaderLineTypeHandle.ref;
9930 md.dwgArrowHeadHandle = data->arrowHeadHandle.ref;
9931 md.dwgTextStyleHandle = data->styleTextStyleHandle.ref != 0
9932 ? data->styleTextStyleHandle.ref
9933 : data->context.textStyleHandle.ref;
9934 md.dwgBlockHandle = data->styleBlockHandle.ref != 0
9935 ? data->styleBlockHandle.ref
9936 : data->context.blockTableRecordHandle.ref;
9937
9938 auto m = std::make_unique<LC_MLeader>(m_currentContainer, std::move(md));
9939 setEntityAttributes(m.get(), data);
9940 m_currentContainer->appendEntity(m.release());
9941}
9942
9943/**
9944 * MLEADERSTYLE dictionary entry capture. Styles are kept in the DWG advanced
9945 * metadata store for future style resolution and raw/native round-trip work;
9946 * the current LC_MLeader entity still copies effective scalar values from the
9947 * entity itself.
9948 */
9949void RS_FilterDXFRW::addMLeaderStyle(const DRW_MLeaderStyle *data) {
9950 if (data == nullptr)
9951 return;
9952 if (m_graphic != nullptr) {
9953 m_graphic->dwgAdvancedMetadata().addMLeaderStyle(*data);
9954 }
9955 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addMLeaderStyle: %s",
9956 data->name.empty() ? "(unnamed)" : data->name.c_str());
9957}
9958
9959void RS_FilterDXFRW::addDetailViewStyle(const DRW_DetailViewStyle &data) {
9960 if (m_graphic != nullptr) {
9961 m_graphic->dwgAdvancedMetadata().addDetailViewStyle(data);
9962 }
9963 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDetailViewStyle: %s",
9964 data.m_modelDoc.m_displayName.empty()
9965 ? data.m_modelDoc.m_description.c_str()
9966 : data.m_modelDoc.m_displayName.c_str());
9967}
9968
9969void RS_FilterDXFRW::addSectionViewStyle(const DRW_SectionViewStyle &data) {
9970 if (m_graphic != nullptr) {
9971 m_graphic->dwgAdvancedMetadata().addSectionViewStyle(data);
9972 }
9973 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addSectionViewStyle: %s",
9974 data.m_modelDoc.m_displayName.empty()
9975 ? data.m_modelDoc.m_description.c_str()
9976 : data.m_modelDoc.m_displayName.c_str());
9977}
9978
9979void RS_FilterDXFRW::addBreakData(const DRW_BreakData &data) {
9980 if (m_graphic != nullptr) {
9981 m_graphic->dwgAdvancedMetadata().addBreakData(data);
9982 }
9983 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addBreakData: %d refs",
9984 static_cast<int>(data.m_pointRefHandles.size()));
9985}
9986
9987void RS_FilterDXFRW::addBreakPointRef(const DRW_BreakPointRef &data) {
9988 if (m_graphic != nullptr) {
9989 m_graphic->dwgAdvancedMetadata().addBreakPointRef(data);
9990 }
9991 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addBreakPointRef: %d",
9992 static_cast<int>(data.handle));
9993}
9994
9995void RS_FilterDXFRW::addGroup(const DRW_Group &data) {
9996 if (m_graphic != nullptr) {
9997 m_graphic->dwgAdvancedMetadata().addGroup(data);
9998 }
9999 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addGroup: %s (%d handles)",
10000 data.m_description.empty() ? "(unnamed)"
10001 : data.m_description.c_str(),
10002 static_cast<int>(data.m_entityHandles.size()));
10003}
10004
10005void RS_FilterDXFRW::addImageDefinitionReactor(
10006 const DRW_ImageDefinitionReactor &data) {
10007 if (m_graphic != nullptr) {
10008 m_graphic->dwgAdvancedMetadata().addImageDefinitionReactor(data);
10009 }
10010 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addImageDefinitionReactor: class version %d",
10011 static_cast<int>(data.m_classVersion));
10012}
10013
10014void RS_FilterDXFRW::addRasterVariables(const DRW_RasterVariables &data) {
10015 if (m_graphic != nullptr) {
10016 m_graphic->dwgAdvancedMetadata().addRasterVariables(data);
10017 }
10018 RS_DEBUGRS_Debug::instance()->print(
10019 "RS_FilterDXFRW::addRasterVariables: frame %d quality %d units %d",
10020 data.m_imageFrame, data.m_imageQuality, data.m_units);
10021}
10022
10023void RS_FilterDXFRW::addSpatialFilter(const DRW_SpatialFilter &data) {
10024 if (m_graphic != nullptr) {
10025 m_graphic->dwgAdvancedMetadata().addSpatialFilter(data);
10026 }
10027 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addSpatialFilter: %d boundary points",
10028 static_cast<int>(data.m_boundaryPoints.size()));
10029}
10030
10031void RS_FilterDXFRW::addGeoData(const DRW_GeoData &data) {
10032 if (m_graphic != nullptr) {
10033 m_graphic->dwgAdvancedMetadata().addGeoData(data);
10034 }
10035 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addGeoData: version %d",
10036 static_cast<int>(data.m_version));
10037}
10038
10039void RS_FilterDXFRW::addTableGeometry(const DRW_TableGeometry &data) {
10040 if (m_graphic != nullptr) {
10041 m_graphic->dwgAdvancedMetadata().addTableGeometry(data);
10042 }
10043 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addTableGeometry: %d x %d",
10044 static_cast<int>(data.m_rowCount),
10045 static_cast<int>(data.m_columnCount));
10046}
10047
10048void RS_FilterDXFRW::addTableStyle(const DRW_TableStyle &data) {
10049 if (m_graphic != nullptr) {
10050 m_graphic->dwgAdvancedMetadata().addTableStyle(data);
10051 }
10052 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addTableStyle: %s",
10053 data.m_name.empty() ? "(unnamed)" : data.m_name.c_str());
10054}
10055
10056void RS_FilterDXFRW::addTableContent(const DRW_TableContentObject &data) {
10057 if (m_graphic != nullptr) {
10058 m_graphic->dwgAdvancedMetadata().addTableContent(data);
10059 }
10060 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addTableContent: %d x %d",
10061 static_cast<int>(data.m_content.m_rows.size()),
10062 static_cast<int>(data.m_content.m_columns.size()));
10063}
10064
10065void RS_FilterDXFRW::addObjectContextData(const DRW_ObjectContextData &data) {
10066 if (m_graphic != nullptr) {
10067 m_graphic->dwgAdvancedMetadata().addObjectContextData(data);
10068 }
10069 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addObjectContextData: %s handle %d scale %d",
10070 data.m_recordName.empty() ? "(object-context)"
10071 : data.m_recordName.c_str(),
10072 static_cast<int>(data.handle),
10073 static_cast<int>(data.m_scaleHandle));
10074}
10075
10076void RS_FilterDXFRW::addCellStyleMap(const DRW_CellStyleMap &data) {
10077 if (m_graphic != nullptr) {
10078 m_graphic->dwgAdvancedMetadata().addCellStyleMap(data);
10079 }
10080 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addCellStyleMap: %d styles",
10081 static_cast<int>(data.m_cellStyles.size()));
10082}
10083
10084void RS_FilterDXFRW::addRawDxfObject(const DRW_RawDxfObject &data) {
10085 if (m_graphic != nullptr) {
10086 m_graphic->dwgAdvancedMetadata().addRawDxfObject(data);
10087 }
10088}
10089
10090void RS_FilterDXFRW::addRawDxfEntity(const DRW_RawDxfObject &data) {
10091 if (m_graphic != nullptr) {
10092 m_graphic->dwgAdvancedMetadata().addRawDxfEntity(data);
10093 }
10094}
10095
10096void RS_FilterDXFRW::addRawDxfSection(const DRW_RawDxfSection &data) {
10097 if (m_graphic != nullptr) {
10098 m_graphic->dwgAdvancedMetadata().addRawDxfSection(data);
10099 }
10100}
10101
10102void RS_FilterDXFRW::addDxfClass(const DRW_Class &data) {
10103 if (m_graphic != nullptr) {
10104 m_graphic->dwgAdvancedMetadata().addDxfClass(data);
10105 }
10106}
10107
10108void RS_FilterDXFRW::addUnsupportedObject(const DRW_UnsupportedObject &data) {
10109 if (m_graphic != nullptr) {
10110 m_graphic->dwgAdvancedMetadata().addUnsupportedObject(data);
10111 // Cross-read P1: Navisworks (and related) stay metadata-only until typed
10112 // DRW models land; classify by CLASSES record name so they are not silent.
10113 const std::string &rn = data.m_recordName;
10114 if (rn.find("NAVISWORKS") != std::string::npos) {
10115 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
10116 rn.empty() ? "NAVISWORKSMODEL" : rn, data.m_handle, 0,
10117 "metadata-only unsupported shell");
10118 }
10119 }
10120 m_unsupportedDwgObjects.push_back(data);
10121 RS_DEBUGRS_Debug::instance()->print(
10122 "RS_FilterDXFRW::addUnsupportedObject: %s handle %d (%d bytes)",
10123 data.m_recordName.empty() ? "(fixed)" : data.m_recordName.c_str(),
10124 static_cast<int>(data.m_handle),
10125 static_cast<int>(data.m_rawBytes.size()));
10126}
10127
10128void RS_FilterDXFRW::addDwgFramePublication(
10129 const DRW_DwgFramePublication &data) {
10130 if (m_graphic != nullptr) {
10131 m_graphic->dwgAdvancedMetadata().addDwgFramePublication(data);
10132 }
10133}
10134
10135void RS_FilterDXFRW::addDwgDictionaryMembership(
10136 const DRW_DwgDictionaryMembership &data) {
10137 if (m_graphic != nullptr) {
10138 m_graphic->dwgAdvancedMetadata().addDwgDictionaryMembership(data);
10139 }
10140}
10141
10142void RS_FilterDXFRW::addDwgDictionaryWithDefaultMembership(
10143 const DRW_DwgDictionaryWithDefaultMembership &data) {
10144 if (m_graphic != nullptr) {
10145 m_graphic->dwgAdvancedMetadata().addDwgDictionaryWithDefaultMembership(
10146 data);
10147 }
10148}
10149
10150void RS_FilterDXFRW::addDwgGroupMembership(const DRW_DwgGroupMembership &data) {
10151 if (m_graphic != nullptr) {
10152 m_graphic->dwgAdvancedMetadata().addDwgGroupMembership(data);
10153 }
10154}
10155
10156void RS_FilterDXFRW::addDwgSortEntsMembership(
10157 const DRW_DwgSortEntsMembership &data) {
10158 if (m_graphic != nullptr) {
10159 m_graphic->dwgAdvancedMetadata().addDwgSortEntsMembership(data);
10160 }
10161}
10162
10163void RS_FilterDXFRW::addDwgFieldListMembership(
10164 const DRW_DwgFieldListMembership &data) {
10165 if (m_graphic != nullptr) {
10166 m_graphic->dwgAdvancedMetadata().addDwgFieldListMembership(data);
10167 }
10168}
10169
10170void RS_FilterDXFRW::addDwgFieldPayloadReceipt(
10171 const DRW_DwgFieldPayloadReceipt &data) {
10172 if (m_graphic != nullptr) {
10173 m_graphic->dwgAdvancedMetadata().addDwgFieldPayloadReceipt(data);
10174 }
10175}
10176
10177void RS_FilterDXFRW::addDwgTypedReference(const DRW_DwgTypedReference &data) {
10178 if (m_graphic != nullptr) {
10179 m_graphic->dwgAdvancedMetadata().addDwgTypedReference(data);
10180 }
10181}
10182
10183void RS_FilterDXFRW::addDwgBlockReachability(
10184 const DRW_DwgBlockReachability &data) {
10185 if (m_graphic != nullptr) {
10186 m_graphic->dwgAdvancedMetadata().addDwgBlockReachability(data);
10187 }
10188}
10189
10190void RS_FilterDXFRW::addDwgFrameCoverageReport(
10191 const DRW_DwgFrameCoverageReport &data) {
10192 if (m_graphic != nullptr) {
10193 m_graphic->dwgAdvancedMetadata().addDwgFrameCoverageReport(data);
10194 }
10195}
10196
10197void RS_FilterDXFRW::addDwgClassCoverageReport(
10198 const DRW_DwgClassCoverageReport &data) {
10199 if (m_graphic != nullptr) {
10200 m_graphic->dwgAdvancedMetadata().addDwgClassCoverageReport(data);
10201 }
10202}
10203
10204void RS_FilterDXFRW::addRawDwgSection(const DRW_RawDwgSection &data) {
10205 if (m_graphic != nullptr) {
10206 m_graphic->dwgAdvancedMetadata().addRawDwgSection(data);
10207 }
10208 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addRawDwgSection: %s (%d bytes)",
10209 data.m_name.c_str(), static_cast<int>(data.m_data.size()));
10210}
10211
10212void RS_FilterDXFRW::addDataStorage(const DRW_DataStorageSection &data) {
10213 if (m_graphic != nullptr) {
10214 m_graphic->dwgAdvancedMetadata().addDataStorage(data);
10215 }
10216 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDataStorage: %s records=%d",
10217 data.m_name.c_str(), static_cast<int>(data.records.size()));
10218}
10219
10220void RS_FilterDXFRW::addAcDbPlaceholder(const DRW_AcDbPlaceholder &data) {
10221 if (m_graphic != nullptr) {
10222 m_graphic->dwgAdvancedMetadata().addAcDbPlaceholder(data);
10223 }
10224 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addAcDbPlaceholder: %d",
10225 static_cast<int>(data.handle));
10226}
10227
10228void RS_FilterDXFRW::addCsacDocumentOptions(
10229 const DRW_CsacDocumentOptions &data) {
10230 if (m_graphic != nullptr) {
10231 m_graphic->dwgAdvancedMetadata().addCsacDocumentOptions(data);
10232 }
10233 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addCsacDocumentOptions: handle %d",
10234 static_cast<int>(data.handle));
10235}
10236
10237void RS_FilterDXFRW::addContextDataManager(const DRW_ContextDataManager &data) {
10238 if (m_graphic != nullptr) {
10239 m_graphic->dwgAdvancedMetadata().addContextDataManager(data);
10240 }
10241 RS_DEBUGRS_Debug::instance()->print(
10242 "RS_FilterDXFRW::addContextDataManager: handle %d sub-managers %d",
10243 static_cast<int>(data.handle), static_cast<int>(data.subManagers.size()));
10244}
10245
10246void RS_FilterDXFRW::addProxyEntity(const DRW_ProxyEntity &data) {
10247 if (m_graphic != nullptr) {
10248 m_graphic->dwgAdvancedMetadata().addProxyEntity(data);
10249 }
10250 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addProxyEntity: handle %d",
10251 static_cast<int>(data.handle));
10252}
10253
10254void RS_FilterDXFRW::addVbaProject(const DRW_VbaProject &data) {
10255 if (m_graphic != nullptr) {
10256 m_graphic->dwgAdvancedMetadata().addVbaProject(data);
10257 }
10258 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addVbaProject: handle %d bytes %d",
10259 static_cast<int>(data.handle),
10260 static_cast<int>(data.m_data.size()));
10261}
10262
10263void RS_FilterDXFRW::addProxyObject(const DRW_ProxyObject &data) {
10264 if (m_graphic != nullptr) {
10265 m_graphic->dwgAdvancedMetadata().addProxyObject(data);
10266 }
10267 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addProxyObject: handle %d",
10268 static_cast<int>(data.handle));
10269}
10270
10271void RS_FilterDXFRW::addSun(const DRW_Sun &data) {
10272 if (m_graphic != nullptr) {
10273 m_graphic->dwgAdvancedMetadata().addSun(data);
10274 }
10275 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addSun: handle %d on=%d",
10276 static_cast<int>(data.handle), data.m_isOn ? 1 : 0);
10277}
10278
10279// ── Cross-read parity: metadata-only family exposure (no RS document geometry)
10280// ──
10281void RS_FilterDXFRW::addPointCloud(const DRW_PointCloud *data) {
10282 if (m_graphic != nullptr && data != nullptr) {
10283 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
10284 "POINTCLOUD", data->handle, data->parentHandle, data->savedFilename);
10285 }
10286 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addPointCloud: metadata-only handle %d",
10287 data ? static_cast<int>(data->handle) : 0);
10288}
10289
10290void RS_FilterDXFRW::addPointCloudEx(const DRW_PointCloudEx *data) {
10291 if (m_graphic != nullptr && data != nullptr) {
10292 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
10293 "POINTCLOUDEX", data->handle, data->parentHandle, data->name);
10294 }
10295 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addPointCloudEx: metadata-only handle %d",
10296 data ? static_cast<int>(data->handle) : 0);
10297}
10298
10299void RS_FilterDXFRW::addNavisworksModel(const DRW_NavisworksModel *data) {
10300 if (m_graphic != nullptr && data != nullptr) {
10301 m_graphic->dwgAdvancedMetadata().addNavisworksModel(*data);
10302 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
10303 "NAVISWORKSMODEL", data->handle, data->parentHandle);
10304 }
10305 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addNavisworksModel: metadata-only handle %d",
10306 data ? static_cast<int>(data->handle) : 0);
10307}
10308
10309void RS_FilterDXFRW::addPointCloudDef(const DRW_PointCloudDef &data) {
10310 if (m_graphic != nullptr) {
10311 m_graphic->dwgAdvancedMetadata().addPointCloudDef(data);
10312 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
10313 "POINTCLOUDDEF", data.handle, data.parentHandle, data.m_sourceFilename);
10314 }
10315 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addPointCloudDef: kind %d handle %d",
10316 static_cast<int>(data.m_kind), static_cast<int>(data.handle));
10317}
10318
10319void RS_FilterDXFRW::addNavisworksModelDef(const DRW_NavisworksModelDef &data) {
10320 if (m_graphic != nullptr) {
10321 m_graphic->dwgAdvancedMetadata().addNavisworksModelDef(data);
10322 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
10323 "NAVISWORKSMODELDEF", data.handle, data.parentHandle, data.m_path);
10324 }
10325 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addNavisworksModelDef: handle %d path %s",
10326 static_cast<int>(data.handle), data.m_path.c_str());
10327}
10328
10329void RS_FilterDXFRW::addPointCloudColorMap(const DRW_PointCloudColorMap &data) {
10330 if (m_graphic != nullptr) {
10331 m_graphic->dwgAdvancedMetadata().addPointCloudColorMap(data);
10332 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
10333 "POINTCLOUDCOLORMAP", data.handle, data.parentHandle);
10334 }
10335 RS_DEBUGRS_Debug::instance()->print(
10336 "RS_FilterDXFRW::addPointCloudColorMap: handle %d ramps %d/%d",
10337 static_cast<int>(data.handle), static_cast<int>(data.m_colorRampCount),
10338 static_cast<int>(data.m_classificationColorRampCount));
10339}
10340
10341void RS_FilterDXFRW::addBackground(const DRW_Background &data) {
10342 if (m_graphic != nullptr) {
10343 m_graphic->dwgAdvancedMetadata().addBackground(data);
10344 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
10345 "BACKGROUND", data.handle, data.parentHandle);
10346 }
10347}
10348
10349void RS_FilterDXFRW::addMaterial(const DRW_Material &data) {
10350 if (m_graphic != nullptr) {
10351 m_graphic->dwgAdvancedMetadata().addMaterial(data);
10352 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
10353 "MATERIAL", data.handle, data.parentHandle, data.m_name);
10354 }
10355}
10356
10357void RS_FilterDXFRW::addRenderSettings(const DRW_RenderSettings &data) {
10358 if (m_graphic != nullptr) {
10359 m_graphic->dwgAdvancedMetadata().addRenderSettings(data);
10360 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
10361 "RENDERSETTINGS", data.handle, data.parentHandle);
10362 }
10363}
10364
10365void RS_FilterDXFRW::addSunStudy(const DRW_SunStudy &data) {
10366 if (m_graphic != nullptr) {
10367 m_graphic->dwgAdvancedMetadata().addSunStudy(data);
10368 m_graphic->dwgAdvancedMetadata().noteFamilyExposure("SUNSTUDY", data.handle,
10369 data.parentHandle);
10370 }
10371}
10372
10373void RS_FilterDXFRW::addMotionPath(const DRW_MotionPath &data) {
10374 if (m_graphic != nullptr) {
10375 m_graphic->dwgAdvancedMetadata().addMotionPath(data);
10376 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
10377 "MOTIONPATH", data.handle, data.parentHandle);
10378 }
10379}
10380
10381void RS_FilterDXFRW::addCurvePath(const DRW_CurvePath &data) {
10382 if (m_graphic != nullptr) {
10383 m_graphic->dwgAdvancedMetadata().addCurvePath(data);
10384 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
10385 "CURVEPATH", data.handle, data.parentHandle);
10386 }
10387}
10388
10389void RS_FilterDXFRW::addPointPath(const DRW_PointPath &data) {
10390 if (m_graphic != nullptr) {
10391 m_graphic->dwgAdvancedMetadata().addPointPath(data);
10392 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
10393 "POINTPATH", data.handle, data.parentHandle);
10394 }
10395}
10396
10397void RS_FilterDXFRW::addObjectPtr(const DRW_ObjectPtr &data) {
10398 if (m_graphic != nullptr) {
10399 m_graphic->dwgAdvancedMetadata().addObjectPtr(data);
10400 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
10401 "OBJECT_PTR", data.handle, data.parentHandle);
10402 }
10403}
10404
10405void RS_FilterDXFRW::addPartialViewingIndex(
10406 const DRW_PartialViewingIndex &data) {
10407 if (m_graphic != nullptr) {
10408 m_graphic->dwgAdvancedMetadata().addPartialViewingIndex(data);
10409 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
10410 "PARTIAL_VIEWING_INDEX", data.handle, data.parentHandle);
10411 }
10412}
10413
10414void RS_FilterDXFRW::addDbColor(const DRW_DbColor &data) {
10415 if (m_graphic != nullptr) {
10416 m_graphic->dwgAdvancedMetadata().addDbColor(data);
10417 m_graphic->dwgAdvancedMetadata().noteFamilyExposure("DBCOLOR", data.handle,
10418 data.parentHandle);
10419 }
10420}
10421
10422void RS_FilterDXFRW::addDimensionAssociation(
10423 const DRW_DimensionAssociation &data) {
10424 if (m_graphic != nullptr) {
10425 m_graphic->dwgAdvancedMetadata().addDimensionAssociation(data);
10426 m_graphic->dwgAdvancedMetadata().noteFamilyExposure("DIMASSOC", data.handle,
10427 data.parentHandle);
10428 }
10429}
10430
10431void RS_FilterDXFRW::addEvaluationGraph(const DRW_EvaluationGraph &data) {
10432 if (m_graphic != nullptr) {
10433 m_graphic->dwgAdvancedMetadata().addEvaluationGraph(data);
10434 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
10435 "EVALUATION_GRAPH", data.handle, data.parentHandle);
10436 }
10437}
10438
10439void RS_FilterDXFRW::addBlockRepresentationData(
10440 const DRW_BlockRepresentationData &data) {
10441 if (m_graphic != nullptr) {
10442 m_graphic->dwgAdvancedMetadata().addBlockRepresentationData(data);
10443 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
10444 "BLOCKREPRESENTATIONDATA", data.handle, data.parentHandle);
10445 }
10446}
10447
10448void RS_FilterDXFRW::addSection(const DRW_Section &data) {
10449 if (m_graphic != nullptr) {
10450 m_graphic->dwgAdvancedMetadata().addSection(data);
10451 m_graphic->dwgAdvancedMetadata().noteFamilyExposure("SECTION", data.handle,
10452 data.parentHandle);
10453 }
10454}
10455
10456void RS_FilterDXFRW::addSectionObject(const DRW_SectionObject &data) {
10457 if (m_graphic != nullptr) {
10458 m_graphic->dwgAdvancedMetadata().addSectionObject(data);
10459 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
10460 "SECTIONOBJECT", data.handle, data.parentHandle);
10461 }
10462}
10463
10464void RS_FilterDXFRW::addMPolygon(const DRW_MPolygon *data) {
10465 // Prefer hatch rendering path for geometry, but also record metadata
10466 // exposure.
10467 if (m_graphic != nullptr && data != nullptr) {
10468 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
10469 "MPOLYGON", data->handle, data->parentHandle);
10470 }
10471 addHatch(data);
10472}
10473
10474void RS_FilterDXFRW::addDictionary(const DRW_Dictionary &data) {
10475 if (m_graphic != nullptr) {
10476 m_graphic->dwgAdvancedMetadata().addDictionary(data);
10477 }
10478 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDictionary: handle %d entries=%d",
10479 static_cast<int>(data.handle),
10480 static_cast<int>(data.m_entries.size()));
10481}
10482
10483void RS_FilterDXFRW::addXRecord(const DRW_XRecord &data) {
10484 if (m_graphic != nullptr) {
10485 m_graphic->dwgAdvancedMetadata().addXRecord(data);
10486 }
10487 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addXRecord: handle %d values=%d",
10488 static_cast<int>(data.handle),
10489 static_cast<int>(data.m_values.size()));
10490}
10491
10492void RS_FilterDXFRW::addLayout(const DRW_Layout &data) {
10493 if (m_graphic != nullptr) {
10494 m_graphic->dwgAdvancedMetadata().addLayout(data);
10495 // PR 11 — populate activeLayoutHandle on first import. AutoCAD uses
10496 // tabOrder=0 for the default-visible paper-space layout; if no layout
10497 // carries tabOrder=0 (rare — most DWGs have one), fall back to the
10498 // first layout added so the UI still has a non-zero handle to display.
10499 // setActiveLayoutHandle bumps the modified flag only on actual change,
10500 // so re-loading the same DWG idempotently leaves the dirty state alone.
10501 if (m_graphic->activeLayoutHandle() == 0u && data.handle != 0u) {
10502 const bool isDefaultTab = data.tabOrder == 0;
10503 const bool isFirstLayout = m_graphic->layouts().size() == 1;
10504 if (isDefaultTab || isFirstLayout) {
10505 m_graphic->setActiveLayoutHandle(data.handle);
10506 }
10507 }
10508 }
10509 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addLayout: handle %d name=%s viewports=%d",
10510 static_cast<int>(data.handle), data.name.c_str(),
10511 static_cast<int>(data.viewportHandles.size()));
10512}
10513
10514// PR 8d.2a — five small no-storage OBJECTS families. All custom-class
10515// (≥ 500); the filter routes each call into round-trip-grade metadata
10516// storage so RS_FilterDXFRW::writeObjects can dispatch the native writer.
10517void RS_FilterDXFRW::addScale(const DRW_Scale &data) {
10518 if (m_graphic != nullptr) {
10519 m_graphic->dwgAdvancedMetadata().addScale(data);
10520 }
10521 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addScale: handle %d name=%s factor=%f",
10522 static_cast<int>(data.handle), data.name.c_str(),
10523 data.scaleFactor());
10524}
10525
10526void RS_FilterDXFRW::addIDBuffer(const DRW_IDBuffer &data) {
10527 if (m_graphic != nullptr) {
10528 m_graphic->dwgAdvancedMetadata().addIDBuffer(data);
10529 }
10530 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addIDBuffer: handle %d ids=%d",
10531 static_cast<int>(data.handle),
10532 static_cast<int>(data.objIds.size()));
10533}
10534
10535void RS_FilterDXFRW::addIndex(const DRW_Index &data) {
10536 if (m_graphic != nullptr) {
10537 m_graphic->dwgAdvancedMetadata().addIndex(data);
10538 }
10539 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addIndex: handle %d ts=%u/%u",
10540 static_cast<int>(data.handle),
10541 static_cast<unsigned>(data.timestamp1),
10542 static_cast<unsigned>(data.timestamp2));
10543}
10544
10545void RS_FilterDXFRW::addLayerIndex(const DRW_LayerIndex &data) {
10546 if (m_graphic != nullptr) {
10547 m_graphic->dwgAdvancedMetadata().addLayerIndex(data);
10548 }
10549 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addLayerIndex: handle %d entries=%d",
10550 static_cast<int>(data.handle),
10551 static_cast<int>(data.entries.size()));
10552}
10553
10554void RS_FilterDXFRW::addSpatialIndex(const DRW_SpatialIndex &data) {
10555 if (m_graphic != nullptr) {
10556 m_graphic->dwgAdvancedMetadata().addSpatialIndex(data);
10557 }
10558 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addSpatialIndex: handle %d ts=%u/%u",
10559 static_cast<int>(data.handle),
10560 static_cast<unsigned>(data.timestamp1),
10561 static_cast<unsigned>(data.timestamp2));
10562}
10563
10564void RS_FilterDXFRW::addDictionaryVar(const DRW_DictionaryVar &data) {
10565 if (m_graphic != nullptr) {
10566 m_graphic->dwgAdvancedMetadata().addDictionaryVar(data);
10567 }
10568 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDictionaryVar: handle %d schema=%d",
10569 static_cast<int>(data.handle), data.m_schema);
10570}
10571
10572// PR 8d.2b — four larger no-storage OBJECTS families. All custom-class
10573// (513-516); filter routes each call into round-trip-grade metadata storage
10574// so RS_FilterDXFRW::writeObjects can dispatch the native writer.
10575void RS_FilterDXFRW::addDictionaryWithDefault(
10576 const DRW_DictionaryWithDefault &data) {
10577 if (m_graphic != nullptr) {
10578 m_graphic->dwgAdvancedMetadata().addDictionaryWithDefault(data);
10579 }
10580 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDictionaryWithDefault: handle %d "
10581 "entries=%d default=%d",
10582 static_cast<int>(data.handle),
10583 static_cast<int>(data.m_entries.size()),
10584 static_cast<int>(data.m_defaultEntryHandle));
10585}
10586
10587void RS_FilterDXFRW::addSortEntsTable(const DRW_SortEntsTable &data) {
10588 if (m_graphic != nullptr) {
10589 m_graphic->dwgAdvancedMetadata().addSortEntsTable(data);
10590 }
10591 RS_DEBUGRS_Debug::instance()->print(
10592 "RS_FilterDXFRW::addSortEntsTable: handle %d entries=%d block=%d",
10593 static_cast<int>(data.handle),
10594 static_cast<int>(data.m_entityHandles.size()),
10595 static_cast<int>(data.m_blockOwnerHandle));
10596}
10597
10598void RS_FilterDXFRW::addFieldList(const DRW_FieldList &data) {
10599 if (m_graphic != nullptr) {
10600 m_graphic->dwgAdvancedMetadata().addFieldList(data);
10601 }
10602 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addFieldList: handle %d fields=%d",
10603 static_cast<int>(data.handle),
10604 static_cast<int>(data.m_fieldHandles.size()));
10605}
10606
10607void RS_FilterDXFRW::addDataTable(const DRW_DataTable &data) {
10608 if (m_graphic != nullptr) {
10609 m_graphic->dwgAdvancedMetadata().addDataTable(data);
10610 }
10611 RS_DEBUGRS_Debug::instance()->print(
10612 "RS_FilterDXFRW::addDataTable: handle %d name=%s cols=%d rows=%d",
10613 static_cast<int>(data.handle), data.tableName.c_str(),
10614 static_cast<int>(data.columnCount), static_cast<int>(data.rowCount));
10615}
10616
10617void RS_FilterDXFRW::addDynamicBlockObject(const DRW_DynamicBlockObject &data) {
10618 if (m_graphic != nullptr) {
10619 m_graphic->dwgAdvancedMetadata().addDynamicBlockObject(data);
10620 }
10621 RS_DEBUGRS_Debug::instance()->print(
10622 "RS_FilterDXFRW::addDynamicBlockObject: handle %d rec=%s kind=%d",
10623 static_cast<int>(data.handle), data.m_recordName.c_str(),
10624 static_cast<int>(data.m_kind));
10625}
10626
10627void RS_FilterDXFRW::addField(const DRW_Field &data) {
10628 if (m_graphic != nullptr) {
10629 m_graphic->dwgAdvancedMetadata().addField(data);
10630 }
10631 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addField: handle %d evaluator=%s code=%s",
10632 static_cast<int>(data.handle), data.m_evaluatorId.c_str(),
10633 data.m_fieldCode.c_str());
10634}
10635
10636void RS_FilterDXFRW::addAssociativeObject(const DRW_AssociativeObject &data) {
10637 // TODO: Reconstruct associative dimension/dynamic-block relationship graphs
10638 // from these shell objects after native consumers exist.
10639 if (m_graphic != nullptr) {
10640 m_graphic->dwgAdvancedMetadata().addAssociativeObject(data);
10641 }
10642 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addAssociativeObject: %s handle %d",
10643 data.m_recordName.empty() ? "(assoc)"
10644 : data.m_recordName.c_str(),
10645 static_cast<int>(data.handle));
10646}
10647
10648void RS_FilterDXFRW::addAcShHistoryObject(const DRW_AcShHistoryObject &data) {
10649 // TODO: Connect ACSH history nodes to 3DSOLID modeler geometry history.
10650 if (m_graphic != nullptr) {
10651 m_graphic->dwgAdvancedMetadata().addAcShObject(data);
10652 }
10653 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addAcShHistoryObject: %s handle %d",
10654 data.m_recordName.empty() ? "(acsh)"
10655 : data.m_recordName.c_str(),
10656 static_cast<int>(data.handle));
10657}
10658
10659/**
10660 * Implementation of the method which links image entities to image files.
10661 */
10662void RS_FilterDXFRW::linkImage(const DRW_ImageDef *data) {
10663 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::linkImage");
10664 if (m_graphic != nullptr && data != nullptr)
10665 m_graphic->dwgAdvancedMetadata().addImageDefinition(*data);
10666
10667 if (data == nullptr)
10668 return;
10669
10670 const int handle = data->handle;
10671 QString sfile(QString::fromUtf8(data->name.c_str()));
10672 const QFileInfo fiDxf(m_file);
10673 const QFileInfo fiBitmap(sfile);
10674
10675 // try to find the image file:
10676
10677 // first: absolute path:
10678 if (!fiBitmap.exists()) {
10679 RS_DEBUGRS_Debug::instance()->print("File %s doesn't exist.",
10680 (const char *)QFile::encodeName(sfile));
10681 // try relative path:
10682 const QString f1 = fiDxf.absolutePath() + "/" + sfile;
10683 if (QFileInfo::exists(f1)) {
10684 sfile = f1;
10685 } else {
10686 RS_DEBUGRS_Debug::instance()->print("File %s doesn't exist.",
10687 (const char *)QFile::encodeName(f1));
10688 // try drawing path:
10689 const QString f2 = fiDxf.absolutePath() + "/" + fiBitmap.fileName();
10690 if (QFileInfo::exists(f2)) {
10691 sfile = f2;
10692 } else {
10693 RS_DEBUGRS_Debug::instance()->print("File %s doesn't exist.",
10694 (const char *)QFile::encodeName(f2));
10695 }
10696 }
10697 }
10698
10699 // Also link images in subcontainers (e.g. inserts):
10700 for (RS_Entity *e :
10701 lc::LC_ContainerTraverser{*m_graphic, RS2::ResolveNone}.entities()) {
10702 if (e->rtti() == RS2::EntityImage) {
10703 const auto img = static_cast<RS_Image *>(e);
10704 if (img->getHandle() == handle) {
10705 img->setFile(sfile);
10706 RS_DEBUGRS_Debug::instance()->print("image found: %s",
10707 (const char *)QFile::encodeName(img->getFile()));
10708 img->update();
10709 }
10710 }
10711 }
10712
10713 // update images in blocks:
10714 for (unsigned i = 0; i < m_graphic->countBlocks(); ++i) {
10715 const RS_Block *b = m_graphic->blockAt(i);
10716 for (RS_Entity *e :
10717 lc::LC_ContainerTraverser{*b, RS2::ResolveNone}.entities()) {
10718 if (e->rtti() == RS2::EntityImage) {
10719 const auto img = static_cast<RS_Image *>(e);
10720 if (img->getHandle() == handle) {
10721 img->setFile(sfile);
10722 RS_DEBUGRS_Debug::instance()->print("image in block found: %s",
10723 (const char *)QFile::encodeName(img->getFile()));
10724 img->update();
10725 }
10726 }
10727 }
10728 }
10729 RS_DEBUGRS_Debug::instance()->print("linking image: OK");
10730}
10731
10732using std::map;
10733/**
10734 * Sets the header variables from the DXF file.
10735 */
10736void RS_FilterDXFRW::addHeader(const DRW_Header *data) {
10737 if (data == nullptr || m_currentContainer == nullptr ||
10738 m_currentContainer->rtti() != RS2::EntityGraphic) {
10739 return;
10740 }
10741 RS_Graphic *container = static_cast<RS_Graphic *>(m_currentContainer);
10742
10743 // initialize units vars if not are present in dxf file - they will be
10744 // overriden if they are present
10745 m_graphic->addVariable("$LUNITS", 2, 70);
10746 m_graphic->addVariable("$LUPREC", 4, 70);
10747 m_graphic->addVariable("$AUNITS", 0, 70);
10748 m_graphic->addVariable("$AUPREC", 4, 70);
10749 // initialize points drawing style vars if not present in dxf file
10750 m_graphic->addVariable("$PDMODE", LC_DEFAULTS_PDMode(0|96), DXF_FORMAT_GC_VarName9);
10751 m_graphic->addVariable("$PDSIZE", LC_DEFAULTS_PDSize(- 2.0), DXF_FORMAT_GC_VarName9);
10752 m_graphic->addVariable("$JOINSTYLE", 1, DXF_FORMAT_GC_JoinStyle280);
10753 m_graphic->addVariable("$ENDCAPS", 1, DXF_FORMAT_GC_Endcaps280);
10754
10755 for (auto it = data->vars.begin(); it != data->vars.end(); ++it) {
10756 QString key = QString::fromStdString((*it).first);
10757 // DWG-read header vars arrive under bare keys (e.g. "LUPREC"), while the
10758 // app (and the DXF path) query them $-prefixed ("$LUPREC"). Normalize
10759 // here so DWG-stored vars are reachable. DXF keys already start with '$'
10760 // (drw_header.cpp), so the guard makes this a no-op for that path and
10761 // prevents double-prefixing ("$$ACADVER").
10762 if (!key.startsWith('$')) {
10763 key.prepend('$');
10764 }
10765 DRW_Variant *var = (*it).second;
10766 if (var == nullptr)
10767 continue;
10768 switch (var->type()) {
10769 case DRW_Variant::COORD:
10770 if (var->content.v != nullptr && std::isfinite(var->content.v->x) &&
10771 std::isfinite(var->content.v->y) &&
10772 std::isfinite(var->content.v->z)) {
10773 container->addVariable(
10774 key,
10775 RS_Vector(var->content.v->x, var->content.v->y, var->content.v->z),
10776 var->code());
10777 }
10778 break;
10779 case DRW_Variant::STRING:
10780 if (var->content.s != nullptr)
10781 container->addVariable(key, QString::fromUtf8(var->content.s->c_str()),
10782 var->code());
10783 break;
10784 case DRW_Variant::INTEGER:
10785 container->addVariable(key, var->content.i, var->code());
10786 break;
10787 case DRW_Variant::DOUBLE:
10788 if (std::isfinite(var->content.d))
10789 container->addVariable(key, var->content.d, var->code());
10790 break;
10791 default:
10792 break;
10793 }
10794 }
10795
10796 for (auto it = data->customVars.begin(); it != data->customVars.end(); ++it) {
10797 QString key = QString::fromStdString(it->first);
10798 const DRW_Variant *var = it->second;
10799 if (var != nullptr && var->type() == DRW_Variant::STRING &&
10800 var->content.s != nullptr) {
10801 container->addCustomProperty(key,
10802 QString::fromUtf8(var->content.s->c_str()));
10803 }
10804 }
10805
10806 m_codePage = m_graphic->getVariableString("$DWGCODEPAGE", "ANSI_1252");
10807 m_textStyle = m_graphic->getVariableString("$TEXTSTYLE", "Standard");
10808 m_dimStyle = m_graphic->getVariableString("$DIMSTYLE", "Standard");
10809
10810 QString acadver = m_graphic->getVariableString("$ACADVER", "");
10811 m_versionStr = acadver;
10812 acadver.replace(QRegularExpression("[a-zA-Z]"), "");
10813 bool ok;
10814 const int parsedVersion = acadver.toInt(&ok);
10815 if (ok) {
10816 m_version = parsedVersion;
10817 } else if (m_version == 0) {
10818 // Pre-R13 DWG may omit $ACADVER; fileImport primes m_version from the
10819 // on-disk magic before entities are read.
10820 m_version = 1021;
10821 }
10822
10823 // detect if dxf lib are a old dxflib or libdxfrw < 0.5.4 (used to correct
10824 // mtext alignment)
10825 m_oldMText = false;
10826 m_isLibDxfRw = false;
10827 m_libDxfRwVersion = 0;
10828#if QT_VERSION((6<<16)|(9<<8)|(0)) >= QT_VERSION_CHECK(5, 15, 0)((5<<16)|(15<<8)|(0))
10829 constexpr auto option = Qt::SkipEmptyParts;
10830#else
10831 auto option = QString::SkipEmptyParts;
10832#endif
10833 QStringList commentList =
10834 QString::fromStdString(data->getComments()).split('\n', option);
10835 for (const auto &commentLine : std::as_const(commentList)) {
10836 QStringList commentWords = commentLine.split(' ', option);
10837 if (0 < commentWords.size()) {
10838 if ("dxflib" == commentWords.at(0)) {
10839 m_oldMText = true;
10840 break;
10841 }
10842 if ("dxfrw" == commentWords.at(0)) {
10843 const QStringList libVersionList =
10844 commentWords.at(1).split('.', option);
10845 if (2 < libVersionList.size()) {
10846 m_isLibDxfRw = true;
10847 m_libDxfRwVersion = LIBDXFRW_VERSION(libVersionList.at(0).toInt(),(((libVersionList.at(0).toInt()) << 16) | ((libVersionList
.at(1).toInt()) << 8) | (libVersionList.at(2).toInt()))
10848 libVersionList.at(1).toInt(),(((libVersionList.at(0).toInt()) << 16) | ((libVersionList
.at(1).toInt()) << 8) | (libVersionList.at(2).toInt()))
10849 libVersionList.at(2).toInt())(((libVersionList.at(0).toInt()) << 16) | ((libVersionList
.at(1).toInt()) << 8) | (libVersionList.at(2).toInt()))
;
10850 if (m_libDxfRwVersion < LIBDXFRW_VERSION(0, 5, 4)(((0) << 16) | ((5) << 8) | (4))) {
10851 m_oldMText = true;
10852 }
10853 }
10854 break;
10855 }
10856 }
10857 }
10858}
10859
10860/**
10861 * Implementation of the method used for RS_Export to communicate
10862 * with this filter.
10863 *
10864 * @param g
10865 * @param file Full path to the DXF file that will be written.
10866 * @param type
10867 */
10868bool RS_FilterDXFRW::fileExport(RS_Graphic &g, const QString &file,
10869 const RS2::FormatType type) {
10870 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFDW::fileExport: exporting file '%s'...",
10871 (const char *)QFile::encodeName(file));
10872 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFDW::fileExport: file type '%d'", type);
10873
10874 this->m_graphic = &g;
10875 m_writeFailed = false;
10876
10877 // check if we can write to that directory:
10878#ifndef Q_OS_WIN
10879
10880 QString path = QFileInfo(file).absolutePath();
10881 if (QFileInfo(path).isWritable() == false) {
10882 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::fileExport: can't write file: "
10883 "no permission");
10884 return false;
10885 }
10886 //
10887#endif
10888
10889#ifdef DWGSUPPORT1
10890 auto dwgVersionForFormat = [](RS2::FormatType format) {
10891 switch (format) {
10892 case RS2::FormatDWG:
10893 return DRW::AC1015;
10894 case RS2::FormatDWG2004:
10895 return DRW::AC1018;
10896 case RS2::FormatDWG2007:
10897 return DRW::AC1021;
10898 case RS2::FormatDWG2010:
10899 return DRW::AC1024;
10900 case RS2::FormatDWG2013:
10901 return DRW::AC1027;
10902 case RS2::FormatDWG2018:
10903 return DRW::AC1032;
10904 default:
10905 return DRW::UNKNOWNV;
10906 }
10907 };
10908 DRW::Version dwgVer = dwgVersionForFormat(type);
10909 if (dwgVer != DRW::UNKNOWNV) {
10910 switch (dwgVer) {
10911 case DRW::AC1015:
10912 m_version = 1015;
10913 break;
10914 case DRW::AC1018:
10915 m_version = 1018;
10916 break;
10917 case DRW::AC1021:
10918 m_version = 1021;
10919 break;
10920 case DRW::AC1024:
10921 m_version = 1024;
10922 break;
10923 case DRW::AC1027:
10924 m_version = 1027;
10925 break;
10926 case DRW::AC1032:
10927 m_version = 1032;
10928 break;
10929 default:
10930 break;
10931 }
10932 m_exactColor = false;
10933 m_lastDwgWriteSkipCounters = {};
10934 m_lastDwgOptionalReferenceDropCount = 0;
10935 // Keep failed filter-side validation from exposing a partial target.
10936 // The low-level writer already removes its own incomplete output, but
10937 // the filter can discover dropped metadata after that writer succeeds.
10938 const QFileInfo outputInfo(file);
10939 QTemporaryFile stagedOutput(outputInfo.absolutePath() + "/." +
10940 outputInfo.fileName() + ".librecad-XXXXXX");
10941 stagedOutput.setAutoRemove(true);
10942 if (!stagedOutput.open()) {
10943 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
10944 "RS_FilterDXFRW: cannot create staged DWG output");
10945 return false;
10946 }
10947 const QString stagedOutputPath = stagedOutput.fileName();
10948 stagedOutput.close();
10949 // QTemporaryFile creates the name eagerly. Remove its empty placeholder
10950 // before dwgRW opens its own transaction: Windows cannot reliably replace
10951 // that still-associated temporary path with MoveFileExW. The unique name
10952 // remains reserved for this process, and the outer staged commit still
10953 // protects the caller's destination from partial output.
10954 if (!stagedOutput.remove()) {
10955 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
10956 "RS_FilterDXFRW: cannot release staged DWG path");
10957 return false;
10958 }
10959 m_dwgW = new dwgRW(QFile::encodeName(stagedOutputPath).constData());
10960 clearDwgWriteIdentity();
10961 m_dwgExportAdmissionPlan = {};
10962 m_dwgFieldWritePlan = {};
10963 m_dwgOpaqueTableRuntimeFailures.clear();
10964 m_dwgDataStorageCommittedKinds.clear();
10965 m_dwgDataStorageCommitReceipts.clear();
10966 m_dwgWriteDataStorageKnownHandles.clear();
10967 if (!registerDwgWriteFixedStructuralIdentity(
10968 DRW::DwgNamedObjectsDictionaryHandle) ||
10969 !registerDwgWriteFixedStructuralIdentity(
10970 DRW::DwgAcadGroupDictionaryHandle)) {
10971 delete m_dwgW;
10972 m_dwgW = nullptr;
10973 return false;
10974 }
10975 if (!prepareDwgDataStorageReplayPlan(dwgVer)) {
10976 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
10977 "RS_FilterDXFRW: invalid DWG DataStorage replay plan");
10978 delete m_dwgW;
10979 m_dwgW = nullptr;
10980 return false;
10981 }
10982 bool duplicateDwgMetadataHandle = false;
10983 // P3 #1: reserve every preserved fixed-type OBJECT + raw-object handle
10984 // BEFORE write() so defineBlock() (which mints user-block handles from
10985 // 0x30) can never mint a handle a later object re-emits -> no duplicate
10986 // object-map entry -> no writeDwgHandles() failure -> no BAD_OPEN whole-
10987 // file abort. Mirrors the DXF reserve pass (this DWG branch previously
10988 // had none); reserving a handle that is ultimately not emitted is
10989 // harmless (next() simply skips it). Validated by the [dwg-write][reserve]
10990 // unit test.
10991 {
10992 const auto &md = g.dwgAdvancedMetadata();
10993 // High-water mark over every preserved handle; remap targets are
10994 // minted above it (and above 0x2F, the writer's minting floor).
10995 std::uint32_t highWater = 0x2F;
10996 for (std::uint32_t handle = 1;
10997 handle <= DRW::DwgPaperSpaceEndBlockEntityHandle; ++handle) {
10998 if (isFixedStructuralDwgHandle(handle))
10999 m_dwgWriteKnownHandles.insert(handle);
11000 }
11001 m_dwgWriteKnownHandles.insert(DRW::DwgNamedObjectsDictionaryHandle);
11002 m_dwgWriteKnownHandles.insert(DRW::DwgAcadGroupDictionaryHandle);
11003 const auto isVersionEffectiveTarget = [dwgVer](const char *kind) {
11004 // These object families are not emitted before their native
11005 // writer gate. They must not validate references in older
11006 // output just because their source handles were reserved.
11007 if (std::strcmp(kind, "SUN") == 0 ||
11008 std::strcmp(kind, "DIMENSIONASSOCIATION") == 0 ||
11009 std::strcmp(kind, "EVALUATIONGRAPH") == 0 ||
11010 std::strcmp(kind, "SECTION") == 0)
11011 return dwgVer >= DRW::AC1021;
11012 if (std::strcmp(kind, "DBCOLOR") == 0)
11013 return dwgVer >= DRW::AC1018;
11014 return true;
11015 };
11016 std::map<std::uint32_t, const char *> metadataHandleOwners;
11017 auto reserveAll = [&](const auto &records, const char *kind,
11018 bool includeInKnownHandles = true,
11019 RS_FilterDXFRW_DwgWriteIdentityRegistry::
11020 DwgWriteSourceKind sourceKind =
11021 RS_FilterDXFRW_DwgWriteIdentityRegistry::
11022 DwgWriteSourceKind::Object) {
11023 for (const auto &r : records)
11024 if (r.handle != 0) {
11025 noteDwgWriteSourceKind(r.handle, sourceKind);
11026 const auto [it, inserted] =
11027 metadataHandleOwners.emplace(r.handle, kind);
11028 if (!inserted) {
11029 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
11030 "RS_FilterDXFRW: duplicate DWG metadata "
11031 "handle 0x%X (%s and %s)",
11032 r.handle, it->second, kind);
11033 duplicateDwgMetadataHandle = true;
11034 }
11035 m_dwgW->reserveHandle(r.handle);
11036 highWater = std::max<std::uint32_t>(highWater, r.handle);
11037 // Reservation protects the allocator even when a
11038 // record is later suppressed. Reference validation
11039 // must use only identities that the current replay
11040 // state permits us to emit.
11041 if constexpr (hasDwgReplayState<std::decay_t<decltype(r)>>::value) {
11042 if (includeInKnownHandles && isVersionEffectiveTarget(kind) &&
11043 r.replayState ==
11044 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed)
11045 m_dwgWriteKnownHandles.insert(r.handle);
11046 }
11047 }
11048 };
11049 // Reservation protects a source handle from allocator collisions,
11050 // but dictionary targets must only include objects that this
11051 // export can actually emit. A cross-version, fixed-handle, or
11052 // malformed raw carrier is reserved below but is not a valid
11053 // dictionary target.
11054 for (const auto &r : md.rawObjects()) {
11055 if (r.handle == 0)
11056 continue;
11057 noteDwgWriteSourceKind(
11058 r.handle, r.isEntity ? RS_FilterDXFRW_DwgWriteIdentityRegistry::
11059 DwgWriteSourceKind::Entity
11060 : RS_FilterDXFRW_DwgWriteIdentityRegistry::
11061 DwgWriteSourceKind::Object);
11062 m_dwgW->reserveHandle(r.handle);
11063 highWater = std::max<std::uint32_t>(highWater, r.handle);
11064 if (!r.isEntity && sameRawObjectEncodingFamily(r.version, dwgVer) &&
11065 !isFixedStructuralDwgHandle(r.handle) &&
11066 LC_DwgAdvancedMetadata::rawReplayBlocker(r) ==
11067 LC_DwgAdvancedMetadata::ReplayBlocker::None &&
11068 !r.hasDataStorage) {
11069 // DataStorage-backed raw objects are admitted only by the
11070 // frozen owner decision in writeDwgClasses()/OBJECTS.
11071 // This early pass only publishes ordinary raw targets;
11072 // every raw handle is reserved above regardless.
11073 m_dwgWriteKnownHandles.insert(r.handle);
11074 }
11075 }
11076 // These table records are emitted by deferred writers or by the
11077 // later OBJECTS pass. Reserve their source identities before
11078 // structural remap targets are minted. They are published as
11079 // reference targets only after their matching writer accepts
11080 // them.
11081 reserveAll(md.views(), "VIEW", false);
11082 reserveAll(md.ucsRecords(), "UCS", false);
11083 reserveAll(md.vports(), "VPORT", false);
11084 reserveAll(md.tables(), "TABLE", false);
11085 reserveAll(md.placeholders(), "PLACEHOLDER");
11086 reserveAll(md.dictionaries(), "DICTIONARY");
11087 reserveAll(md.xrecords(), "XRECORD");
11088 reserveAll(md.groups(), "GROUP");
11089 reserveAll(md.layouts(), "LAYOUT");
11090 reserveAll(md.mlineStyles(), "MLINESTYLE");
11091 reserveAll(md.dictionaryVars(), "DICTIONARYVAR");
11092 reserveAll(md.fieldLists(), "FIELDLIST");
11093 reserveAll(md.fields(), "FIELD");
11094 reserveAll(md.suns(), "SUN");
11095 reserveAll(md.mleaderStyles(), "MLEADERSTYLE");
11096 reserveAll(md.materials(), "MATERIAL");
11097 reserveAll(md.lights(), "LIGHT");
11098 reserveAll(md.lightLists(), "LIGHTLIST");
11099 if (dwgVer >= DRW::AC1021) {
11100 for (const auto &light : md.lights()) {
11101 if (light.handle != 0 &&
11102 light.replayState ==
11103 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
11104 m_dwgWriteLightHandlesPlanned.insert(light.handle);
11105 }
11106 }
11107 }
11108 // DRW_VbaProject is a low-level carrier without ReplayState. Its
11109 // handle must remain reserved, but it is not a typed reference
11110 // target in the current writer contract.
11111 reserveAll(md.vbaProjects(), "VBA_PROJECT", false);
11112 // PLOTSETTINGS is likewise emitted only as an owned custom
11113 // object, never as a typed reference target in this writer.
11114 reserveAll(md.plotSettings(), "PLOTSETTINGS", false);
11115 reserveAll(md.rasterVariables(), "RASTERVARIABLES");
11116 reserveAll(md.wipeoutVariables(), "WIPEOUTVARIABLES");
11117 reserveAll(md.geoData(), "GEODATA");
11118 reserveAll(md.spatialFilters(), "SPATIAL_FILTER");
11119 reserveAll(md.scales(), "SCALE");
11120 reserveAll(md.idBuffers(), "IDBUFFER");
11121 reserveAll(md.layerIndexes(), "LAYER_INDEX");
11122 reserveAll(md.spatialIndexes(), "SPATIAL_INDEX");
11123 reserveAll(md.dictionariesWithDefault(), "DICTIONARYWDFLT");
11124 reserveAll(md.sortEntsTables(), "SORTENTSTABLE");
11125 reserveAll(md.dbColors(), "DBCOLOR");
11126 reserveAll(md.underlayDefinitions(), "UNDERLAYDEFINITION");
11127 reserveAll(md.pointCloudDefinitions(), "POINTCLOUDDEF");
11128 reserveAll(md.navisworksModelDefinitions(), "NAVISWORKSMODELDEF");
11129 reserveAll(md.navisworksModels(), "NAVISWORKSMODEL");
11130 reserveAll(md.pointCloudColorMaps(), "POINTCLOUDCOLORMAP");
11131 reserveAll(md.cameras(), "CAMERA");
11132 reserveAll(md.geoPositionMarkers(), "GEOPOSITIONMARKER");
11133 reserveAll(md.sectionObjects(), "SECTIONOBJECT");
11134 reserveAll(md.tvDeviceProperties(), "TVDEVICEPROPERTIES");
11135 reserveAll(md.backgrounds(), "BACKGROUND", false);
11136 reserveAll(md.renderSettings(), "RENDERSETTINGS", false);
11137 reserveAll(md.visualStyles(), "VISUALSTYLE", false);
11138 reserveAll(md.sunStudies(), "SUNSTUDY", false);
11139 reserveAll(md.dimensionAssociations(), "DIMENSIONASSOCIATION", false);
11140 reserveAll(md.evaluationGraphs(), "EVALUATIONGRAPH", false);
11141 reserveAll(md.sections(), "SECTION", false);
11142 reserveAll(md.viewportEntityHeaders(), "VPEHEADER", true,
11143 RS_FilterDXFRW_DwgWriteIdentityRegistry::DwgWriteSourceKind::
11144 TableRecord);
11145 reserveAll(md.vxControls(), "VXCONTROL");
11146 reserveAll(md.vxTableRecords(), "VXTABLERECORD");
11147 reserveAll(md.motionPaths(), "MOTIONPATH");
11148 reserveAll(md.curvePaths(), "CURVEPATH");
11149 reserveAll(md.pointPaths(), "POINTPATH");
11150 reserveAll(md.objectPtrs(), "OBJECT_PTR");
11151 reserveAll(md.partialViewingIndexes(), "PARTIALVIEWINGINDEX");
11152 reserveAll(md.blockRepresentationData(), "BLOCKREPRESENTATIONDATA");
11153 // P3 #2: structural-collision remap. The writer emits its control
11154 // objects / canonical table records at FIXED low handles (LTYPE
11155 // control 0x05, VIEW control 0x06, UCS control 0x07, ...) that
11156 // AC1021+ sources reuse for ordinary preserved OBJECTS. Reserving
11157 // alone cannot break that tie (both sides want the same handle),
11158 // so colliding typed objects are moved to fresh handles above the
11159 // high-water mark; writeObjects rewrites the object's own handle
11160 // and every typed reference to it. Colliding raw objects are
11161 // blocked in writeObjects instead (verbatim bytes cannot be
11162 // rewritten).
11163 auto remapIfColliding = [&](const auto &records,
11164 bool skipGeneratedDictionaries = false) {
11165 for (const auto &r : records) {
11166 if (r.handle == 0 || !isFixedStructuralDwgHandle(r.handle))
11167 continue;
11168 if (skipGeneratedDictionaries &&
11169 (r.handle == DRW::DwgNamedObjectsDictionaryHandle ||
11170 r.handle == DRW::DwgAcadGroupDictionaryHandle))
11171 continue;
11172 if (m_dwgWriteHandleRemap.count(r.handle) != 0)
11173 continue;
11174 const std::uint32_t target = ++highWater;
11175 m_dwgW->reserveHandle(target);
11176 m_dwgWriteHandleRemap.emplace(r.handle, target);
11177 }
11178 };
11179 remapIfColliding(md.placeholders());
11180 remapIfColliding(md.dictionaries(), true);
11181 remapIfColliding(md.xrecords());
11182 remapIfColliding(md.groups());
11183 remapIfColliding(md.layouts());
11184 remapIfColliding(md.mlineStyles());
11185 remapIfColliding(md.dictionaryVars());
11186 remapIfColliding(md.fieldLists());
11187 remapIfColliding(md.fields());
11188 remapIfColliding(md.suns());
11189 remapIfColliding(md.mleaderStyles());
11190 remapIfColliding(md.materials());
11191 remapIfColliding(md.lights());
11192 remapIfColliding(md.lightLists());
11193 remapIfColliding(md.vbaProjects());
11194 remapIfColliding(md.plotSettings());
11195 remapIfColliding(md.rasterVariables());
11196 remapIfColliding(md.wipeoutVariables());
11197 remapIfColliding(md.geoData());
11198 remapIfColliding(md.spatialFilters());
11199 remapIfColliding(md.scales());
11200 remapIfColliding(md.idBuffers());
11201 remapIfColliding(md.layerIndexes());
11202 remapIfColliding(md.spatialIndexes());
11203 remapIfColliding(md.dictionariesWithDefault());
11204 remapIfColliding(md.sortEntsTables());
11205 remapIfColliding(md.dbColors());
11206 remapIfColliding(md.underlayDefinitions());
11207 remapIfColliding(md.pointCloudDefinitions());
11208 remapIfColliding(md.navisworksModelDefinitions());
11209 remapIfColliding(md.navisworksModels());
11210 remapIfColliding(md.pointCloudColorMaps());
11211 remapIfColliding(md.cameras());
11212 remapIfColliding(md.geoPositionMarkers());
11213 remapIfColliding(md.sectionObjects());
11214 remapIfColliding(md.tvDeviceProperties());
11215 remapIfColliding(md.backgrounds());
11216 remapIfColliding(md.renderSettings());
11217 remapIfColliding(md.visualStyles());
11218 remapIfColliding(md.sunStudies());
11219 remapIfColliding(md.dimensionAssociations());
11220 remapIfColliding(md.evaluationGraphs());
11221 remapIfColliding(md.sections());
11222 remapIfColliding(md.tables());
11223 remapIfColliding(md.viewportEntityHeaders());
11224 remapIfColliding(md.vxControls());
11225 remapIfColliding(md.vxTableRecords());
11226 remapIfColliding(md.motionPaths());
11227 remapIfColliding(md.curvePaths());
11228 remapIfColliding(md.pointPaths());
11229 remapIfColliding(md.objectPtrs());
11230 remapIfColliding(md.partialViewingIndexes());
11231 remapIfColliding(md.blockRepresentationData());
11232 }
11233 if (duplicateDwgMetadataHandle) {
11234 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
11235 "RS_FilterDXFRW: refusing DWG export with duplicate "
11236 "typed metadata identities");
11237 delete m_dwgW;
11238 m_dwgW = nullptr;
11239 return false;
11240 }
11241 bool success = m_dwgW->write(this, dwgVer, false) && !m_writeFailed;
11242 m_lastDwgWriteSkipCounters = m_dwgW->getWriteSkipCounters();
11243 m_lastDwgOptionalReferenceDropCount = dwgWriteOptionalReferenceDropCount();
11244 if (!success) {
11245 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
11246 "RS_FilterDXFRW: DWG low-level write failed (error=%d, "
11247 "filterFailed=%d, skips=%zu)",
11248 static_cast<int>(m_dwgW->getError()),
11249 m_writeFailed ? 1 : 0,
11250 m_lastDwgWriteSkipCounters.total());
11251 }
11252 // Geometry / typed-object / class-registration skips mean the save is
11253 // incomplete. Raw same-version replay may still skip cross-family
11254 // blobs; those are counted but do not alone fail the save.
11255 const auto &skips = m_lastDwgWriteSkipCounters;
11256 const std::size_t hardSkips = skips.entityWrites + skips.objectWrites +
11257 skips.classRegistrations +
11258 skips.blockDefinitions;
11259 if (success && hardSkips > 0) {
11260 success = false;
11261 m_writeFailed = true;
11262 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
11263 "RS_FilterDXFRW: DWG write dropped %zu typed item(s) "
11264 "(entities=%zu objects=%zu classes=%zu blocks=%zu; "
11265 "raw skips=%zu)",
11266 hardSkips, skips.entityWrites, skips.objectWrites,
11267 skips.classRegistrations, skips.blockDefinitions,
11268 skips.rawObjectWrites + skips.rawSectionWrites);
11269 }
11270 delete m_dwgW;
11271 m_dwgW = nullptr;
11272 if (!success)
11273 return false;
11274
11275 // QFile::rename() does not replace an existing destination on every
11276 // supported platform. Preserve the old file while committing the
11277 // completed staged output, and restore it if the commit fails.
11278 const auto commitStagedOutput = [&]() {
11279 if (!QFileInfo::exists(file))
11280 return QFile::rename(stagedOutputPath, file);
11281
11282 QTemporaryFile backup(outputInfo.absolutePath() + "/." +
11283 outputInfo.fileName() + ".librecad-backup-XXXXXX");
11284 backup.setAutoRemove(false);
11285 if (!backup.open())
11286 return false;
11287 const QString backupPath = backup.fileName();
11288 backup.close();
11289 if (!QFile::remove(backupPath) || !QFile::rename(file, backupPath)) {
11290 QFile::remove(backupPath);
11291 return false;
11292 }
11293 if (QFile::rename(stagedOutputPath, file)) {
11294 QFile::remove(backupPath);
11295 return true;
11296 }
11297 (void)QFile::rename(backupPath, file);
11298 return false;
11299 };
11300 if (!commitStagedOutput()) {
11301 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
11302 "RS_FilterDXFRW: cannot commit staged DWG output");
11303 m_writeFailed = true;
11304 return false;
11305 }
11306 return true;
11307 }
11308#endif
11309
11310 // set version for DXF filter:
11311 m_exactColor = false;
11312 DRW::Version exportVersion;
11313 if (type == RS2::FormatDXFRW12) {
11314 exportVersion = DRW::AC1009;
11315 m_version = 1009;
11316 } else if (type == RS2::FormatDXFRW14) {
11317 exportVersion = DRW::AC1014;
11318 m_version = 1014;
11319 } else if (type == RS2::FormatDXFRW2000) {
11320 exportVersion = DRW::AC1015;
11321 m_version = 1015;
11322 } else if (type == RS2::FormatDXFRW2004) {
11323 exportVersion = DRW::AC1018;
11324 m_version = 1018;
11325 m_exactColor = true;
11326 } else if (type == RS2::FormatDXFRW) {
11327 exportVersion = DRW::AC1021;
11328 m_version = 1021;
11329 m_exactColor = true;
11330 } else {
11331 exportVersion = DRW::AC1032;
11332 m_version = 1032;
11333 m_exactColor = true;
11334 }
11335 /**
11336 * fixme - sand - files - RESTORE!!! Under win, encodeName() prevents using
11337 * unicode file names!!! Due to that, blocks/files may be saved incorrectly if
11338 * name is localized
11339 */
11340 m_dxfW = new dxfRW(QFile::encodeName(file));
11341 m_dxfBlockInsertHandles.clear();
11342 // fixme - sand - save to binary format enabling/disabling!!
11343 const bool binary = false;
11344
11345 // Before write(): (1) reserve every verbatim code-5 handle preserved in the
11346 // raw-passthrough net (rawDxfObjects/rawDxfEntities re-emit their original
11347 // handle) in the codec's HandleAllocator so a minted handle can never
11348 // collide with a preserved raw handle, AND a preserved raw handle that lands
11349 // in the codec's fixed-low band (0x10-0x25) is skipped by next() too — this
11350 // is the DWG-path reserve-and-preserve mechanism (dwgWriter::reserveHandle),
11351 // and (2) register a DXF CLASS record for each custom-class object present in
11352 // the raw net so AutoCAD/ODA accept those instances (entry + instance
11353 // co-emit).
11354 {
11355 const auto &metadata = g.dwgAdvancedMetadata();
11356 std::vector<DRW_Class> classes;
11357 std::map<std::string, std::size_t> classIdx;
11358 std::map<std::string, DRW_Class> sourceClasses;
11359 std::vector<DRW_Class> emptyRecordNameClasses;
11360 std::set<std::string> conflictingSourceClasses;
11361 for (const DRW_Class &cls : metadata.dxfClasses()) {
11362 if (cls.recName.empty()) {
11363 DRW_Class preserved = cls;
11364 preserved.instanceCount = 0;
11365 emptyRecordNameClasses.push_back(std::move(preserved));
11366 continue;
11367 }
11368 const std::string normalizedName = normalizeDwgTableName(cls.recName);
11369 const auto [it, inserted] = sourceClasses.emplace(normalizedName, cls);
11370 if (!inserted && !sameDxfClassDefinition(it->second, cls))
11371 conflictingSourceClasses.insert(normalizedName);
11372 }
11373 bool classPlanningFailed = !conflictingSourceClasses.empty();
11374 for (const auto &entry : sourceClasses) {
11375 DRW_Class preserved = entry.second;
11376 preserved.instanceCount = 0;
11377 classIdx.emplace(entry.first, classes.size());
11378 classes.push_back(std::move(preserved));
11379 }
11380 for (DRW_Class &cls : emptyRecordNameClasses)
11381 classes.push_back(std::move(cls));
11382 auto registerClassFor = [&](const std::string &recordName) {
11383 DRW_Class cls;
11384 const std::string normalizedName = normalizeDwgTableName(recordName);
11385 if (conflictingSourceClasses.count(normalizedName) != 0) {
11386 classPlanningFailed = true;
11387 return;
11388 }
11389 auto sourceIt = sourceClasses.find(normalizedName);
11390 if (sourceIt != sourceClasses.end()) {
11391 cls = sourceIt->second;
11392 } else if (!dxfRW::dxfClassForRecordName(recordName, cls) &&
11393 !dxfRW::dxfClassForRecordName(normalizedName, cls)) {
11394 return;
11395 }
11396 auto it = classIdx.find(normalizedName);
11397 if (it == classIdx.end()) {
11398 cls.instanceCount = 1;
11399 classIdx.emplace(normalizedName, classes.size());
11400 classes.push_back(cls);
11401 } else {
11402 ++classes[it->second].instanceCount;
11403 }
11404 };
11405 std::set<std::uint32_t> rawObjectHandles;
11406 for (const DRW_RawDxfObject &o : metadata.rawDxfObjects()) {
11407 if (o.handle != 0) {
11408 m_dxfW->reserveHandle(o.handle);
11409 rawObjectHandles.insert(o.handle);
11410 }
11411 registerClassFor(o.name);
11412 }
11413 for (const DRW_RawDxfObject &e : metadata.rawDxfEntities()) {
11414 if (e.handle != 0)
11415 m_dxfW->reserveHandle(e.handle);
11416 registerClassFor(e.name); // custom ENTITIES need a CLASS too
11417 }
11418
11419 std::set<std::uint32_t> plotSettingsHandles;
11420 bool plotSettingsPlanningFailed = false;
11421 for (const DRW_PlotSettings &settings : metadata.plotSettings()) {
11422 if (settings.handle == 0)
11423 continue;
11424 if (!plotSettingsHandles.insert(settings.handle).second ||
11425 !m_dxfW->reserveHandle(settings.handle)) {
11426 plotSettingsPlanningFailed = true;
11427 break;
11428 }
11429 }
11430
11431 if (classPlanningFailed || plotSettingsPlanningFailed) {
11432 RS_DEBUGRS_Debug::instance()->print(
11433 RS_Debug::D_WARNING,
11434 classPlanningFailed
11435 ? "RS_FilterDXFRW: refusing DXF export with conflicting "
11436 "case-insensitive CLASS metadata"
11437 : "RS_FilterDXFRW: refusing DXF export with duplicate "
11438 "typed PLOTSETTINGS handles");
11439 delete m_dxfW;
11440 m_dxfW = nullptr;
11441 return false;
11442 }
11443
11444 auto rawSectionHandle = [](const DRW_Variant &group,
11445 std::uint32_t &handle) {
11446 if ((group.code() != 5 && group.code() != 105) ||
11447 group.type() != DRW_Variant::STRING || group.c_str() == nullptr)
11448 return false;
11449 const char *text = group.c_str();
11450 char *end = nullptr;
11451 errno(*__errno_location ()) = 0;
11452 const unsigned long value = std::strtoul(text, &end, 16);
11453 if (errno(*__errno_location ()) == ERANGE34 || end == text || *end != '\0' ||
11454 value > std::numeric_limits<std::uint32_t>::max())
11455 return false;
11456 handle = static_cast<std::uint32_t>(value);
11457 return true;
11458 };
11459 for (const DRW_RawDxfSection &section : metadata.rawDxfSections()) {
11460 for (const DRW_Variant &group : section.m_groups) {
11461 std::uint32_t handle = 0;
11462 if (rawSectionHandle(group, handle) && handle != 0)
11463 m_dxfW->reserveHandle(handle);
11464 }
11465 }
11466
11467 // F4: the routed data-only OBJECTS (SUN/SCALE/DICTIONARYVAR/
11468 // RASTERVARIABLES) are typed-emitted on DWG->DXF (the DWG reader does NOT
11469 // put them in the raw net — see writeObjects). For each such record that
11470 // will be typed-emitted (ReplayAllowed, nonzero handle, NOT already in the
11471 // raw net → not double-counted), reserve its verbatim code-5 handle so a
11472 // minted handle can't collide, and register the same CLASS the raw-net
11473 // objects use (else AutoCAD/ezdxf drop the instance). The dedup predicate
11474 // here MUST match writeObjects' emitTyped.
11475 auto reserveTyped = [&](std::uint32_t handle,
11476 LC_DwgAdvancedMetadata::ReplayState state,
11477 const char *recordName) {
11478 if (handle == 0 ||
11479 state != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
11480 rawObjectHandles.count(handle) != 0)
11481 return;
11482 m_dxfW->reserveHandle(handle);
11483 registerClassFor(recordName);
11484 };
11485 for (const auto &record : metadata.suns())
11486 reserveTyped(record.handle, record.replayState, "SUN");
11487 for (const auto &record : metadata.scales())
11488 reserveTyped(record.handle, record.replayState, "SCALE");
11489 for (const auto &record : metadata.dictionaryVars())
11490 reserveTyped(record.handle, record.replayState, "DICTIONARYVAR");
11491 for (const auto &record : metadata.rasterVariables())
11492 reserveTyped(record.handle, record.replayState, "RASTERVARIABLES");
11493 for (const auto &record : metadata.mleaderStyles())
11494 reserveTyped(record.handle, record.replayState, "MLEADERSTYLE");
11495 for (const auto &record : metadata.geoData())
11496 reserveTyped(record.handle, record.replayState, "GEODATA");
11497 for (const auto &record : metadata.spatialFilters())
11498 reserveTyped(record.handle, record.replayState, "SPATIAL_FILTER");
11499 for (const auto &record : metadata.sortEntsTables())
11500 reserveTyped(record.handle, record.replayState, "SORTENTSTABLE");
11501 for (const auto &record : metadata.fields())
11502 reserveTyped(record.handle, record.replayState, "FIELD");
11503 for (const auto &record : metadata.partialViewingIndexes())
11504 reserveTyped(record.handle, record.replayState, "PARTIALVIEWINGINDEX");
11505 for (const auto &record : metadata.fieldLists())
11506 reserveTyped(record.handle, record.replayState, "FIELDLIST");
11507 // SLICE 2: WIPEOUTVARIABLES is a CUSTOM class -> reserve + register CLASS.
11508 for (const auto &record : metadata.wipeoutVariables())
11509 reserveTyped(record.handle, record.replayState, "WIPEOUTVARIABLES");
11510 for (const auto &record : metadata.pointCloudDefinitions())
11511 reserveTyped(record.handle, record.replayState,
11512 pointCloudDefinitionRecordName(record.kind));
11513 for (const auto &record : metadata.navisworksModelDefinitions())
11514 reserveTyped(record.handle, record.replayState, "NAVISWORKSMODELDEF");
11515 if (exportVersion >= DRW::AC1015) {
11516 for (const auto &record : metadata.navisworksModels())
11517 reserveTyped(record.handle, record.replayState, "NAVISWORKSMODEL");
11518 }
11519 for (const auto &record : metadata.pointCloudColorMaps())
11520 reserveTyped(record.handle, record.replayState, "POINTCLOUDCOLORMAP");
11521 for (const auto &record : metadata.backgrounds())
11522 reserveTyped(record.handle, record.replayState,
11523 backgroundRecordName(record.kind));
11524 for (const auto &record : metadata.materials())
11525 reserveTyped(record.handle, record.replayState, "MATERIAL");
11526 for (const auto &record : metadata.renderSettings())
11527 reserveTyped(record.handle, record.replayState,
11528 renderSettingsRecordName(record.kind));
11529 for (const auto &record : metadata.sunStudies())
11530 reserveTyped(record.handle, record.replayState, "SUNSTUDY");
11531 for (const auto &record : metadata.motionPaths())
11532 reserveTyped(record.handle, record.replayState, "MOTIONPATH");
11533 for (const auto &record : metadata.curvePaths())
11534 reserveTyped(record.handle, record.replayState, "ACDBCURVEPATH");
11535 for (const auto &record : metadata.pointPaths())
11536 reserveTyped(record.handle, record.replayState, "ACDBPOINTPATH");
11537 for (const auto &record : metadata.objectPtrs())
11538 reserveTyped(record.handle, record.replayState, "OBJECTPTR");
11539 for (const auto &record : metadata.dbColors())
11540 reserveTyped(record.handle, record.replayState, "DBCOLOR");
11541 for (const auto &record : metadata.dimensionAssociations())
11542 reserveTyped(record.handle, record.replayState, "DIMASSOC");
11543 for (const auto &record : metadata.evaluationGraphs())
11544 reserveTyped(record.handle, record.replayState, "EVALUATION_GRAPH");
11545 for (const auto &record : metadata.sections())
11546 reserveTyped(record.handle, record.replayState,
11547 sectionRecordName(record.kind));
11548 for (const auto &record : metadata.cameras()) {
11549 if (record.handle != 0 &&
11550 record.replayState ==
11551 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed)
11552 m_dxfW->reserveHandle(record.handle);
11553 }
11554 // SLICE 1: fixed built-ins -> reserve handles but register NO CLASS.
11555 auto reserveFixedTyped = [&](std::uint32_t handle,
11556 LC_DwgAdvancedMetadata::ReplayState state) {
11557 if (handle == 0 ||
11558 state != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
11559 rawObjectHandles.count(handle) != 0)
11560 return;
11561 m_dxfW->reserveHandle(handle);
11562 };
11563 for (const auto &record : metadata.mlineStyles())
11564 reserveFixedTyped(record.handle, record.replayState);
11565 for (const auto &record : metadata.layouts())
11566 reserveFixedTyped(record.handle, record.replayState);
11567 for (const auto &record : metadata.geoPositionMarkers())
11568 reserveFixedTyped(record.handle, record.replayState);
11569 for (const auto &record : metadata.sectionObjects())
11570 reserveFixedTyped(record.handle, record.replayState);
11571
11572 if (exportVersion <= DRW::AC1009 && !classes.empty()) {
11573 RS_DEBUGRS_Debug::instance()->print(
11574 RS_Debug::D_WARNING,
11575 "RS_FilterDXFRW: cannot preserve DXF CLASS records in R12 output");
11576 delete m_dxfW;
11577 m_dxfW = nullptr;
11578 return false;
11579 }
11580 if (!classes.empty())
11581 m_dxfW->setDxfClasses(classes);
11582
11583 // (3) Re-attach raw-net-routed named dictionaries to the codec's
11584 // regenerated root NamedObjectsDictionary (handle C). The child dict NAME
11585 // is not on the child object — it lives in the SOURCE root dict's entry
11586 // list (the typed DictionaryRecord with parentHandle==0). Build the
11587 // (name, verbatim-handle) entries for the codec, and a suppression set so
11588 // the source ACAD_GROUP dict (codec regenerates it at D) and any C/D
11589 // collisions are not re-emitted. The source root dict itself is never in
11590 // the raw net (processDictionary skips parentHandle==0).
11591 m_dxfSuppressedObjectHandles.clear();
11592 m_dxfEmittedNamedDictHandles.clear();
11593 std::uint32_t sourceRootHandle = 0;
11594 std::uint32_t acadGroupHandle = 0;
11595 std::map<std::uint32_t, std::string> rootEntryName; // child handle -> name
11596 for (const auto &d : metadata.dictionaries()) {
11597 if (d.parentHandle != 0)
11598 continue;
11599 sourceRootHandle = d.handle;
11600 for (const auto &e : d.entries) {
11601 if (e.name == "ACAD_GROUP")
11602 acadGroupHandle = e.handle;
11603 else
11604 rootEntryName.emplace(e.handle, e.name);
11605 }
11606 }
11607 if (acadGroupHandle != 0)
11608 m_dxfSuppressedObjectHandles.insert(acadGroupHandle);
11609
11610 // (3a) Structural-collision remap. The codec emits its own table/block
11611 // records and root dictionaries at FIXED low handles (table heads, LAYER
11612 // 0x10, APPID 0x12, LTYPE 0x14-0x16, BLOCK_RECORD/BLOCK/ENDBLK 0x1C-0x21,
11613 // root C / ACAD_GROUP D). Real source files reuse those very handles for
11614 // unrelated OBJECTS (extension dicts, material dicts, xrecords) preserved
11615 // verbatim in the raw net — an unavoidable raw-vs-fixed-structural
11616 // collision the reserve-and-preserve mechanism alone cannot break (both
11617 // want the same handle). The fixed structural set is non-negotiable (the
11618 // codec's 330/350 cross-refs are literals), so the colliding raw objects
11619 // are remapped to fresh allocator handles; writeRawDxfObject rewrites the
11620 // object's own handle AND every reference to a remapped handle, keeping
11621 // the raw subtree internally consistent. 0xC/0xD raw dicts are suppressed
11622 // (the codec regenerates root/group) and so are NOT remapped.
11623 static const std::set<std::uint32_t> kFixedStructural = {
11624 0x1, 0x2, 0x3, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0x10,
11625 0x12, 0x14, 0x15, 0x16, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21};
11626 for (const DRW_PlotSettings &settings : metadata.plotSettings()) {
11627 if (settings.handle != 0 &&
11628 kFixedStructural.count(settings.handle) != 0) {
11629 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
11630 "RS_FilterDXFRW: refusing DXF export with PLOTSETTINGS "
11631 "handle colliding with a structural record");
11632 delete m_dxfW;
11633 m_dxfW = nullptr;
11634 return false;
11635 }
11636 }
11637 std::map<std::uint32_t, std::uint32_t> handleRemap;
11638 bool remapFailed = false;
11639 auto remapIfColliding = [&](std::uint32_t h) {
11640 if (h == 0 || kFixedStructural.count(h) == 0)
11641 return;
11642 if (h == 0xCu || h == 0xDu || m_dxfSuppressedObjectHandles.count(h) != 0)
11643 return; // suppressed, not emitted -> nothing to remap
11644 if (remapFailed || handleRemap.count(h) != 0)
11645 return;
11646 const std::uint32_t replacement = m_dxfW->allocHandle();
11647 // allocHandle() runs before dxfRW::write(), whose new-session reset
11648 // clears generated handles. Keep the remap explicit so that reset
11649 // cannot reissue this handle to a structural record.
11650 if (replacement == 0 || !m_dxfW->reserveHandle(replacement)) {
11651 remapFailed = true;
11652 return;
11653 }
11654 handleRemap.emplace(h, replacement);
11655 };
11656 for (const DRW_RawDxfObject &o : metadata.rawDxfObjects())
11657 remapIfColliding(o.handle);
11658 for (const DRW_RawDxfObject &e : metadata.rawDxfEntities())
11659 remapIfColliding(e.handle);
11660 for (const DRW_RawDxfSection &section : metadata.rawDxfSections()) {
11661 for (const DRW_Variant &group : section.m_groups) {
11662 std::uint32_t handle = 0;
11663 if (rawSectionHandle(group, handle))
11664 remapIfColliding(handle);
11665 }
11666 }
11667 if (remapFailed) {
11668 RS_DEBUGRS_Debug::instance()->print(
11669 RS_Debug::D_WARNING,
11670 "RS_FilterDXFRW: DXF handle space exhausted while remapping");
11671 delete m_dxfW;
11672 m_dxfW = nullptr;
11673 return false;
11674 }
11675 if (!metadata.rawDxfSections().empty())
11676 m_dxfW->setRawDxfSections(metadata.rawDxfSections());
11677 // setHandleRemap is pushed AFTER the named-dict block below so any remap
11678 // a colliding named-dict handle adds is included in the codec's table.
11679
11680 std::vector<std::pair<std::string, std::string>> rootEntries;
11681 for (const DRW_RawDxfObject &o : metadata.rawDxfObjects()) {
11682 if (o.name != "DICTIONARY" && o.name != "ACDBDICTIONARYWDFLT")
11683 continue;
11684 if (o.handle == 0xCu || o.handle == 0xDu) { // collide with fixed C/D
11685 m_dxfSuppressedObjectHandles.insert(o.handle);
11686 continue;
11687 }
11688 if (o.handle == acadGroupHandle)
11689 continue; // already suppressed
11690 if (o.parentHandle != sourceRootHandle)
11691 continue; // only direct root children get a root entry
11692 auto it = rootEntryName.find(o.handle);
11693 if (it == rootEntryName.end())
11694 continue; // name not recoverable -> cannot re-attach
11695 std::string handleStr; // code-5; matches the (possibly remapped) re-emit
11696 for (const DRW_Variant &g : o.groups)
11697 if (g.code() == 5) {
11698 handleStr = g.c_str();
11699 break;
11700 }
11701 // If this child dict's handle was remapped (structural collision), the
11702 // root entry must point at the NEW handle so the 350 ref resolves.
11703 auto rm = handleRemap.find(o.handle);
11704 if (rm != handleRemap.end())
11705 handleStr = m_dxfW->toHexStrHandle(rm->second);
11706 if (!handleStr.empty())
11707 rootEntries.emplace_back(it->second, handleStr);
11708 }
11709
11710 // (3b) F4-followup: DWG->DXF parent dictionaries for the data-only
11711 // OBJECTS. On the DWG path the only INVALID_OWNER_HANDLE fixes ezdxf
11712 // applies are typed OBJECTS (SUN / SCALE / DICTIONARYVAR /
11713 // RASTERVARIABLES / LAYOUT / ...) whose 330 owner is a named dictionary
11714 // that LibreCAD does not regenerate (e.g. ACAD_DICTIONARYVAR @0x70
11715 // owning the DICTIONARYVARs). Emit EXACTLY those parent dictionaries as
11716 // real, C-owned OBJECTS so the children resolve to a valid owner; keep ONLY
11717 // the entries that target an object we actually emit (the data-only
11718 // children) so the dict introduces no new dangling 350. We deliberately do
11719 // NOT emit the dictionaries whose children are unmodeled objects LibreCAD
11720 // drops: emitting those would replace one missing-owner fix with many
11721 // dangling-entry fixes and corrupt cross-refs.
11722 //
11723 // The set of objects we emit on this path (their code-5 handles):
11724 // the ReplayAllowed, non-raw-net data-only records (matches writeObjects'
11725 // emitTyped), plus the raw-net objects (re-emitted verbatim above).
11726 std::set<std::uint32_t> emittedObjectHandles = rawObjectHandles;
11727 std::set<std::uint32_t> neededParents; // 330 owners we must materialize
11728 auto noteDataOnly = [&](std::uint32_t handle, std::uint32_t parent,
11729 LC_DwgAdvancedMetadata::ReplayState state) {
11730 if (handle == 0 ||
11731 state != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
11732 rawObjectHandles.count(handle) != 0)
11733 return; // not emitted on this path
11734 emittedObjectHandles.insert(handle);
11735 if (parent != 0)
11736 neededParents.insert(parent);
11737 };
11738 for (const auto &r : metadata.suns())
11739 noteDataOnly(r.handle, r.parentHandle, r.replayState);
11740 for (const auto &r : metadata.scales())
11741 noteDataOnly(r.handle, r.parentHandle, r.replayState);
11742 for (const auto &r : metadata.dictionaryVars())
11743 noteDataOnly(r.handle, r.parentHandle, r.replayState);
11744 for (const auto &r : metadata.rasterVariables())
11745 noteDataOnly(r.handle, r.parentHandle, r.replayState);
11746 for (const auto &r : metadata.mleaderStyles())
11747 noteDataOnly(r.handle, r.parentHandle, r.replayState);
11748 for (const auto &r : metadata.geoData())
11749 noteDataOnly(r.handle, r.parentHandle, r.replayState);
11750 for (const auto &r : metadata.spatialFilters())
11751 noteDataOnly(r.handle, r.parentHandle, r.replayState);
11752 for (const auto &r : metadata.sortEntsTables())
11753 noteDataOnly(r.handle, r.parentHandle, r.replayState);
11754 for (const auto &r : metadata.fields())
11755 noteDataOnly(r.handle, r.parentHandle, r.replayState);
11756 for (const auto &r : metadata.fieldLists())
11757 noteDataOnly(r.handle, r.parentHandle, r.replayState);
11758 for (const auto &r : metadata.mlineStyles())
11759 noteDataOnly(r.handle, r.parentHandle, r.replayState);
11760 for (const auto &r : metadata.layouts())
11761 noteDataOnly(r.handle, r.parentHandle, r.replayState);
11762 for (const auto &r : metadata.wipeoutVariables())
11763 noteDataOnly(r.handle, r.parentHandle, r.replayState);
11764 for (const auto &r : metadata.pointCloudDefinitions())
11765 noteDataOnly(r.handle, r.parentHandle, r.replayState);
11766 for (const auto &r : metadata.navisworksModelDefinitions())
11767 noteDataOnly(r.handle, r.parentHandle, r.replayState);
11768 for (const auto &r : metadata.pointCloudColorMaps())
11769 noteDataOnly(r.handle, r.parentHandle, r.replayState);
11770 for (const DRW_PlotSettings &settings : metadata.plotSettings()) {
11771 if (settings.handle != 0) {
11772 emittedObjectHandles.insert(settings.handle);
11773 if (settings.parentHandle != 0)
11774 neededParents.insert(
11775 static_cast<std::uint32_t>(settings.parentHandle));
11776 }
11777 }
11778
11779 std::vector<DRW_Dictionary> namedDicts;
11780 std::set<std::uint32_t> emittedDictHandles;
11781 for (const auto &d : metadata.dictionaries()) {
11782 if (d.parentHandle == 0)
11783 continue; // source root: harvested for names only
11784 if (d.handle == 0 || d.handle == 0xCu || d.handle == 0xDu)
11785 continue;
11786 if (d.handle == acadGroupHandle)
11787 continue;
11788 if (m_dxfSuppressedObjectHandles.count(d.handle) != 0)
11789 continue;
11790 if (rawObjectHandles.count(d.handle) != 0)
11791 continue; // DXF-read dict already round-tripped via the raw net
11792 if (neededParents.count(d.handle) == 0)
11793 continue; // not a parent of any object we emit -> skip
11794 if (emittedDictHandles.count(d.handle) != 0)
11795 continue; // de-dup (dictionaries() may list a handle once, guard
11796 // anyway)
11797
11798 // Reserve + structural-collision remap, mirroring the raw path so a
11799 // dict handle landing on a fixed literal is moved.
11800 m_dxfW->reserveHandle(d.handle);
11801 remapIfColliding(d.handle);
11802 auto remapOf = [&](std::uint32_t h) {
11803 auto rm = handleRemap.find(h);
11804 return rm != handleRemap.end() ? rm->second : h;
11805 };
11806 DRW_Dictionary dict = dictionaryFromMetadata(d);
11807 dict.handle = remapOf(d.handle);
11808 // Owner: re-point a child of the source root at C (handle 0 ->
11809 // writeObjectOwner emits "C"). If the explicit parent is itself an
11810 // emitted dict, keep it (remapped); otherwise fall back to C so the
11811 // dict is never itself a dangling-owner object.
11812 std::uint32_t parent = static_cast<std::uint32_t>(d.parentHandle);
11813 if (parent == sourceRootHandle || neededParents.count(parent) == 0)
11814 dict.parentHandle = 0;
11815 else
11816 dict.parentHandle = static_cast<int>(remapOf(parent));
11817 // Keep ONLY entries whose target is an object we actually emit, so the
11818 // dict adds no dangling 350.
11819 std::vector<DRW_Dictionary::Entry> keptEntries;
11820 keptEntries.reserve(dict.m_entries.size());
11821 for (auto &entry : dict.m_entries) {
11822 if (emittedObjectHandles.count(entry.m_handle) == 0)
11823 continue;
11824 entry.m_handle = remapOf(entry.m_handle);
11825 keptEntries.push_back(entry);
11826 }
11827 dict.m_entries = std::move(keptEntries);
11828 namedDicts.push_back(std::move(dict));
11829 emittedDictHandles.insert(d.handle);
11830 m_dxfEmittedNamedDictHandles.insert(d.handle);
11831
11832 // Re-attach under root C so the dict object is reachable. Use the
11833 // harvested name if known, else the source dict name, else a synthetic.
11834 auto nameIt = rootEntryName.find(d.handle);
11835 std::string entryName = nameIt != rootEntryName.end()
11836 ? nameIt->second
11837 : (!d.name.empty() ? d.name : std::string());
11838 if (!entryName.empty())
11839 rootEntries.emplace_back(entryName,
11840 m_dxfW->toHexStrHandle(remapOf(d.handle)));
11841 }
11842 if (!namedDicts.empty())
11843 m_dxfW->setNamedDictObjects(namedDicts);
11844
11845 // (4) F3: typed GROUP emit (both directions). GROUP is read into typed
11846 // metadata only (processGroup -> addGroup; never routed to the DXF raw
11847 // net — confirmed against the DXF reader's processGroup which makes no
11848 // addRawDxfObject call), so there is no raw-net dedup to do here. The
11849 // codec mints each group's handle, augments the ACAD_GROUP D dict, and
11850 // resolves member entityHandles (SOURCE handles) through the source->minted
11851 // map captured during writeEntity. The source ACAD_GROUP dict is already
11852 // suppressed above (acadGroupHandle); the codec regenerates D.
11853 //
11854 // Group NAMES live in the ACAD_GROUP dict's entries (groupName ->
11855 // groupHandle), not on the GROUP record; harvest them. Unnamed groups
11856 // (isUnnamed) carry a generated "*An" name AutoCAD assigns; synthesize a
11857 // stable "*A<n>" when no name is recoverable so the D-dict entry + GROUP
11858 // are still well-formed.
11859 std::map<std::uint32_t, std::string> groupHandleToName;
11860 for (const auto &d : metadata.dictionaries()) {
11861 if (d.handle != acadGroupHandle || acadGroupHandle == 0)
11862 continue;
11863 for (const auto &e : d.entries)
11864 groupHandleToName.emplace(e.handle, e.name);
11865 }
11866 std::vector<DRW_Group> groupsToWrite;
11867 groupsToWrite.reserve(metadata.groups().size());
11868 int unnamedSeq = 0;
11869 for (const auto &record : metadata.groups()) {
11870 if (record.replayState !=
11871 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed)
11872 continue;
11873 DRW_Group group = groupFromMetadata(record);
11874 auto nameIt = groupHandleToName.find(record.handle);
11875 if (nameIt != groupHandleToName.end() && !nameIt->second.empty())
11876 group.name = nameIt->second;
11877 else
11878 group.name = "*A" + std::to_string(++unnamedSeq);
11879 groupsToWrite.push_back(std::move(group));
11880 }
11881 if (!groupsToWrite.empty())
11882 m_dxfW->setGroups(groupsToWrite);
11883
11884 if (!handleRemap.empty())
11885 m_dxfW->setHandleRemap(handleRemap);
11886
11887 if (!rootEntries.empty())
11888 m_dxfW->setRootDictEntries(rootEntries);
11889 }
11890
11891 // bool success = m_dxfW->write(this, exportVersion, false); //ascii
11892 const bool success =
11893 m_dxfW->write(this, exportVersion, binary) && !m_writeFailed; // binary
11894 delete m_dxfW;
11895
11896 if (!success) {
11897 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFDW::fileExport: can't write file");
11898 return false;
11899 }
11900 /*RLZ pte*/
11901 /* RS_DEBUG->print("writing tables...");
11902 dw->sectionTables();
11903 // VPORT:
11904 dxf.writeVPort(*dw);
11905 dw->tableEnd();
11906
11907 // VIEW:
11908 RS_DEBUG->print("writing views...");
11909 dxf.writeView(*dw);
11910
11911 // UCS:
11912 RS_DEBUG->print("writing ucs...");
11913 dxf.writeUcs(*dw);
11914
11915 // Appid:
11916 RS_DEBUG->print("writing appid...");
11917 dw->tableAppid(1);
11918 writeAppid(*dw, "ACAD");
11919 dw->tableEnd();
11920 */
11921 return success;
11922}
11923
11924/**
11925 * Prepare unnamed blocks.
11926 */
11927void RS_FilterDXFRW::prepareBlocks() {
11928 int dimNum = 0;
11929 int hatchNum = 0;
11930
11931 // check for existing *D?? or *U??
11932 for (unsigned i = 0; i < m_graphic->countBlocks(); i++) {
11933 const RS_Block *blk = m_graphic->blockAt(i);
11934 const QString prefix = blk->getName().left(2).toUpper();
11935 QString sufix = blk->getName().mid(2);
11936 if (prefix == "*D") {
11937 if (sufix.toInt() > dimNum) {
11938 dimNum = sufix.toInt();
11939 }
11940 } else if (prefix == "*U") {
11941 if (sufix.toInt() > hatchNum) {
11942 hatchNum = sufix.toInt();
11943 }
11944 }
11945 }
11946 // Add a name to each dimension, in dxfR12 also for hatches
11947 for (RS_Entity *e :
11948 lc::LC_ContainerTraverser{*m_graphic, RS2::ResolveNone}.entities()) {
11949 if (e->isAlive()) {
11950 switch (e->rtti()) {
11951 case RS2::EntityDimLinear:
11952 case RS2::EntityDimOrdinate:
11953 case RS2::EntityDimAligned:
11954 case RS2::EntityDimAngular:
11955 case RS2::EntityDimRadial:
11956 case RS2::EntityDimDiametric:
11957 case RS2::EntityDimLeader: {
11958 const QString prefix = "*D" + QString::number(++dimNum);
11959 m_noNameBlock[e] = prefix;
11960 break;
11961 }
11962 case RS2::EntityHatch: {
11963 if (m_version == 1009) {
11964 if (!static_cast<RS_Hatch *>(e)->isSolid()) {
11965 const QString prefix = "*U" + QString::number(++hatchNum);
11966 m_noNameBlock[e] = prefix;
11967 }
11968 }
11969 break;
11970 }
11971 default:
11972 break;
11973 } // end switch
11974 } // end if !RS2::FlagUndone
11975 }
11976}
11977
11978/**
11979 * Writes block records (just the name, not the entities in it).
11980 */
11981void RS_FilterDXFRW::prepareDxfEntityHandleMap() {
11982 if (m_dxfW == nullptr || m_graphic == nullptr)
11983 return;
11984
11985 m_dxfBlockInsertHandles.clear();
11986 std::map<std::uint32_t, std::size_t> sourceHandleCounts;
11987 std::set<std::uint32_t> rawEntityHandles;
11988 std::set<std::uint32_t> rawObjectHandles;
11989 auto scanEntities = [&](RS_EntityContainer *container) {
11990 if (container == nullptr)
11991 return;
11992 for (RS_Entity *entity :
11993 lc::LC_ContainerTraverser{*container, RS2::ResolveNone}.entities()) {
11994 if (entity == nullptr || entity->getFlag(RS2::FlagDeleted) ||
11995 isGeneratedInsertAttrib(entity)) {
11996 continue;
11997 }
11998 const std::uint32_t sourceHandle = entity->sourceHandle();
11999 if (sourceHandle != 0)
12000 ++sourceHandleCounts[sourceHandle];
12001 }
12002 };
12003
12004 scanEntities(m_graphic);
12005 for (unsigned i = 0; i < m_graphic->countBlocks(); ++i) {
12006 RS_Block *block = m_graphic->blockAt(i);
12007 if (block != nullptr && !block->isDeleted())
12008 scanEntities(block);
12009 }
12010
12011 // Raw records are emitted later than BLOCK_RECORDS but their identities are
12012 // needed now: typed entities and GROUP/raw references must know the final
12013 // handle before writeEntities starts. A zero convenience handle represents
12014 // either a legacy/null value or a wider-than-uint32 lexeme and is therefore
12015 // intentionally excluded from the map.
12016 const auto &metadata = m_graphic->dwgAdvancedMetadata();
12017 std::map<std::string, std::size_t> rawSelfHandleCounts;
12018 auto rawSelfHandleKey = [](const DRW_RawDxfObject &raw) {
12019 for (const DRW_Variant &group : raw.groups) {
12020 if (group.code() != 5 || group.type() != DRW_Variant::STRING ||
12021 group.c_str() == nullptr || group.c_str()[0] == '\0') {
12022 continue;
12023 }
12024 std::string key = group.c_str();
12025 std::transform(key.begin(), key.end(), key.begin(), [](unsigned char ch) {
12026 return static_cast<char>(std::toupper(ch));
12027 });
12028 return key;
12029 }
12030 return std::string{};
12031 };
12032 auto noteRawSelfHandle = [&](const DRW_RawDxfObject &raw, bool suppressed) {
12033 if (suppressed)
12034 return;
12035 const std::string key = rawSelfHandleKey(raw);
12036 if (key.empty())
12037 return;
12038 const std::size_t count = ++rawSelfHandleCounts[key];
12039 if (count > 1) {
12040 m_writeFailed = true;
12041 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
12042 "RS_FilterDXFRW: duplicate raw DXF self-handle %s; "
12043 "export rejected",
12044 key.c_str());
12045 }
12046 };
12047 for (const DRW_RawDxfObject &raw : metadata.rawDxfEntities())
12048 noteRawSelfHandle(raw, false);
12049 for (const DRW_RawDxfObject &raw : metadata.rawDxfObjects())
12050 noteRawSelfHandle(raw, m_dxfSuppressedObjectHandles.count(raw.handle) != 0);
12051
12052 for (const DRW_RawDxfObject &raw : metadata.rawDxfEntities()) {
12053 if (raw.handle != 0) {
12054 ++sourceHandleCounts[raw.handle];
12055 rawEntityHandles.insert(raw.handle);
12056 }
12057 }
12058 for (const DRW_RawDxfObject &raw : metadata.rawDxfObjects()) {
12059 if (raw.handle == 0 || m_dxfSuppressedObjectHandles.count(raw.handle) != 0)
12060 continue;
12061 ++sourceHandleCounts[raw.handle];
12062 rawObjectHandles.insert(raw.handle);
12063 }
12064
12065 // Only a unique source handle can safely be used as a lookup key. A
12066 // duplicate source handle is still written with the legacy minted-handle
12067 // path, but must not become a false BLOCK_RECORD reverse reference or a
12068 // guessed GROUP member target.
12069 for (const auto &[sourceHandle, count] : sourceHandleCounts) {
12070 if (count > 1) {
12071 m_dxfW->markSourceHandleAmbiguous(sourceHandle);
12072 RS_DEBUGRS_Debug::instance()->print(
12073 RS_Debug::D_WARNING,
12074 "RS_FilterDXFRW: source handle 0x%X is shared by %zu entities; "
12075 "deferred references will be omitted",
12076 sourceHandle, count);
12077 continue;
12078 }
12079 if (count != 1)
12080 continue;
12081 if (rawEntityHandles.count(sourceHandle) != 0 ||
12082 rawObjectHandles.count(sourceHandle) != 0) {
12083 // Raw records retain their source handle unless a fixed structural
12084 // handle collision requires the already-configured remap. Bind it
12085 // before entity emission so typed and raw deferred references agree.
12086 if (!m_dxfW->bindSourceEntityHandle(sourceHandle,
12087 m_dxfW->remapHandle(sourceHandle)))
12088 m_writeFailed = true;
12089 } else if (m_dxfW->preallocateEntityHandle(sourceHandle) == 0) {
12090 m_writeFailed = true;
12091 }
12092 }
12093
12094 auto collectInsertRefs = [&](RS_EntityContainer *container) {
12095 if (container == nullptr)
12096 return;
12097 for (RS_Entity *entity :
12098 lc::LC_ContainerTraverser{*container, RS2::ResolveNone}.entities()) {
12099 if (entity == nullptr || entity->getFlag(RS2::FlagDeleted) ||
12100 entity->rtti() != RS2::EntityInsert) {
12101 continue;
12102 }
12103 const std::uint32_t sourceHandle = entity->sourceHandle();
12104 const auto count = sourceHandleCounts.find(sourceHandle);
12105 if (sourceHandle == 0 || count == sourceHandleCounts.end() ||
12106 count->second != 1) {
12107 continue;
12108 }
12109 const std::uint32_t emittedHandle =
12110 m_dxfW->preallocateEntityHandle(sourceHandle);
12111 const auto *insert = static_cast<const RS_Insert *>(entity);
12112 if (emittedHandle == 0 || insert->getName().isEmpty()) {
12113 if (emittedHandle == 0)
12114 m_writeFailed = true;
12115 continue;
12116 }
12117 auto &handles = m_dxfBlockInsertHandles[insert->getName().toUpper()];
12118 if (std::find(handles.cbegin(), handles.cend(), emittedHandle) ==
12119 handles.cend()) {
12120 handles.push_back(emittedHandle);
12121 }
12122 }
12123 };
12124
12125 collectInsertRefs(m_graphic);
12126 for (unsigned i = 0; i < m_graphic->countBlocks(); ++i) {
12127 RS_Block *block = m_graphic->blockAt(i);
12128 if (block != nullptr && !block->isDeleted())
12129 collectInsertRefs(block);
12130 }
12131}
12132
12133void RS_FilterDXFRW::writeBlockRecords() {
12134 // first prepare and send unnamed blocks, the while loop can be omitted for
12135 // R12
12136 prepareBlocks();
12137 prepareDxfEntityHandleMap();
12138 const auto insertRefsFor =
12139 [this](const QString &name) -> const std::vector<std::uint32_t> & {
12140 static const std::vector<std::uint32_t> empty;
12141 const auto it = m_dxfBlockInsertHandles.find(name.toUpper());
12142 return it == m_dxfBlockInsertHandles.end() ? empty : it->second;
12143 };
12144 QHash<RS_Entity *, QString>::const_iterator it = m_noNameBlock.constBegin();
12145 while (it != m_noNameBlock.constEnd()) {
12146 noteDxfWrite(m_dxfW->writeBlockRecord(it.value().toStdString(), 0, {},
12147 insertRefsFor(it.value())));
12148 ++it;
12149 }
12150
12151 // next send "normal" blocks
12152 for (unsigned i = 0; i < m_graphic->countBlocks(); i++) {
12153 const RS_Block *blk = m_graphic->blockAt(i);
12154 if (!blk->isDeleted()) {
12155 RS_DEBUGRS_Debug::instance()->print("writing block record: %s",
12156 (const char *)blk->getName().toLocal8Bit());
12157 noteDxfWrite(m_dxfW->writeBlockRecord(
12158 blk->getName().toUtf8().data(), blk->getInsertionUnits(),
12159 blk->getPreviewData(), insertRefsFor(blk->getName())));
12160 }
12161 }
12162}
12163
12164/**
12165 * Writes blocks.
12166 */
12167void RS_FilterDXFRW::writeBlocks() {
12168#ifdef DWGSUPPORT1
12169 if (m_dwgW) {
12170 const auto identityCheckpoint = checkpointDwgWriteIdentity();
12171 const std::uint64_t blockTransaction =
12172 m_dwgW->beginDwgWriteTransaction(false, false, false);
12173 if (blockTransaction == 0) {
12174 m_writeFailed = true;
12175 return;
12176 }
12177 const auto rollbackBlockPhase = [&]() {
12178 (void)m_dwgW->rollbackDwgWriteTransaction(blockTransaction);
12179 restoreDwgWriteIdentity(identityCheckpoint);
12180 m_writeFailed = true;
12181 };
12182 if (!validateDwgExportAdmissionPlan(
12183 DwgOpaqueTableFailurePhase::PostClasses)) {
12184 rollbackBlockPhase();
12185 return;
12186 }
12187 const auto &blockPlan = m_dwgExportAdmissionPlan.blockDefinitions;
12188 std::vector<std::pair<RS_Block *, std::uint32_t>> blockHandles;
12189 blockHandles.reserve(blockPlan.size());
12190
12191 // Register every block before encoding any contents. This makes a
12192 // nested INSERT resolvable even when its referenced definition is
12193 // later in the graphic's block list. Names were normalized and
12194 // admitted before CLASSES; do not rediscover them in this phase.
12195 for (const DwgBlockDefinitionPlanRecord &definition : blockPlan) {
12196 RS_Block *blk = definition.block;
12197 DRW_Coord bp{blk->getBasePoint().x, blk->getBasePoint().y,
12198 blk->getBasePoint().z};
12199 const std::uint32_t handle = m_dwgW->defineBlock(
12200 blk->getName().toUtf8().constData(), bp, blk->getInsertionUnits());
12201 if (handle == 0) {
12202 m_writeFailed = true;
12203 continue;
12204 }
12205 if (!bindDwgWriteBlockName(definition.normalizedName, handle)) {
12206 m_writeFailed = true;
12207 continue;
12208 }
12209 const std::uint32_t sourceHandle = definition.sourceHandle;
12210 if (sourceHandle != 0) {
12211 noteDwgWriteSourceKind(
12212 sourceHandle,
12213 RS_FilterDXFRW_DwgWriteIdentityRegistry::DwgWriteSourceKind::Block);
12214 if (!selectDwgWriteIdentity(RS_FilterDXFRW_DwgWriteIdentityRegistry::
12215 DwgWriteSourceKind::Block,
12216 sourceHandle, handle)) {
12217 m_writeFailed = true;
12218 continue;
12219 }
12220 const bool inserted =
12221 m_dwgWriteBlockHandleRemap.emplace(sourceHandle, handle).second;
12222 if (!inserted)
12223 m_dwgWriteDuplicateBlockHandles.insert(sourceHandle);
12224 }
12225 blockHandles.emplace_back(blk, handle);
12226 }
12227
12228 if (m_writeFailed) {
12229 rollbackBlockPhase();
12230 return;
12231 }
12232
12233 prepareDwgEntityHandleMap();
12234
12235 for (const auto &blockHandle : blockHandles) {
12236 RS_Block *blk = blockHandle.first;
12237 const bool failedBefore = m_writeFailed;
12238 if (!m_dwgW->beginBlockContent(blockHandle.second)) {
12239 rollbackBlockPhase();
12240 return;
12241 }
12242 std::set<RS_Entity *> consumed;
12243 reconstructPolylineSidecars(blk, consumed);
12244 if (m_writeFailed) {
12245 rollbackBlockPhase();
12246 return;
12247 }
12248 if (!reconstructSingleFrameSidecars(blk, consumed)) {
12249 rollbackBlockPhase();
12250 return;
12251 }
12252 for (RS_Entity *entity :
12253 lc::LC_ContainerTraverser{*blk, RS2::ResolveNone}.entities()) {
12254 if (!entity->getFlag(RS2::FlagDeleted) && consumed.count(entity) == 0) {
12255 writeEntity(entity);
12256 }
12257 }
12258 if (failedBefore || m_writeFailed) {
12259 rollbackBlockPhase();
12260 return;
12261 }
12262 const bool ended = m_dwgW->endBlockContent();
12263 if (!ended || m_writeFailed) {
12264 rollbackBlockPhase();
12265 return;
12266 }
12267 }
12268
12269 if (!m_dwgW->commitDwgWriteTransaction(blockTransaction)) {
12270 rollbackBlockPhase();
12271 return;
12272 }
12273 return;
12274 }
12275#endif
12276
12277 RS_Block *blk;
12278
12279 // write unnamed blocks
12280 QHash<RS_Entity *, QString>::const_iterator it = m_noNameBlock.constBegin();
12281 while (it != m_noNameBlock.constEnd()) {
12282 DRW_Block block;
12283 block.name = it.value().toStdString();
12284 block.basePoint.x = 0.0;
12285 block.basePoint.y = 0.0;
12286 block.basePoint.z = 0.0;
12287 block.flags = 1; // flag for unnamed block
12288 noteDxfWrite(m_dxfW->writeBlock(&block));
12289 const auto ct = static_cast<RS_EntityContainer *>(it.key());
12290 for (RS_Entity *e :
12291 lc::LC_ContainerTraverser{*ct, RS2::ResolveNone}.entities()) {
12292 if (e->isAlive()) {
12293 writeEntity(e);
12294 }
12295 }
12296 ++it;
12297 }
12298
12299 // next write "normal" blocks
12300 for (unsigned i = 0; i < m_graphic->countBlocks(); i++) {
12301 const RS_Block *blk = m_graphic->blockAt(i);
12302 if (!blk->isDeleted()) {
12303 RS_DEBUGRS_Debug::instance()->print("writing block: %s",
12304 (const char *)blk->getName().toLocal8Bit());
12305
12306 DRW_Block block;
12307 block.name = blk->getName().toUtf8().data();
12308 block.basePoint.x = blk->getBasePoint().x;
12309 block.basePoint.y = blk->getBasePoint().y;
12310 block.basePoint.z = blk->getBasePoint().z;
12311 noteDxfWrite(m_dxfW->writeBlock(&block));
12312 for (RS_Entity *e :
12313 lc::LC_ContainerTraverser{*blk, RS2::ResolveNone}.entities()) {
12314 if (!(e->getFlag(RS2::FlagDeleted))) {
12315 writeEntity(e);
12316 }
12317 }
12318 }
12319 }
12320}
12321
12322#ifdef DWGSUPPORT1
12323RS_FilterDXFRW::DwgDataStorageSourceRowKey
12324RS_FilterDXFRW::makeDwgDataStorageSourceRowKey(const std::string &sectionName,
12325 DRW::Version version,
12326 std::size_t rawSectionIndex,
12327 std::size_t recordIndex) {
12328 DwgDataStorageSourceRowKey key;
12329 key.sectionName = normalizeDwgTableName(sectionName);
12330 key.version = version;
12331 key.rawSectionIndex = rawSectionIndex;
12332 key.recordIndex = recordIndex;
12333 return key;
12334}
12335
12336RS_FilterDXFRW::DwgDataStorageIdentityResult
12337RS_FilterDXFRW::resolveDwgDataStorageIdentity(
12338 std::uint32_t ownerHandle, std::uint64_t storageHandle,
12339 bool hasStorageHandle, const std::string &storageHandleKey,
12340 bool hasStorageHandleKey, std::size_t rawSectionIndex,
12341 std::size_t recordIndex) {
12342 DwgDataStorageIdentityResult result;
12343 result.rawSectionIndex = rawSectionIndex;
12344 result.recordIndex = recordIndex;
12345 result.numericHandle = storageHandle;
12346 result.hasNumericHandle = hasStorageHandle && storageHandle != 0;
12347 result.handleKey = hasStorageHandleKey
12348 ? normalizeDwgDataStorageHandleKey(storageHandleKey)
12349 : std::string{};
12350 result.hasHandleKey = hasStorageHandleKey && !result.handleKey.empty();
12351
12352 if (!result.hasNumericHandle && !result.hasHandleKey) {
12353 result.status = DwgDataStorageIdentityStatus::Missing;
12354 result.matchesOwnerBySourceValue = true;
12355 return result;
12356 }
12357
12358 const std::string ownerKey = dwgDataStorageNumericHandleKey(ownerHandle);
12359 const std::string numericKey =
12360 result.hasNumericHandle
12361 ? dwgDataStorageNumericHandleKey(result.numericHandle)
12362 : std::string{};
12363 if (result.hasNumericHandle && result.hasHandleKey &&
12364 numericKey != result.handleKey) {
12365 result.status = DwgDataStorageIdentityStatus::Conflict;
12366 return result;
12367 }
12368
12369 const bool numericMatches = result.hasNumericHandle && ownerHandle != 0 &&
12370 result.numericHandle == ownerHandle;
12371 const bool keyMatches =
12372 result.hasHandleKey && ownerHandle != 0 && result.handleKey == ownerKey;
12373 if ((result.hasNumericHandle && !numericMatches) ||
12374 (result.hasHandleKey && !keyMatches)) {
12375 result.status = DwgDataStorageIdentityStatus::OwnerMismatch;
12376 return result;
12377 }
12378
12379 if (result.hasNumericHandle && result.hasHandleKey)
12380 result.status = DwgDataStorageIdentityStatus::Consistent;
12381 else if (result.hasNumericHandle)
12382 result.status = DwgDataStorageIdentityStatus::NumericOnly;
12383 else
12384 result.status = DwgDataStorageIdentityStatus::LexicalOnly;
12385 result.matchesOwnerBySourceValue = true;
12386 return result;
12387}
12388
12389std::vector<RS_FilterDXFRW::DwgDataStorageCarrier>
12390RS_FilterDXFRW::collectDwgDataStorageCarriers() const {
12391 std::vector<DwgDataStorageCarrier> result;
12392 if (m_graphic == nullptr)
12393 return result;
12394
12395 const auto &metadata = m_graphic->dwgAdvancedMetadata();
12396 const std::string sectionName =
12397 normalizeDwgTableName(DRW_DataStorageSection{}.m_name);
12398 using Descriptor = DwgDataStorageWriterDescriptor;
12399 const auto descriptor =
12400 [](const char *family, const char *className, const char *recordName,
12401 DRW::Version minVersion, DRW::Version maxVersion = DRW::UNKNOWNV,
12402 std::uint16_t classNumber = 0, bool supportsDataStorage = true) {
12403 Descriptor result;
12404 result.family = family;
12405 result.className = className;
12406 result.recordName = recordName;
12407 result.classNumber = classNumber;
12408 result.minVersion = minVersion;
12409 result.maxVersion = maxVersion;
12410 result.supportsDataStorage = supportsDataStorage;
12411 result.binding =
12412 findDwgDataStorageWriterBinding(family, className, recordName);
12413 // The strings above identify an intentionally unbound policy entry or
12414 // select an executable low-level capability. Once bound, copy all
12415 // wire identity and version fields from the authoritative table so a
12416 // carrier cannot advertise metadata that its writer does not implement.
12417 DwgDataStorageWriterCapability capability;
12418 if (result.binding != DwgDataStorageWriterBinding::None &&
12419 getDwgDataStorageWriterCapability(result.binding, capability)) {
12420 result.family = capability.family;
12421 result.className = capability.className;
12422 result.recordName = capability.recordName;
12423 result.classNumber = capability.classNumber;
12424 result.minVersion = capability.minVersion;
12425 result.maxVersion = capability.maxVersion;
12426 result.supportsDataStorage =
12427 supportsDataStorage && capability.acceptsPresenceBit;
12428 result.phase = capability.phase == DwgDataStorageWriterPhase::Section
12429 ? DwgDataStorageCarrier::EmissionPhase::Section
12430 : DwgDataStorageCarrier::EmissionPhase::Objects;
12431 }
12432 return result;
12433 };
12434 const auto descriptorForBinding = [](DwgDataStorageWriterBinding binding) {
12435 Descriptor result;
12436 DwgDataStorageWriterCapability capability;
12437 if (binding == DwgDataStorageWriterBinding::None ||
12438 !getDwgDataStorageWriterCapability(binding, capability))
12439 return result;
12440 result.family = capability.family;
12441 result.className = capability.className;
12442 result.recordName = capability.recordName;
12443 result.classNumber = capability.classNumber;
12444 result.minVersion = capability.minVersion;
12445 result.maxVersion = capability.maxVersion;
12446 result.supportsDataStorage = capability.acceptsPresenceBit;
12447 result.binding = binding;
12448 result.phase = capability.phase == DwgDataStorageWriterPhase::Section
12449 ? DwgDataStorageCarrier::EmissionPhase::Section
12450 : DwgDataStorageCarrier::EmissionPhase::Objects;
12451 return result;
12452 };
12453 const auto appendTyped = [&result, &sectionName](const auto &records,
12454 const Descriptor &writer) {
12455 for (const auto &record : records) {
12456 if (!record.hasDsData)
12457 continue;
12458 DwgDataStorageCarrier carrier;
12459 carrier.kind = DwgDataStorageCarrier::Kind::Typed;
12460 carrier.binding = writer.binding;
12461 carrier.phase = writer.phase;
12462 carrier.capability =
12463 writer.supportsDataStorage &&
12464 writer.binding != DwgDataStorageWriterBinding::None
12465 ? DwgDataStorageCarrier::WriterCapability::Typed
12466 : DwgDataStorageCarrier::WriterCapability::None;
12467 carrier.capabilityMinVersion = writer.minVersion;
12468 carrier.capabilityMaxVersion = writer.maxVersion;
12469 carrier.sectionName = sectionName;
12470 carrier.writerFamily = writer.family;
12471 carrier.className = writer.className;
12472 carrier.recordName = writer.recordName;
12473 carrier.classNumber = writer.classNumber;
12474 carrier.ownerHandle = record.handle;
12475 carrier.storageHandle = record.handle;
12476 carrier.hasStorageHandle = true;
12477 carrier.replayAllowed =
12478 record.replayState ==
12479 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed;
12480 result.push_back(std::move(carrier));
12481 }
12482 };
12483 const auto appendTypedByDescriptor = [&result, &sectionName](
12484 const auto &record,
12485 const Descriptor &writer) {
12486 DwgDataStorageCarrier carrier;
12487 carrier.kind = DwgDataStorageCarrier::Kind::Typed;
12488 carrier.binding = writer.binding;
12489 carrier.phase = writer.phase;
12490 carrier.capability =
12491 writer.supportsDataStorage &&
12492 writer.binding != DwgDataStorageWriterBinding::None
12493 ? DwgDataStorageCarrier::WriterCapability::Typed
12494 : DwgDataStorageCarrier::WriterCapability::None;
12495 carrier.capabilityMinVersion = writer.minVersion;
12496 carrier.capabilityMaxVersion = writer.maxVersion;
12497 carrier.sectionName = sectionName;
12498 carrier.writerFamily = writer.family == nullptr ? "" : writer.family;
12499 carrier.className = writer.className == nullptr ? "" : writer.className;
12500 carrier.recordName = writer.recordName == nullptr ? "" : writer.recordName;
12501 carrier.classNumber = writer.classNumber;
12502 carrier.ownerHandle = record.handle;
12503 carrier.storageHandle = record.handle;
12504 carrier.hasStorageHandle = true;
12505 carrier.replayAllowed = record.replayState ==
12506 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed;
12507 result.push_back(std::move(carrier));
12508 };
12509 const auto appendTypedWith =
12510 [&appendTypedByDescriptor](const auto &records,
12511 const auto &descriptorFor) {
12512 for (const auto &record : records) {
12513 if (!record.hasDsData)
12514 continue;
12515 appendTypedByDescriptor(record, descriptorFor(record));
12516 }
12517 };
12518
12519 const auto underlayDescriptor = [&descriptor,
12520 &descriptorForBinding](const auto &record) {
12521 switch (static_cast<DRW_UnderlayDefinition::Kind>(record.kind)) {
12522 case DRW_UnderlayDefinition::PDF:
12523 return descriptorForBinding(DwgDataStorageWriterBinding::UnderlayPdf);
12524 case DRW_UnderlayDefinition::DGN:
12525 return descriptorForBinding(DwgDataStorageWriterBinding::UnderlayDgn);
12526 case DRW_UnderlayDefinition::DWF:
12527 return descriptorForBinding(DwgDataStorageWriterBinding::UnderlayDwf);
12528 default:
12529 return descriptor("", "", "", DRW::UNKNOWNV);
12530 }
12531 };
12532 const auto pointCloudDescriptor =
12533 [&descriptor, &descriptorForBinding](const auto &record) {
12534 switch (static_cast<DRW_PointCloudDef::Kind>(record.kind)) {
12535 case DRW_PointCloudDef::Definition:
12536 return descriptorForBinding(
12537 DwgDataStorageWriterBinding::PointCloudDefinition);
12538 case DRW_PointCloudDef::DefinitionEx:
12539 return descriptorForBinding(
12540 DwgDataStorageWriterBinding::PointCloudDefinitionEx);
12541 case DRW_PointCloudDef::Reactor:
12542 return descriptorForBinding(
12543 DwgDataStorageWriterBinding::PointCloudDefinitionReactor);
12544 case DRW_PointCloudDef::ReactorEx:
12545 return descriptorForBinding(
12546 DwgDataStorageWriterBinding::PointCloudDefinitionReactorEx);
12547 default:
12548 return descriptor("", "", "", DRW::UNKNOWNV);
12549 }
12550 };
12551 const auto backgroundDescriptor = [&descriptor](const auto &record) {
12552 switch (static_cast<DRW_Background::Kind>(record.kind)) {
12553 case DRW_Background::Skylight:
12554 return descriptor("BACKGROUND", "AcDbSkyBackground", "SKYLIGHTBACKGROUND",
12555 DRW::AC1015, DRW::UNKNOWNV,
12556 DRW_Background::kDwgClassNumSkylight, false);
12557 case DRW_Background::Solid:
12558 return descriptor("BACKGROUND", "AcDbSolidBackground", "SOLIDBACKGROUND",
12559 DRW::AC1015, DRW::UNKNOWNV,
12560 DRW_Background::kDwgClassNumSolid, false);
12561 case DRW_Background::Gradient:
12562 return descriptor("BACKGROUND", "AcDbGradientBackground",
12563 "GRADIENTBACKGROUND", DRW::AC1015, DRW::UNKNOWNV,
12564 DRW_Background::kDwgClassNumGradient, false);
12565 case DRW_Background::GroundPlane:
12566 return descriptor("BACKGROUND", "AcDbGroundPlaneBackground",
12567 "GROUNDPLANEBACKGROUND", DRW::AC1015, DRW::UNKNOWNV,
12568 DRW_Background::kDwgClassNumGroundPlane, false);
12569 case DRW_Background::Image:
12570 return descriptor("BACKGROUND", "AcDbImageBackground", "IMAGEBACKGROUND",
12571 DRW::AC1015, DRW::UNKNOWNV,
12572 DRW_Background::kDwgClassNumImage, false);
12573 case DRW_Background::Ibl:
12574 return descriptor("BACKGROUND", "AcDbIBLBackground", "IBLBACKGROUND",
12575 DRW::AC1015, DRW::UNKNOWNV,
12576 DRW_Background::kDwgClassNumIbl, false);
12577 default:
12578 return descriptor("", "", "", DRW::UNKNOWNV);
12579 }
12580 };
12581 const auto renderSettingsDescriptor = [&descriptor](const auto &record) {
12582 switch (static_cast<DRW_RenderSettings::Kind>(record.kind)) {
12583 case DRW_RenderSettings::Settings:
12584 return descriptor("RENDERSETTINGS", "AcDbRenderSettings",
12585 "RENDERSETTINGS", DRW::AC1015, DRW::UNKNOWNV,
12586 DRW_RenderSettings::kDwgClassNumSettings, false);
12587 case DRW_RenderSettings::Global:
12588 return descriptor("RENDERSETTINGS", "AcDbRenderGlobal", "RENDERGLOBAL",
12589 DRW::AC1015, DRW::UNKNOWNV,
12590 DRW_RenderSettings::kDwgClassNumGlobal, false);
12591 case DRW_RenderSettings::Environment:
12592 return descriptor("RENDERSETTINGS", "AcDbRenderEnvironment",
12593 "RENDERENVIRONMENT", DRW::AC1015, DRW::UNKNOWNV,
12594 DRW_RenderSettings::kDwgClassNumEnvironment, false);
12595 case DRW_RenderSettings::Entry:
12596 return descriptor("RENDERSETTINGS", "AcDbRenderEntry", "RENDERENTRY",
12597 DRW::AC1015, DRW::UNKNOWNV,
12598 DRW_RenderSettings::kDwgClassNumEntry, false);
12599 case DRW_RenderSettings::RapidRT:
12600 return descriptor("RENDERSETTINGS", "AcDbRapidRTRenderSettings",
12601 "RAPIDRTRENDERSETTINGS", DRW::AC1015, DRW::UNKNOWNV,
12602 DRW_RenderSettings::kDwgClassNumRapidRT, false);
12603 case DRW_RenderSettings::MentalRay:
12604 return descriptor("RENDERSETTINGS", "AcDbMentalRayRenderSettings",
12605 "MENTALRAYRENDERSETTINGS", DRW::AC1015, DRW::UNKNOWNV,
12606 DRW_RenderSettings::kDwgClassNumMentalRay, false);
12607 default:
12608 return descriptor("", "", "", DRW::UNKNOWNV);
12609 }
12610 };
12611 const auto sectionDescriptor = [&descriptor](const auto &record) {
12612 switch (static_cast<DRW_Section::Kind>(record.kind)) {
12613 case DRW_Section::Manager:
12614 return descriptor("SECTION", "AcDbSectionManager", "SECTION_MANAGER",
12615 DRW::AC1021, DRW::UNKNOWNV,
12616 DRW_Section::kDwgClassNumManager, false);
12617 case DRW_Section::Settings:
12618 return descriptor("SECTION", "AcDbSectionSettings", "SECTION_SETTINGS",
12619 DRW::AC1021, DRW::UNKNOWNV,
12620 DRW_Section::kDwgClassNumSettings, false);
12621 default:
12622 return descriptor("", "", "", DRW::UNKNOWNV);
12623 }
12624 };
12625
12626 // Keep this list in lockstep with the typed registrations and OBJECTS
12627 // dispatch below. Image definitions, image reactors, and viewport entity
12628 // headers are emitted through other writer phases and have no independent
12629 // DataStorage object receipt here.
12630 appendTypedWith(metadata.underlayDefinitions(), underlayDescriptor);
12631 appendTypedWith(metadata.pointCloudDefinitions(), pointCloudDescriptor);
12632 appendTyped(metadata.navisworksModelDefinitions(),
12633 descriptorForBinding(
12634 DwgDataStorageWriterBinding::NavisworksModelDefinition));
12635 appendTyped(
12636 metadata.pointCloudColorMaps(),
12637 descriptorForBinding(DwgDataStorageWriterBinding::PointCloudColorMap));
12638 appendTyped(
12639 metadata.rasterVariables(),
12640 descriptorForBinding(DwgDataStorageWriterBinding::RasterVariables));
12641 appendTyped(metadata.vxControls(),
12642 descriptorForBinding(DwgDataStorageWriterBinding::VxControl));
12643 appendTyped(metadata.vxTableRecords(),
12644 descriptorForBinding(DwgDataStorageWriterBinding::VxTableRecord));
12645 appendTyped(metadata.visualStyles(),
12646 descriptor("VISUALSTYLE", "AcDbVisualStyle", "VISUALSTYLE",
12647 DRW::AC1015, DRW::UNKNOWNV, 0, false));
12648 appendTyped(metadata.lightLists(),
12649 descriptorForBinding(DwgDataStorageWriterBinding::LightList));
12650 appendTyped(
12651 metadata.tvDeviceProperties(),
12652 descriptorForBinding(DwgDataStorageWriterBinding::TvDeviceProperties));
12653 appendTyped(metadata.suns(), descriptor("SUN", "AcDbSun", "SUN", DRW::AC1021,
12654 DRW::UNKNOWNV, 0, false));
12655 appendTypedWith(metadata.backgrounds(), backgroundDescriptor);
12656 appendTyped(metadata.materials(),
12657 descriptorForBinding(DwgDataStorageWriterBinding::Material));
12658 appendTypedWith(metadata.renderSettings(), renderSettingsDescriptor);
12659 appendTyped(metadata.sunStudies(),
12660 descriptor("SUNSTUDY", "AcDbSunStudy", "SUNSTUDY", DRW::AC1015,
12661 DRW::UNKNOWNV, 0, false));
12662 appendTyped(metadata.motionPaths(),
12663 descriptor("MOTIONPATH", "AcDbMotionPath", "MOTIONPATH",
12664 DRW::AC1015, DRW::UNKNOWNV, 0, false));
12665 appendTyped(metadata.curvePaths(),
12666 descriptor("CURVEPATH", "AcDbCurvePath", "CURVEPATH", DRW::AC1015,
12667 DRW::UNKNOWNV, 0, false));
12668 appendTyped(metadata.pointPaths(),
12669 descriptor("POINTPATH", "AcDbPointPath", "POINTPATH", DRW::AC1015,
12670 DRW::UNKNOWNV, 0, false));
12671 appendTyped(metadata.objectPtrs(),
12672 descriptor("OBJECT_PTR", "AcDbObjectPtr", "OBJECT_PTR",
12673 DRW::AC1015, DRW::UNKNOWNV, 0, false));
12674 appendTyped(metadata.partialViewingIndexes(),
12675 descriptor("PARTIAL_VIEWING_INDEX", "AcDbPartialViewingIndex",
12676 "PARTIAL_VIEWING_INDEX", DRW::AC1015, DRW::UNKNOWNV, 0,
12677 false));
12678 appendTyped(metadata.dbColors(),
12679 descriptorForBinding(DwgDataStorageWriterBinding::DbColor));
12680 appendTyped(
12681 metadata.dimensionAssociations(),
12682 descriptorForBinding(DwgDataStorageWriterBinding::DimensionAssociation));
12683 appendTyped(
12684 metadata.evaluationGraphs(),
12685 descriptorForBinding(DwgDataStorageWriterBinding::EvaluationGraph));
12686 appendTyped(metadata.blockRepresentationData(),
12687 descriptor("BLOCKREPRESENTATIONDATA", "",
12688 "BLOCKREPRESENTATIONDATA", DRW::AC1015, DRW::UNKNOWNV,
12689 0, false));
12690 appendTypedWith(metadata.sections(), sectionDescriptor);
12691 appendTyped(metadata.mleaderStyles(),
12692 descriptorForBinding(DwgDataStorageWriterBinding::MLeaderStyle));
12693 appendTyped(metadata.groups(),
12694 descriptorForBinding(DwgDataStorageWriterBinding::Group));
12695 appendTyped(metadata.spatialFilters(),
12696 descriptorForBinding(DwgDataStorageWriterBinding::SpatialFilter));
12697 appendTyped(metadata.geoData(),
12698 descriptorForBinding(DwgDataStorageWriterBinding::GeoData));
12699 appendTyped(
12700 metadata.placeholders(),
12701 descriptorForBinding(DwgDataStorageWriterBinding::AcDbPlaceholder));
12702 appendTyped(metadata.dictionaries(),
12703 descriptorForBinding(DwgDataStorageWriterBinding::Dictionary));
12704 appendTyped(metadata.xrecords(),
12705 descriptorForBinding(DwgDataStorageWriterBinding::XRecord));
12706 appendTyped(metadata.layouts(),
12707 descriptorForBinding(DwgDataStorageWriterBinding::Layout));
12708 appendTyped(metadata.scales(),
12709 descriptorForBinding(DwgDataStorageWriterBinding::Scale));
12710 appendTyped(metadata.mlineStyles(),
12711 descriptorForBinding(DwgDataStorageWriterBinding::MLineStyle));
12712 appendTyped(
12713 metadata.wipeoutVariables(),
12714 descriptorForBinding(DwgDataStorageWriterBinding::WipeoutVariables));
12715 appendTyped(metadata.idBuffers(),
12716 descriptorForBinding(DwgDataStorageWriterBinding::IDBuffer));
12717 appendTyped(metadata.layerIndexes(),
12718 descriptorForBinding(DwgDataStorageWriterBinding::LayerIndex));
12719 appendTyped(metadata.spatialIndexes(),
12720 descriptorForBinding(DwgDataStorageWriterBinding::SpatialIndex));
12721 appendTyped(metadata.dictionaryVars(),
12722 descriptorForBinding(DwgDataStorageWriterBinding::DictionaryVar));
12723 appendTyped(
12724 metadata.dictionariesWithDefault(),
12725 descriptorForBinding(DwgDataStorageWriterBinding::DictionaryWithDefault));
12726 appendTyped(metadata.sortEntsTables(),
12727 descriptorForBinding(DwgDataStorageWriterBinding::SortEntsTable));
12728 appendTyped(metadata.fieldLists(),
12729 descriptorForBinding(DwgDataStorageWriterBinding::FieldList));
12730 appendTyped(metadata.fields(),
12731 descriptorForBinding(DwgDataStorageWriterBinding::Field));
12732 // Only TABLESTYLE has a typed writer in this metadata collection. Other
12733 // table records use their own native/raw paths and must not become a
12734 // false DataStorage carrier here.
12735 for (const auto &record : metadata.tables()) {
12736 if (!record.hasDsData || !record.nativeTableStyle)
12737 continue;
12738 DwgDataStorageCarrier carrier;
12739 const Descriptor writer =
12740 descriptorForBinding(DwgDataStorageWriterBinding::TableStyle);
12741 carrier.kind = DwgDataStorageCarrier::Kind::Typed;
12742 carrier.binding = writer.binding;
12743 carrier.phase = writer.phase;
12744 carrier.capability = writer.supportsDataStorage
12745 ? DwgDataStorageCarrier::WriterCapability::Typed
12746 : DwgDataStorageCarrier::WriterCapability::None;
12747 carrier.capabilityMinVersion = writer.minVersion;
12748 carrier.capabilityMaxVersion = writer.maxVersion;
12749 carrier.sectionName = sectionName;
12750 carrier.writerFamily = writer.family;
12751 carrier.className = writer.className;
12752 carrier.recordName = writer.recordName;
12753 carrier.classNumber = writer.classNumber;
12754 carrier.ownerHandle = record.handle;
12755 carrier.storageHandle = record.handle;
12756 carrier.hasStorageHandle = true;
12757 carrier.replayAllowed = record.replayState ==
12758 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed;
12759 result.push_back(std::move(carrier));
12760 }
12761
12762 for (std::size_t index = 0; index < metadata.rawObjects().size(); ++index) {
12763 const auto &record = metadata.rawObjects()[index];
12764 if (!record.hasDataStorage)
12765 continue;
12766 DwgDataStorageCarrier carrier;
12767 carrier.kind = DwgDataStorageCarrier::Kind::Raw;
12768 carrier.binding = DwgDataStorageWriterBinding::None;
12769 carrier.phase = DwgDataStorageCarrier::EmissionPhase::Objects;
12770 carrier.capability = DwgDataStorageCarrier::WriterCapability::Raw;
12771 carrier.sectionName = sectionName;
12772 carrier.writerFamily =
12773 record.recordName.empty() ? "RAW_OBJECT" : record.recordName;
12774 carrier.className = record.className;
12775 carrier.recordName = record.recordName;
12776 const bool validCustomClassOrdinal =
12777 !record.isCustomClass ||
12778 (record.objectType >= 500 &&
12779 static_cast<std::uint64_t>(record.objectType) <=
12780 std::numeric_limits<std::uint16_t>::max());
12781 carrier.classNumber = validCustomClassOrdinal && record.isCustomClass
12782 ? static_cast<std::uint16_t>(record.objectType)
12783 : 0;
12784 carrier.ownerHandle = record.handle;
12785 carrier.storageHandle = record.handle;
12786 carrier.hasStorageHandle = true;
12787 carrier.replayAllowed = record.replayState ==
12788 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed;
12789 carrier.rawObjectIndex = index;
12790 result.push_back(std::move(carrier));
12791 }
12792 return result;
12793}
12794
12795bool RS_FilterDXFRW::prepareDwgDataStorageReplayPlan(DRW::Version version) {
12796 m_dwgDataStorageReplayPlan = {};
12797 m_dwgDataStorageAdmissionSummary = {};
12798 m_dwgDataStorageSectionReceipts.clear();
12799 m_dwgDataStorageCommittedKinds.clear();
12800 m_dwgDataStorageCommitReceipts.clear();
12801 if (m_nextDwgDataStorageReplayGeneration == 0 ||
12802 m_nextDwgDataStorageReplayGeneration ==
12803 std::numeric_limits<std::uint64_t>::max()) {
12804 return false;
12805 }
12806 m_dwgDataStorageReplayPlan.generation =
12807 m_nextDwgDataStorageReplayGeneration++;
12808 m_dwgDataStorageReplayPlan.version = version;
12809 if (m_graphic == nullptr)
12810 return false;
12811
12812 const auto &metadata = m_graphic->dwgAdvancedMetadata();
12813 const std::string canonicalName =
12814 normalizeDwgTableName(DRW_DataStorageSection{}.m_name);
12815 const auto writerCapabilityMatches =
12816 [](const DwgDataStorageCarrier &carrier) {
12817 if (carrier.kind != DwgDataStorageCarrier::Kind::Typed)
12818 return true;
12819 DwgDataStorageWriterCapability capability;
12820 if (!getDwgDataStorageWriterCapability(carrier.binding, capability) ||
12821 !capability.acceptsPresenceBit ||
12822 capability.requiresClass != !carrier.className.empty() ||
12823 carrier.writerFamily != capability.family ||
12824 carrier.className != capability.className ||
12825 carrier.recordName != capability.recordName ||
12826 (carrier.classNumber != 0 && capability.classNumber != 0 &&
12827 carrier.classNumber != capability.classNumber)) {
12828 return false;
12829 }
12830 const bool phaseMatches =
12831 (carrier.phase == DwgDataStorageCarrier::EmissionPhase::Objects &&
12832 capability.phase == DwgDataStorageWriterPhase::Objects) ||
12833 (carrier.phase == DwgDataStorageCarrier::EmissionPhase::Section &&
12834 capability.phase == DwgDataStorageWriterPhase::Section);
12835 return phaseMatches &&
12836 carrier.capabilityMinVersion == capability.minVersion &&
12837 carrier.capabilityMaxVersion == capability.maxVersion;
12838 };
12839 const auto carrierCapabilityAvailable =
12840 [this, version,
12841 &writerCapabilityMatches](const DwgDataStorageCarrier &carrier) {
12842 if (carrier.capability ==
12843 DwgDataStorageCarrier::WriterCapability::None ||
12844 carrier.phase != DwgDataStorageCarrier::EmissionPhase::Objects)
12845 return false;
12846 if (!writerCapabilityMatches(carrier))
12847 return false;
12848 // dwgRW receives its target version only when write() begins. At this
12849 // stage, use the explicit target version and immutable capability
12850 // table; validate the live writer capability during write preflight.
12851 if (carrier.capabilityMinVersion != DRW::UNKNOWNV &&
12852 version < carrier.capabilityMinVersion)
12853 return false;
12854 if (carrier.capabilityMaxVersion != DRW::UNKNOWNV &&
12855 version > carrier.capabilityMaxVersion)
12856 return false;
12857 return true;
12858 };
12859 const auto resolveCarrierIdentity =
12860 [&](const DwgDataStorageCarrier &carrier, std::size_t rawSectionIndex,
12861 std::size_t recordIndex, std::uint64_t numericHandle,
12862 const std::string &handleKey) {
12863 const std::uint64_t identityHandle =
12864 carrier.hasStorageHandle ? carrier.storageHandle : numericHandle;
12865 const std::string &identityKey =
12866 carrier.hasStorageHandleKey ? carrier.storageHandleKey : handleKey;
12867 return resolveDwgDataStorageIdentity(
12868 carrier.ownerHandle, identityHandle,
12869 carrier.hasStorageHandle || numericHandle != 0, identityKey,
12870 carrier.hasStorageHandleKey || !handleKey.empty(), rawSectionIndex,
12871 recordIndex);
12872 };
12873
12874 m_dwgDataStorageReplayPlan.carriers = collectDwgDataStorageCarriers();
12875 const std::size_t invalidIndex = std::numeric_limits<std::size_t>::max();
12876 for (const auto &carrier : m_dwgDataStorageReplayPlan.carriers) {
12877 if (carrier.kind == DwgDataStorageCarrier::Kind::Raw) {
12878 if (carrier.rawObjectIndex >= metadata.rawObjects().size()) {
12879 m_dwgDataStorageReplayPlan.valid = false;
12880 continue;
12881 }
12882 const auto &raw = metadata.rawObjects()[carrier.rawObjectIndex];
12883 if (raw.isCustomClass &&
12884 (raw.objectType < 500 ||
12885 static_cast<std::uint64_t>(raw.objectType) >
12886 std::numeric_limits<std::uint16_t>::max())) {
12887 m_dwgDataStorageReplayPlan.valid = false;
12888 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
12889 "RS_FilterDXFRW: DataStorage raw class ordinal %d for "
12890 "handle 0x%X is outside the DWG 16-bit domain",
12891 raw.objectType, raw.handle);
12892 }
12893 }
12894 const bool unavailableTyped =
12895 carrier.kind == DwgDataStorageCarrier::Kind::Typed &&
12896 carrier.capability == DwgDataStorageCarrier::WriterCapability::None;
12897 const bool kindMatchesCapability =
12898 unavailableTyped ||
12899 (carrier.capability == DwgDataStorageCarrier::WriterCapability::Typed &&
12900 carrier.kind == DwgDataStorageCarrier::Kind::Typed) ||
12901 (carrier.capability == DwgDataStorageCarrier::WriterCapability::Raw &&
12902 carrier.kind == DwgDataStorageCarrier::Kind::Raw);
12903 if (!kindMatchesCapability) {
12904 m_dwgDataStorageReplayPlan.valid = false;
12905 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
12906 "RS_FilterDXFRW: DataStorage carrier kind/capability "
12907 "disagree for owner 0x%X",
12908 carrier.ownerHandle);
12909 }
12910 }
12911 if (!m_dwgDataStorageReplayPlan.valid)
12912 return false;
12913
12914 const auto findMatchingRawSection =
12915 [&, invalidIndex](const std::string &name, DRW::Version rawVersion) {
12916 std::size_t match = invalidIndex;
12917 for (std::size_t index = 0; index < metadata.rawDwgSections().size();
12918 ++index) {
12919 const auto &raw = metadata.rawDwgSections()[index];
12920 if (normalizeDwgTableName(raw.name) != normalizeDwgTableName(name) ||
12921 raw.version != rawVersion ||
12922 raw.replayState !=
12923 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
12924 continue;
12925 }
12926 if (match != invalidIndex)
12927 return invalidIndex;
12928 match = index;
12929 }
12930 return match;
12931 };
12932
12933 m_dwgDataStorageReplayPlan.decisionByCarrier.assign(
12934 m_dwgDataStorageReplayPlan.carriers.size(), invalidIndex);
12935 const auto storageIdentityKey = [](const std::string &sectionName,
12936 const std::string &recordKey) {
12937 std::string key;
12938 key.reserve(sectionName.size() + recordKey.size() + 1u);
12939 key.append(sectionName);
12940 key.push_back('\n');
12941 key.append(recordKey);
12942 return key;
12943 };
12944 if (version <= DRW::AC1024) {
12945 // Older DWG layouts may expose the common-object marker bit without
12946 // having a DataStorage section. It is advisory there; only an actual
12947 // section or parsed storage record creates a replay obligation.
12948 m_dwgDataStorageReplayPlan.valid = metadata.dataStorages().empty();
12949 for (const auto &raw : metadata.rawDwgSections()) {
12950 if (normalizeDwgTableName(raw.name) == canonicalName)
12951 m_dwgDataStorageReplayPlan.valid = false;
12952 }
12953 if (m_dwgDataStorageReplayPlan.valid) {
12954 for (std::size_t index = 0;
12955 index < m_dwgDataStorageReplayPlan.carriers.size(); ++index) {
12956 const auto &carrier = m_dwgDataStorageReplayPlan.carriers[index];
12957 if (!carrier.replayAllowed || carrier.ownerHandle == 0)
12958 continue;
12959 DwgDataStorageDecisionRecord decision;
12960 decision.decision = DwgDataStorageDecision::LegacyAdvisory;
12961 decision.sectionName = carrier.sectionName;
12962 decision.writerFamily = carrier.writerFamily;
12963 decision.className = carrier.className;
12964 decision.recordName = carrier.recordName;
12965 decision.classNumber = carrier.classNumber;
12966 decision.binding = carrier.binding;
12967 decision.ownerHandle = carrier.ownerHandle;
12968 decision.storageHandle = carrier.storageHandle;
12969 decision.storageHandleKey = carrier.storageHandleKey;
12970 decision.identityStatus =
12971 resolveDwgDataStorageIdentity(
12972 carrier.ownerHandle, carrier.storageHandle,
12973 carrier.hasStorageHandle, carrier.storageHandleKey,
12974 carrier.hasStorageHandleKey)
12975 .status;
12976 decision.typedCarrierIndex =
12977 carrier.capability == DwgDataStorageCarrier::WriterCapability::Typed
12978 ? index
12979 : invalidIndex;
12980 decision.rawCarrierIndex =
12981 carrier.capability == DwgDataStorageCarrier::WriterCapability::Raw
12982 ? index
12983 : invalidIndex;
12984 decision.selectedPhase = carrier.phase;
12985 decision.selectedCapability = carrier.capability;
12986 const std::size_t decisionIndex =
12987 m_dwgDataStorageReplayPlan.decisions.size();
12988 m_dwgDataStorageReplayPlan.decisions.push_back(decision);
12989 m_dwgDataStorageReplayPlan.decisionByCarrier[index] = decisionIndex;
12990 m_dwgDataStorageReplayPlan.decisionByOwner.emplace(carrier.ownerHandle,
12991 decisionIndex);
12992 }
12993 }
12994 return m_dwgDataStorageReplayPlan.valid;
12995 }
12996
12997 // The current DWG writer emits the common-object presence bit and raw
12998 // modeler/surface bodies, but has no independent wire field to patch an
12999 // explicit DataStorage identity. Such a link is safe only when it names
13000 // the entity's own handle; reject a non-owner identity before CLASSES and
13001 // OBJECTS can observe different decisions. This validation is independent
13002 // of carrier collection because these families are currently raw-only.
13003 const auto validateGeometryStorageIdentities = [&]() {
13004 for (const auto &geometry : metadata.modelerGeometry()) {
13005 const DwgDataStorageIdentityResult identity =
13006 resolveDwgDataStorageIdentity(
13007 geometry.handle, geometry.dataStorageHandle,
13008 geometry.dataStorageHandle != 0, geometry.dataStorageHandleKey,
13009 !geometry.dataStorageHandleKey.empty());
13010 if (!geometry.hasDsData || identity.matchesOwnerBySourceValue) {
13011 continue;
13012 }
13013 m_dwgDataStorageReplayPlan.valid = false;
13014 RS_DEBUGRS_Debug::instance()->print(
13015 RS_Debug::D_WARNING,
13016 "RS_FilterDXFRW: explicit DataStorage identity for modeler "
13017 "owner 0x%X is not independently patchable",
13018 geometry.handle);
13019 }
13020 for (const auto &surface : metadata.surfaceGeometry()) {
13021 const DwgDataStorageIdentityResult identity =
13022 resolveDwgDataStorageIdentity(
13023 surface.handle, surface.dataStorageHandle,
13024 surface.dataStorageHandle != 0, surface.dataStorageHandleKey,
13025 !surface.dataStorageHandleKey.empty());
13026 if (!surface.hasDsData || identity.matchesOwnerBySourceValue) {
13027 continue;
13028 }
13029 m_dwgDataStorageReplayPlan.valid = false;
13030 RS_DEBUGRS_Debug::instance()->print(
13031 RS_Debug::D_WARNING,
13032 "RS_FilterDXFRW: explicit DataStorage identity for surface "
13033 "owner 0x%X is not independently patchable",
13034 surface.handle);
13035 }
13036 };
13037 validateGeometryStorageIdentities();
13038 for (const auto &carrier : m_dwgDataStorageReplayPlan.carriers) {
13039 if (carrier.capability != DwgDataStorageCarrier::WriterCapability::Typed ||
13040 carrier.ownerHandle == 0 || !carrierCapabilityAvailable(carrier))
13041 continue;
13042 const DwgDataStorageIdentityResult identity = resolveCarrierIdentity(
13043 carrier, invalidIndex, invalidIndex, carrier.storageHandle,
13044 carrier.hasStorageHandleKey
13045 ? normalizeDwgDataStorageHandleKey(carrier.storageHandleKey)
13046 : dwgDataStorageNumericHandleKey(carrier.storageHandle));
13047 if (!identity.matchesOwnerBySourceValue) {
13048 m_dwgDataStorageReplayPlan.valid = false;
13049 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
13050 "RS_FilterDXFRW: explicit DataStorage identity for owner "
13051 "0x%X is not independently patchable",
13052 carrier.ownerHandle);
13053 }
13054 }
13055 if (!m_dwgDataStorageReplayPlan.valid)
13056 return false;
13057
13058 const auto carrierMatches = [&](const DwgDataStorageCarrier &carrier,
13059 std::uint64_t handle,
13060 const std::string &recordKey) {
13061 if (!carrier.hasStorageHandle && !carrier.hasStorageHandleKey)
13062 return false;
13063 const DwgDataStorageIdentityResult identity = resolveDwgDataStorageIdentity(
13064 carrier.ownerHandle, carrier.storageHandle, carrier.hasStorageHandle,
13065 carrier.storageHandleKey, carrier.hasStorageHandleKey);
13066 if (!identity.matchesOwnerBySourceValue)
13067 return false;
13068 const bool numericMatch =
13069 carrier.hasStorageHandle && carrier.storageHandle == handle;
13070 const bool keyMatch =
13071 carrier.hasStorageHandleKey &&
13072 normalizeDwgDataStorageHandleKey(carrier.storageHandleKey) == recordKey;
13073 return carrier.hasStorageHandle && carrier.hasStorageHandleKey
13074 ? numericMatch && keyMatch
13075 : numericMatch || keyMatch;
13076 };
13077
13078 for (std::size_t storageIndex = 0;
13079 storageIndex < metadata.dataStorages().size(); ++storageIndex) {
13080 const auto &storage = metadata.dataStorages()[storageIndex];
13081 const std::size_t rawSectionIndex =
13082 findMatchingRawSection(storage.name, storage.version);
13083 if (storage.version != version || storage.parseFailed ||
13084 !storage.structurallyValid || !storage.replayAllowed ||
13085 rawSectionIndex == invalidIndex ||
13086 !m_dwgDataStorageReplayPlan.sectionNames
13087 .insert(normalizeDwgTableName(storage.name))
13088 .second) {
13089 m_dwgDataStorageReplayPlan.valid = false;
13090 continue;
13091 }
13092 m_dwgDataStorageReplayPlan.rawSectionIndexes.insert(rawSectionIndex);
13093
13094 for (const auto &record : storage.full.records) {
13095 const std::string key =
13096 record.handleKey.empty()
13097 ? dwgDataStorageNumericHandleKey(record.handle)
13098 : normalizeDwgDataStorageHandleKey(record.handleKey);
13099 if (record.handle == 0 ||
13100 (!record.handleKey.empty() &&
13101 key != dwgDataStorageNumericHandleKey(record.handle))) {
13102 m_dwgDataStorageReplayPlan.valid = false;
13103 continue;
13104 }
13105 }
13106
13107 // Duplicate records are revision history, not a structural error.
13108 // Bind a carrier to the parser's preferred occurrence, while the raw
13109 // section replay below retains every source-order occurrence.
13110 std::map<std::string, std::size_t> preferredRecordIndexes;
13111 for (std::size_t recordIndex = 0; recordIndex < storage.full.records.size();
13112 ++recordIndex) {
13113 const auto &record = storage.full.records[recordIndex];
13114 const std::string key =
13115 record.handleKey.empty()
13116 ? dwgDataStorageNumericHandleKey(record.handle)
13117 : normalizeDwgDataStorageHandleKey(record.handleKey);
13118 if (record.handle == 0 ||
13119 (!record.handleKey.empty() &&
13120 key != dwgDataStorageNumericHandleKey(record.handle)))
13121 continue;
13122 preferredRecordIndexes.emplace(key, recordIndex);
13123 }
13124 for (auto &entry : preferredRecordIndexes) {
13125 const auto byKey = storage.full.recordIndexByHandleKey.find(entry.first);
13126 if (byKey != storage.full.recordIndexByHandleKey.end() &&
13127 byKey->second < storage.full.records.size()) {
13128 entry.second = byKey->second;
13129 continue;
13130 }
13131 std::uint64_t numeric = 0;
13132 for (const auto &record : storage.full.records) {
13133 const std::string recordKey =
13134 record.handleKey.empty()
13135 ? dwgDataStorageNumericHandleKey(record.handle)
13136 : normalizeDwgDataStorageHandleKey(record.handleKey);
13137 if (recordKey == entry.first) {
13138 numeric = record.handle;
13139 break;
13140 }
13141 }
13142 const auto byNumber = storage.full.recordIndexByHandle.find(numeric);
13143 if (byNumber != storage.full.recordIndexByHandle.end() &&
13144 byNumber->second < storage.full.records.size())
13145 entry.second = byNumber->second;
13146 }
13147
13148 for (const auto &preferred : preferredRecordIndexes) {
13149 const std::string &key = preferred.first;
13150 const std::size_t recordIndex = preferred.second;
13151 if (recordIndex >= storage.full.records.size()) {
13152 m_dwgDataStorageReplayPlan.valid = false;
13153 continue;
13154 }
13155 const auto &record = storage.full.records[recordIndex];
13156 if (record.handle > std::numeric_limits<std::uint32_t>::max()) {
13157 // The low-level parser retains the complete UInt64 identity,
13158 // but LibreCAD object ownership and handle remapping are
13159 // UInt32. Preserve this record only through an unchanged,
13160 // same-version section replay; never narrow it for linking.
13161 RS_DEBUGRS_Debug::instance()->Log(RS_Debug::D_WARNING)
13162 << "RS_FilterDXFRW: DataStorage record 0x"
13163 << QString::number(record.handle, 16).toUpper()
13164 << " is retained as an opaque wide-identity record";
13165 m_dwgDataStorageReplayPlan.opaqueRecordIndexes[rawSectionIndex].insert(
13166 recordIndex);
13167 continue;
13168 }
13169 std::vector<std::size_t> matchingCarriers;
13170 for (std::size_t index = 0;
13171 index < m_dwgDataStorageReplayPlan.carriers.size(); ++index) {
13172 const auto &carrier = m_dwgDataStorageReplayPlan.carriers[index];
13173 if (carrier.replayAllowed &&
13174 carrierMatches(carrier, record.handle, key)) {
13175 matchingCarriers.push_back(index);
13176 }
13177 }
13178 // A replayed section must not retain a record whose owner will
13179 // not be emitted. Otherwise the section contains a dangling
13180 // identity and the output is structurally inconsistent.
13181 if (matchingCarriers.empty()) {
13182 m_dwgDataStorageReplayPlan.valid = false;
13183 RS_DEBUGRS_Debug::instance()->Log(RS_Debug::D_WARNING)
13184 << "RS_FilterDXFRW: DataStorage record 0x"
13185 << QString::number(record.handle, 16).toUpper()
13186 << " has no emitted owner";
13187 continue;
13188 }
13189 std::size_t typedCarrier = std::numeric_limits<std::size_t>::max();
13190 std::size_t rawCarrier = std::numeric_limits<std::size_t>::max();
13191 for (const std::size_t index : matchingCarriers) {
13192 const auto &carrier = m_dwgDataStorageReplayPlan.carriers[index];
13193 if (carrier.capability ==
13194 DwgDataStorageCarrier::WriterCapability::Typed &&
13195 carrierCapabilityAvailable(carrier)) {
13196 if (typedCarrier != std::numeric_limits<std::size_t>::max())
13197 m_dwgDataStorageReplayPlan.valid = false;
13198 typedCarrier = index;
13199 } else if (carrier.capability ==
13200 DwgDataStorageCarrier::WriterCapability::Raw &&
13201 carrierCapabilityAvailable(carrier)) {
13202 if (rawCarrier != std::numeric_limits<std::size_t>::max())
13203 m_dwgDataStorageReplayPlan.valid = false;
13204 rawCarrier = index;
13205 } else if (carrier.capability ==
13206 DwgDataStorageCarrier::WriterCapability::None) {
13207 // An unsupported typed writer is only a claim. It must
13208 // not block a matching raw carrier, but it still makes
13209 // the plan fail when no usable carrier exists.
13210 }
13211 }
13212 DwgDataStorageDecisionRecord decision;
13213 decision.storageIndex = storageIndex;
13214 decision.rawSectionIndex = rawSectionIndex;
13215 decision.recordIndex = recordIndex;
13216 decision.sourceRow = makeDwgDataStorageSourceRowKey(
13217 storage.name, storage.version, rawSectionIndex, recordIndex);
13218 decision.sectionName = normalizeDwgTableName(storage.name);
13219 decision.ownerHandle =
13220 typedCarrier != std::numeric_limits<std::size_t>::max()
13221 ? m_dwgDataStorageReplayPlan.carriers[typedCarrier].ownerHandle
13222 : rawCarrier != std::numeric_limits<std::size_t>::max()
13223 ? m_dwgDataStorageReplayPlan.carriers[rawCarrier].ownerHandle
13224 : 0;
13225 decision.storageHandle = record.handle;
13226 decision.storageHandleKey = key;
13227 const std::size_t selectedCarrier =
13228 typedCarrier != std::numeric_limits<std::size_t>::max() ? typedCarrier
13229 : rawCarrier;
13230 const DwgDataStorageIdentityResult identity =
13231 selectedCarrier == invalidIndex
13232 ? DwgDataStorageIdentityResult{}
13233 : resolveCarrierIdentity(
13234 m_dwgDataStorageReplayPlan.carriers[selectedCarrier],
13235 rawSectionIndex, recordIndex, record.handle, key);
13236 decision.identityMatchesOwnerBySourceValue =
13237 identity.matchesOwnerBySourceValue;
13238 decision.identityStatus = identity.status;
13239 decision.typedCarrierIndex = typedCarrier;
13240 decision.rawCarrierIndex = rawCarrier;
13241 decision.selectedCarrierIndex = selectedCarrier;
13242 if (selectedCarrier != std::numeric_limits<std::size_t>::max()) {
13243 const auto &carrier =
13244 m_dwgDataStorageReplayPlan.carriers[selectedCarrier];
13245 decision.writerFamily = carrier.writerFamily;
13246 decision.className = carrier.className;
13247 decision.recordName = carrier.recordName;
13248 decision.classNumber = carrier.classNumber;
13249 decision.binding = carrier.binding;
13250 decision.selectedPhase = carrier.phase;
13251 decision.selectedCapability = carrier.capability;
13252 }
13253 if (typedCarrier != std::numeric_limits<std::size_t>::max()) {
13254 decision.decision = DwgDataStorageDecision::EmitTyped;
13255 } else if (rawCarrier != std::numeric_limits<std::size_t>::max() &&
13256 metadata.rawObjects().size() >
13257 m_dwgDataStorageReplayPlan.carriers[rawCarrier]
13258 .rawObjectIndex) {
13259 const auto &raw =
13260 metadata
13261 .rawObjects()[m_dwgDataStorageReplayPlan.carriers[rawCarrier]
13262 .rawObjectIndex];
13263 if (!raw.rawBytes.empty() &&
13264 sameRawObjectEncodingFamily(raw.version, version) &&
13265 !isFixedStructuralDwgHandle(raw.handle) &&
13266 LC_DwgAdvancedMetadata::rawReplayBlocker(raw) ==
13267 LC_DwgAdvancedMetadata::ReplayBlocker::None) {
13268 decision.decision = DwgDataStorageDecision::EmitRaw;
13269 }
13270 }
13271 if (decision.decision == DwgDataStorageDecision::Fail ||
13272 decision.ownerHandle == 0 ||
13273 !m_dwgDataStorageReplayPlan.decisionByOwner
13274 .emplace(decision.ownerHandle,
13275 m_dwgDataStorageReplayPlan.decisions.size())
13276 .second) {
13277 m_dwgDataStorageReplayPlan.valid = false;
13278 if (decision.decision == DwgDataStorageDecision::Fail) {
13279 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
13280 "RS_FilterDXFRW: DataStorage record 0x%llX has no "
13281 "compatible writer",
13282 static_cast<unsigned long long>(record.handle));
13283 }
13284 }
13285 const std::size_t decisionIndex =
13286 m_dwgDataStorageReplayPlan.decisions.size();
13287 if (!decision.sourceRow.isValid() ||
13288 !m_dwgDataStorageReplayPlan.decisionBySourceRow
13289 .emplace(decision.sourceRow, decisionIndex)
13290 .second) {
13291 m_dwgDataStorageReplayPlan.valid = false;
13292 }
13293 if (!m_dwgDataStorageReplayPlan.decisionByStorageIdentity
13294 .emplace(storageIdentityKey(decision.sectionName, key),
13295 decisionIndex)
13296 .second) {
13297 m_dwgDataStorageReplayPlan.valid = false;
13298 }
13299 m_dwgDataStorageReplayPlan.decisions.push_back(std::move(decision));
13300 for (const std::size_t index : matchingCarriers)
13301 m_dwgDataStorageReplayPlan.decisionByCarrier[index] = decisionIndex;
13302 }
13303 }
13304
13305 for (std::size_t index = 0;
13306 index < m_dwgDataStorageReplayPlan.carriers.size(); ++index) {
13307 const auto &carrier = m_dwgDataStorageReplayPlan.carriers[index];
13308 if (m_dwgDataStorageReplayPlan.decisionByCarrier[index] == invalidIndex &&
13309 carrier.capability != DwgDataStorageCarrier::WriterCapability::None) {
13310 m_dwgDataStorageReplayPlan.valid = false;
13311 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
13312 "RS_FilterDXFRW: DataStorage carrier for owner 0x%X has "
13313 "no selected record",
13314 carrier.ownerHandle);
13315 }
13316 }
13317
13318 for (const auto &raw : metadata.rawDwgSections()) {
13319 if (normalizeDwgTableName(raw.name) != canonicalName)
13320 continue;
13321 const bool matched = std::any_of(
13322 metadata.dataStorages().cbegin(), metadata.dataStorages().cend(),
13323 [&](const LC_DwgAdvancedMetadata::DataStorageRecord &storage) {
13324 return storage.version == raw.version &&
13325 normalizeDwgTableName(storage.name) ==
13326 normalizeDwgTableName(raw.name);
13327 });
13328 if (!matched)
13329 m_dwgDataStorageReplayPlan.valid = false;
13330 }
13331 return m_dwgDataStorageReplayPlan.valid;
13332}
13333
13334bool RS_FilterDXFRW::validateDwgDataStoragePreflight(
13335 DRW::Version targetVersion) const {
13336 if (m_dwgW == nullptr || m_graphic == nullptr ||
13337 targetVersion <= DRW::AC1024) {
13338 return true;
13339 }
13340
13341 const auto &plan = m_dwgDataStorageReplayPlan;
13342 if (!plan.valid || plan.frozen || plan.version != targetVersion ||
13343 plan.generation == 0) {
13344 return false;
13345 }
13346
13347 const auto &metadata = m_graphic->dwgAdvancedMetadata();
13348 const std::size_t invalidIndex = std::numeric_limits<std::size_t>::max();
13349 std::set<DwgDataStorageSourceRowKey> seenSourceRows;
13350 for (const auto &decision : plan.decisions) {
13351 if (decision.storageIndex >= metadata.dataStorages().size() ||
13352 decision.rawSectionIndex >= metadata.rawDwgSections().size() ||
13353 decision.recordIndex == invalidIndex ||
13354 decision.selectedCarrierIndex == invalidIndex ||
13355 decision.selectedCarrierIndex >= plan.carriers.size()) {
13356 return false;
13357 }
13358 const auto &section = metadata.rawDwgSections()[decision.rawSectionIndex];
13359 const DwgDataStorageSourceRowKey expectedSourceRow =
13360 makeDwgDataStorageSourceRowKey(section.name, section.version,
13361 decision.rawSectionIndex,
13362 decision.recordIndex);
13363 if (!decision.sourceRow.isValid() ||
13364 decision.sourceRow != expectedSourceRow ||
13365 !seenSourceRows.insert(decision.sourceRow).second) {
13366 return false;
13367 }
13368
13369 const auto &storage = metadata.dataStorages()[decision.storageIndex];
13370 if (storage.version != targetVersion || storage.parseFailed ||
13371 !storage.structurallyValid || !storage.replayAllowed ||
13372 normalizeDwgTableName(storage.name) !=
13373 normalizeDwgTableName(section.name) ||
13374 section.version != targetVersion ||
13375 section.replayState !=
13376 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
13377 section.data.empty() ||
13378 decision.recordIndex >= storage.full.records.size()) {
13379 return false;
13380 }
13381
13382 const auto &record = storage.full.records[decision.recordIndex];
13383 const std::string recordKey =
13384 record.handleKey.empty()
13385 ? dwgDataStorageNumericHandleKey(record.handle)
13386 : normalizeDwgDataStorageHandleKey(record.handleKey);
13387 if (record.handle == 0 ||
13388 record.handle > std::numeric_limits<std::uint32_t>::max() ||
13389 decision.ownerHandle != record.handle ||
13390 decision.storageHandle != record.handle ||
13391 decision.storageHandleKey.empty() ||
13392 normalizeDwgDataStorageHandleKey(decision.storageHandleKey) !=
13393 recordKey) {
13394 return false;
13395 }
13396 if (decision.identityStatus == DwgDataStorageIdentityStatus::Conflict ||
13397 decision.identityStatus ==
13398 DwgDataStorageIdentityStatus::OwnerMismatch) {
13399 return false;
13400 }
13401 if (hasDwgWriteHandleRemap(decision.ownerHandle) ||
13402 m_dwgWriteEntityHandleRemap.count(decision.ownerHandle) != 0 ||
13403 m_dwgWriteDuplicateEntityHandles.count(decision.ownerHandle) != 0) {
13404 return false;
13405 }
13406
13407 const auto &carrier = plan.carriers[decision.selectedCarrierIndex];
13408 const bool emitRaw = decision.decision == DwgDataStorageDecision::EmitRaw;
13409 const DwgDataStorageCarrier::Kind expectedKind =
13410 emitRaw ? DwgDataStorageCarrier::Kind::Raw
13411 : DwgDataStorageCarrier::Kind::Typed;
13412 const DwgDataStorageCarrier::WriterCapability expectedCapability =
13413 emitRaw ? DwgDataStorageCarrier::WriterCapability::Raw
13414 : DwgDataStorageCarrier::WriterCapability::Typed;
13415 if (decision.decision != DwgDataStorageDecision::EmitRaw &&
13416 decision.decision != DwgDataStorageDecision::EmitTyped) {
13417 return false;
13418 }
13419 if (carrier.kind != expectedKind ||
13420 carrier.capability != expectedCapability ||
13421 carrier.phase != DwgDataStorageCarrier::EmissionPhase::Objects ||
13422 carrier.ownerHandle != decision.ownerHandle ||
13423 carrier.binding != decision.binding || !carrier.replayAllowed) {
13424 return false;
13425 }
13426
13427 if (!emitRaw) {
13428 DwgDataStorageWriterCapability capability;
13429 if (!getDwgDataStorageWriterCapability(carrier.binding, capability) ||
13430 !m_dwgW->canWriteDwgDataStorageBinding(carrier.binding) ||
13431 carrier.writerFamily != capability.family ||
13432 carrier.className != capability.className ||
13433 carrier.recordName != capability.recordName ||
13434 (carrier.classNumber != 0 && capability.classNumber != 0 &&
13435 carrier.classNumber != capability.classNumber) ||
13436 carrier.capabilityMinVersion != capability.minVersion ||
13437 carrier.capabilityMaxVersion != capability.maxVersion) {
13438 return false;
13439 }
13440 if (carrier.binding == DwgDataStorageWriterBinding::MLeaderStyle) {
13441 const auto styleIt = std::find_if(
13442 metadata.mleaderStyles().cbegin(), metadata.mleaderStyles().cend(),
13443 [&decision](const auto &style) {
13444 return style.handle == decision.ownerHandle;
13445 });
13446 std::uint32_t payloadBitEstimate = 0;
13447 if (styleIt == metadata.mleaderStyles().cend() ||
13448 !validateDwgMLeaderStylePayload(*styleIt, targetVersion,
13449 payloadBitEstimate)) {
13450 return false;
13451 }
13452 }
13453 } else {
13454 if (decision.rawCarrierIndex != decision.selectedCarrierIndex ||
13455 carrier.rawObjectIndex >= metadata.rawObjects().size()) {
13456 return false;
13457 }
13458 const auto &raw = metadata.rawObjects()[carrier.rawObjectIndex];
13459 if (!raw.hasDataStorage || raw.handle != decision.ownerHandle ||
13460 raw.version != targetVersion || raw.rawBytes.empty() ||
13461 isFixedStructuralDwgHandle(raw.handle) ||
13462 LC_DwgAdvancedMetadata::rawReplayBlocker(raw) !=
13463 LC_DwgAdvancedMetadata::ReplayBlocker::None ||
13464 !sameRawObjectEncodingFamily(raw.version, targetVersion)) {
13465 return false;
13466 }
13467 }
13468 }
13469
13470 return seenSourceRows.size() == plan.decisions.size() &&
13471 plan.decisionBySourceRow.size() == plan.decisions.size() &&
13472 plan.decisionByStorageIdentity.size() == plan.decisions.size();
13473}
13474
13475bool RS_FilterDXFRW::freezeDwgDataStorageReplayPlan() {
13476 if (m_dwgW == nullptr || m_graphic == nullptr ||
13477 !m_dwgDataStorageReplayPlan.valid) {
13478 return false;
13479 }
13480
13481 for (auto &decision : m_dwgDataStorageReplayPlan.decisions) {
13482 if (decision.ownerHandle == 0 ||
13483 hasDwgWriteHandleRemap(decision.ownerHandle) ||
13484 m_dwgWriteEntityHandleRemap.count(decision.ownerHandle) != 0 ||
13485 m_dwgWriteDuplicateEntityHandles.count(decision.ownerHandle) != 0) {
13486 RS_DEBUGRS_Debug::instance()->print(
13487 RS_Debug::D_WARNING,
13488 "RS_FilterDXFRW: DataStorage owner 0x%X cannot be frozen "
13489 "(handleRemap=%d entityRemap=%d duplicate=%d)",
13490 decision.ownerHandle,
13491 hasDwgWriteHandleRemap(decision.ownerHandle) ? 1 : 0,
13492 m_dwgWriteEntityHandleRemap.count(decision.ownerHandle) != 0 ? 1 : 0,
13493 m_dwgWriteDuplicateEntityHandles.count(decision.ownerHandle) != 0
13494 ? 1
13495 : 0);
13496 decision.decision = DwgDataStorageDecision::Fail;
13497 m_dwgDataStorageReplayPlan.valid = false;
13498 }
13499 }
13500 if (!m_dwgDataStorageReplayPlan.valid)
13501 return false;
13502
13503 if (m_dwgW->getVersion() > DRW::AC1024) {
13504 for (auto &decision : m_dwgDataStorageReplayPlan.decisions) {
13505 if (m_nextDwgDataStorageAdmissionToken == 0 ||
13506 m_nextDwgDataStorageAdmissionToken ==
13507 std::numeric_limits<std::uint64_t>::max()) {
13508 m_dwgDataStorageReplayPlan.valid = false;
13509 break;
13510 }
13511 decision.admissionToken = m_nextDwgDataStorageAdmissionToken++;
13512 }
13513 if (!m_dwgDataStorageReplayPlan.valid)
13514 return false;
13515
13516 const auto invalidIndex = std::numeric_limits<std::size_t>::max();
13517 const auto &metadata = m_graphic->dwgAdvancedMetadata();
13518 std::map<std::uint32_t, DwgDataStorageCommitReceipt> plannedReceipts;
13519 for (const auto &decision : m_dwgDataStorageReplayPlan.decisions) {
13520 if (decision.ownerHandle == 0 ||
13521 decision.selectedCarrierIndex == invalidIndex ||
13522 decision.selectedCarrierIndex >=
13523 m_dwgDataStorageReplayPlan.carriers.size()) {
13524 m_dwgDataStorageReplayPlan.valid = false;
13525 break;
13526 }
13527 const auto &carrier =
13528 m_dwgDataStorageReplayPlan.carriers[decision.selectedCarrierIndex];
13529 DwgDataStorageCommitReceipt receipt;
13530 receipt.generation = m_dwgDataStorageReplayPlan.generation;
13531 receipt.carrierIndex = decision.selectedCarrierIndex;
13532 receipt.kind = decision.decision == DwgDataStorageDecision::EmitRaw
13533 ? DwgDataStorageCarrier::Kind::Raw
13534 : DwgDataStorageCarrier::Kind::Typed;
13535 receipt.binding = carrier.binding;
13536 receipt.phase = carrier.phase;
13537 receipt.capability = carrier.capability;
13538 receipt.state = DwgDataStorageLedgerState::Planned;
13539 receipt.sourceKind = DwgWriteSourceKind::Object;
13540 receipt.classNumber = carrier.classNumber;
13541 receipt.admissionToken = decision.admissionToken;
13542 receipt.sourceRow = decision.sourceRow;
13543 receipt.writerFamily = carrier.writerFamily;
13544 receipt.className = carrier.className;
13545 receipt.recordName = carrier.recordName;
13546 receipt.sourceHandle = decision.ownerHandle;
13547 receipt.outputHandle = resolveDwgWriteHandle(decision.ownerHandle);
13548 if (carrier.kind == DwgDataStorageCarrier::Kind::Raw &&
13549 carrier.rawObjectIndex < metadata.rawObjects().size()) {
13550 const auto &raw = metadata.rawObjects()[carrier.rawObjectIndex];
13551 receipt.sourceKind = raw.isEntity ? DwgWriteSourceKind::Entity
13552 : DwgWriteSourceKind::Object;
13553 receipt.expectedBlockOwner = raw.blockOwnerHandle != DRW::NoHandle
13554 ? raw.blockOwnerHandle
13555 : raw.parentHandle;
13556 }
13557 if (!plannedReceipts.emplace(receipt.sourceHandle, std::move(receipt))
13558 .second) {
13559 m_dwgDataStorageReplayPlan.valid = false;
13560 break;
13561 }
13562 }
13563 if (!m_dwgDataStorageReplayPlan.valid)
13564 return false;
13565 m_dwgDataStorageCommitReceipts = std::move(plannedReceipts);
13566 }
13567 if (!m_dwgDataStorageReplayPlan.valid)
13568 return false;
13569 if (m_dwgW->getVersion() > DRW::AC1024) {
13570 DwgDataStorageAdmissionSummary summary;
13571 summary.version = m_dwgDataStorageReplayPlan.version;
13572 summary.generation = m_dwgDataStorageReplayPlan.generation;
13573 summary.valid = true;
13574 summary.entries.reserve(m_dwgDataStorageReplayPlan.decisions.size());
13575 const auto invalidIndex = std::numeric_limits<std::size_t>::max();
13576 const auto &metadata = m_graphic->dwgAdvancedMetadata();
13577 for (std::size_t decisionIndex = 0;
13578 decisionIndex < m_dwgDataStorageReplayPlan.decisions.size();
13579 ++decisionIndex) {
13580 const auto &decision =
13581 m_dwgDataStorageReplayPlan.decisions[decisionIndex];
13582 if ((decision.decision != DwgDataStorageDecision::EmitTyped &&
13583 decision.decision != DwgDataStorageDecision::EmitRaw) ||
13584 decision.selectedCarrierIndex == invalidIndex ||
13585 decision.selectedCarrierIndex >=
13586 m_dwgDataStorageReplayPlan.carriers.size()) {
13587 summary.valid = false;
13588 break;
13589 }
13590 const auto &carrier =
13591 m_dwgDataStorageReplayPlan.carriers[decision.selectedCarrierIndex];
13592 DwgDataStorageAdmissionEntry entry;
13593 entry.decisionIndex = decisionIndex;
13594 entry.carrierIndex = decision.selectedCarrierIndex;
13595 entry.sourceHandle = decision.ownerHandle;
13596 entry.outputHandle = resolveDwgWriteHandle(decision.ownerHandle);
13597 entry.classNumber = carrier.classNumber;
13598 entry.admissionToken = decision.admissionToken;
13599 entry.sourceRow = decision.sourceRow;
13600 entry.kind = carrier.kind;
13601 entry.phase = carrier.phase;
13602 entry.capability = carrier.capability;
13603 entry.binding = carrier.binding;
13604 entry.sourceKind = DwgWriteSourceKind::Object;
13605 if (carrier.kind == DwgDataStorageCarrier::Kind::Raw) {
13606 if (carrier.rawObjectIndex >= metadata.rawObjects().size()) {
13607 summary.valid = false;
13608 break;
13609 }
13610 const auto &raw = metadata.rawObjects()[carrier.rawObjectIndex];
13611 entry.sourceKind = raw.isEntity ? DwgWriteSourceKind::Entity
13612 : DwgWriteSourceKind::Object;
13613 entry.expectedBlockOwner = raw.blockOwnerHandle != DRW::NoHandle
13614 ? raw.blockOwnerHandle
13615 : raw.parentHandle;
13616 entry.classRequired = raw.isCustomClass;
13617 } else {
13618 DwgDataStorageWriterCapability capability;
13619 if (!getDwgDataStorageWriterCapability(carrier.binding, capability)) {
13620 summary.valid = false;
13621 break;
13622 }
13623 entry.classRequired = capability.requiresClass;
13624 if (carrier.binding == DwgDataStorageWriterBinding::MLeaderStyle) {
13625 const auto styleIt = std::find_if(
13626 metadata.mleaderStyles().cbegin(),
13627 metadata.mleaderStyles().cend(), [&decision](const auto &style) {
13628 return style.handle == decision.ownerHandle;
13629 });
13630 if (styleIt == metadata.mleaderStyles().cend() ||
13631 !validateDwgMLeaderStylePayload(*styleIt, m_dwgW->getVersion(),
13632 entry.payloadBitEstimate)) {
13633 summary.valid = false;
13634 break;
13635 }
13636 entry.payloadValidated = true;
13637 }
13638 }
13639 const auto receiptIt =
13640 m_dwgDataStorageCommitReceipts.find(decision.ownerHandle);
13641 if (entry.sourceHandle == 0 || entry.outputHandle == 0 ||
13642 receiptIt == m_dwgDataStorageCommitReceipts.end() ||
13643 receiptIt->second.generation !=
13644 m_dwgDataStorageReplayPlan.generation ||
13645 receiptIt->second.carrierIndex != decision.selectedCarrierIndex ||
13646 receiptIt->second.kind != carrier.kind ||
13647 receiptIt->second.admissionToken != entry.admissionToken ||
13648 receiptIt->second.outputHandle != entry.outputHandle) {
13649 summary.valid = false;
13650 break;
13651 }
13652 summary.entries.push_back(std::move(entry));
13653 }
13654 if (summary.entries.size() != m_dwgDataStorageReplayPlan.decisions.size()) {
13655 summary.valid = false;
13656 }
13657 if (!summary.valid) {
13658 m_dwgDataStorageReplayPlan.valid = false;
13659 return false;
13660 }
13661 m_dwgDataStorageAdmissionSummary = std::move(summary);
13662 }
13663 m_dwgDataStorageReplayPlan.frozen = true;
13664
13665 for (const std::uint32_t handle : m_dwgWriteDataStorageKnownHandles)
13666 m_dwgWriteKnownHandles.erase(handle);
13667 m_dwgWriteDataStorageKnownHandles.clear();
13668 for (const auto &decision : m_dwgDataStorageReplayPlan.decisions) {
13669 if (decision.decision != DwgDataStorageDecision::EmitRaw)
13670 continue;
13671 const auto &carrier =
13672 m_dwgDataStorageReplayPlan.carriers[decision.rawCarrierIndex];
13673 m_dwgWriteKnownHandles.insert(carrier.ownerHandle);
13674 m_dwgWriteDataStorageKnownHandles.insert(carrier.ownerHandle);
13675 }
13676 return true;
13677}
13678
13679bool RS_FilterDXFRW::validateDwgDataStorageAdmissionSummary(
13680 bool requireClassAdmission) const {
13681 if (m_dwgW == nullptr || m_dwgW->getVersion() <= DRW::AC1024)
13682 return true;
13683
13684 const auto &plan = m_dwgDataStorageReplayPlan;
13685 const auto &summary = m_dwgDataStorageAdmissionSummary;
13686 if (!plan.frozen || !plan.valid || plan.version != m_dwgW->getVersion() ||
13687 plan.generation == 0 || !summary.valid ||
13688 summary.version != plan.version ||
13689 summary.generation != plan.generation ||
13690 summary.entries.size() != plan.decisions.size() ||
13691 m_dwgDataStorageCommitReceipts.size() != summary.entries.size()) {
13692 return false;
13693 }
13694
13695 std::set<std::uint32_t> seenHandles;
13696 for (const auto &entry : summary.entries) {
13697 if (entry.decisionIndex >= plan.decisions.size() ||
13698 entry.carrierIndex >= plan.carriers.size() || entry.sourceHandle == 0 ||
13699 entry.outputHandle == 0 ||
13700 !seenHandles.insert(entry.sourceHandle).second) {
13701 return false;
13702 }
13703 const auto &decision = plan.decisions[entry.decisionIndex];
13704 const auto &carrier = plan.carriers[entry.carrierIndex];
13705 if (m_graphic == nullptr ||
13706 entry.sourceRow.rawSectionIndex >=
13707 m_graphic->dwgAdvancedMetadata().rawDwgSections().size() ||
13708 entry.sourceRow.recordIndex ==
13709 std::numeric_limits<std::size_t>::max()) {
13710 return false;
13711 }
13712 const auto &sourceSection =
13713 m_graphic->dwgAdvancedMetadata()
13714 .rawDwgSections()[entry.sourceRow.rawSectionIndex];
13715 const auto expectedSourceRow = makeDwgDataStorageSourceRowKey(
13716 sourceSection.name, sourceSection.version,
13717 entry.sourceRow.rawSectionIndex, entry.sourceRow.recordIndex);
13718 if (decision.storageIndex >=
13719 m_graphic->dwgAdvancedMetadata().dataStorages().size()) {
13720 return false;
13721 }
13722 const auto &sourceStorage =
13723 m_graphic->dwgAdvancedMetadata().dataStorages()[decision.storageIndex];
13724 if (decision.recordIndex >= sourceStorage.full.records.size() ||
13725 sourceStorage.version != sourceSection.version ||
13726 normalizeDwgTableName(sourceStorage.name) !=
13727 normalizeDwgTableName(sourceSection.name)) {
13728 return false;
13729 }
13730 const auto &sourceRecord = sourceStorage.full.records[decision.recordIndex];
13731 const std::string sourceRecordKey =
13732 sourceRecord.handleKey.empty()
13733 ? dwgDataStorageNumericHandleKey(sourceRecord.handle)
13734 : normalizeDwgDataStorageHandleKey(sourceRecord.handleKey);
13735 if (sourceRecord.handle == 0 ||
13736 sourceRecord.handle != decision.ownerHandle ||
13737 decision.storageHandle != sourceRecord.handle ||
13738 decision.storageHandleKey.empty() ||
13739 normalizeDwgDataStorageHandleKey(decision.storageHandleKey) !=
13740 sourceRecordKey) {
13741 return false;
13742 }
13743 const auto ownerIt = plan.decisionByOwner.find(entry.sourceHandle);
13744 if (ownerIt == plan.decisionByOwner.end() ||
13745 ownerIt->second != entry.decisionIndex ||
13746 decision.ownerHandle != entry.sourceHandle ||
13747 decision.selectedCarrierIndex != entry.carrierIndex ||
13748 decision.selectedPhase != entry.phase ||
13749 decision.selectedCapability != entry.capability ||
13750 decision.admissionToken == 0 ||
13751 decision.admissionToken != entry.admissionToken ||
13752 !decision.sourceRow.isValid() ||
13753 decision.rawSectionIndex != entry.sourceRow.rawSectionIndex ||
13754 decision.recordIndex != entry.sourceRow.recordIndex ||
13755 decision.sourceRow != expectedSourceRow ||
13756 entry.sourceRow != expectedSourceRow ||
13757 decision.binding != entry.binding ||
13758 carrier.ownerHandle != entry.sourceHandle ||
13759 carrier.kind != entry.kind || carrier.phase != entry.phase ||
13760 carrier.capability != entry.capability ||
13761 carrier.binding != entry.binding ||
13762 carrier.classNumber != entry.classNumber ||
13763 resolveDwgWriteHandle(entry.sourceHandle) != entry.outputHandle) {
13764 return false;
13765 }
13766
13767 DwgWriteSourceKind currentSourceKind = DwgWriteSourceKind::Object;
13768 std::uint32_t currentBlockOwner = DRW::NoHandle;
13769 bool currentClassRequired = false;
13770 if (carrier.kind == DwgDataStorageCarrier::Kind::Raw) {
13771 if (m_graphic == nullptr ||
13772 carrier.rawObjectIndex >=
13773 m_graphic->dwgAdvancedMetadata().rawObjects().size()) {
13774 return false;
13775 }
13776 const auto &raw =
13777 m_graphic->dwgAdvancedMetadata().rawObjects()[carrier.rawObjectIndex];
13778 currentSourceKind = raw.isEntity ? DwgWriteSourceKind::Entity
13779 : DwgWriteSourceKind::Object;
13780 currentBlockOwner = raw.blockOwnerHandle != DRW::NoHandle
13781 ? raw.blockOwnerHandle
13782 : raw.parentHandle;
13783 currentClassRequired = raw.isCustomClass;
13784 } else {
13785 DwgDataStorageWriterCapability capability;
13786 if (!getDwgDataStorageWriterCapability(carrier.binding, capability)) {
13787 return false;
13788 }
13789 currentClassRequired = capability.requiresClass;
13790 }
13791 if (entry.sourceKind != currentSourceKind ||
13792 entry.expectedBlockOwner != currentBlockOwner ||
13793 entry.classRequired != currentClassRequired) {
13794 return false;
13795 }
13796
13797 if (carrier.binding == DwgDataStorageWriterBinding::MLeaderStyle) {
13798 if (m_graphic == nullptr)
13799 return false;
13800 const auto styleIt = std::find_if(
13801 m_graphic->dwgAdvancedMetadata().mleaderStyles().cbegin(),
13802 m_graphic->dwgAdvancedMetadata().mleaderStyles().cend(),
13803 [&entry](const auto &style) {
13804 return style.handle == entry.sourceHandle;
13805 });
13806 std::uint32_t payloadBitEstimate = 0;
13807 if (styleIt == m_graphic->dwgAdvancedMetadata().mleaderStyles().cend() ||
13808 !entry.payloadValidated ||
13809 !validateDwgMLeaderStylePayload(*styleIt, m_dwgW->getVersion(),
13810 payloadBitEstimate) ||
13811 entry.payloadBitEstimate != payloadBitEstimate) {
13812 return false;
13813 }
13814 } else if (entry.payloadValidated || entry.payloadBitEstimate != 0) {
13815 return false;
13816 }
13817
13818 const auto receiptIt =
13819 m_dwgDataStorageCommitReceipts.find(entry.sourceHandle);
13820 if (receiptIt == m_dwgDataStorageCommitReceipts.end())
13821 return false;
13822 const auto &receipt = receiptIt->second;
13823 if (receipt.generation != summary.generation ||
13824 receipt.carrierIndex != entry.carrierIndex ||
13825 receipt.kind != entry.kind || receipt.binding != entry.binding ||
13826 receipt.phase != entry.phase ||
13827 receipt.capability != entry.capability ||
13828 receipt.sourceHandle != entry.sourceHandle ||
13829 receipt.outputHandle != entry.outputHandle ||
13830 receipt.sourceKind != entry.sourceKind ||
13831 receipt.expectedBlockOwner != entry.expectedBlockOwner ||
13832 receipt.classNumber != entry.classNumber ||
13833 receipt.admissionToken != entry.admissionToken ||
13834 receipt.sourceRow != entry.sourceRow ||
13835 receipt.state == DwgDataStorageLedgerState::Failed ||
13836 receipt.state == DwgDataStorageLedgerState::Suppressed ||
13837 (entry.classRequired && requireClassAdmission &&
13838 !receipt.classAdmitted)) {
13839 return false;
13840 }
13841 if (receipt.objectCommitted &&
13842 (!receipt.frameValid || !receipt.frame.valid ||
13843 receipt.writerClassNumber != receipt.frame.classNumber ||
13844 receipt.frame.admissionToken != receipt.admissionToken)) {
13845 return false;
13846 }
13847 }
13848 return seenHandles.size() == plan.decisions.size();
13849}
13850
13851bool RS_FilterDXFRW::recordDwgDataStorageClassAdmission(
13852 std::uint32_t sourceHandle, DwgDataStorageCarrier::Kind kind) {
13853 if (m_dwgW == nullptr || m_dwgW->getVersion() <= DRW::AC1024)
13854 return true;
13855 const auto &plan = m_dwgDataStorageReplayPlan;
13856 if (m_graphic == nullptr || !plan.frozen || !plan.valid ||
13857 plan.version != m_dwgW->getVersion() || plan.generation == 0)
13858 return false;
13859 const auto decisionIt = plan.decisionByOwner.find(sourceHandle);
13860 if (decisionIt == plan.decisionByOwner.end() ||
13861 decisionIt->second >= plan.decisions.size())
13862 return false;
13863 const auto &decision = plan.decisions[decisionIt->second];
13864 const DwgDataStorageCarrier::Kind expectedKind =
13865 decision.decision == DwgDataStorageDecision::EmitRaw
13866 ? DwgDataStorageCarrier::Kind::Raw
13867 : DwgDataStorageCarrier::Kind::Typed;
13868 if (kind != expectedKind || decision.selectedCarrierIndex >=
13869 m_dwgDataStorageReplayPlan.carriers.size()) {
13870 return false;
13871 }
13872
13873 const auto &carrier = plan.carriers[decision.selectedCarrierIndex];
13874 auto [it, inserted] = m_dwgDataStorageCommitReceipts.emplace(
13875 sourceHandle, DwgDataStorageCommitReceipt{});
13876 auto &receipt = it->second;
13877 if (inserted) {
13878 receipt.generation = plan.generation;
13879 receipt.carrierIndex = decision.selectedCarrierIndex;
13880 receipt.kind = expectedKind;
13881 receipt.binding = carrier.binding;
13882 receipt.phase = carrier.phase;
13883 receipt.capability = carrier.capability;
13884 receipt.sourceHandle = sourceHandle;
13885 receipt.writerFamily = carrier.writerFamily;
13886 receipt.className = carrier.className;
13887 receipt.recordName = carrier.recordName;
13888 receipt.classNumber = carrier.classNumber;
13889 receipt.admissionToken = decision.admissionToken;
13890 receipt.sourceRow = decision.sourceRow;
13891 receipt.sourceKind = DwgWriteSourceKind::Object;
13892 if (carrier.kind == DwgDataStorageCarrier::Kind::Raw &&
13893 carrier.rawObjectIndex <
13894 m_graphic->dwgAdvancedMetadata().rawObjects().size()) {
13895 const auto &raw =
13896 m_graphic->dwgAdvancedMetadata().rawObjects()[carrier.rawObjectIndex];
13897 receipt.sourceKind = raw.isEntity ? DwgWriteSourceKind::Entity
13898 : DwgWriteSourceKind::Object;
13899 receipt.expectedBlockOwner = raw.blockOwnerHandle != DRW::NoHandle
13900 ? raw.blockOwnerHandle
13901 : raw.parentHandle;
13902 }
13903 }
13904 if (receipt.generation != plan.generation ||
13905 receipt.carrierIndex != decision.selectedCarrierIndex ||
13906 receipt.kind != expectedKind || receipt.binding != carrier.binding ||
13907 receipt.writerFamily != carrier.writerFamily ||
13908 receipt.className != carrier.className ||
13909 receipt.recordName != carrier.recordName ||
13910 receipt.classNumber != carrier.classNumber ||
13911 receipt.admissionToken != decision.admissionToken ||
13912 receipt.sourceRow != decision.sourceRow ||
13913 receipt.state != DwgDataStorageLedgerState::Planned ||
13914 receipt.classAdmitted || receipt.objectCommitted ||
13915 receipt.sectionCommitted) {
13916 return false;
13917 }
13918 receipt.classAdmitted = true;
13919 receipt.state = DwgDataStorageLedgerState::ClassAdmitted;
13920 return true;
13921}
13922
13923bool RS_FilterDXFRW::bindDwgDataStorageFrame(std::uint32_t sourceHandle,
13924 DwgDataStorageCarrier::Kind kind) {
13925 if (m_dwgW == nullptr)
13926 return false;
13927 m_dwgW->clearDwgObjectFrameProvenance();
13928 if (m_dwgW->getVersion() <= DRW::AC1024)
13929 return true;
13930
13931 const auto *decision = dataStorageDecisionForOwner(sourceHandle);
13932 if (decision == nullptr)
13933 return true;
13934 const DwgDataStorageCarrier::Kind expectedKind =
13935 decision->decision == DwgDataStorageDecision::EmitRaw
13936 ? DwgDataStorageCarrier::Kind::Raw
13937 : DwgDataStorageCarrier::Kind::Typed;
13938 if (kind != expectedKind ||
13939 decision->selectedPhase !=
13940 DwgDataStorageCarrier::EmissionPhase::Objects ||
13941 decision->selectedCarrierIndex >=
13942 m_dwgDataStorageReplayPlan.carriers.size()) {
13943 return false;
13944 }
13945
13946 const auto &carrier =
13947 m_dwgDataStorageReplayPlan.carriers[decision->selectedCarrierIndex];
13948 std::uint16_t writerClassNumber = 0;
13949 std::uint16_t writerOperation = 0;
13950 if (kind == DwgDataStorageCarrier::Kind::Typed) {
13951 DwgDataStorageWriterCapability capability;
13952 if (!getDwgDataStorageWriterCapability(carrier.binding, capability))
13953 return false;
13954 writerOperation = static_cast<std::uint16_t>(capability.operation);
13955 if (!carrier.className.empty()) {
13956 writerClassNumber = m_dwgW->getDwgTypedClassNumber(
13957 carrier.className.c_str(), carrier.recordName.c_str(),
13958 carrier.classNumber);
13959 if (writerClassNumber == 0)
13960 return false;
13961 }
13962 }
13963 // Both typed and raw classes can be remapped during admission. Query the
13964 // writer's owner ledger so the frame receipt carries the actual ordinal.
13965 const std::uint32_t outputHandle = resolveDwgWriteHandle(sourceHandle);
13966 std::uint16_t admittedClassNumber = 0;
13967 if (m_dwgW->getDwgClassInstanceClass(outputHandle, admittedClassNumber)) {
13968 if (writerClassNumber != 0 && writerClassNumber != admittedClassNumber)
13969 return false;
13970 writerClassNumber = admittedClassNumber;
13971 }
13972 return m_dwgW->setDwgObjectFrameProvenance(writerClassNumber, writerOperation,
13973 decision->admissionToken);
13974}
13975
13976bool RS_FilterDXFRW::recordDwgDataStorageObjectCommit(
13977 std::uint32_t sourceHandle, DwgDataStorageCarrier::Kind kind,
13978 DwgWriteSourceKind sourceKind, std::uint32_t outputHandle,
13979 const DRW::DwgObjectFrameReceipt &frame) {
13980 if (m_dwgW == nullptr || m_dwgW->getVersion() <= DRW::AC1024)
13981 return true;
13982 const auto &plan = m_dwgDataStorageReplayPlan;
13983 if (m_graphic == nullptr || !plan.frozen || !plan.valid ||
13984 plan.version != m_dwgW->getVersion() || plan.generation == 0)
13985 return false;
13986 const auto decisionIt = plan.decisionByOwner.find(sourceHandle);
13987 if (decisionIt == plan.decisionByOwner.end() ||
13988 decisionIt->second >= plan.decisions.size())
13989 return false;
13990 const auto &decision = plan.decisions[decisionIt->second];
13991 const DwgDataStorageCarrier::Kind expectedKind =
13992 decision.decision == DwgDataStorageDecision::EmitRaw
13993 ? DwgDataStorageCarrier::Kind::Raw
13994 : DwgDataStorageCarrier::Kind::Typed;
13995 if (kind != expectedKind ||
13996 decision.selectedCarrierIndex >=
13997 m_dwgDataStorageReplayPlan.carriers.size() ||
13998 !frame.valid || outputHandle == 0 || frame.objectHandle != outputHandle ||
13999 frame.version != m_dwgW->getVersion()) {
14000 return false;
14001 }
14002 const auto &carrier = plan.carriers[decision.selectedCarrierIndex];
14003 DwgDataStorageWriterCapability writerCapability;
14004 if (expectedKind == DwgDataStorageCarrier::Kind::Typed &&
14005 !getDwgDataStorageWriterCapability(carrier.binding, writerCapability)) {
14006 return false;
14007 }
14008 const std::uint16_t expectedWriterOperation =
14009 expectedKind == DwgDataStorageCarrier::Kind::Typed
14010 ? static_cast<std::uint16_t>(writerCapability.operation)
14011 : 0;
14012 const std::uint16_t expectedWriterClass =
14013 expectedKind == DwgDataStorageCarrier::Kind::Typed &&
14014 !carrier.className.empty()
14015 ? m_dwgW->getDwgTypedClassNumber(carrier.className.c_str(),
14016 carrier.recordName.c_str(),
14017 carrier.classNumber)
14018 : 0;
14019 const bool classRequired =
14020 expectedKind == DwgDataStorageCarrier::Kind::Raw
14021 ? (carrier.rawObjectIndex <
14022 m_graphic->dwgAdvancedMetadata().rawObjects().size() &&
14023 m_graphic->dwgAdvancedMetadata()
14024 .rawObjects()[carrier.rawObjectIndex]
14025 .isCustomClass)
14026 : !carrier.className.empty();
14027 if (decision.admissionToken == 0 ||
14028 frame.admissionToken != decision.admissionToken ||
14029 frame.writerOperation != expectedWriterOperation ||
14030 (classRequired && frame.classNumber == 0) ||
14031 (expectedWriterClass != 0 && frame.classNumber != expectedWriterClass)) {
14032 return false;
14033 }
14034 auto [it, inserted] = m_dwgDataStorageCommitReceipts.emplace(
14035 sourceHandle, DwgDataStorageCommitReceipt{});
14036 auto &receipt = it->second;
14037 if (inserted) {
14038 receipt.generation = plan.generation;
14039 receipt.carrierIndex = decision.selectedCarrierIndex;
14040 receipt.kind = expectedKind;
14041 receipt.binding = carrier.binding;
14042 receipt.phase = carrier.phase;
14043 receipt.capability = carrier.capability;
14044 receipt.sourceHandle = sourceHandle;
14045 receipt.writerFamily = carrier.writerFamily;
14046 receipt.className = carrier.className;
14047 receipt.recordName = carrier.recordName;
14048 receipt.classNumber = carrier.classNumber;
14049 receipt.admissionToken = decision.admissionToken;
14050 }
14051 if (receipt.generation != plan.generation ||
14052 receipt.carrierIndex != decision.selectedCarrierIndex ||
14053 receipt.kind != expectedKind || receipt.binding != carrier.binding ||
14054 receipt.phase != carrier.phase ||
14055 receipt.capability != carrier.capability ||
14056 receipt.writerFamily != carrier.writerFamily ||
14057 receipt.className != carrier.className ||
14058 receipt.recordName != carrier.recordName ||
14059 receipt.classNumber != carrier.classNumber ||
14060 receipt.admissionToken != decision.admissionToken ||
14061 receipt.sourceRow != decision.sourceRow ||
14062 receipt.sourceKind != sourceKind ||
14063 (classRequired && !receipt.classAdmitted) ||
14064 (receipt.outputHandle != 0 && receipt.outputHandle != outputHandle) ||
14065 (receipt.state != DwgDataStorageLedgerState::Planned &&
14066 receipt.state != DwgDataStorageLedgerState::ClassAdmitted) ||
14067 receipt.objectCommitted) {
14068 return false;
14069 }
14070 receipt.sourceKind = sourceKind;
14071 if (carrier.kind == DwgDataStorageCarrier::Kind::Raw &&
14072 carrier.rawObjectIndex <
14073 m_graphic->dwgAdvancedMetadata().rawObjects().size()) {
14074 const auto &raw =
14075 m_graphic->dwgAdvancedMetadata().rawObjects()[carrier.rawObjectIndex];
14076 const std::uint32_t expectedOwner = raw.blockOwnerHandle != DRW::NoHandle
14077 ? raw.blockOwnerHandle
14078 : raw.parentHandle;
14079 if (receipt.expectedBlockOwner != DRW::NoHandle &&
14080 receipt.expectedBlockOwner != expectedOwner) {
14081 return false;
14082 }
14083 receipt.expectedBlockOwner = expectedOwner;
14084 }
14085 receipt.outputHandle = outputHandle;
14086 receipt.objectCommitted = true;
14087 receipt.frameValid = true;
14088 receipt.frame = frame;
14089 receipt.writerClassNumber = frame.classNumber;
14090 const auto sectionIt =
14091 m_dwgDataStorageSectionReceipts.find(decision.rawSectionIndex);
14092 receipt.sectionCommitted =
14093 sectionIt != m_dwgDataStorageSectionReceipts.end() &&
14094 sectionIt->second.recordIndexes.count(decision.recordIndex) != 0;
14095 receipt.state = receipt.sectionCommitted
14096 ? DwgDataStorageLedgerState::SectionCommitted
14097 : DwgDataStorageLedgerState::ObjectCommitted;
14098 m_dwgDataStorageCommittedKinds[sourceHandle] = kind;
14099 return true;
14100}
14101
14102bool RS_FilterDXFRW::recordDwgDataStorageSectionCommit(
14103 std::size_t rawSectionIndex, const std::set<std::size_t> &recordIndexes) {
14104 if (m_dwgW == nullptr || m_dwgW->getVersion() <= DRW::AC1024)
14105 return true;
14106 const auto &plan = m_dwgDataStorageReplayPlan;
14107 if (m_graphic == nullptr || !plan.frozen || !plan.valid ||
14108 plan.version != m_dwgW->getVersion() || plan.generation == 0)
14109 return false;
14110 const auto planSection = plan.rawSectionIndexes.find(rawSectionIndex);
14111 if (planSection == plan.rawSectionIndexes.end())
14112 return false;
14113 if (rawSectionIndex >=
14114 m_graphic->dwgAdvancedMetadata().rawDwgSections().size())
14115 return false;
14116 const auto &rawSection =
14117 m_graphic->dwgAdvancedMetadata().rawDwgSections()[rawSectionIndex];
14118 const LC_DwgAdvancedMetadata::DataStorageRecord *storage = nullptr;
14119 for (const auto &candidate :
14120 m_graphic->dwgAdvancedMetadata().dataStorages()) {
14121 if (candidate.version == rawSection.version &&
14122 normalizeDwgTableName(candidate.name) ==
14123 normalizeDwgTableName(rawSection.name)) {
14124 if (storage != nullptr)
14125 return false;
14126 storage = &candidate;
14127 }
14128 }
14129 if (storage == nullptr)
14130 return false;
14131 std::set<std::size_t> expected;
14132 for (std::size_t index = 0; index < storage->full.records.size(); ++index)
14133 expected.insert(index);
14134 if (recordIndexes != expected)
14135 return false;
14136 if (m_dwgDataStorageSectionReceipts.find(rawSectionIndex) !=
14137 m_dwgDataStorageSectionReceipts.end())
14138 return false;
14139 for (const auto &decision : plan.decisions) {
14140 if (decision.rawSectionIndex != rawSectionIndex)
14141 continue;
14142 const auto ledger =
14143 m_dwgDataStorageCommitReceipts.find(decision.ownerHandle);
14144 if (ledger == m_dwgDataStorageCommitReceipts.end() ||
14145 ledger->second.generation != plan.generation ||
14146 ledger->second.state == DwgDataStorageLedgerState::Failed ||
14147 ledger->second.state == DwgDataStorageLedgerState::Suppressed)
14148 return false;
14149 }
14150 DwgDataStorageSectionReceipt receipt;
14151 receipt.generation = plan.generation;
14152 receipt.rawSectionIndex = rawSectionIndex;
14153 receipt.recordIndexes = recordIndexes;
14154 for (const std::size_t recordIndex : recordIndexes) {
14155 receipt.sourceRows.insert(makeDwgDataStorageSourceRowKey(
14156 rawSection.name, rawSection.version, rawSectionIndex, recordIndex));
14157 const bool linked =
14158 std::any_of(plan.decisions.cbegin(), plan.decisions.cend(),
14159 [rawSectionIndex, recordIndex](const auto &decision) {
14160 return decision.rawSectionIndex == rawSectionIndex &&
14161 decision.recordIndex == recordIndex;
14162 });
14163 if (!linked)
14164 receipt.opaqueRecordIndexes.insert(recordIndex);
14165 }
14166 m_dwgDataStorageSectionReceipts[rawSectionIndex] = receipt;
14167 for (const auto &decision : plan.decisions) {
14168 if (decision.rawSectionIndex != rawSectionIndex)
14169 continue;
14170 const auto ledger =
14171 m_dwgDataStorageCommitReceipts.find(decision.ownerHandle);
14172 if (ledger == m_dwgDataStorageCommitReceipts.end())
14173 continue;
14174 auto &entry = ledger->second;
14175 entry.sectionCommitted = true;
14176 if (entry.objectCommitted)
14177 entry.state = DwgDataStorageLedgerState::SectionCommitted;
14178 }
14179 return true;
14180}
14181
14182void RS_FilterDXFRW::failDwgDataStorageCommit(std::uint32_t sourceHandle) {
14183 const auto receiptIt = m_dwgDataStorageCommitReceipts.find(sourceHandle);
14184 if (receiptIt == m_dwgDataStorageCommitReceipts.end())
14185 return;
14186 auto &receipt = receiptIt->second;
14187 if (receipt.generation != m_dwgDataStorageReplayPlan.generation)
14188 return;
14189 receipt.state = DwgDataStorageLedgerState::Failed;
14190 receipt.objectCommitted = false;
14191 receipt.sectionCommitted = false;
14192 receipt.frameValid = false;
14193 receipt.frame = {};
14194}
14195
14196bool RS_FilterDXFRW::hasDwgDataStorageClassAdmission(
14197 std::uint32_t sourceHandle, DwgDataStorageCarrier::Kind kind) const {
14198 if (m_dwgW == nullptr || m_dwgW->getVersion() <= DRW::AC1024 ||
14199 sourceHandle == 0)
14200 return false;
14201 const auto &plan = m_dwgDataStorageReplayPlan;
14202 if (!plan.frozen || !plan.valid || plan.version != m_dwgW->getVersion() ||
14203 plan.generation == 0)
14204 return false;
14205 const auto decisionIt = plan.decisionByOwner.find(sourceHandle);
14206 if (decisionIt == plan.decisionByOwner.end() ||
14207 decisionIt->second >= plan.decisions.size())
14208 return false;
14209 const auto receiptIt = m_dwgDataStorageCommitReceipts.find(sourceHandle);
14210 if (receiptIt == m_dwgDataStorageCommitReceipts.end())
14211 return false;
14212 const auto &decision = plan.decisions[decisionIt->second];
14213 const auto &receipt = receiptIt->second;
14214 return receipt.generation == plan.generation &&
14215 receipt.carrierIndex == decision.selectedCarrierIndex &&
14216 receipt.kind == kind && receipt.binding == decision.binding &&
14217 receipt.classAdmitted;
14218}
14219
14220bool RS_FilterDXFRW::validateDwgDataStorageReplayReceipts(
14221 DRW::Version targetVersion) const {
14222 if (targetVersion <= DRW::AC1024)
14223 return true;
14224
14225 const auto &plan = m_dwgDataStorageReplayPlan;
14226 if (!plan.frozen || !plan.valid || plan.version != targetVersion ||
14227 plan.generation == 0)
14228 return false;
14229
14230 std::map<std::size_t, std::set<std::size_t>> expected;
14231 if (m_graphic == nullptr)
14232 return false;
14233 const auto &metadata = m_graphic->dwgAdvancedMetadata();
14234 for (const std::size_t rawSectionIndex : plan.rawSectionIndexes) {
14235 if (rawSectionIndex >= metadata.rawDwgSections().size())
14236 return false;
14237 const auto &rawSection = metadata.rawDwgSections()[rawSectionIndex];
14238 const LC_DwgAdvancedMetadata::DataStorageRecord *storage = nullptr;
14239 for (const auto &candidate : metadata.dataStorages()) {
14240 if (candidate.version == rawSection.version &&
14241 normalizeDwgTableName(candidate.name) ==
14242 normalizeDwgTableName(rawSection.name)) {
14243 if (storage != nullptr)
14244 return false;
14245 storage = &candidate;
14246 }
14247 }
14248 if (storage == nullptr)
14249 return false;
14250 auto &indexes = expected[rawSectionIndex];
14251 for (std::size_t index = 0; index < storage->full.records.size(); ++index)
14252 indexes.insert(index);
14253 }
14254 for (const auto &decision : plan.decisions) {
14255 if (decision.rawSectionIndex == std::numeric_limits<std::size_t>::max() ||
14256 decision.recordIndex == std::numeric_limits<std::size_t>::max())
14257 return false;
14258 const auto it = expected.find(decision.rawSectionIndex);
14259 if (it == expected.end() || it->second.count(decision.recordIndex) == 0)
14260 return false;
14261 }
14262
14263 if (m_dwgDataStorageSectionReceipts.size() != expected.size())
14264 return false;
14265 for (const auto &entry : expected) {
14266 const auto receiptIt = m_dwgDataStorageSectionReceipts.find(entry.first);
14267 if (receiptIt == m_dwgDataStorageSectionReceipts.end())
14268 return false;
14269 const auto &rawSection = metadata.rawDwgSections()[entry.first];
14270 const auto &receipt = receiptIt->second;
14271 if (receipt.generation != plan.generation ||
14272 receipt.rawSectionIndex != entry.first ||
14273 receipt.recordIndexes != entry.second)
14274 return false;
14275 std::set<DwgDataStorageSourceRowKey> expectedSourceRows;
14276 for (const std::size_t recordIndex : entry.second) {
14277 expectedSourceRows.insert(makeDwgDataStorageSourceRowKey(
14278 rawSection.name, rawSection.version, entry.first, recordIndex));
14279 }
14280 if (receipt.sourceRows != expectedSourceRows)
14281 return false;
14282 std::set<std::size_t> linked;
14283 for (const auto &decision : plan.decisions) {
14284 if (decision.rawSectionIndex == entry.first)
14285 linked.insert(decision.recordIndex);
14286 }
14287 std::set<std::size_t> opaque = entry.second;
14288 for (const std::size_t recordIndex : linked)
14289 opaque.erase(recordIndex);
14290 if (receipt.opaqueRecordIndexes != opaque)
14291 return false;
14292 }
14293
14294 // A section receipt proves only that the opaque section was copied. The
14295 // OBJECTS phase must also commit exactly the carrier selected by the
14296 // frozen decision, otherwise CLASSES and OBJECTS can describe different
14297 // instances under the same source handle. The receipt is authoritative;
14298 // the legacy committed-kind map is only a compatibility index.
14299 if (m_dwgDataStorageCommitReceipts.size() != plan.decisions.size())
14300 return false;
14301 for (const auto &decision : plan.decisions) {
14302 if (decision.selectedCarrierIndex >= plan.carriers.size())
14303 return false;
14304 if (decision.admissionToken == 0)
14305 return false;
14306 if (!decision.sourceRow.isValid() ||
14307 plan.decisionBySourceRow.find(decision.sourceRow) ==
14308 plan.decisionBySourceRow.end() ||
14309 plan.decisionBySourceRow.at(decision.sourceRow) >=
14310 plan.decisions.size() ||
14311 &plan.decisions[plan.decisionBySourceRow.at(decision.sourceRow)] !=
14312 &decision)
14313 return false;
14314 const std::size_t expectedCarrierIndex =
14315 decision.decision == DwgDataStorageDecision::EmitRaw
14316 ? decision.rawCarrierIndex
14317 : decision.typedCarrierIndex;
14318 if (decision.selectedCarrierIndex != expectedCarrierIndex)
14319 return false;
14320 const auto &selectedCarrier = plan.carriers[decision.selectedCarrierIndex];
14321 const DwgDataStorageCarrier::Kind expectedKind =
14322 decision.decision == DwgDataStorageDecision::EmitRaw
14323 ? DwgDataStorageCarrier::Kind::Raw
14324 : DwgDataStorageCarrier::Kind::Typed;
14325 const DwgDataStorageCarrier::WriterCapability expectedCapability =
14326 decision.decision == DwgDataStorageDecision::EmitRaw
14327 ? DwgDataStorageCarrier::WriterCapability::Raw
14328 : DwgDataStorageCarrier::WriterCapability::Typed;
14329 if (selectedCarrier.ownerHandle != decision.ownerHandle ||
14330 selectedCarrier.phase !=
14331 DwgDataStorageCarrier::EmissionPhase::Objects ||
14332 selectedCarrier.kind != expectedKind ||
14333 selectedCarrier.capability != expectedCapability ||
14334 decision.binding != selectedCarrier.binding ||
14335 decision.identityStatus == DwgDataStorageIdentityStatus::Conflict ||
14336 decision.identityStatus == DwgDataStorageIdentityStatus::OwnerMismatch)
14337 return false;
14338 if (decision.selectedPhase !=
14339 DwgDataStorageCarrier::EmissionPhase::Objects ||
14340 decision.selectedCapability != expectedCapability)
14341 return false;
14342 const auto receiptIt =
14343 m_dwgDataStorageCommitReceipts.find(decision.ownerHandle);
14344 if (receiptIt == m_dwgDataStorageCommitReceipts.end())
14345 return false;
14346 const auto &receipt = receiptIt->second;
14347 DwgWriteSourceKind expectedSourceKind = DwgWriteSourceKind::Object;
14348 std::uint32_t expectedBlockOwner = DRW::NoHandle;
14349 if (selectedCarrier.kind == DwgDataStorageCarrier::Kind::Raw &&
14350 m_graphic != nullptr &&
14351 selectedCarrier.rawObjectIndex <
14352 m_graphic->dwgAdvancedMetadata().rawObjects().size()) {
14353 const auto &raw = m_graphic->dwgAdvancedMetadata()
14354 .rawObjects()[selectedCarrier.rawObjectIndex];
14355 expectedSourceKind = raw.isEntity ? DwgWriteSourceKind::Entity
14356 : DwgWriteSourceKind::Object;
14357 expectedBlockOwner = raw.blockOwnerHandle != DRW::NoHandle
14358 ? raw.blockOwnerHandle
14359 : raw.parentHandle;
14360 }
14361 const bool classRequired =
14362 expectedKind == DwgDataStorageCarrier::Kind::Raw
14363 ? (selectedCarrier.rawObjectIndex <
14364 (m_graphic == nullptr ? 0u
14365 : m_graphic->dwgAdvancedMetadata()
14366 .rawObjects()
14367 .size()) &&
14368 m_graphic->dwgAdvancedMetadata()
14369 .rawObjects()[selectedCarrier.rawObjectIndex]
14370 .isCustomClass)
14371 : !selectedCarrier.className.empty();
14372 std::uint16_t expectedWriterOperation = 0;
14373 std::uint16_t expectedWriterClass = 0;
14374 if (expectedKind == DwgDataStorageCarrier::Kind::Typed) {
14375 DwgDataStorageWriterCapability capability;
14376 if (!getDwgDataStorageWriterCapability(selectedCarrier.binding,
14377 capability)) {
14378 return false;
14379 }
14380 expectedWriterOperation =
14381 static_cast<std::uint16_t>(capability.operation);
14382 if (!selectedCarrier.className.empty()) {
14383 expectedWriterClass = m_dwgW->getDwgTypedClassNumber(
14384 selectedCarrier.className.c_str(),
14385 selectedCarrier.recordName.c_str(), selectedCarrier.classNumber);
14386 }
14387 }
14388 if (receipt.generation != plan.generation ||
14389 receipt.carrierIndex != decision.selectedCarrierIndex ||
14390 receipt.kind != expectedKind || receipt.binding != decision.binding ||
14391 receipt.phase != decision.selectedPhase ||
14392 receipt.capability != expectedCapability ||
14393 receipt.sourceHandle != decision.ownerHandle ||
14394 receipt.outputHandle == 0 ||
14395 receipt.outputHandle != resolveDwgWriteHandle(decision.ownerHandle) ||
14396 receipt.sourceKind != expectedSourceKind ||
14397 receipt.expectedBlockOwner != expectedBlockOwner ||
14398 receipt.writerFamily != selectedCarrier.writerFamily ||
14399 receipt.className != selectedCarrier.className ||
14400 receipt.recordName != selectedCarrier.recordName ||
14401 receipt.classNumber != selectedCarrier.classNumber ||
14402 receipt.admissionToken != decision.admissionToken ||
14403 receipt.sourceRow != decision.sourceRow ||
14404 (classRequired && !receipt.classAdmitted) || !receipt.objectCommitted ||
14405 !receipt.sectionCommitted ||
14406 receipt.state != DwgDataStorageLedgerState::SectionCommitted ||
14407 !receipt.frameValid || !receipt.frame.valid ||
14408 receipt.frame.objectHandle != receipt.outputHandle ||
14409 receipt.frame.version != targetVersion ||
14410 (classRequired && receipt.frame.classNumber == 0) ||
14411 receipt.writerClassNumber != receipt.frame.classNumber ||
14412 receipt.frame.writerOperation != expectedWriterOperation ||
14413 receipt.frame.admissionToken != decision.admissionToken ||
14414 (expectedWriterClass != 0 &&
14415 receipt.frame.classNumber != expectedWriterClass))
14416 return false;
14417 }
14418 return true;
14419}
14420
14421const RS_FilterDXFRW::DwgDataStorageDecisionRecord *
14422RS_FilterDXFRW::dataStorageDecisionForCarrier(std::size_t carrierIndex) const {
14423 const auto &plan = m_dwgDataStorageReplayPlan;
14424 if (!plan.frozen || !plan.valid ||
14425 carrierIndex >= plan.decisionByCarrier.size())
14426 return nullptr;
14427 const std::size_t decisionIndex = plan.decisionByCarrier[carrierIndex];
14428 if (decisionIndex >= plan.decisions.size())
14429 return nullptr;
14430 return &plan.decisions[decisionIndex];
14431}
14432
14433const RS_FilterDXFRW::DwgDataStorageDecisionRecord *
14434RS_FilterDXFRW::dataStorageDecisionForOwner(std::uint32_t ownerHandle) const {
14435 const auto &plan = m_dwgDataStorageReplayPlan;
14436 if (!plan.frozen || !plan.valid || ownerHandle == 0)
14437 return nullptr;
14438 const auto it = plan.decisionByOwner.find(ownerHandle);
14439 if (it == plan.decisionByOwner.end() || it->second >= plan.decisions.size())
14440 return nullptr;
14441 return &plan.decisions[it->second];
14442}
14443
14444bool RS_FilterDXFRW::isDwgDataStorageTypedSelected(
14445 std::uint32_t ownerHandle) const {
14446 const auto *decision = dataStorageDecisionForOwner(ownerHandle);
14447 return decision == nullptr ||
14448 decision->decision == DwgDataStorageDecision::EmitTyped;
14449}
14450
14451bool RS_FilterDXFRW::isDwgDataStorageRawSelected(
14452 std::uint32_t ownerHandle) const {
14453 const auto *decision = dataStorageDecisionForOwner(ownerHandle);
14454 return decision != nullptr &&
14455 decision->decision == DwgDataStorageDecision::EmitRaw;
14456}
14457
14458bool RS_FilterDXFRW::allowsDwgDataStoragePresenceBit(
14459 std::uint32_t sourceHandle) const {
14460 if (m_dwgW == nullptr || sourceHandle == 0)
14461 return false;
14462 // AC1027 introduced the common-object DataStorage presence bit. Earlier
14463 // versions may carry an advisory source marker, but cannot encode it.
14464 return m_dwgW->getVersion() <= DRW::AC1024 ||
14465 canReplayDwgDataStorage(sourceHandle);
14466}
14467
14468bool RS_FilterDXFRW::encodedDwgDataStoragePresenceBit(
14469 std::uint32_t sourceHandle, bool requested) const {
14470 if (!requested || m_dwgW == nullptr || m_dwgW->getVersion() <= DRW::AC1024) {
14471 return false;
14472 }
14473 return canReplayDwgDataStorage(sourceHandle);
14474}
14475
14476bool RS_FilterDXFRW::canReplayDwgDataStorage(std::uint32_t sourceHandle) const {
14477 if (m_dwgW == nullptr || m_graphic == nullptr || sourceHandle == 0)
14478 return false;
14479 if (!m_dwgDataStorageReplayPlan.frozen || !m_dwgDataStorageReplayPlan.valid ||
14480 m_dwgDataStorageReplayPlan.version != m_dwgW->getVersion()) {
14481 return false;
14482 }
14483 const auto it = m_dwgDataStorageReplayPlan.decisionByOwner.find(sourceHandle);
14484 if (it == m_dwgDataStorageReplayPlan.decisionByOwner.end() ||
14485 it->second >= m_dwgDataStorageReplayPlan.decisions.size()) {
14486 return false;
14487 }
14488 const DwgDataStorageDecision decision =
14489 m_dwgDataStorageReplayPlan.decisions[it->second].decision;
14490 return decision == DwgDataStorageDecision::EmitTyped ||
14491 decision == DwgDataStorageDecision::EmitRaw;
14492}
14493
14494bool RS_FilterDXFRW::stageDwgTableRecordIdentity(DwgWriteSourceKind kind,
14495 std::uint32_t sourceHandle,
14496 std::uint32_t outputHandle) {
14497 if (m_dwgW == nullptr || sourceHandle == 0 || outputHandle == 0)
14498 return sourceHandle == 0;
14499 noteDwgWriteSourceKind(sourceHandle, kind);
14500 const bool ambiguous = isDwgWriteSourceAmbiguous(sourceHandle, kind);
14501 const bool selected =
14502 !ambiguous && selectDwgWriteIdentity(kind, sourceHandle, outputHandle);
14503 if (!selected) {
14504 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
14505 "RS_FilterDXFRW: cannot stage DWG table identity kind=%d "
14506 "source=0x%X output=0x%X ambiguous=%d",
14507 static_cast<int>(kind), sourceHandle, outputHandle,
14508 ambiguous ? 1 : 0);
14509 m_writeFailed = true;
14510 return false;
14511 }
14512 return true;
14513}
14514
14515bool RS_FilterDXFRW::commitDwgTableRecordIdentities() {
14516 if (m_dwgW == nullptr)
14517 return true;
14518
14519 const auto checkpoint = checkpointDwgWriteIdentity();
14520 const auto fail = [this, &checkpoint]() {
14521 restoreDwgWriteIdentity(checkpoint);
14522 m_writeFailed = true;
14523 return false;
14524 };
14525
14526 std::vector<std::uint32_t> emittedHandles;
14527 if (!m_dwgW->getEmittedDwgTableRecordHandles(emittedHandles))
14528 return fail();
14529 const std::set<std::uint32_t> emittedSet(emittedHandles.cbegin(),
14530 emittedHandles.cend());
14531
14532 // Validate the completion result before changing any identity state.
14533 // A set alone would hide duplicate handles and could turn a malformed
14534 // low-level result into a falsely complete table transaction.
14535 if (emittedSet.size() != emittedHandles.size() ||
14536 std::any_of(emittedHandles.cbegin(), emittedHandles.cend(),
14537 [](std::uint32_t handle) { return handle == 0; })) {
14538 return fail();
14539 }
14540 for (const auto &entry : m_dwgWriteIdentityByOutput) {
14541 const auto &identity = entry.second;
14542 if (identity.kind == DwgWriteSourceKind::TableRecord &&
14543 identity.state == DwgWriteIdentityState::Selected &&
14544 emittedSet.count(identity.outputHandle) == 0) {
14545 return fail();
14546 }
14547 }
14548 for (const std::uint32_t outputHandle : emittedHandles) {
14549 const auto ownerIt = m_dwgWriteIdentityOwnerByOutput.find(outputHandle);
14550 if (ownerIt == m_dwgWriteIdentityOwnerByOutput.end())
14551 continue;
14552 const auto identityIt = m_dwgWriteIdentityByOutput.find(ownerIt->second);
14553 if (ownerIt->second.kind != DwgWriteSourceKind::TableRecord ||
14554 identityIt == m_dwgWriteIdentityByOutput.end() ||
14555 (identityIt->second.state != DwgWriteIdentityState::Selected &&
14556 identityIt->second.state != DwgWriteIdentityState::Committed)) {
14557 return fail();
14558 }
14559 }
14560
14561 // A table record is only committed after the writer has completed every
14562 // deferred table/control record. This prevents a failed or skipped
14563 // encoder from publishing a handle that later references would trust.
14564 for (auto &entry : m_dwgWriteIdentityByOutput) {
14565 auto &identity = entry.second;
14566 if (identity.kind != DwgWriteSourceKind::TableRecord ||
14567 identity.state != DwgWriteIdentityState::Selected)
14568 continue;
14569 if (emittedSet.count(identity.outputHandle) == 0 ||
14570 !commitDwgWriteIdentity(identity.kind, identity.outputHandle)) {
14571 return fail();
14572 }
14573 }
14574
14575 // Register writer-created records (standard fixed records and APPIDs
14576 // discovered from EED) so the ledger accounts for every table output. A
14577 // source-backed record above owns its output identity already.
14578 for (const std::uint32_t outputHandle : emittedSet) {
14579 const auto ownerIt = m_dwgWriteIdentityOwnerByOutput.find(outputHandle);
14580 if (ownerIt != m_dwgWriteIdentityOwnerByOutput.end()) {
14581 if (ownerIt->second.kind != DwgWriteSourceKind::TableRecord ||
14582 !isDwgWriteIdentityCommitted(ownerIt->second.kind, outputHandle)) {
14583 return fail();
14584 }
14585 continue;
14586 }
14587 if (!reserveDwgWriteIdentity(DwgWriteSourceKind::TableRecord, 0,
14588 outputHandle) ||
14589 !selectDwgWriteIdentity(DwgWriteSourceKind::TableRecord, 0,
14590 outputHandle) ||
14591 !commitDwgWriteIdentity(DwgWriteSourceKind::TableRecord,
14592 outputHandle)) {
14593 return fail();
14594 }
14595 }
14596 return true;
14597}
14598
14599bool RS_FilterDXFRW::commitDwgTableControlIdentities() {
14600 if (m_dwgW == nullptr)
14601 return true;
14602
14603 const auto checkpoint = checkpointDwgWriteIdentity();
14604 const auto fail = [this, &checkpoint]() {
14605 restoreDwgWriteIdentity(checkpoint);
14606 m_writeFailed = true;
14607 return false;
14608 };
14609
14610 std::vector<std::uint32_t> emittedHandles;
14611 if (!m_dwgW->getEmittedDwgTableControlHandles(emittedHandles))
14612 return fail();
14613
14614 const std::set<std::uint32_t> emittedSet(emittedHandles.cbegin(),
14615 emittedHandles.cend());
14616 if (emittedSet.size() != emittedHandles.size() ||
14617 std::any_of(emittedHandles.cbegin(), emittedHandles.cend(),
14618 [](std::uint32_t handle) { return handle == 0; })) {
14619 return fail();
14620 }
14621 for (const std::uint32_t outputHandle : emittedHandles) {
14622 const auto ownerIt = m_dwgWriteIdentityOwnerByOutput.find(outputHandle);
14623 if (ownerIt == m_dwgWriteIdentityOwnerByOutput.end())
14624 continue;
14625 const auto identityIt = m_dwgWriteIdentityByOutput.find(ownerIt->second);
14626 if (ownerIt->second.kind != DwgWriteSourceKind::TableControl ||
14627 identityIt == m_dwgWriteIdentityByOutput.end() ||
14628 (identityIt->second.state != DwgWriteIdentityState::Selected &&
14629 identityIt->second.state != DwgWriteIdentityState::Committed)) {
14630 return fail();
14631 }
14632 }
14633
14634 // Controls have no source-side table record. Register them only after
14635 // the writer confirms that every control frame was emitted successfully.
14636 for (const std::uint32_t outputHandle : emittedHandles) {
14637 if (outputHandle == 0) {
14638 return fail();
14639 }
14640 const auto ownerIt = m_dwgWriteIdentityOwnerByOutput.find(outputHandle);
14641 if (ownerIt != m_dwgWriteIdentityOwnerByOutput.end()) {
14642 if (ownerIt->second.kind != DwgWriteSourceKind::TableControl ||
14643 !isDwgWriteIdentityCommitted(ownerIt->second.kind, outputHandle)) {
14644 return fail();
14645 }
14646 continue;
14647 }
14648 if (!reserveDwgWriteIdentity(DwgWriteSourceKind::TableControl, 0,
14649 outputHandle) ||
14650 !selectDwgWriteIdentity(DwgWriteSourceKind::TableControl, 0,
14651 outputHandle) ||
14652 !commitDwgWriteIdentity(DwgWriteSourceKind::TableControl,
14653 outputHandle)) {
14654 return fail();
14655 }
14656 }
14657 return true;
14658}
14659
14660bool RS_FilterDXFRW::commitDwgGeneratedEntityWrite(std::uint32_t outputHandle) {
14661 if (m_dwgW == nullptr || outputHandle == 0)
14662 return false;
14663
14664 const auto ownerIt = m_dwgWriteIdentityOwnerByOutput.find(outputHandle);
14665 if (ownerIt != m_dwgWriteIdentityOwnerByOutput.end()) {
14666 if (ownerIt->second.kind != DwgWriteSourceKind::Entity ||
14667 !selectDwgWriteIdentity(DwgWriteSourceKind::Entity, 0, outputHandle) ||
14668 !commitDwgWriteIdentity(DwgWriteSourceKind::Entity, outputHandle)) {
14669 m_writeFailed = true;
14670 return false;
14671 }
14672 return true;
14673 }
14674
14675 if (!reserveDwgWriteIdentity(DwgWriteSourceKind::Entity, 0, outputHandle) ||
14676 !selectDwgWriteIdentity(DwgWriteSourceKind::Entity, 0, outputHandle) ||
14677 !commitDwgWriteIdentity(DwgWriteSourceKind::Entity, outputHandle)) {
14678 m_writeFailed = true;
14679 return false;
14680 }
14681 return true;
14682}
14683
14684void RS_FilterDXFRW::prepareDwgEntityHandleMap() {
14685 if (m_dwgW == nullptr || !m_dwgWriteEntityHandleByEntity.empty())
14686 return;
14687
14688 auto reserveContainerEntities = [this](RS_EntityContainer *container) {
14689 if (container == nullptr)
14690 return;
14691 for (RS_Entity *entity :
14692 lc::LC_ContainerTraverser{*container, RS2::ResolveNone}.entities()) {
14693 if (entity == nullptr || entity->getFlag(RS2::FlagDeleted))
14694 continue;
14695 // INSERT attributes are encoded inside the parent INSERT frame;
14696 // they do not own independent output handles.
14697 if (isGeneratedInsertAttrib(entity))
14698 continue;
14699 const std::uint32_t sourceHandle = entity->sourceHandle();
14700 if (sourceHandle == 0)
14701 continue;
14702 noteDwgWriteSourceKind(
14703 sourceHandle,
14704 RS_FilterDXFRW_DwgWriteIdentityRegistry::DwgWriteSourceKind::Entity);
14705
14706 const std::uint32_t emittedHandle = m_dwgW->allocNextHandle();
14707 if (emittedHandle == 0) {
14708 m_writeFailed = true;
14709 continue;
14710 }
14711 if (!selectDwgWriteIdentity(RS_FilterDXFRW_DwgWriteIdentityRegistry::
14712 DwgWriteSourceKind::Entity,
14713 sourceHandle, emittedHandle)) {
14714 m_writeFailed = true;
14715 continue;
14716 }
14717 m_dwgWriteEntityHandleByEntity.emplace(entity, emittedHandle);
14718 const bool inserted =
14719 m_dwgWriteEntityHandleRemap.emplace(sourceHandle, emittedHandle)
14720 .second;
14721 if (!inserted) {
14722 m_dwgWriteDuplicateEntityHandles.insert(sourceHandle);
14723 }
14724 }
14725 };
14726
14727 reserveContainerEntities(m_graphic);
14728 for (unsigned i = 0; i < m_graphic->countBlocks(); ++i) {
14729 RS_Block *block = m_graphic->blockAt(i);
14730 if (block != nullptr && !block->isDeleted())
14731 reserveContainerEntities(block);
14732 }
14733}
14734
14735bool RS_FilterDXFRW::prepareDwgExportAdmissionPlan() {
14736 if (m_dwgW == nullptr || m_graphic == nullptr)
14737 return false;
14738
14739 DwgExportAdmissionPlan plan;
14740 plan.version = m_dwgW->getVersion();
14741 plan.blockListCount = m_graphic->countBlocks();
14742 const auto addRecord = [&plan](DwgExportAdmissionRecord record) {
14743 plan.records.push_back(std::move(record));
14744 return plan.records.size() - 1u;
14745 };
14746 const auto refuse = [&plan, &addRecord](DwgExportAdmissionFamily family,
14747 DwgExportAdmissionReason reason,
14748 std::uint32_t sourceHandle,
14749 std::size_t sourceOrdinal,
14750 std::string normalizedName,
14751 std::uint32_t relatedHandle = 0) {
14752 plan.valid = false;
14753 return addRecord({family, DwgExportAdmissionPolicy::Refuse, reason,
14754 sourceHandle, relatedHandle, sourceOrdinal,
14755 std::move(normalizedName)});
14756 };
14757
14758 std::set<std::string> blockNames;
14759 for (unsigned index = 0; index < m_graphic->countBlocks(); ++index) {
14760 RS_Block *block = m_graphic->blockAt(index);
14761 if (block == nullptr) {
14762 refuse(DwgExportAdmissionFamily::BlockDefinition,
14763 DwgExportAdmissionReason::InvalidBlockDefinition, 0, index, {});
14764 continue;
14765 }
14766 if (block->isDeleted())
14767 continue;
14768 const std::string name =
14769 normalizeDwgTableName(block->getName().toUtf8().toStdString());
14770 if (name.empty()) {
14771 refuse(DwgExportAdmissionFamily::BlockDefinition,
14772 DwgExportAdmissionReason::InvalidBlockDefinition,
14773 block->sourceHandle(), index, {});
14774 continue;
14775 }
14776 if (!blockNames.insert(name).second) {
14777 refuse(DwgExportAdmissionFamily::BlockDefinition,
14778 DwgExportAdmissionReason::DuplicateBlockDefinition,
14779 block->sourceHandle(), index, name);
14780 continue;
14781 }
14782 plan.blockDefinitions.push_back({block, block->sourceHandle(), name});
14783 }
14784
14785 const auto hasBlockTarget = [this, &blockNames](const std::string &name) {
14786 std::uint32_t ignoredHandle = 0;
14787 return resolveDwgWriteBlockName(name, &ignoredHandle) ==
14788 DwgWriteReferenceStatus::LegacySpaceAlias ||
14789 blockNames.count(name) != 0;
14790 };
14791 std::size_t insertOrdinal = 0;
14792 // Build through a local plan, then attach the INSERT lookup below once the
14793 // plan is stable. This avoids publishing a partially populated plan.
14794 std::map<const RS_Insert *, std::size_t> insertRecordIndexes;
14795 const auto collectInserts = [&insertRecordIndexes, &addRecord, &refuse,
14796 &hasBlockTarget, &insertOrdinal](
14797 const RS_EntityContainer *container) {
14798 if (container == nullptr)
14799 return;
14800 for (RS_Entity *entity :
14801 lc::LC_ContainerTraverser{*container, RS2::ResolveNone}.entities()) {
14802 if (entity == nullptr || entity->getFlag(RS2::FlagDeleted) ||
14803 entity->rtti() != RS2::EntityInsert) {
14804 continue;
14805 }
14806 const auto *insert = static_cast<const RS_Insert *>(entity);
14807 const std::string name =
14808 normalizeDwgTableName(insert->getName().toUtf8().toStdString());
14809 const std::size_t ordinal = insertOrdinal++;
14810 const std::size_t recordIndex =
14811 !name.empty() && hasBlockTarget(name)
14812 ? addRecord({DwgExportAdmissionFamily::Insert,
14813 DwgExportAdmissionPolicy::Native,
14814 DwgExportAdmissionReason::None,
14815 insert->sourceHandle(), 0, ordinal, name})
14816 : refuse(DwgExportAdmissionFamily::Insert,
14817 DwgExportAdmissionReason::MissingBlockDefinition,
14818 insert->sourceHandle(), ordinal, name);
14819 insertRecordIndexes.emplace(insert, recordIndex);
14820 }
14821 };
14822 collectInserts(m_graphic);
14823 for (unsigned index = 0; index < m_graphic->countBlocks(); ++index) {
14824 const RS_Block *block = m_graphic->blockAt(index);
14825 if (block != nullptr && !block->isDeleted())
14826 collectInserts(block);
14827 }
14828 plan.insertRecordIndexes = std::move(insertRecordIndexes);
14829
14830 std::size_t wipeoutOrdinal = 0;
14831 std::map<const LC_Wipeout *, std::size_t> wipeoutRecordIndexes;
14832 const auto collectWipeouts = [&wipeoutRecordIndexes, &addRecord, &refuse,
14833 &wipeoutOrdinal](
14834 const RS_EntityContainer *container) {
14835 if (container == nullptr)
14836 return;
14837 for (RS_Entity *entity :
14838 lc::LC_ContainerTraverser{*container, RS2::ResolveNone}.entities()) {
14839 if (entity == nullptr || entity->getFlag(RS2::FlagDeleted) ||
14840 entity->rtti() != RS2::EntityWipeout) {
14841 continue;
14842 }
14843 const auto *wipeout = static_cast<const LC_Wipeout *>(entity);
14844 const LC_WipeoutData &data = wipeout->getData();
14845 const std::size_t ordinal = wipeoutOrdinal++;
14846 const bool hasExternalReference =
14847 data.hasNativeFrame &&
14848 (data.imageDefHandle != 0 || data.imageDefReactorHandle != 0);
14849 const std::size_t recordIndex =
14850 hasExternalReference
14851 ? refuse(DwgExportAdmissionFamily::Wipeout,
14852 DwgExportAdmissionReason::UnsupportedWipeoutReference,
14853 wipeout->sourceHandle(), ordinal, {},
14854 data.imageDefHandle)
14855 : addRecord({DwgExportAdmissionFamily::Wipeout,
14856 DwgExportAdmissionPolicy::Native,
14857 DwgExportAdmissionReason::None,
14858 wipeout->sourceHandle(),
14859 0,
14860 ordinal,
14861 {}});
14862 wipeoutRecordIndexes.emplace(wipeout, recordIndex);
14863 }
14864 };
14865 collectWipeouts(m_graphic);
14866 for (unsigned index = 0; index < m_graphic->countBlocks(); ++index) {
14867 const RS_Block *block = m_graphic->blockAt(index);
14868 if (block != nullptr && !block->isDeleted())
14869 collectWipeouts(block);
14870 }
14871 plan.wipeoutRecordIndexes = std::move(wipeoutRecordIndexes);
14872
14873 struct RawPlotSettingsCandidate {
14874 std::size_t index = std::numeric_limits<std::size_t>::max();
14875 bool hasDataStorage = false;
14876 };
14877 const auto &metadata = m_graphic->dwgAdvancedMetadata();
14878 plan.rawObjectCount = metadata.rawObjects().size();
14879 plan.dictionaryCount = metadata.dictionaries().size();
14880 plan.tableCount = metadata.tables().size();
14881 plan.cellStyleMapCount = metadata.cellStyleMaps().size();
14882 std::set<std::uint32_t> opaqueTableHandles;
14883 for (std::size_t index = 0; index < metadata.rawObjects().size(); ++index) {
14884 const auto &raw = metadata.rawObjects()[index];
14885 if (!requiresDwgOpaqueTableAdmission(raw))
14886 continue;
14887 const DwgOpaqueTableInspection inspection =
14888 inspectDwgOpaqueTableRawObject(index);
14889 DwgExportAdmissionReason reason =
14890 DwgExportAdmissionReason::OpaqueTableReference;
14891 if (inspection.referenceKind == DwgOpaqueTableReferenceKind::RawFrame) {
14892 reason = DwgExportAdmissionReason::InvalidOpaqueTableRaw;
14893 } else if (inspection.referenceKind ==
14894 DwgOpaqueTableReferenceKind::MissingSummary) {
14895 reason = DwgExportAdmissionReason::MissingOpaqueTableSummary;
14896 } else if (inspection.referenceKind ==
14897 DwgOpaqueTableReferenceKind::AmbiguousSummary) {
14898 reason = DwgExportAdmissionReason::AmbiguousOpaqueTableSummary;
14899 } else if (inspection.referenceKind ==
14900 DwgOpaqueTableReferenceKind::InvalidSummary) {
14901 reason = DwgExportAdmissionReason::InvalidOpaqueTableSummary;
14902 }
14903 const bool duplicate =
14904 inspection.sourceHandle != 0 &&
14905 !opaqueTableHandles.insert(inspection.sourceHandle).second;
14906 const std::size_t recordIndex =
14907 inspection.eligible && !duplicate
14908 ? addRecord({DwgExportAdmissionFamily::OpaqueTable,
14909 DwgExportAdmissionPolicy::SameVersionRaw,
14910 DwgExportAdmissionReason::None,
14911 inspection.sourceHandle, 0, index,
14912 normalizeDwgTableName(raw.recordName)})
14913 : refuse(DwgExportAdmissionFamily::OpaqueTable,
14914 duplicate ? DwgExportAdmissionReason::InvalidOpaqueTableRaw
14915 : reason,
14916 inspection.sourceHandle, index,
14917 normalizeDwgTableName(raw.recordName),
14918 duplicate ? inspection.sourceHandle
14919 : inspection.referenceHandle);
14920 plan.opaqueTableRecordIndexes.emplace(
14921 index, DwgOpaqueTablePlanRecord{
14922 index, recordIndex, inspection.rawFingerprint,
14923 inspection.summaryFingerprint, inspection.referenceKind,
14924 inspection.referenceHandle, inspection.referenceOrdinal,
14925 inspection.referencePlan, inspection.ownerGraph,
14926 inspection.preflightFailure});
14927 }
14928 std::map<std::uint32_t, RawPlotSettingsCandidate> rawPlotCandidates;
14929 std::set<std::uint32_t> invalidRawPlotHandles;
14930 std::set<std::uint32_t> duplicateRawPlotHandles;
14931 for (std::size_t index = 0; index < metadata.rawObjects().size(); ++index) {
14932 const auto &record = metadata.rawObjects()[index];
14933 if (!isPlotSettingsRawObject(record))
14934 continue;
14935 const bool replayable =
14936 !record.isEntity && record.version == plan.version &&
14937 record.handle != 0 && !isFixedStructuralDwgHandle(record.handle) &&
14938 record.replayState ==
14939 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
14940 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
14941 LC_DwgAdvancedMetadata::ReplayBlocker::None &&
14942 (!record.hasDataStorage || canReplayDwgDataStorage(record.handle));
14943 if (!replayable) {
14944 invalidRawPlotHandles.insert(record.handle);
14945 continue;
14946 }
14947 const auto [it, inserted] = rawPlotCandidates.emplace(
14948 record.handle, RawPlotSettingsCandidate{index, record.hasDataStorage});
14949 if (!inserted)
14950 duplicateRawPlotHandles.insert(record.handle);
14951 }
14952
14953 plan.plotSettingsCount = metadata.plotSettings().size();
14954 std::set<std::uint32_t> typedPlotSettingsHandles;
14955 for (std::size_t index = 0; index < metadata.plotSettings().size(); ++index) {
14956 const DRW_PlotSettings &settings = metadata.plotSettings()[index];
14957 if (settings.handle != 0)
14958 typedPlotSettingsHandles.insert(settings.handle);
14959 DwgExportAdmissionRecord record;
14960 record.family = DwgExportAdmissionFamily::PlotSettings;
14961 record.sourceHandle = settings.handle;
14962 record.sourceOrdinal = index;
14963 const auto rawIt = rawPlotCandidates.find(settings.handle);
14964 if (settings.handle != 0 && rawIt != rawPlotCandidates.end() &&
14965 duplicateRawPlotHandles.count(settings.handle) == 0 &&
14966 (!settings.hasDataStorageBinaryData() ||
14967 rawIt->second.hasDataStorage)) {
14968 record.policy = DwgExportAdmissionPolicy::SameVersionRaw;
14969 record.relatedSourceHandle = settings.handle;
14970 plan.rawPlotSettingsRecordIndexes.emplace(settings.handle,
14971 rawIt->second.index);
14972 plan.rawPlotSettingsHandles.insert(settings.handle);
14973 } else if (settings.hasDataStorageBinaryData()) {
14974 record.policy = DwgExportAdmissionPolicy::Refuse;
14975 record.reason =
14976 DwgExportAdmissionReason::UnsupportedPlotSettingsDataStorage;
14977 plan.valid = false;
14978 } else if (hasDxfTableAppDataUnsupportedByDwg(settings)) {
14979 record.policy = DwgExportAdmissionPolicy::Refuse;
14980 record.reason =
14981 DwgExportAdmissionReason::UnsupportedPlotSettingsApplicationData;
14982 plan.valid = false;
14983 } else if (settings.handle != 0 &&
14984 duplicateRawPlotHandles.count(settings.handle) != 0) {
14985 record.policy = DwgExportAdmissionPolicy::Refuse;
14986 record.reason = DwgExportAdmissionReason::DuplicateRawPlotSettings;
14987 plan.valid = false;
14988 } else {
14989 record.policy = DwgExportAdmissionPolicy::Typed;
14990 record.reason = DwgExportAdmissionReason::None;
14991 }
14992 const std::size_t recordIndex = addRecord(std::move(record));
14993 plan.plotSettingsRecordIndexes.emplace(index, recordIndex);
14994 }
14995 if (metadata.plotSettings().empty()) {
14996 addRecord({DwgExportAdmissionFamily::PlotSettings,
14997 DwgExportAdmissionPolicy::Native,
14998 DwgExportAdmissionReason::None,
14999 0,
15000 0,
15001 0,
15002 {}});
15003 }
15004 for (const auto &entry : rawPlotCandidates) {
15005 const std::uint32_t handle = entry.first;
15006 if (typedPlotSettingsHandles.count(handle) != 0 ||
15007 duplicateRawPlotHandles.count(handle) != 0) {
15008 continue;
15009 }
15010 plan.rawPlotSettingsHandles.insert(handle);
15011 plan.rawPlotSettingsRecordIndexes.emplace(handle, entry.second.index);
15012 addRecord({DwgExportAdmissionFamily::PlotSettings,
15013 DwgExportAdmissionPolicy::SameVersionRaw,
15014 DwgExportAdmissionReason::None,
15015 handle,
15016 handle,
15017 entry.second.index,
15018 {}});
15019 }
15020 for (std::uint32_t handle : invalidRawPlotHandles) {
15021 if (handle != 0 && typedPlotSettingsHandles.count(handle) != 0)
15022 continue;
15023 refuse(DwgExportAdmissionFamily::PlotSettings,
15024 DwgExportAdmissionReason::RawPlotSettingsUnavailable, handle,
15025 std::numeric_limits<std::size_t>::max(), {});
15026 }
15027 for (std::uint32_t handle : duplicateRawPlotHandles) {
15028 if (typedPlotSettingsHandles.count(handle) != 0)
15029 continue;
15030 refuse(DwgExportAdmissionFamily::PlotSettings,
15031 DwgExportAdmissionReason::DuplicateRawPlotSettings, handle,
15032 std::numeric_limits<std::size_t>::max(), {});
15033 }
15034
15035 plan.frozen = true;
15036 m_dwgOpaqueTableRuntimeFailures.clear();
15037 m_dwgExportAdmissionPlan = std::move(plan);
15038 if (!m_dwgExportAdmissionPlan.valid) {
15039 const auto firstFailure =
15040 std::find_if(m_dwgExportAdmissionPlan.records.cbegin(),
15041 m_dwgExportAdmissionPlan.records.cend(),
15042 [](const DwgExportAdmissionRecord &record) {
15043 return record.policy == DwgExportAdmissionPolicy::Refuse;
15044 });
15045 if (firstFailure != m_dwgExportAdmissionPlan.records.cend()) {
15046 const std::size_t firstFailureIndex =
15047 static_cast<std::size_t>(std::distance(
15048 m_dwgExportAdmissionPlan.records.cbegin(), firstFailure));
15049 const auto opaque = std::find_if(
15050 m_dwgExportAdmissionPlan.opaqueTableRecordIndexes.cbegin(),
15051 m_dwgExportAdmissionPlan.opaqueTableRecordIndexes.cend(),
15052 [firstFailureIndex](const auto &entry) {
15053 return entry.second.admissionRecordIndex == firstFailureIndex;
15054 });
15055 if (opaque != m_dwgExportAdmissionPlan.opaqueTableRecordIndexes.cend() &&
15056 opaque->second.preflightFailure.has_value()) {
15057 const DwgOpaqueTableFailure &failure = *opaque->second.preflightFailure;
15058 const long long ordinal =
15059 failure.ordinal == std::numeric_limits<std::size_t>::max()
15060 ? -1LL
15061 : static_cast<long long>(failure.ordinal);
15062 RS_DEBUGRS_Debug::instance()->print(
15063 RS_Debug::D_WARNING,
15064 "RS_FilterDXFRW: DWG export admission refused family=%d "
15065 "reason=%d handle=0x%X name=%s opaque-kind=%s "
15066 "related=0x%X ordinal=%lld",
15067 static_cast<int>(firstFailure->family),
15068 static_cast<int>(firstFailure->reason), firstFailure->sourceHandle,
15069 firstFailure->normalizedName.c_str(),
15070 dwgOpaqueTableFailureKindName(failure.kind), failure.relatedHandle,
15071 ordinal);
15072 } else {
15073 RS_DEBUGRS_Debug::instance()->print(
15074 RS_Debug::D_WARNING,
15075 "RS_FilterDXFRW: DWG export admission refused family=%d "
15076 "reason=%d handle=0x%X name=%s",
15077 static_cast<int>(firstFailure->family),
15078 static_cast<int>(firstFailure->reason), firstFailure->sourceHandle,
15079 firstFailure->normalizedName.c_str());
15080 }
15081 }
15082 }
15083 return m_dwgExportAdmissionPlan.valid;
15084}
15085
15086bool RS_FilterDXFRW::prepareDwgFieldWritePlan() {
15087 if (m_dwgW == nullptr || m_graphic == nullptr)
15088 return false;
15089
15090 DwgFieldWritePlan plan;
15091 plan.version = m_dwgW->getVersion();
15092 const auto &metadata = m_graphic->dwgAdvancedMetadata();
15093 plan.fieldRecordCount = metadata.fields().size();
15094 plan.fieldListRecordCount = metadata.fieldLists().size();
15095
15096 for (std::size_t index = 0; index < metadata.fields().size(); ++index) {
15097 const auto &record = metadata.fields()[index];
15098 if (record.replayState !=
15099 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
15100 record.handle == 0 || !isDwgDataStorageTypedSelected(record.handle)) {
15101 continue;
15102 }
15103 DwgFieldWritePlan::FieldCandidate candidate;
15104 candidate.metadataIndex = index;
15105 candidate.sourceHandle = record.handle;
15106 candidate.replayState = record.replayState;
15107 candidate.sourceAuthorization =
15108 metadata.authorizeDwgFieldSource(record).status;
15109 candidate.payload = fieldFromMetadata(record);
15110 if (!canWriteDwgFieldRecord(metadata, record, candidate.payload,
15111 plan.version) ||
15112 !plan.fieldCandidateIndexes
15113 .emplace(record.handle, plan.fieldCandidates.size())
15114 .second) {
15115 plan.valid = false;
15116 break;
15117 }
15118 plan.fieldCandidates.push_back(std::move(candidate));
15119 }
15120 if (plan.valid) {
15121 for (auto &candidate : plan.fieldCandidates) {
15122 candidate.childFieldCandidateIndexes.reserve(
15123 candidate.payload.m_childHandles.size());
15124 for (const std::uint32_t childHandle : candidate.payload.m_childHandles) {
15125 if (childHandle == DRW::NoHandle) {
15126 plan.valid = false;
15127 break;
15128 }
15129 const auto child = plan.fieldCandidateIndexes.find(childHandle);
15130 if (child == plan.fieldCandidateIndexes.cend()) {
15131 plan.valid = false;
15132 break;
15133 }
15134 candidate.childFieldCandidateIndexes.push_back(child->second);
15135 }
15136 if (!plan.valid)
15137 break;
15138 }
15139 }
15140 if (plan.valid) {
15141 std::vector<std::size_t> incoming(plan.fieldCandidates.size(), 0u);
15142 for (const auto &candidate : plan.fieldCandidates) {
15143 for (const std::size_t childIndex : candidate.childFieldCandidateIndexes) {
15144 if (childIndex >= incoming.size()) {
15145 plan.valid = false;
15146 break;
15147 }
15148 ++incoming[childIndex];
15149 }
15150 if (!plan.valid)
15151 break;
15152 }
15153 std::vector<std::size_t> ready;
15154 ready.reserve(incoming.size());
15155 for (std::size_t index = 0; index < incoming.size(); ++index) {
15156 if (incoming[index] == 0u)
15157 ready.push_back(index);
15158 }
15159 for (std::size_t index = 0; plan.valid && index < ready.size(); ++index) {
15160 for (const std::size_t childIndex :
15161 plan.fieldCandidates[ready[index]].childFieldCandidateIndexes) {
15162 if (incoming[childIndex] == 0u) {
15163 plan.valid = false;
15164 break;
15165 }
15166 if (--incoming[childIndex] == 0u)
15167 ready.push_back(childIndex);
15168 }
15169 }
15170 if (plan.valid && ready.size() != plan.fieldCandidates.size())
15171 plan.valid = false;
15172 }
15173 if (plan.valid) {
15174 for (std::size_t index = 0; index < metadata.fieldLists().size();
15175 ++index) {
15176 const auto &record = metadata.fieldLists()[index];
15177 if (record.replayState !=
15178 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
15179 record.handle == 0 || !isDwgDataStorageTypedSelected(record.handle)) {
15180 continue;
15181 }
15182 DwgFieldWritePlan::FieldListCandidate candidate;
15183 candidate.metadataIndex = index;
15184 candidate.sourceHandle = record.handle;
15185 candidate.replayState = record.replayState;
15186 candidate.sourceAuthorization =
15187 metadata.authorizeDwgFieldListSource(record).status;
15188 candidate.payload = fieldListFromMetadata(record);
15189 if (!canWriteDwgFieldListRecord(metadata, record, candidate.payload,
15190 plan.version)) {
15191 plan.valid = false;
15192 break;
15193 }
15194 candidate.fieldCandidateIndexes.reserve(candidate.payload.m_fieldHandles.size());
15195 for (std::uint32_t fieldHandle : candidate.payload.m_fieldHandles) {
15196 if (fieldHandle == DRW::NoHandle)
15197 continue;
15198 const auto field = plan.fieldCandidateIndexes.find(fieldHandle);
15199 if (field == plan.fieldCandidateIndexes.cend()) {
15200 plan.valid = false;
15201 break;
15202 }
15203 candidate.fieldCandidateIndexes.push_back(field->second);
15204 }
15205 if (!plan.valid ||
15206 !plan.fieldListCandidateIndexes
15207 .emplace(record.handle, plan.fieldListCandidates.size())
15208 .second) {
15209 plan.valid = false;
15210 break;
15211 }
15212 plan.fieldListCandidates.push_back(std::move(candidate));
15213 }
15214 }
15215
15216 plan.frozen = true;
15217 m_dwgFieldWritePlan = std::move(plan);
15218 return m_dwgFieldWritePlan.valid;
15219}
15220
15221bool RS_FilterDXFRW::validateDwgFieldWritePlan(
15222 bool requireClassAdmission) const {
15223 const DwgFieldWritePlan &plan = m_dwgFieldWritePlan;
15224 if (m_dwgW == nullptr || m_graphic == nullptr || !plan.frozen ||
15225 !plan.valid || plan.version != m_dwgW->getVersion() ||
15226 plan.fieldRecordCount !=
15227 m_graphic->dwgAdvancedMetadata().fields().size() ||
15228 plan.fieldListRecordCount !=
15229 m_graphic->dwgAdvancedMetadata().fieldLists().size() ||
15230 plan.fieldCandidateIndexes.size() != plan.fieldCandidates.size() ||
15231 plan.fieldListCandidateIndexes.size() != plan.fieldListCandidates.size()) {
15232 return false;
15233 }
15234 const auto &metadata = m_graphic->dwgAdvancedMetadata();
15235 for (std::size_t candidateIndex = 0;
15236 candidateIndex < plan.fieldCandidates.size(); ++candidateIndex) {
15237 const DwgFieldWritePlan::FieldCandidate &candidate =
15238 plan.fieldCandidates[candidateIndex];
15239 if (candidate.metadataIndex >= metadata.fields().size() ||
15240 metadata.fields()[candidate.metadataIndex].handle !=
15241 candidate.sourceHandle ||
15242 metadata.fields()[candidate.metadataIndex].replayState !=
15243 candidate.replayState ||
15244 metadata.authorizeDwgFieldSource(
15245 metadata.fields()[candidate.metadataIndex])
15246 .status != candidate.sourceAuthorization ||
15247 !LC_DwgAdvancedMetadata::matchesDwgFieldRecord(
15248 metadata.fields()[candidate.metadataIndex], candidate.payload)) {
15249 return false;
15250 }
15251 const auto indexIt = plan.fieldCandidateIndexes.find(candidate.sourceHandle);
15252 if (indexIt == plan.fieldCandidateIndexes.cend() ||
15253 indexIt->second != candidateIndex ||
15254 candidate.childFieldCandidateIndexes.size() !=
15255 candidate.payload.m_childHandles.size()) {
15256 return false;
15257 }
15258 for (std::size_t childIndex = 0;
15259 childIndex < candidate.payload.m_childHandles.size(); ++childIndex) {
15260 const std::size_t targetIndex =
15261 candidate.childFieldCandidateIndexes[childIndex];
15262 if (candidate.payload.m_childHandles[childIndex] == DRW::NoHandle ||
15263 targetIndex >= plan.fieldCandidates.size() ||
15264 plan.fieldCandidates[targetIndex].sourceHandle !=
15265 candidate.payload.m_childHandles[childIndex]) {
15266 return false;
15267 }
15268 }
15269 }
15270 for (std::size_t candidateIndex = 0;
15271 candidateIndex < plan.fieldListCandidates.size(); ++candidateIndex) {
15272 const DwgFieldWritePlan::FieldListCandidate &candidate =
15273 plan.fieldListCandidates[candidateIndex];
15274 if (candidate.metadataIndex >= metadata.fieldLists().size() ||
15275 metadata.fieldLists()[candidate.metadataIndex].handle !=
15276 candidate.sourceHandle ||
15277 metadata.fieldLists()[candidate.metadataIndex].replayState !=
15278 candidate.replayState ||
15279 metadata.authorizeDwgFieldListSource(
15280 metadata.fieldLists()[candidate.metadataIndex])
15281 .status != candidate.sourceAuthorization ||
15282 !LC_DwgAdvancedMetadata::matchesDwgFieldListRecord(
15283 metadata.fieldLists()[candidate.metadataIndex], candidate.payload)) {
15284 return false;
15285 }
15286 const auto indexIt =
15287 plan.fieldListCandidateIndexes.find(candidate.sourceHandle);
15288 if (indexIt == plan.fieldListCandidateIndexes.cend() ||
15289 indexIt->second != candidateIndex) {
15290 return false;
15291 }
15292 std::size_t referenceIndex = 0;
15293 for (std::uint32_t fieldHandle : candidate.payload.m_fieldHandles) {
15294 if (fieldHandle == DRW::NoHandle)
15295 continue;
15296 if (referenceIndex >= candidate.fieldCandidateIndexes.size() ||
15297 candidate.fieldCandidateIndexes[referenceIndex] >=
15298 plan.fieldCandidates.size() ||
15299 plan.fieldCandidates[candidate.fieldCandidateIndexes[referenceIndex]]
15300 .sourceHandle != fieldHandle) {
15301 return false;
15302 }
15303 ++referenceIndex;
15304 }
15305 if (referenceIndex != candidate.fieldCandidateIndexes.size())
15306 return false;
15307 }
15308 return !requireClassAdmission || plan.classesAdmitted;
15309}
15310
15311bool RS_FilterDXFRW::validateDwgFieldWriteReceipts() const {
15312 if (!validateDwgFieldWritePlan(/*requireClassAdmission=*/true))
15313 return false;
15314
15315 const DwgFieldWritePlan &plan = m_dwgFieldWritePlan;
15316 const auto framesMatch = [](const DRW::DwgObjectFrameReceipt &left,
15317 const DRW::DwgObjectFrameReceipt &right) {
15318 if (left.valid != right.valid || left.generation != right.generation ||
15319 left.objectHandle != right.objectHandle ||
15320 left.version != right.version || left.dataBitSize != right.dataBitSize ||
15321 left.handleBitSize != right.handleBitSize ||
15322 left.classNumber != right.classNumber ||
15323 left.writerOperation != right.writerOperation ||
15324 left.admissionToken != right.admissionToken ||
15325 left.occurrences.size() != right.occurrences.size()) {
15326 return false;
15327 }
15328 for (std::size_t index = 0; index < left.occurrences.size(); ++index) {
15329 const auto &leftOccurrence = left.occurrences[index];
15330 const auto &rightOccurrence = right.occurrences[index];
15331 if (leftOccurrence.objectHandle != rightOccurrence.objectHandle ||
15332 leftOccurrence.stream != rightOccurrence.stream ||
15333 leftOccurrence.serializedSegment != rightOccurrence.serializedSegment ||
15334 leftOccurrence.segmentOrdinal != rightOccurrence.segmentOrdinal ||
15335 leftOccurrence.token.code != rightOccurrence.token.code ||
15336 leftOccurrence.token.reference != rightOccurrence.token.reference ||
15337 leftOccurrence.token.startBit != rightOccurrence.token.startBit ||
15338 leftOccurrence.token.endBit != rightOccurrence.token.endBit) {
15339 return false;
15340 }
15341 }
15342 return true;
15343 };
15344 const auto hasCommittedFrame = [this, &framesMatch, &plan](
15345 std::uint32_t sourceHandle,
15346 std::uint32_t outputHandle,
15347 const DRW::DwgObjectFrameReceipt &frame) {
15348 std::uint32_t resolvedOutputHandle = 0;
15349 std::uint16_t classNumber = 0;
15350 bool classCommitted = false;
15351 const DRW::DwgObjectFrameReceipt *currentFrame =
15352 findDwgWriteObjectOccurrences(outputHandle);
15353 return outputHandle != 0 && frame.valid && frame.generation != 0 &&
15354 frame.objectHandle == outputHandle && frame.version == plan.version &&
15355 currentFrame != nullptr && framesMatch(frame, *currentFrame) &&
15356 lookupDwgWriteIdentity(DwgWriteSourceKind::Object, sourceHandle,
15357 &resolvedOutputHandle) ==
15358 DwgWriteIdentityLookup::Committed &&
15359 resolvedOutputHandle == outputHandle &&
15360 isDwgWriteIdentityCommitted(DwgWriteSourceKind::Object,
15361 outputHandle) &&
15362 findDwgClassFrameAdmission(DwgWriteSourceKind::Object,
15363 sourceHandle, outputHandle, classNumber,
15364 classCommitted) &&
15365 classCommitted && classNumber == frame.classNumber;
15366 };
15367 const auto hasReference = [this](std::uint32_t producerSourceHandle,
15368 std::uint32_t producerOutputHandle,
15369 const char *name, std::uint32_t ordinal,
15370 std::uint8_t wireHandleCode,
15371 std::uint32_t targetSourceHandle,
15372 std::uint32_t targetOutputHandle,
15373 bool allowFixedStructuralTarget) {
15374 const auto matchingReceipts = std::count_if(
15375 m_dwgWriteReferenceReceipts.cbegin(),
15376 m_dwgWriteReferenceReceipts.cend(),
15377 [=](const DwgWriteReferenceReceipt &receipt) {
15378 if (receipt.producerKind != DwgWriteSourceKind::Object ||
15379 receipt.producerSourceHandle != producerSourceHandle ||
15380 receipt.producerOutputHandle != producerOutputHandle ||
15381 receipt.referenceName != name || receipt.referenceOrdinal != ordinal ||
15382 !receipt.required || receipt.wireHandleCode != wireHandleCode ||
15383 receipt.targetOutputHandle == 0) {
15384 return false;
15385 }
15386 if (allowFixedStructuralTarget && receipt.targetSourceHandle == 0) {
15387 return receipt.targetKind == DwgWriteSourceKind::FixedStructural;
15388 }
15389 return receipt.targetSourceHandle == targetSourceHandle &&
15390 (targetOutputHandle == 0 ||
15391 receipt.targetOutputHandle == targetOutputHandle);
15392 });
15393 return matchingReceipts == 1;
15394 };
15395
15396 const DRW_Field::DwgReferencePolicy childFieldPolicy =
15397 DRW_Field::dwgReferencePolicy(plan.version,
15398 DRW_Field::DwgReferenceKind::ChildField);
15399 const DRW_Field::DwgReferencePolicy objectPolicy =
15400 DRW_Field::dwgReferencePolicy(plan.version,
15401 DRW_Field::DwgReferenceKind::Object);
15402 const DRW_Field::DwgReferencePolicy fieldListPolicy =
15403 DRW_Field::dwgReferencePolicy(plan.version,
15404 DRW_Field::DwgReferenceKind::FieldListMember);
15405 if (!childFieldPolicy.hasHandleStreamSlot || childFieldPolicy.allowsNull ||
15406 !objectPolicy.hasHandleStreamSlot || objectPolicy.allowsNull ||
15407 !fieldListPolicy.hasHandleStreamSlot || !fieldListPolicy.allowsNull) {
15408 return false;
15409 }
15410
15411 for (const DwgFieldWritePlan::FieldCandidate &candidate :
15412 plan.fieldCandidates) {
15413 if (!candidate.classAdmitted || !candidate.objectEmitted ||
15414 !hasCommittedFrame(candidate.sourceHandle, candidate.outputHandle,
15415 candidate.frame)) {
15416 return false;
15417 }
15418 if (candidate.childFieldCandidateIndexes.size() !=
15419 candidate.payload.m_childHandles.size()) {
15420 return false;
15421 }
15422 for (std::size_t ordinal = 0;
15423 ordinal < candidate.payload.m_childHandles.size(); ++ordinal) {
15424 const std::size_t targetIndex = candidate.childFieldCandidateIndexes[ordinal];
15425 if (targetIndex >= plan.fieldCandidates.size())
15426 return false;
15427 const DwgFieldWritePlan::FieldCandidate &target =
15428 plan.fieldCandidates[targetIndex];
15429 if (!target.objectEmitted ||
15430 !hasReference(candidate.sourceHandle, candidate.outputHandle,
15431 "FIELD child", static_cast<std::uint32_t>(ordinal),
15432 childFieldPolicy.wireHandleCode, target.sourceHandle,
15433 target.outputHandle,
15434 /*allowFixedStructuralTarget=*/false)) {
15435 return false;
15436 }
15437 }
15438 for (std::size_t ordinal = 0;
15439 ordinal < candidate.payload.m_objectHandles.size(); ++ordinal) {
15440 const std::uint32_t sourceHandle = candidate.payload.m_objectHandles[ordinal];
15441 if (sourceHandle == DRW::NoHandle ||
15442 !hasReference(candidate.sourceHandle, candidate.outputHandle,
15443 "FIELD object", static_cast<std::uint32_t>(ordinal),
15444 objectPolicy.wireHandleCode, sourceHandle,
15445 /*targetOutputHandle=*/0,
15446 /*allowFixedStructuralTarget=*/true)) {
15447 return false;
15448 }
15449 }
15450 const auto hasCadValueReference =
15451 [&hasReference, &candidate, &plan](const DRW_CadValue &value,
15452 const char *name,
15453 std::uint32_t ordinal) {
15454 const DRW_Field::DwgReferencePolicy policy =
15455 DRW_Field::dwgReferencePolicy(
15456 plan.version, DRW_Field::DwgReferenceKind::CadValueObjectId,
15457 &value);
15458 return !policy.hasHandleStreamSlot ||
15459 (value.m_handle != DRW::NoHandle &&
15460 hasReference(candidate.sourceHandle, candidate.outputHandle,
15461 name, ordinal, policy.wireHandleCode,
15462 value.m_handle, /*targetOutputHandle=*/0,
15463 /*allowFixedStructuralTarget=*/true));
15464 };
15465 if (!hasCadValueReference(candidate.payload.m_value, "FIELD value", 0))
15466 return false;
15467 for (std::size_t ordinal = 0;
15468 ordinal < candidate.payload.m_childValues.size(); ++ordinal) {
15469 if (!hasCadValueReference(candidate.payload.m_childValues[ordinal].m_value,
15470 "FIELD child value",
15471 static_cast<std::uint32_t>(ordinal))) {
15472 return false;
15473 }
15474 }
15475 }
15476
15477 for (const DwgFieldWritePlan::FieldListCandidate &candidate :
15478 plan.fieldListCandidates) {
15479 if (!candidate.classAdmitted || !candidate.objectEmitted ||
15480 !hasCommittedFrame(candidate.sourceHandle, candidate.outputHandle,
15481 candidate.frame)) {
15482 return false;
15483 }
15484 std::size_t referenceIndex = 0;
15485 for (std::size_t ordinal = 0;
15486 ordinal < candidate.payload.m_fieldHandles.size(); ++ordinal) {
15487 const std::uint32_t sourceHandle = candidate.payload.m_fieldHandles[ordinal];
15488 if (sourceHandle == DRW::NoHandle)
15489 continue;
15490 if (referenceIndex >= candidate.fieldCandidateIndexes.size())
15491 return false;
15492 const std::size_t targetIndex =
15493 candidate.fieldCandidateIndexes[referenceIndex++];
15494 if (targetIndex >= plan.fieldCandidates.size())
15495 return false;
15496 const DwgFieldWritePlan::FieldCandidate &target =
15497 plan.fieldCandidates[targetIndex];
15498 if (target.sourceHandle != sourceHandle || !target.objectEmitted ||
15499 !hasReference(candidate.sourceHandle, candidate.outputHandle,
15500 "FIELDLIST field", static_cast<std::uint32_t>(ordinal),
15501 fieldListPolicy.wireHandleCode, target.sourceHandle,
15502 target.outputHandle,
15503 /*allowFixedStructuralTarget=*/false)) {
15504 return false;
15505 }
15506 }
15507 if (referenceIndex != candidate.fieldCandidateIndexes.size())
15508 return false;
15509 }
15510 return true;
15511}
15512
15513bool RS_FilterDXFRW::validateDwgExportAdmissionPlan(
15514 DwgOpaqueTableFailurePhase phase) const {
15515 const DwgExportAdmissionPlan &plan = m_dwgExportAdmissionPlan;
15516 if (m_dwgW == nullptr || m_graphic == nullptr || !plan.frozen ||
15517 !plan.valid || plan.version != m_dwgW->getVersion()) {
15518 return false;
15519 }
15520 const auto &metadata = m_graphic->dwgAdvancedMetadata();
15521 const bool metadataCountChanged =
15522 plan.blockListCount != m_graphic->countBlocks() ||
15523 plan.plotSettingsCount != metadata.plotSettings().size() ||
15524 plan.rawObjectCount != metadata.rawObjects().size() ||
15525 plan.dictionaryCount != metadata.dictionaries().size() ||
15526 plan.tableCount != metadata.tables().size() ||
15527 plan.cellStyleMapCount != metadata.cellStyleMaps().size();
15528 if (metadataCountChanged) {
15529 for (const auto &entry : plan.opaqueTableRecordIndexes) {
15530 const DwgOpaqueTablePlanRecord &planned = entry.second;
15531 DwgOpaqueTableFailure failure{phase,
15532 DwgOpaqueTableFailureKind::SourceChanged,
15533 planned.rawFingerprint.handle, 0,
15534 std::numeric_limits<std::size_t>::max()};
15535 if (entry.first < metadata.rawObjects().size() &&
15536 requiresDwgOpaqueTableAdmission(metadata.rawObjects()[entry.first])) {
15537 const DwgOpaqueTableInspection inspection =
15538 inspectDwgOpaqueTableRawObject(entry.first);
15539 if (inspection.preflightFailure.has_value()) {
15540 failure = *inspection.preflightFailure;
15541 failure.phase = phase;
15542 }
15543 }
15544 recordDwgOpaqueTableRuntimeFailure(entry.first, failure);
15545 }
15546 return false;
15547 }
15548 for (const DwgBlockDefinitionPlanRecord &definition : plan.blockDefinitions) {
15549 if (definition.block == nullptr || definition.block->isDeleted() ||
15550 definition.sourceHandle != definition.block->sourceHandle() ||
15551 definition.normalizedName !=
15552 normalizeDwgTableName(
15553 definition.block->getName().toUtf8().toStdString())) {
15554 return false;
15555 }
15556 }
15557 for (const auto &entry : plan.insertRecordIndexes) {
15558 const RS_Insert *insert = entry.first;
15559 if (insert == nullptr || insert->getFlag(RS2::FlagDeleted) ||
15560 entry.second >= plan.records.size()) {
15561 return false;
15562 }
15563 const DwgExportAdmissionRecord &record = plan.records[entry.second];
15564 if (record.family != DwgExportAdmissionFamily::Insert ||
15565 record.policy == DwgExportAdmissionPolicy::Refuse ||
15566 record.sourceHandle != insert->sourceHandle() ||
15567 record.normalizedName !=
15568 normalizeDwgTableName(insert->getName().toUtf8().toStdString())) {
15569 return false;
15570 }
15571 }
15572 for (const auto &entry : plan.wipeoutRecordIndexes) {
15573 const LC_Wipeout *wipeout = entry.first;
15574 if (wipeout == nullptr || wipeout->getFlag(RS2::FlagDeleted) ||
15575 entry.second >= plan.records.size()) {
15576 return false;
15577 }
15578 const DwgExportAdmissionRecord &record = plan.records[entry.second];
15579 const LC_WipeoutData &data = wipeout->getData();
15580 if (record.family != DwgExportAdmissionFamily::Wipeout ||
15581 record.policy != DwgExportAdmissionPolicy::Native ||
15582 record.sourceHandle != wipeout->sourceHandle() ||
15583 (data.hasNativeFrame &&
15584 (data.imageDefHandle != 0 || data.imageDefReactorHandle != 0))) {
15585 return false;
15586 }
15587 }
15588 for (const auto &entry : plan.opaqueTableRecordIndexes) {
15589 const DwgOpaqueTablePlanRecord &planned = entry.second;
15590 if (planned.rawObjectIndex != entry.first ||
15591 planned.admissionRecordIndex >= plan.records.size()) {
15592 recordDwgOpaqueTableRuntimeFailure(
15593 entry.first, {phase, DwgOpaqueTableFailureKind::SourceChanged,
15594 planned.rawFingerprint.handle, 0,
15595 std::numeric_limits<std::size_t>::max()});
15596 return false;
15597 }
15598 const DwgOpaqueTableInspection inspection =
15599 inspectDwgOpaqueTableRawObject(entry.first);
15600 const DwgExportAdmissionRecord &record =
15601 plan.records[planned.admissionRecordIndex];
15602 const auto &rawObjects = m_graphic->dwgAdvancedMetadata().rawObjects();
15603 if (entry.first >= rawObjects.size() ||
15604 !requiresDwgOpaqueTableAdmission(rawObjects[entry.first]) ||
15605 !inspection.isOpaqueTable || !inspection.eligible ||
15606 !(inspection.rawFingerprint == planned.rawFingerprint) ||
15607 !(inspection.summaryFingerprint == planned.summaryFingerprint) ||
15608 inspection.referenceKind != planned.referenceKind ||
15609 inspection.referenceHandle != planned.referenceHandle ||
15610 inspection.referenceOrdinal != planned.referenceOrdinal ||
15611 inspection.referencePlan != planned.referencePlan ||
15612 inspection.ownerGraph != planned.ownerGraph ||
15613 inspection.preflightFailure != planned.preflightFailure ||
15614 record.family != DwgExportAdmissionFamily::OpaqueTable ||
15615 record.policy != DwgExportAdmissionPolicy::SameVersionRaw ||
15616 record.sourceHandle != inspection.sourceHandle ||
15617 record.sourceOrdinal != entry.first ||
15618 record.normalizedName !=
15619 normalizeDwgTableName(rawObjects[entry.first].recordName)) {
15620 DwgOpaqueTableFailure failure{phase,
15621 DwgOpaqueTableFailureKind::SourceChanged,
15622 planned.rawFingerprint.handle, 0,
15623 std::numeric_limits<std::size_t>::max()};
15624 if (inspection.preflightFailure.has_value()) {
15625 failure = *inspection.preflightFailure;
15626 failure.phase = phase;
15627 }
15628 recordDwgOpaqueTableRuntimeFailure(entry.first, failure);
15629 return false;
15630 }
15631 }
15632 if (!validateDwgOpaqueTableReferenceReceipts(
15633 DwgOpaqueTableReceiptStage::TableRecordsEmitted, phase)) {
15634 return false;
15635 }
15636 const auto &plotSettings = m_graphic->dwgAdvancedMetadata().plotSettings();
15637 for (const auto &entry : plan.plotSettingsRecordIndexes) {
15638 if (entry.first >= plotSettings.size() ||
15639 entry.second >= plan.records.size())
15640 return false;
15641 const DwgExportAdmissionRecord &record = plan.records[entry.second];
15642 if (record.family != DwgExportAdmissionFamily::PlotSettings ||
15643 record.policy == DwgExportAdmissionPolicy::Refuse ||
15644 record.sourceHandle != plotSettings[entry.first].handle) {
15645 return false;
15646 }
15647 }
15648 const auto &rawObjects = m_graphic->dwgAdvancedMetadata().rawObjects();
15649 for (const auto &entry : plan.rawPlotSettingsRecordIndexes) {
15650 if (entry.second >= rawObjects.size())
15651 return false;
15652 const auto &record = rawObjects[entry.second];
15653 if (!isPlotSettingsRawObject(record) || record.handle != entry.first ||
15654 record.isEntity || record.version != plan.version ||
15655 isFixedStructuralDwgHandle(record.handle) ||
15656 record.replayState !=
15657 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
15658 LC_DwgAdvancedMetadata::rawReplayBlocker(record) !=
15659 LC_DwgAdvancedMetadata::ReplayBlocker::None ||
15660 (record.hasDataStorage && !canReplayDwgDataStorage(record.handle))) {
15661 return false;
15662 }
15663 }
15664 return true;
15665}
15666
15667const char *RS_FilterDXFRW::dwgOpaqueTableFailurePhaseName(
15668 DwgOpaqueTableFailurePhase phase) {
15669 switch (phase) {
15670 case DwgOpaqueTableFailurePhase::Preflight:
15671 return "preflight";
15672 case DwgOpaqueTableFailurePhase::PostClasses:
15673 return "post-classes";
15674 case DwgOpaqueTableFailurePhase::Objects:
15675 return "objects";
15676 case DwgOpaqueTableFailurePhase::Finalized:
15677 return "finalized";
15678 }
15679 return "unknown";
15680}
15681
15682const char *
15683RS_FilterDXFRW::dwgOpaqueTableFailureKindName(DwgOpaqueTableFailureKind kind) {
15684 switch (kind) {
15685 case DwgOpaqueTableFailureKind::RawFrame:
15686 return "raw-frame";
15687 case DwgOpaqueTableFailureKind::MissingSummary:
15688 return "missing-summary";
15689 case DwgOpaqueTableFailureKind::AmbiguousSummary:
15690 return "ambiguous-summary";
15691 case DwgOpaqueTableFailureKind::InvalidSummary:
15692 return "invalid-summary";
15693 case DwgOpaqueTableFailureKind::OwnerGraph:
15694 return "owner-graph";
15695 case DwgOpaqueTableFailureKind::SourceGroup:
15696 return "source-group";
15697 case DwgOpaqueTableFailureKind::RootEntry:
15698 return "root-entry";
15699 case DwgOpaqueTableFailureKind::ChildEntry:
15700 return "child-entry";
15701 case DwgOpaqueTableFailureKind::Reference:
15702 return "reference";
15703 case DwgOpaqueTableFailureKind::SourceChanged:
15704 return "source-changed";
15705 case DwgOpaqueTableFailureKind::TargetNodReceipt:
15706 return "target-nod-receipt";
15707 case DwgOpaqueTableFailureKind::ChildFrameReceipt:
15708 return "child-frame-receipt";
15709 case DwgOpaqueTableFailureKind::RawFrameReceipt:
15710 return "raw-frame-receipt";
15711 case DwgOpaqueTableFailureKind::TableRecordReceipt:
15712 return "table-record-receipt";
15713 }
15714 return "unknown";
15715}
15716
15717QString RS_FilterDXFRW::dwgExportAdmissionReport() const {
15718 const auto familyName = [](DwgExportAdmissionFamily family) {
15719 switch (family) {
15720 case DwgExportAdmissionFamily::BlockDefinition:
15721 return "block";
15722 case DwgExportAdmissionFamily::Insert:
15723 return "insert";
15724 case DwgExportAdmissionFamily::Wipeout:
15725 return "wipeout";
15726 case DwgExportAdmissionFamily::OpaqueTable:
15727 return "opaque-table";
15728 case DwgExportAdmissionFamily::PlotSettings:
15729 return "plot-settings";
15730 }
15731 return "unknown";
15732 };
15733 const auto policyName = [](DwgExportAdmissionPolicy policy) {
15734 switch (policy) {
15735 case DwgExportAdmissionPolicy::Native:
15736 return "native";
15737 case DwgExportAdmissionPolicy::Typed:
15738 return "typed";
15739 case DwgExportAdmissionPolicy::SameVersionRaw:
15740 return "same-version-raw";
15741 case DwgExportAdmissionPolicy::Refuse:
15742 return "refuse";
15743 }
15744 return "unknown";
15745 };
15746 const auto reasonName = [](DwgExportAdmissionReason reason) {
15747 switch (reason) {
15748 case DwgExportAdmissionReason::None:
15749 return "none";
15750 case DwgExportAdmissionReason::InvalidBlockDefinition:
15751 return "invalid-block";
15752 case DwgExportAdmissionReason::DuplicateBlockDefinition:
15753 return "duplicate-block";
15754 case DwgExportAdmissionReason::MissingBlockDefinition:
15755 return "missing-block";
15756 case DwgExportAdmissionReason::UnsupportedWipeoutReference:
15757 return "wipeout-reference";
15758 case DwgExportAdmissionReason::InvalidOpaqueTableRaw:
15759 return "invalid-opaque-table-raw";
15760 case DwgExportAdmissionReason::MissingOpaqueTableSummary:
15761 return "missing-opaque-table-summary";
15762 case DwgExportAdmissionReason::AmbiguousOpaqueTableSummary:
15763 return "ambiguous-opaque-table-summary";
15764 case DwgExportAdmissionReason::InvalidOpaqueTableSummary:
15765 return "invalid-opaque-table-summary";
15766 case DwgExportAdmissionReason::OpaqueTableReference:
15767 return "opaque-table-reference";
15768 case DwgExportAdmissionReason::UnsupportedPlotSettingsDataStorage:
15769 return "plot-settings-data-storage";
15770 case DwgExportAdmissionReason::UnsupportedPlotSettingsApplicationData:
15771 return "plot-settings-application-data";
15772 case DwgExportAdmissionReason::RawPlotSettingsUnavailable:
15773 return "raw-plot-settings-unavailable";
15774 case DwgExportAdmissionReason::DuplicateRawPlotSettings:
15775 return "duplicate-raw-plot-settings";
15776 case DwgExportAdmissionReason::SourceChanged:
15777 return "source-changed";
15778 }
15779 return "unknown";
15780 };
15781 const auto handle = [](std::uint32_t value) {
15782 return QStringLiteral("0x%1")(QString(QtPrivate::qMakeStringPrivate(u"" "0x%1"))).arg(QString::number(value, 16).toUpper());
15783 };
15784 const auto ordinal = [](std::size_t value) {
15785 return value == std::numeric_limits<std::size_t>::max()
15786 ? QStringLiteral("-")(QString(QtPrivate::qMakeStringPrivate(u"" "-")))
15787 : QString::number(static_cast<qulonglong>(value));
15788 };
15789 const auto field = [](const std::string &value) {
15790 QString result = QString::fromStdString(value);
15791 result.replace(QLatin1Char('\\'), QStringLiteral("\\\\")(QString(QtPrivate::qMakeStringPrivate(u"" "\\\\"))));
15792 result.replace(QLatin1Char('\n'), QStringLiteral("\\n")(QString(QtPrivate::qMakeStringPrivate(u"" "\\n"))));
15793 result.replace(QLatin1Char('\r'), QStringLiteral("\\r")(QString(QtPrivate::qMakeStringPrivate(u"" "\\r"))));
15794 result.replace(QLatin1Char('\t'), QStringLiteral("\\t")(QString(QtPrivate::qMakeStringPrivate(u"" "\\t"))));
15795 return result;
15796 };
15797 const auto failureText = [&handle,
15798 &ordinal](const DwgOpaqueTableFailure &failure) {
15799 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"
)))
15800 .arg(QString::fromLatin1(
15801 RS_FilterDXFRW::dwgOpaqueTableFailurePhaseName(failure.phase)),
15802 QString::fromLatin1(
15803 RS_FilterDXFRW::dwgOpaqueTableFailureKindName(failure.kind)),
15804 handle(failure.sourceHandle), handle(failure.relatedHandle),
15805 ordinal(failure.ordinal));
15806 };
15807
15808 const DwgExportAdmissionPlan &plan = m_dwgExportAdmissionPlan;
15809 QStringList lines;
15810 lines << QStringLiteral("dwg-admission-report")(QString(QtPrivate::qMakeStringPrivate(u"" "dwg-admission-report"
)))
15811 << QStringLiteral("frozen=%1 valid=%2 version=%3")(QString(QtPrivate::qMakeStringPrivate(u"" "frozen=%1 valid=%2 version=%3"
)))
15812 .arg(plan.frozen ? QStringLiteral("true")(QString(QtPrivate::qMakeStringPrivate(u"" "true")))
15813 : QStringLiteral("false")(QString(QtPrivate::qMakeStringPrivate(u"" "false"))),
15814 plan.valid ? QStringLiteral("true")(QString(QtPrivate::qMakeStringPrivate(u"" "true")))
15815 : QStringLiteral("false")(QString(QtPrivate::qMakeStringPrivate(u"" "false"))),
15816 dwgVersionDisplay(plan.version));
15817 for (std::size_t index = 0; index < plan.records.size(); ++index) {
15818 const DwgExportAdmissionRecord &record = plan.records[index];
15819 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")))
15820 "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")))
15821 .arg(QString::number(static_cast<qulonglong>(index)),
15822 QString::fromLatin1(familyName(record.family)),
15823 QString::fromLatin1(policyName(record.policy)),
15824 QString::fromLatin1(reasonName(record.reason)),
15825 handle(record.sourceHandle),
15826 handle(record.relatedSourceHandle),
15827 record.family == DwgExportAdmissionFamily::OpaqueTable
15828 ? ordinal(record.sourceOrdinal)
15829 : QStringLiteral("-")(QString(QtPrivate::qMakeStringPrivate(u"" "-"))),
15830 field(record.normalizedName));
15831 }
15832 for (const auto &entry : plan.opaqueTableRecordIndexes) {
15833 const DwgOpaqueTablePlanRecord &record = entry.second;
15834 const auto runtime = m_dwgOpaqueTableRuntimeFailures.find(entry.first);
15835 lines << QStringLiteral("opaque-raw-index=%1 preflight=%2 runtime=%3")(QString(QtPrivate::qMakeStringPrivate(u"" "opaque-raw-index=%1 preflight=%2 runtime=%3"
)))
15836 .arg(QString::number(static_cast<qulonglong>(entry.first)),
15837 record.preflightFailure.has_value()
15838 ? failureText(*record.preflightFailure)
15839 : QStringLiteral("none")(QString(QtPrivate::qMakeStringPrivate(u"" "none"))),
15840 runtime != m_dwgOpaqueTableRuntimeFailures.cend()
15841 ? failureText(runtime->second)
15842 : QStringLiteral("none")(QString(QtPrivate::qMakeStringPrivate(u"" "none"))));
15843 }
15844 return lines.join(QLatin1Char('\n'));
15845}
15846
15847void RS_FilterDXFRW::recordDwgOpaqueTableRuntimeFailure(
15848 std::size_t rawObjectIndex, DwgOpaqueTableFailure failure) const {
15849 if (failure.phase == DwgOpaqueTableFailurePhase::Preflight)
15850 return;
15851 const auto [it, inserted] = m_dwgOpaqueTableRuntimeFailures.emplace(
15852 rawObjectIndex, std::move(failure));
15853 if (!inserted)
15854 return;
15855 const long long ordinal =
15856 it->second.ordinal == std::numeric_limits<std::size_t>::max()
15857 ? -1LL
15858 : static_cast<long long>(it->second.ordinal);
15859 RS_DEBUGRS_Debug::instance()->print(
15860 RS_Debug::D_WARNING,
15861 "RS_FilterDXFRW: DWG opaque table runtime refusal phase=%s kind=%s "
15862 "handle=0x%X related=0x%X ordinal=%lld",
15863 dwgOpaqueTableFailurePhaseName(it->second.phase),
15864 dwgOpaqueTableFailureKindName(it->second.kind), it->second.sourceHandle,
15865 it->second.relatedHandle, ordinal);
15866}
15867
15868bool RS_FilterDXFRW::validateDwgOpaqueTableReferenceReceipts(
15869 DwgOpaqueTableReceiptStage stage, DwgOpaqueTableFailurePhase phase) const {
15870 const DwgExportAdmissionPlan &plan = m_dwgExportAdmissionPlan;
15871 if (m_dwgW == nullptr || m_graphic == nullptr || !plan.frozen ||
15872 !plan.valid) {
15873 return false;
15874 }
15875 const auto recordAll = [this, &plan, phase](DwgOpaqueTableFailureKind kind) {
15876 for (const auto &entry : plan.opaqueTableRecordIndexes) {
15877 const DwgOpaqueTablePlanRecord &planned = entry.second;
15878 recordDwgOpaqueTableRuntimeFailure(
15879 entry.first, {phase, kind, planned.rawFingerprint.handle, 0,
15880 std::numeric_limits<std::size_t>::max()});
15881 }
15882 };
15883
15884 std::vector<std::uint32_t> emittedHandles;
15885 if (!m_dwgW->getEmittedDwgTableRecordHandles(emittedHandles)) {
15886 recordAll(DwgOpaqueTableFailureKind::TableRecordReceipt);
15887 return false;
15888 }
15889 const std::set<std::uint32_t> emittedSet(emittedHandles.cbegin(),
15890 emittedHandles.cend());
15891 if (emittedSet.size() != emittedHandles.size() ||
15892 std::any_of(emittedHandles.cbegin(), emittedHandles.cend(),
15893 [](std::uint32_t handle) { return handle == 0; })) {
15894 recordAll(DwgOpaqueTableFailureKind::TableRecordReceipt);
15895 return false;
15896 }
15897
15898 std::vector<DRW::DwgNamedObjectDictionaryEntry> namedEntries;
15899 if (!m_dwgW->getEmittedDwgNamedObjectDictionaryEntries(namedEntries)) {
15900 recordAll(DwgOpaqueTableFailureKind::TargetNodReceipt);
15901 return false;
15902 }
15903 const auto &metadata = m_graphic->dwgAdvancedMetadata();
15904 const auto validateOwnerGraph = [this, &metadata, &namedEntries, stage](
15905 const DwgOpaqueTableOwnerGraphPlanRecord
15906 &graph) {
15907 if (graph.rawObjectIndex >= metadata.rawObjects().size() ||
15908 graph.rootDictionaryIndex >= metadata.dictionaries().size() ||
15909 graph.childDictionaryIndex >= metadata.dictionaries().size() ||
15910 graph.rootDictionaryIndex == graph.childDictionaryIndex ||
15911 graph.rootSourceHandle == 0 ||
15912 graph.rootOutputHandle != DRW::DwgNamedObjectsDictionaryHandle ||
15913 graph.childSourceHandle == 0 ||
15914 graph.childSourceHandle != graph.childOutputHandle ||
15915 graph.childSourceHandle != graph.rawOwnerHandle ||
15916 graph.rawSourceHandle == 0 ||
15917 graph.rawSourceHandle != graph.rawOutputHandle ||
15918 graph.rootSourceHandle == graph.childSourceHandle ||
15919 graph.rootSourceHandle == graph.rawSourceHandle ||
15920 graph.childSourceHandle == graph.rawSourceHandle) {
15921 return false;
15922 }
15923 const auto &raw = metadata.rawObjects()[graph.rawObjectIndex];
15924 const auto &root = metadata.dictionaries()[graph.rootDictionaryIndex];
15925 const auto &child = metadata.dictionaries()[graph.childDictionaryIndex];
15926 if (raw.handle != graph.rawSourceHandle ||
15927 raw.parentHandle != graph.rawOwnerHandle ||
15928 root.handle != graph.rootSourceHandle ||
15929 root.parentHandle != DRW::NoHandle ||
15930 graph.rootEntryOrdinal >= root.entries.size() ||
15931 root.entries[graph.rootEntryOrdinal].name != graph.rootEntryName ||
15932 root.entries[graph.rootEntryOrdinal].handle !=
15933 graph.childOutputHandle ||
15934 child.handle != graph.childSourceHandle ||
15935 child.parentHandle != graph.rootSourceHandle ||
15936 graph.childEntryOrdinal >= child.entries.size() ||
15937 child.entries[graph.childEntryOrdinal].name != graph.childEntryName ||
15938 child.entries[graph.childEntryOrdinal].handle !=
15939 graph.rawOutputHandle ||
15940 dwgOpaqueTableDictionaryDigest(root) != graph.rootIdentityDigest ||
15941 dwgOpaqueTableDictionaryDigest(child) != graph.childIdentityDigest) {
15942 return false;
15943 }
15944 const auto entryCount = static_cast<std::size_t>(
15945 std::count_if(namedEntries.cbegin(), namedEntries.cend(),
15946 [&graph](const auto &entry) {
15947 return entry.name == graph.rootEntryName &&
15948 entry.childHandle == graph.childOutputHandle;
15949 }));
15950 if (entryCount != 1)
15951 return false;
15952 if (stage == DwgOpaqueTableReceiptStage::TableRecordsEmitted)
15953 return true;
15954
15955 const DRW::DwgObjectFrameReceipt *childFrame =
15956 findDwgWriteObjectOccurrences(graph.childOutputHandle);
15957 const DRW::DwgObjectFrameReceipt *rawFrame =
15958 findDwgWriteObjectOccurrences(graph.rawOutputHandle);
15959 return childFrame != nullptr && childFrame->valid &&
15960 childFrame->objectHandle == graph.childOutputHandle &&
15961 childFrame->version == m_dwgW->getVersion() && rawFrame != nullptr &&
15962 rawFrame->valid && rawFrame->objectHandle == graph.rawOutputHandle &&
15963 rawFrame->version == m_dwgW->getVersion() &&
15964 isDwgWriteIdentityCommitted(RS_FilterDXFRW_DwgWriteIdentityRegistry::
15965 DwgWriteSourceKind::Object,
15966 graph.childOutputHandle) &&
15967 isDwgWriteIdentityCommitted(RS_FilterDXFRW_DwgWriteIdentityRegistry::
15968 DwgWriteSourceKind::RawCarrier,
15969 graph.rawOutputHandle);
15970 };
15971
15972 const auto matchesNamedTableRecord =
15973 [this](const DwgOpaqueTableReferencePlanRecord &reference) {
15974 const auto hasOutput =
15975 [&reference](const std::map<std::string, std::uint32_t> &handles,
15976 const char *name) {
15977 const auto it = handles.find(name);
15978 return it != handles.end() &&
15979 it->second == reference.targetOutputHandle;
15980 };
15981 if (reference.kind == DwgOpaqueTableReferenceKind::LineType) {
15982 if (reference.targetSourceHandle == DRW::DwgLtypeByBlockHandle)
15983 return hasOutput(m_dwgWriteLTypeHandleByName, "BYBLOCK");
15984 if (reference.targetSourceHandle == DRW::DwgLtypeByLayerHandle)
15985 return hasOutput(m_dwgWriteLTypeHandleByName, "BYLAYER");
15986 if (reference.targetSourceHandle == DRW::DwgLtypeContinuousHandle) {
15987 return hasOutput(m_dwgWriteLTypeHandleByName, "CONTINUOUS");
15988 }
15989 }
15990 if (reference.kind == DwgOpaqueTableReferenceKind::TextStyle &&
15991 reference.targetSourceHandle == DRW::DwgStandardTextStyleHandle) {
15992 return hasOutput(m_dwgWriteTextStyleHandleByName, "STANDARD");
15993 }
15994 return false;
15995 };
15996
15997 const auto &rawObjects = m_graphic->dwgAdvancedMetadata().rawObjects();
15998 for (const auto &entry : plan.opaqueTableRecordIndexes) {
15999 const DwgOpaqueTablePlanRecord &planned = entry.second;
16000 if (entry.first >= rawObjects.size() ||
16001 planned.rawObjectIndex != entry.first) {
16002 return false;
16003 }
16004 const auto &raw = rawObjects[entry.first];
16005 if (planned.ownerGraph.has_value() &&
16006 !validateOwnerGraph(*planned.ownerGraph)) {
16007 recordDwgOpaqueTableRuntimeFailure(
16008 entry.first, {phase, DwgOpaqueTableFailureKind::SourceChanged,
16009 planned.rawFingerprint.handle, 0,
16010 std::numeric_limits<std::size_t>::max()});
16011 return false;
16012 }
16013 for (const DwgOpaqueTableReferencePlanRecord &reference :
16014 planned.referencePlan) {
16015 if (reference.producerRawObjectIndex != entry.first ||
16016 reference.producerSourceHandle != raw.handle ||
16017 reference.producerOutputHandle != raw.handle ||
16018 reference.targetOrdinal == std::numeric_limits<std::size_t>::max() ||
16019 reference.targetDomain !=
16020 DwgOpaqueTableReferenceTargetDomain::TableRecord ||
16021 reference.proofPhase !=
16022 DwgOpaqueTableReferenceProofPhase::TableRecords ||
16023 reference.targetSourceHandle == 0 ||
16024 reference.targetOutputHandle != reference.targetSourceHandle ||
16025 emittedSet.count(reference.targetOutputHandle) == 0 ||
16026 !matchesNamedTableRecord(reference)) {
16027 recordDwgOpaqueTableRuntimeFailure(
16028 entry.first,
16029 {phase, DwgOpaqueTableFailureKind::TableRecordReceipt,
16030 planned.rawFingerprint.handle, reference.targetOutputHandle,
16031 reference.targetOrdinal});
16032 return false;
16033 }
16034
16035 if (stage == DwgOpaqueTableReceiptStage::TableRecordsEmitted)
16036 continue;
16037
16038 const DRW::DwgObjectFrameReceipt *frame =
16039 findDwgWriteObjectOccurrences(reference.producerOutputHandle);
16040 if (frame == nullptr || !frame->valid ||
16041 frame->objectHandle != reference.producerOutputHandle ||
16042 frame->version != plan.version ||
16043 !isDwgWriteIdentityCommitted(RS_FilterDXFRW_DwgWriteIdentityRegistry::
16044 DwgWriteSourceKind::RawCarrier,
16045 reference.producerOutputHandle)) {
16046 recordDwgOpaqueTableRuntimeFailure(
16047 entry.first,
16048 {phase, DwgOpaqueTableFailureKind::RawFrameReceipt,
16049 planned.rawFingerprint.handle, reference.producerOutputHandle,
16050 reference.targetOrdinal});
16051 return false;
16052 }
16053 if (stage == DwgOpaqueTableReceiptStage::Finalized &&
16054 !isDwgWriteIdentityCommitted(RS_FilterDXFRW_DwgWriteIdentityRegistry::
16055 DwgWriteSourceKind::TableRecord,
16056 reference.targetOutputHandle)) {
16057 recordDwgOpaqueTableRuntimeFailure(
16058 entry.first,
16059 {phase, DwgOpaqueTableFailureKind::TableRecordReceipt,
16060 planned.rawFingerprint.handle, reference.targetOutputHandle,
16061 reference.targetOrdinal});
16062 return false;
16063 }
16064 }
16065 }
16066 return true;
16067}
16068
16069bool RS_FilterDXFRW::isDwgInsertAdmitted(const RS_Insert *insert) const {
16070 const DwgExportAdmissionPlan &plan = m_dwgExportAdmissionPlan;
16071 if (insert == nullptr || !plan.frozen || !plan.valid)
16072 return false;
16073 const auto it = plan.insertRecordIndexes.find(insert);
16074 if (it == plan.insertRecordIndexes.end() || it->second >= plan.records.size())
16075 return false;
16076 const DwgExportAdmissionRecord &record = plan.records[it->second];
16077 return record.family == DwgExportAdmissionFamily::Insert &&
16078 record.policy != DwgExportAdmissionPolicy::Refuse &&
16079 record.sourceHandle == insert->sourceHandle() &&
16080 record.normalizedName ==
16081 normalizeDwgTableName(insert->getName().toUtf8().toStdString());
16082}
16083
16084bool RS_FilterDXFRW::isDwgWipeoutAdmitted(const LC_Wipeout *wipeout) const {
16085 const DwgExportAdmissionPlan &plan = m_dwgExportAdmissionPlan;
16086 if (wipeout == nullptr || !plan.frozen || !plan.valid)
16087 return false;
16088 const auto it = plan.wipeoutRecordIndexes.find(wipeout);
16089 if (it == plan.wipeoutRecordIndexes.end() ||
16090 it->second >= plan.records.size()) {
16091 return false;
16092 }
16093 const DwgExportAdmissionRecord &record = plan.records[it->second];
16094 const LC_WipeoutData &data = wipeout->getData();
16095 return record.family == DwgExportAdmissionFamily::Wipeout &&
16096 record.policy == DwgExportAdmissionPolicy::Native &&
16097 record.sourceHandle == wipeout->sourceHandle() &&
16098 (!data.hasNativeFrame ||
16099 (data.imageDefHandle == 0 && data.imageDefReactorHandle == 0));
16100}
16101
16102RS_FilterDXFRW::DwgOpaqueTableInspection
16103RS_FilterDXFRW::inspectDwgOpaqueTableRawObject(
16104 std::size_t rawObjectIndex) const {
16105 DwgOpaqueTableInspection result;
16106 if (m_dwgW == nullptr || m_graphic == nullptr)
16107 return result;
16108 const auto &metadata = m_graphic->dwgAdvancedMetadata();
16109 if (rawObjectIndex >= metadata.rawObjects().size())
16110 return result;
16111
16112 const auto &raw = metadata.rawObjects()[rawObjectIndex];
16113 if (!isOpaqueTableRawObject(raw))
16114 return result;
16115 result.isOpaqueTable = true;
16116 result.sourceHandle = raw.handle;
16117 DwgOpaqueTableRawFingerprint &rawFingerprint = result.rawFingerprint;
16118 rawFingerprint.version = raw.version;
16119 rawFingerprint.objectType = raw.objectType;
16120 rawFingerprint.handle = raw.handle;
16121 rawFingerprint.parentHandle = raw.parentHandle;
16122 rawFingerprint.commonHandleDataValidated =
16123 drwDwgCommonLinkEvidenceValidated(raw.commonLinkEvidence);
16124 rawFingerprint.commonLinkEvidence = raw.commonLinkEvidence;
16125 rawFingerprint.reactorHandles = raw.reactorHandles;
16126 rawFingerprint.xDictHandle = raw.xDictHandle;
16127 rawFingerprint.numReactors = raw.numReactors;
16128 rawFingerprint.xDictFlag = raw.xDictFlag;
16129 rawFingerprint.blockOwnerHandle = raw.blockOwnerHandle;
16130 rawFingerprint.bodyBitSize = raw.bodyBitSize;
16131 rawFingerprint.objectOffset = raw.objectOffset;
16132 rawFingerprint.objectSize = raw.objectSize;
16133 rawFingerprint.isEntity = raw.isEntity;
16134 rawFingerprint.isCustomClass = raw.isCustomClass;
16135 rawFingerprint.hasDataStorage = raw.hasDataStorage;
16136 rawFingerprint.replayState = raw.replayState;
16137 rawFingerprint.hasClassDefinition = raw.hasClassDefinition;
16138 rawFingerprint.classProxyFlag = raw.classProxyFlag;
16139 rawFingerprint.recordName = raw.recordName;
16140 rawFingerprint.className = raw.className;
16141 rawFingerprint.classAppName = raw.classAppName;
16142 rawFingerprint.classWasProxy = raw.classWasProxy;
16143 rawFingerprint.classEntityFlagRaw = raw.classEntityFlagRaw;
16144 rawFingerprint.classDwgVersion = raw.classDwgVersion;
16145 rawFingerprint.classMaintenanceVersion = raw.classMaintenanceVersion;
16146 rawFingerprint.classUnknown1 = raw.classUnknown1;
16147 rawFingerprint.classUnknown2 = raw.classUnknown2;
16148 rawFingerprint.rawByteCount = raw.rawBytes.size();
16149 rawFingerprint.rawIdentityDigest = dwgOpaqueTableRawDigest(raw);
16150 const auto reject = [&result,
16151 &raw](DwgOpaqueTableReferenceKind referenceKind,
16152 DwgOpaqueTableFailureKind failureKind,
16153 std::uint32_t relatedHandle = 0,
16154 std::size_t ordinal =
16155 std::numeric_limits<std::size_t>::max()) {
16156 result.referenceKind = referenceKind;
16157 result.referenceHandle = relatedHandle;
16158 result.referenceOrdinal = ordinal;
16159 result.preflightFailure =
16160 DwgOpaqueTableFailure{DwgOpaqueTableFailurePhase::Preflight,
16161 failureKind, raw.handle, relatedHandle, ordinal};
16162 return result;
16163 };
16164 if (raw.isEntity || !raw.isCustomClass || raw.handle == 0 ||
16165 raw.objectType < 500 ||
16166 static_cast<std::uint64_t>(raw.objectType) >
16167 std::numeric_limits<std::uint16_t>::max() ||
16168 raw.rawBytes.empty() || isFixedStructuralDwgHandle(raw.handle) ||
16169 !sameRawObjectEncodingFamily(raw.version, m_dwgW->getVersion()) ||
16170 raw.replayState != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
16171 LC_DwgAdvancedMetadata::rawReplayBlocker(raw) !=
16172 LC_DwgAdvancedMetadata::ReplayBlocker::None ||
16173 (raw.hasDataStorage && !canReplayDwgDataStorage(raw.handle))) {
16174 return reject(DwgOpaqueTableReferenceKind::RawFrame,
16175 DwgOpaqueTableFailureKind::RawFrame);
16176 }
16177 const bool isTableStyle = isTableStyleRawObject(raw);
16178 if (raw.parentHandle != DRW::NoHandle &&
16179 !isFixedStructuralDwgHandle(raw.parentHandle)) {
16180 if (isTableStyle && !raw.typedPayloadValidated)
16181 return reject(DwgOpaqueTableReferenceKind::RawFrame,
16182 DwgOpaqueTableFailureKind::RawFrame);
16183 if (!isTableStyle) {
16184 return reject(DwgOpaqueTableReferenceKind::Owner,
16185 DwgOpaqueTableFailureKind::OwnerGraph, raw.parentHandle);
16186 }
16187 const auto &dictionaries = metadata.dictionaries();
16188 const auto childCount = std::count_if(
16189 dictionaries.cbegin(), dictionaries.cend(), [&raw](const auto &record) {
16190 return record.handle == raw.parentHandle;
16191 });
16192 if (childCount != 1 || isFixedStructuralDwgHandle(raw.parentHandle) ||
16193 m_dwgWriteHandleRemap.count(raw.parentHandle) != 0) {
16194 return reject(DwgOpaqueTableReferenceKind::Owner,
16195 DwgOpaqueTableFailureKind::OwnerGraph, raw.parentHandle);
16196 }
16197 const auto child = std::find_if(dictionaries.cbegin(), dictionaries.cend(),
16198 [&raw](const auto &record) {
16199 return record.handle == raw.parentHandle;
16200 });
16201 const auto childIndex =
16202 static_cast<std::size_t>(std::distance(dictionaries.cbegin(), child));
16203 if (child == dictionaries.cend() || child->parentHandle == DRW::NoHandle ||
16204 child->handle == raw.handle || !isCanonicalDwgNamedDictionary(*child)) {
16205 return reject(DwgOpaqueTableReferenceKind::Owner,
16206 DwgOpaqueTableFailureKind::OwnerGraph, raw.parentHandle);
16207 }
16208 if (child->entries.size() != 1u || child->entries[0].name.empty() ||
16209 child->entries[0].handle != raw.handle) {
16210 return reject(DwgOpaqueTableReferenceKind::Owner,
16211 DwgOpaqueTableFailureKind::ChildEntry, raw.parentHandle);
16212 }
16213 const std::uint32_t rootSourceHandle = child->parentHandle;
16214 const auto rootCount =
16215 std::count_if(dictionaries.cbegin(), dictionaries.cend(),
16216 [rootSourceHandle](const auto &record) {
16217 return record.handle == rootSourceHandle;
16218 });
16219 if (rootCount != 1)
16220 return reject(DwgOpaqueTableReferenceKind::Owner,
16221 DwgOpaqueTableFailureKind::OwnerGraph, raw.parentHandle);
16222 const auto root = std::find_if(dictionaries.cbegin(), dictionaries.cend(),
16223 [rootSourceHandle](const auto &record) {
16224 return record.handle == rootSourceHandle;
16225 });
16226 const auto rootIndex =
16227 static_cast<std::size_t>(std::distance(dictionaries.cbegin(), root));
16228 if (root == dictionaries.cend() || root->parentHandle != DRW::NoHandle ||
16229 root->handle == child->handle || root->handle == raw.handle ||
16230 !isCanonicalDwgNamedDictionary(*root) || root->entries.size() != 2u) {
16231 return reject(DwgOpaqueTableReferenceKind::Owner,
16232 DwgOpaqueTableFailureKind::OwnerGraph, raw.parentHandle);
16233 }
16234 if (root->entries[0].name != "ACAD_GROUP" ||
16235 root->entries[0].handle != DRW::DwgAcadGroupDictionaryHandle) {
16236 return reject(DwgOpaqueTableReferenceKind::Owner,
16237 DwgOpaqueTableFailureKind::SourceGroup,
16238 root->entries[0].handle, 0);
16239 }
16240 if (root->entries[1].name.empty() ||
16241 root->entries[1].name == "ACAD_GROUP" ||
16242 root->entries[1].handle != child->handle) {
16243 return reject(DwgOpaqueTableReferenceKind::Owner,
16244 DwgOpaqueTableFailureKind::RootEntry, raw.parentHandle);
16245 }
16246
16247 DwgOpaqueTableOwnerGraphPlanRecord graph;
16248 graph.rawObjectIndex = rawObjectIndex;
16249 graph.rawSourceHandle = raw.handle;
16250 graph.rawOutputHandle = raw.handle;
16251 graph.rawOwnerHandle = raw.parentHandle;
16252 graph.rootDictionaryIndex = rootIndex;
16253 graph.childDictionaryIndex = childIndex;
16254 graph.rootSourceHandle = rootSourceHandle;
16255 graph.rootOutputHandle = DRW::DwgNamedObjectsDictionaryHandle;
16256 graph.childSourceHandle = child->handle;
16257 graph.childOutputHandle = child->handle;
16258 graph.rootEntryOrdinal = 1;
16259 graph.childEntryOrdinal = 0;
16260 graph.rootEntryName = root->entries[1].name;
16261 graph.childEntryName = child->entries[0].name;
16262 graph.rootIdentityDigest = dwgOpaqueTableDictionaryDigest(*root);
16263 graph.childIdentityDigest = dwgOpaqueTableDictionaryDigest(*child);
16264 result.ownerGraph = std::move(graph);
16265 }
16266
16267 const auto fixedReferenceOutputHandle =
16268 [](DwgOpaqueTableReferenceKind kind,
16269 std::uint32_t handle) -> std::uint32_t {
16270 if (kind == DwgOpaqueTableReferenceKind::LineType &&
16271 (handle == DRW::DwgLtypeByBlockHandle ||
16272 handle == DRW::DwgLtypeByLayerHandle ||
16273 handle == DRW::DwgLtypeContinuousHandle)) {
16274 return handle;
16275 }
16276 if (kind == DwgOpaqueTableReferenceKind::TextStyle &&
16277 handle == DRW::DwgStandardTextStyleHandle) {
16278 return handle;
16279 }
16280 return 0;
16281 };
16282 const auto firstUnplannedReference =
16283 [&raw, rawObjectIndex, &result, &reject,
16284 &fixedReferenceOutputHandle](const std::vector<std::uint32_t> &handles,
16285 DwgOpaqueTableReferenceKind kind)
16286 -> std::optional<DwgOpaqueTableInspection> {
16287 for (std::size_t ordinal = 0; ordinal < handles.size(); ++ordinal) {
16288 const std::uint32_t handle = handles[ordinal];
16289 if (handle == DRW::NoHandle)
16290 continue;
16291 const std::uint32_t outputHandle =
16292 fixedReferenceOutputHandle(kind, handle);
16293 if (outputHandle == 0)
16294 return reject(kind, DwgOpaqueTableFailureKind::Reference, handle,
16295 ordinal);
16296 DwgOpaqueTableReferencePlanRecord reference;
16297 reference.kind = kind;
16298 reference.producerRawObjectIndex = rawObjectIndex;
16299 reference.producerSourceHandle = raw.handle;
16300 reference.producerOutputHandle = raw.handle;
16301 reference.targetSourceHandle = handle;
16302 reference.targetOrdinal = ordinal;
16303 reference.targetDomain = DwgOpaqueTableReferenceTargetDomain::TableRecord;
16304 reference.targetOutputHandle = outputHandle;
16305 reference.proofPhase = DwgOpaqueTableReferenceProofPhase::TableRecords;
16306 result.referencePlan.push_back(reference);
16307 }
16308 return std::nullopt;
16309 };
16310 const bool isCellStyleMap =
16311 raw.recordName == "CELLSTYLEMAP" || raw.className == "AcDbCellStyleMap";
16312 const bool isTableContent =
16313 raw.recordName == "TABLECONTENT" || raw.className == "AcDbTableContent";
16314 if (isCellStyleMap) {
16315 std::size_t mapIndex = std::numeric_limits<std::size_t>::max();
16316 for (std::size_t index = 0; index < metadata.cellStyleMaps().size();
16317 ++index) {
16318 if (metadata.cellStyleMaps()[index].handle != raw.handle)
16319 continue;
16320 if (mapIndex != std::numeric_limits<std::size_t>::max()) {
16321 return reject(DwgOpaqueTableReferenceKind::AmbiguousSummary,
16322 DwgOpaqueTableFailureKind::AmbiguousSummary, raw.handle,
16323 index);
16324 }
16325 mapIndex = index;
16326 }
16327 if (mapIndex == std::numeric_limits<std::size_t>::max()) {
16328 return reject(DwgOpaqueTableReferenceKind::MissingSummary,
16329 DwgOpaqueTableFailureKind::MissingSummary, raw.handle);
16330 }
16331 const auto &map = metadata.cellStyleMaps()[mapIndex];
16332 DwgOpaqueTableSummaryFingerprint &summary = result.summaryFingerprint;
16333 summary.kind = DwgOpaqueTableSummaryKind::CellStyleMap;
16334 summary.summaryIndex = mapIndex;
16335 summary.handle = map.handle;
16336 summary.parentHandle = map.parentHandle;
16337 summary.replayState = map.replayState;
16338 summary.relationCount = dwgOpaqueTableRelationCount(map);
16339 summary.identityDigest = dwgOpaqueTableSummaryDigest(map);
16340 if (map.replayState != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
16341 map.parentHandle != raw.parentHandle) {
16342 return reject(DwgOpaqueTableReferenceKind::InvalidSummary,
16343 DwgOpaqueTableFailureKind::InvalidSummary,
16344 map.parentHandle);
16345 }
16346 if (const auto reference = firstUnplannedReference(
16347 map.m_textStyleHandles, DwgOpaqueTableReferenceKind::TextStyle)) {
16348 return *reference;
16349 }
16350 if (const auto reference = firstUnplannedReference(
16351 map.m_lineTypeHandles, DwgOpaqueTableReferenceKind::LineType)) {
16352 return *reference;
16353 }
16354 result.eligible = true;
16355 return result;
16356 }
16357
16358 if (!isTableStyle && !isTableContent)
16359 return reject(DwgOpaqueTableReferenceKind::RawFrame,
16360 DwgOpaqueTableFailureKind::RawFrame);
16361 std::size_t tableIndex = std::numeric_limits<std::size_t>::max();
16362 for (std::size_t index = 0; index < metadata.tables().size(); ++index) {
16363 const auto &candidate = metadata.tables()[index];
16364 const bool matchingKind = isTableStyle
16365 ? candidate.nativeTableStyle.has_value()
16366 : candidate.recordName == "TABLECONTENT";
16367 if (candidate.handle != raw.handle || !matchingKind)
16368 continue;
16369 if (tableIndex != std::numeric_limits<std::size_t>::max()) {
16370 return reject(DwgOpaqueTableReferenceKind::AmbiguousSummary,
16371 DwgOpaqueTableFailureKind::AmbiguousSummary, raw.handle,
16372 index);
16373 }
16374 tableIndex = index;
16375 }
16376 if (tableIndex == std::numeric_limits<std::size_t>::max()) {
16377 return reject(DwgOpaqueTableReferenceKind::MissingSummary,
16378 DwgOpaqueTableFailureKind::MissingSummary, raw.handle);
16379 }
16380 const auto &table = metadata.tables()[tableIndex];
16381 DwgOpaqueTableSummaryFingerprint &summary = result.summaryFingerprint;
16382 summary.kind = isTableStyle ? DwgOpaqueTableSummaryKind::TableStyle
16383 : DwgOpaqueTableSummaryKind::TableContent;
16384 summary.summaryIndex = tableIndex;
16385 summary.handle = table.handle;
16386 summary.parentHandle = table.parentHandle;
16387 summary.replayState = table.replayState;
16388 summary.fallbackRendered = table.fallbackRendered;
16389 summary.fallbackInvalidated = table.fallbackInvalidated;
16390 summary.relationCount = dwgOpaqueTableRelationCount(table);
16391 summary.identityDigest = dwgOpaqueTableSummaryDigest(table);
16392 if (table.replayState != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
16393 table.fallbackRendered || table.fallbackInvalidated ||
16394 table.parentHandle != raw.parentHandle) {
16395 return reject(DwgOpaqueTableReferenceKind::InvalidSummary,
16396 DwgOpaqueTableFailureKind::InvalidSummary,
16397 table.parentHandle);
16398 }
16399 if (table.tableStyleHandle != DRW::NoHandle) {
16400 return reject(DwgOpaqueTableReferenceKind::TableStyle,
16401 DwgOpaqueTableFailureKind::Reference, table.tableStyleHandle);
16402 }
16403 if (table.m_tableUnknownHandle != DRW::NoHandle) {
16404 return reject(DwgOpaqueTableReferenceKind::Unknown,
16405 DwgOpaqueTableFailureKind::Reference,
16406 table.m_tableUnknownHandle);
16407 }
16408 if (const auto reference = firstUnplannedReference(
16409 table.valueHandles, DwgOpaqueTableReferenceKind::Value)) {
16410 return *reference;
16411 }
16412 if (const auto reference = firstUnplannedReference(
16413 table.blockHandles, DwgOpaqueTableReferenceKind::Block)) {
16414 return *reference;
16415 }
16416 if (const auto reference = firstUnplannedReference(
16417 table.fieldHandles, DwgOpaqueTableReferenceKind::Field)) {
16418 return *reference;
16419 }
16420 if (const auto reference = firstUnplannedReference(
16421 table.attributeHandles, DwgOpaqueTableReferenceKind::Attribute)) {
16422 return *reference;
16423 }
16424 if (const auto reference = firstUnplannedReference(
16425 table.geometryHandles, DwgOpaqueTableReferenceKind::Geometry)) {
16426 return *reference;
16427 }
16428 if (const auto reference = firstUnplannedReference(
16429 table.textStyleHandles, DwgOpaqueTableReferenceKind::TextStyle)) {
16430 return *reference;
16431 }
16432 if (const auto reference = firstUnplannedReference(
16433 table.lineTypeHandles, DwgOpaqueTableReferenceKind::LineType)) {
16434 return *reference;
16435 }
16436 result.eligible = true;
16437 return result;
16438}
16439
16440bool RS_FilterDXFRW::isDwgOpaqueTableRawSelected(
16441 std::size_t rawObjectIndex) const {
16442 const DwgExportAdmissionPlan &plan = m_dwgExportAdmissionPlan;
16443 if (!plan.frozen || !plan.valid)
16444 return false;
16445 const auto it = plan.opaqueTableRecordIndexes.find(rawObjectIndex);
16446 if (it == plan.opaqueTableRecordIndexes.end() ||
16447 it->second.admissionRecordIndex >= plan.records.size()) {
16448 return false;
16449 }
16450 const DwgExportAdmissionRecord &record =
16451 plan.records[it->second.admissionRecordIndex];
16452 return record.family == DwgExportAdmissionFamily::OpaqueTable &&
16453 record.policy == DwgExportAdmissionPolicy::SameVersionRaw &&
16454 record.sourceOrdinal == rawObjectIndex;
16455}
16456
16457bool RS_FilterDXFRW::isDwgTypedPlotSettingsSelected(std::size_t index) const {
16458 const DwgExportAdmissionPlan &plan = m_dwgExportAdmissionPlan;
16459 if (!plan.frozen || !plan.valid)
16460 return false;
16461 const auto it = plan.plotSettingsRecordIndexes.find(index);
16462 if (it == plan.plotSettingsRecordIndexes.end() ||
16463 it->second >= plan.records.size()) {
16464 return false;
16465 }
16466 const DwgExportAdmissionPolicy policy = plan.records[it->second].policy;
16467 return policy == DwgExportAdmissionPolicy::Native ||
16468 policy == DwgExportAdmissionPolicy::Typed;
16469}
16470
16471bool RS_FilterDXFRW::isDwgRawPlotSettingsSelected(
16472 std::uint32_t sourceHandle) const {
16473 return m_dwgExportAdmissionPlan.frozen && m_dwgExportAdmissionPlan.valid &&
16474 sourceHandle != 0 &&
16475 m_dwgExportAdmissionPlan.rawPlotSettingsHandles.count(sourceHandle) !=
16476 0;
16477}
16478
16479bool RS_FilterDXFRW::prepareDwgEntityWritePlans() {
16480 if (m_dwgW == nullptr || m_graphic == nullptr)
16481 return true;
16482
16483 const DRW::Version version = m_dwgW->getVersion();
16484 const std::uint16_t mleaderClassNumber =
16485 version >= DRW::AC1024
16486 ? m_dwgW->getDwgTypedClassNumber("AcDbMLeader", "MULTILEADER",
16487 DRW_MLeader::kDwgClassNum)
16488 : 0;
16489 const auto fixedRoute = [](DwgTypedConversionFixedRoute route)
16490 -> std::optional<DwgFixedEntityRoute> {
16491 switch (route) {
16492 case DwgTypedConversionFixedRoute::PointExtrusion:
16493 return DwgFixedEntityRoute::PointExtrusion;
16494 case DwgTypedConversionFixedRoute::LineExtrusion:
16495 return DwgFixedEntityRoute::LineExtrusion;
16496 case DwgTypedConversionFixedRoute::Circle:
16497 return DwgFixedEntityRoute::Circle;
16498 case DwgTypedConversionFixedRoute::Arc:
16499 return DwgFixedEntityRoute::Arc;
16500 case DwgTypedConversionFixedRoute::Ray:
16501 return DwgFixedEntityRoute::Ray;
16502 case DwgTypedConversionFixedRoute::Xline:
16503 return DwgFixedEntityRoute::Xline;
16504 case DwgTypedConversionFixedRoute::Trace:
16505 return DwgFixedEntityRoute::Trace;
16506 case DwgTypedConversionFixedRoute::Solid:
16507 return DwgFixedEntityRoute::Solid;
16508 case DwgTypedConversionFixedRoute::Face3d:
16509 return DwgFixedEntityRoute::Face3d;
16510 }
16511 return std::nullopt;
16512 };
16513 const auto stage = [this, version](
16514 const RS_Entity *entity, DwgEntityWritePlanRoute route,
16515 DwgFixedEntityRoute fixed, std::uint16_t frameType) {
16516 if (entity == nullptr || entity->sourceHandle() == 0 || frameType == 0 ||
16517 m_dwgWriteDuplicateEntityHandles.count(entity->sourceHandle()) != 0) {
16518 return false;
16519 }
16520 const auto entityIt = m_dwgWriteEntityHandleByEntity.find(entity);
16521 return entityIt != m_dwgWriteEntityHandleByEntity.end() &&
16522 stageDwgEntityWritePlan(entity, entity->sourceHandle(),
16523 entityIt->second, route, fixed, frameType,
16524 version);
16525 };
16526 const auto inspect = [&](RS_EntityContainer *container,
16527 bool reconstructsSidecars) {
16528 if (container == nullptr)
16529 return true;
16530 for (RS_Entity *entity :
16531 lc::LC_ContainerTraverser{*container, RS2::ResolveNone}.entities()) {
16532 if (entity == nullptr || entity->getFlag(RS2::FlagDeleted) ||
16533 isGeneratedInsertAttrib(entity) || entity->sourceHandle() == 0) {
16534 continue;
16535 }
16536
16537 if (entity->rtti() == RS2::EntityMLeader) {
16538 const auto *mleader = static_cast<const LC_MLeader *>(entity);
16539 const DwgMLeaderWriteRoute route =
16540 selectDwgMLeaderWriteRoute(mleader->getData(), version);
16541 if (route == DwgMLeaderWriteRoute::Invalid)
16542 return false;
16543 if (route == DwgMLeaderWriteRoute::Native &&
16544 (mleaderClassNumber < 500 ||
16545 !stage(entity, DwgEntityWritePlanRoute::CustomSingleFrame,
16546 DwgFixedEntityRoute::Line, mleaderClassNumber))) {
16547 return false;
16548 }
16549 continue;
16550 }
16551
16552 if (hasValidDwg3DLineSidecar(entity)) {
16553 if (!reconstructsSidecars ||
16554 !stage(entity, DwgEntityWritePlanRoute::CustomSingleFrame,
16555 DwgFixedEntityRoute::Line, DRW_3DLine::kDwgClassNum)) {
16556 return false;
16557 }
16558 continue;
16559 }
16560
16561 if (hasDwgUnderlaySidecar(entity)) {
16562 DwgUnderlaySidecar sidecar;
16563 if (!reconstructsSidecars || version < DRW::AC1015 ||
16564 !parseDwgUnderlaySidecar(entity, sidecar)) {
16565 return false;
16566 }
16567 const auto &definitions =
16568 m_graphic->dwgAdvancedMetadata().underlayDefinitions();
16569 const bool hasDefinition = std::any_of(
16570 definitions.cbegin(), definitions.cend(),
16571 [&sidecar](const auto &definition) {
16572 return definition.replayState ==
16573 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
16574 definition.handle == sidecar.value.definitionHandle &&
16575 static_cast<int>(definition.kind) ==
16576 static_cast<int>(sidecar.value.kind);
16577 });
16578 if (!hasDefinition)
16579 return false;
16580 const std::uint16_t classNumber =
16581 underlayEntityClassNumber(sidecar.value.kind, m_dwgW);
16582 if (classNumber < 500 ||
16583 !stage(entity, DwgEntityWritePlanRoute::CustomSingleFrame,
16584 DwgFixedEntityRoute::Line, classNumber)) {
16585 return false;
16586 }
16587 continue;
16588 }
16589
16590 if (hasDwgTypedConversionSidecar(entity)) {
16591 const auto descriptor = findDwgTypedConversionFixedDescriptor(entity);
16592 if (!descriptor)
16593 return false;
16594 const auto route = fixedRoute(descriptor->route);
16595 if (!reconstructsSidecars || !route ||
16596 !stage(entity, DwgEntityWritePlanRoute::FixedSingleFrame, *route,
16597 descriptor->wireType)) {
16598 return false;
16599 }
16600 continue;
16601 }
16602
16603 if (entity->rtti() == RS2::EntityPoint &&
16604 !hasDwgTypedConversionSidecar(entity) &&
16605 !stage(entity, DwgEntityWritePlanRoute::FixedSingleFrame,
16606 DwgFixedEntityRoute::Point, dwgType::POINT)) {
16607 return false;
16608 }
16609
16610 if (entity->rtti() == RS2::EntityCircle &&
16611 !stage(entity, DwgEntityWritePlanRoute::FixedSingleFrame,
16612 DwgFixedEntityRoute::Circle, dwgType::CIRCLE)) {
16613 return false;
16614 }
16615
16616 if (entity->rtti() == RS2::EntityArc &&
16617 !stage(entity, DwgEntityWritePlanRoute::FixedSingleFrame,
16618 DwgFixedEntityRoute::Arc, dwgType::ARC)) {
16619 return false;
16620 }
16621
16622 if (entity->rtti() == RS2::EntityLine &&
16623 !hasDwgTypedConversionSidecar(entity) &&
16624 !stage(entity, DwgEntityWritePlanRoute::FixedSingleFrame,
16625 DwgFixedEntityRoute::Line, dwgType::LINE)) {
16626 return false;
16627 }
16628 }
16629 return true;
16630 };
16631
16632 if (!inspect(m_graphic, /*reconstructsSidecars=*/true))
16633 return false;
16634 for (unsigned i = 0; i < m_graphic->countBlocks(); ++i) {
16635 RS_Block *block = m_graphic->blockAt(i);
16636 if (block != nullptr && !block->isDeleted() &&
16637 !inspect(block, /*reconstructsSidecars=*/true)) {
16638 return false;
16639 }
16640 }
16641 return true;
16642}
16643
16644bool RS_FilterDXFRW::admitDwgEntityWritePlans() {
16645 if (m_dwgW == nullptr)
16646 return true;
16647
16648 for (const auto &entry : m_dwgWriteEntityPlansByEntity) {
16649 const DwgEntityWritePlan &plan = entry.second;
16650 if (plan.admitted) {
16651 if (plan.committed)
16652 return false;
16653 continue;
16654 }
16655
16656 bool admitted = false;
16657 if (plan.route == DwgEntityWritePlanRoute::FixedSingleFrame) {
16658 admitted = admitDwgFixedEntityFrame(plan.fixedRoute, plan.sourceHandle,
16659 plan.outputHandle,
16660 plan.expectedFrameType, plan.version);
16661 } else {
16662 admitted =
16663 plan.expectedFrameType >= 500 &&
16664 admitDwgClassFrameDescriptor(
16665 DwgWriteSourceKind::Entity, plan.sourceHandle, plan.outputHandle,
16666 plan.expectedFrameType, 0, plan.version) &&
16667 m_dwgW->registerDwgEntityClassInstance(plan.expectedFrameType,
16668 plan.outputHandle);
16669 }
16670 if (!admitted || !admitDwgEntityWritePlan(plan.entity))
16671 return false;
16672 }
16673 return true;
16674}
16675
16676bool RS_FilterDXFRW::commitDwgEntityWrite(const RS_Entity *entity) {
16677 if (m_dwgW == nullptr || entity == nullptr)
16678 return false;
16679
16680 const auto entityIt = m_dwgWriteEntityHandleByEntity.find(entity);
16681 if (entityIt == m_dwgWriteEntityHandleByEntity.end())
16682 return entity->sourceHandle() == 0;
16683
16684 const std::uint32_t outputHandle = entityIt->second;
16685 const auto *frame = findDwgWriteObjectOccurrences(outputHandle);
16686 if (frame == nullptr) {
16687 m_writeFailed = true;
16688 return false;
16689 }
16690 if (DwgEntityWritePlan *plan = findDwgEntityWritePlan(entity)) {
16691 if (!plan->admitted || plan->outputHandle != outputHandle ||
16692 plan->sourceHandle != entity->sourceHandle()) {
16693 m_writeFailed = true;
16694 return false;
16695 }
16696 if (plan->route == DwgEntityWritePlanRoute::FixedSingleFrame) {
16697 if (!commitDwgFixedEntityFrame(plan->fixedRoute, plan->sourceHandle,
16698 outputHandle, *frame)) {
16699 m_writeFailed = true;
16700 return false;
16701 }
16702 } else if (!commitDwgClassFrame(DwgWriteSourceKind::Entity,
16703 plan->sourceHandle, outputHandle,
16704 plan->expectedFrameType)) {
16705 m_writeFailed = true;
16706 return false;
16707 }
16708 if (!commitDwgEntityWritePlan(entity, *frame)) {
16709 m_writeFailed = true;
16710 return false;
16711 }
16712 } else {
16713 // Unmigrated routes retain their established receipt behavior until
16714 // their multi-frame/metadata contracts join the shared plan.
16715 const DwgWriteSourceKey fixedSourceKey{DwgWriteSourceKind::Entity,
16716 entity->sourceHandle(), 0};
16717 if (entity->sourceHandle() != 0) {
16718 const auto fixedIt =
16719 m_dwgWriteFixedEntityFrameReceiptsBySource.find(fixedSourceKey);
16720 if (fixedIt != m_dwgWriteFixedEntityFrameReceiptsBySource.end() &&
16721 !commitDwgFixedEntityFrame(fixedIt->second.route,
16722 entity->sourceHandle(), outputHandle,
16723 *frame)) {
16724 m_writeFailed = true;
16725 return false;
16726 }
16727 }
16728 if (entity->sourceHandle() != 0 &&
16729 m_dwgW->hasDwgClassDefinition(frame->classNumber) &&
16730 (!admitDwgClassFrameDescriptor(
16731 DwgWriteSourceKind::Entity, entity->sourceHandle(), outputHandle,
16732 frame->classNumber, 0, m_dwgW->getVersion()) ||
16733 !commitDwgClassFrame(DwgWriteSourceKind::Entity,
16734 entity->sourceHandle(), outputHandle,
16735 frame->classNumber))) {
16736 m_writeFailed = true;
16737 return false;
16738 }
16739 }
16740
16741 if (!commitDwgWriteIdentity(
16742 RS_FilterDXFRW_DwgWriteIdentityRegistry::DwgWriteSourceKind::Entity,
16743 outputHandle)) {
16744 m_writeFailed = true;
16745 return false;
16746 }
16747
16748 const std::uint32_t sourceHandle = entity->sourceHandle();
16749 if (sourceHandle != 0 &&
16750 m_dwgWriteDuplicateEntityHandles.count(sourceHandle) == 0) {
16751 m_dwgWriteEntityHandlesEmitted.insert(sourceHandle);
16752 }
16753 return true;
16754}
16755
16756bool RS_FilterDXFRW::recordDwgWriteObjectOccurrences(
16757 std::uint32_t outputHandle, const DRW::DwgObjectFrameReceipt &frame) {
16758 if (m_dwgW == nullptr)
16759 return false;
16760
16761 // Bind the provenance token to the writer's current committed frame. A
16762 // copied receipt from an earlier object must not be attached to this one.
16763 DRW::DwgObjectFrameReceipt currentFrame;
16764 if (!m_dwgW->getLastDwgObjectFrame(currentFrame) || !currentFrame.valid ||
16765 currentFrame.generation != frame.generation ||
16766 currentFrame.objectHandle != frame.objectHandle ||
16767 currentFrame.version != frame.version)
16768 return false;
16769
16770 return RS_FilterDXFRW_DwgWriteIdentityRegistry::
16771 recordDwgWriteObjectOccurrences(outputHandle, frame);
16772}
16773
16774bool RS_FilterDXFRW::recordDwgEntityFrame(std::uint32_t outputHandle) {
16775 if (m_dwgW == nullptr || outputHandle == 0)
16776 return false;
16777
16778 DRW::DwgObjectFrameReceipt frame;
16779 if (!m_dwgW->getLastDwgObjectFrame(frame) ||
16780 !recordDwgWriteObjectOccurrences(outputHandle, frame)) {
16781 m_writeFailed = true;
16782 return false;
16783 }
16784 return true;
16785}
16786
16787bool RS_FilterDXFRW::commitDwgPolylineCompound(const RS_Entity *anchor) {
16788 DRW::DwgCompoundEntityWriteReceipt receipt;
16789 if (m_dwgW == nullptr || anchor == nullptr ||
16790 !m_dwgW->getLastDwgCompoundEntityWriteReceipt(receipt) ||
16791 !receipt.valid || receipt.parentHandle == 0 ||
16792 receipt.frames.size() < 3 ||
16793 receipt.frames.front().role != DRW::DwgCompoundEntityFrameRole::Parent ||
16794 receipt.frames.back().role != DRW::DwgCompoundEntityFrameRole::SeqEnd) {
16795 return false;
16796 }
16797
16798 std::set<std::uint32_t> handles;
16799 std::set<std::uint64_t> generations;
16800 for (std::size_t index = 0; index < receipt.frames.size(); ++index) {
16801 const auto &expected = receipt.frames[index];
16802 const bool isParent = index == 0;
16803 const bool isSeqEnd = index + 1 == receipt.frames.size();
16804 if (!expected.frame.valid || expected.frame.generation == 0 ||
16805 expected.frame.objectHandle == 0 ||
16806 expected.frame.version != m_dwgW->getVersion() ||
16807 !handles.insert(expected.frame.objectHandle).second ||
16808 !generations.insert(expected.frame.generation).second ||
16809 (isParent &&
16810 expected.role != DRW::DwgCompoundEntityFrameRole::Parent) ||
16811 (!isParent && !isSeqEnd &&
16812 expected.role != DRW::DwgCompoundEntityFrameRole::Vertex) ||
16813 (isSeqEnd &&
16814 expected.role != DRW::DwgCompoundEntityFrameRole::SeqEnd)) {
16815 return false;
16816 }
16817 }
16818 if (receipt.frames.front().frame.objectHandle != receipt.parentHandle)
16819 return false;
16820
16821 const auto checkpoint = checkpointDwgWriteIdentity();
16822 const auto fail = [this, &checkpoint]() {
16823 restoreDwgWriteIdentity(checkpoint);
16824 return false;
16825 };
16826 for (const auto &entry : receipt.frames) {
16827 if (!RS_FilterDXFRW_DwgWriteIdentityRegistry::
16828 recordDwgWriteObjectOccurrences(entry.frame.objectHandle,
16829 entry.frame)) {
16830 return fail();
16831 }
16832 }
16833
16834 const std::uint32_t parentHandle = receipt.parentHandle;
16835 const bool parentCommitted = anchor->sourceHandle() == 0
16836 ? commitDwgGeneratedEntityWrite(parentHandle)
16837 : commitDwgEntityWrite(anchor);
16838 if (!parentCommitted)
16839 return fail();
16840
16841 for (std::size_t index = 1; index < receipt.frames.size(); ++index) {
16842 const std::uint32_t childHandle = receipt.frames[index].frame.objectHandle;
16843 if (!reserveDwgWriteIdentity(DwgWriteSourceKind::GeneratedChild, 0,
16844 childHandle) ||
16845 !selectDwgWriteIdentity(DwgWriteSourceKind::GeneratedChild, 0,
16846 childHandle) ||
16847 !commitDwgWriteIdentity(DwgWriteSourceKind::GeneratedChild,
16848 childHandle)) {
16849 return fail();
16850 }
16851 }
16852 return true;
16853}
16854
16855void RS_FilterDXFRW::rollbackDwgEntityWrite(const RS_Entity *entity) {
16856 if (m_dwgW == nullptr || entity == nullptr)
16857 return;
16858
16859 const auto entityIt = m_dwgWriteEntityHandleByEntity.find(entity);
16860 if (entityIt == m_dwgWriteEntityHandleByEntity.end())
16861 return;
16862
16863 (void)rollbackDwgWriteIdentity(
16864 RS_FilterDXFRW_DwgWriteIdentityRegistry::DwgWriteSourceKind::Entity,
16865 entityIt->second);
16866}
16867#endif
16868
16869void RS_FilterDXFRW::writeHeader(DRW_Header &data) {
16870 RS_Vector v;
16871 /*TODO $ISOMETRICGRID == $SNAPSTYLE and "GRID on/off" not handled because is
16872 part of active vport to save is required read/write VPORT table */
16873 QHash<QString, RS_Variable> vars = m_graphic->getVariableDict();
16874 if (!vars.contains("$DWGCODEPAGE")) {
16875 // RLZ: TODO verify this
16876 m_codePage = RS_SYSTEMRS_System::instance()->localeToISO(QLocale::system().name().toLocal8Bit());
16877 // RS_Variable v(
16878 // QString(RS_SYSTEM->localeToISO(QLocale::system().name().toLocal8Bit())),0
16879 // );
16880 vars.insert(QString("$DWGCODEPAGE"), RS_Variable(m_codePage, 0));
16881 }
16882
16883 QHash<QString, RS_Variable>::iterator it = vars.begin();
16884 while (it != vars.end()) {
16885 auto value = it.value();
16886 int code = value.getCode();
16887 auto key = it.key().toStdString();
16888 switch (value.getType()) {
16889 case RS2::VariableInt:
16890 data.addInt(key, value.getInt(), code);
16891 break;
16892 case RS2::VariableDouble:
16893 data.addDouble(key, value.getDouble(), code);
16894 break;
16895 case RS2::VariableString:
16896 data.addStr(key, value.getString().toUtf8().data(), code);
16897 break;
16898 case RS2::VariableVector:
16899 v = value.getVector();
16900 data.addCoord(key, DRW_Coord(v.x, v.y, v.z), code);
16901 break;
16902 default:
16903 break;
16904 }
16905 ++it;
16906 }
16907 // $EXTMIN must use getMin() and $EXTMAX getMax(); the previous code
16908 // overwrote vmin with getMax() before reading it, so $EXTMIN==$EXTMAX
16909 // (a degenerate point bbox → broken ZOOM-EXTENTS/auto-scale) on every
16910 // DWG+DXF write, and the Z extent was forced to 0. (write-review P3 #3)
16911 const RS_Vector vmin = m_graphic->getMin();
16912 const RS_Vector vmax = m_graphic->getMax();
16913 data.addCoord("$EXTMIN", DRW_Coord(vmin.x, vmin.y, vmin.z), 0);
16914 data.addCoord("$EXTMAX", DRW_Coord(vmax.x, vmax.y, vmax.z), 0);
16915
16916 // when saving a block, there is no active layer. ignore it to avoid crash
16917 if (m_graphic->getActiveLayer() == nullptr) {
16918 return;
16919 }
16920 data.addStr("$CLAYER",
16921 (m_graphic->getActiveLayer()->getName()).toUtf8().data(), 8);
16922
16923 const QHash<QString, RS_Variable> customVars =
16924 m_graphic->getCustomProperties();
16925
16926 QHashIterator<QString, RS_Variable> customVar(customVars);
16927 while (customVar.hasNext()) {
16928 customVar.next();
16929 auto val = customVar.value().getString();
16930 if (!val.isEmpty()) {
16931 auto key = customVar.key().toStdString();
16932 data.addCustomVar(std::move(key), val.toStdString());
16933 }
16934 }
16935}
16936
16937void RS_FilterDXFRW::writeDwgClasses() {
16938#ifdef DWGSUPPORT1
16939 if (m_dwgW == nullptr || m_graphic == nullptr)
16940 return;
16941 prepareDwgEntityHandleMap();
16942 // The concrete DWG writer and its allocator are created immediately
16943 // before this callback. Freeze DataStorage decisions here, before
16944 // CLASSES is emitted, so entity remaps are part of the same decision.
16945 if (!m_dwgDataStorageReplayPlan.frozen) {
16946 prepareDwgEntityHandleMap();
16947 if (m_writeFailed ||
16948 !validateDwgDataStoragePreflight(m_dwgW->getVersion()) ||
16949 !freezeDwgDataStorageReplayPlan()) {
16950 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
16951 "RS_FilterDXFRW: DWG DataStorage replay plan changed "
16952 "during handle planning");
16953 m_writeFailed = true;
16954 m_dwgW->markDwgWriteFailure();
16955 return;
16956 }
16957 }
16958 if (!prepareDwgExportAdmissionPlan()) {
16959 m_writeFailed = true;
16960 m_dwgW->markDwgWriteFailure();
16961 return;
16962 }
16963 if (!prepareDwgFieldWritePlan()) {
16964 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
16965 "RS_FilterDXFRW: DWG FIELD admission plan is invalid");
16966 m_writeFailed = true;
16967 m_dwgW->markDwgWriteFailure();
16968 return;
16969 }
16970 for (const auto &entry : m_dwgExportAdmissionPlan.opaqueTableRecordIndexes) {
16971 const auto &graph = entry.second.ownerGraph;
16972 if (!graph.has_value())
16973 continue;
16974 if (graph->rootSourceHandle == 0 ||
16975 graph->rootOutputHandle != DRW::DwgNamedObjectsDictionaryHandle ||
16976 graph->childSourceHandle == 0 ||
16977 graph->childSourceHandle != graph->childOutputHandle ||
16978 graph->childSourceHandle != graph->rawOwnerHandle ||
16979 graph->rootSourceHandle == graph->childSourceHandle ||
16980 graph->rootSourceHandle == graph->rawSourceHandle ||
16981 graph->childSourceHandle == graph->rawSourceHandle ||
16982 graph->rootEntryName.empty() ||
16983 !m_dwgW->registerDwgNamedObjectDictionaryEntry(
16984 graph->rootEntryName, graph->childOutputHandle)) {
16985 m_writeFailed = true;
16986 m_dwgW->markDwgWriteFailure();
16987 return;
16988 }
16989 }
16990 const auto &metadata = m_graphic->dwgAdvancedMetadata();
16991 const bool canWriteModernObjects = m_dwgW->getVersion() >= DRW::AC1021;
16992 const bool canWriteAssociativeObjects = canWriteModernObjects;
16993 // PR 13f — custom-class registration gate for the RasterVariables /
16994 // GeoData / SpatialFilter families. Broadened from AC1021+ to AC1015+
16995 // because their encoders + parsers are version-clean (only the
16996 // standard `version > AC1018` split-buffer routing). The dispatch
16997 // loop in writeObjects uses a matching `canRegisterCustomClassObjects`
16998 // gate so the CLASSES section entry and the OBJECTS instance land
16999 // together (otherwise the reader's class lookup fails and the object
17000 // is silently dropped).
17001 const bool canRegisterCustomClassObjects =
17002 m_dwgW->getVersion() >= DRW::AC1015;
17003
17004 // DIMARC is the first entity route with a deterministic custom class and
17005 // exactly one frame. Admit only source-backed records here; generated
17006 // and compound routes still use their later, route-specific contracts.
17007 const auto admitSourceBackedDimArc = [this]() {
17008 const std::uint16_t classNumber = m_dwgW->getDwgTypedClassNumber(
17009 "AcDbArcDimension", "ARC_DIMENSION", DRW_DimArc::kDwgClassNum);
17010 if (classNumber < 500)
17011 return false;
17012
17013 const auto inspect = [this, classNumber](RS_EntityContainer *container) {
17014 if (container == nullptr)
17015 return true;
17016 for (RS_Entity *entity :
17017 lc::LC_ContainerTraverser{*container, RS2::ResolveNone}.entities()) {
17018 if (entity == nullptr || entity->getFlag(RS2::FlagDeleted) ||
17019 entity->rtti() != RS2::EntityDimArc || entity->sourceHandle() == 0)
17020 continue;
17021
17022 const std::uint32_t sourceHandle = entity->sourceHandle();
17023 if (m_dwgWriteDuplicateEntityHandles.count(sourceHandle) != 0)
17024 return false;
17025 const auto entityIt = m_dwgWriteEntityHandleByEntity.find(entity);
17026 if (entityIt == m_dwgWriteEntityHandleByEntity.end() ||
17027 entityIt->second == 0 ||
17028 !admitDwgClassFrameDescriptor(
17029 DwgWriteSourceKind::Entity, sourceHandle, entityIt->second,
17030 classNumber, 0, m_dwgW->getVersion()) ||
17031 !m_dwgW->registerDwgEntityClassInstance(classNumber,
17032 entityIt->second))
17033 return false;
17034 }
17035 return true;
17036 };
17037
17038 if (!inspect(m_graphic))
17039 return false;
17040 for (unsigned i = 0; i < m_graphic->countBlocks(); ++i) {
17041 RS_Block *block = m_graphic->blockAt(i);
17042 if (block != nullptr && !block->isDeleted() && !inspect(block))
17043 return false;
17044 }
17045 return true;
17046 };
17047 if (canRegisterCustomClassObjects && !admitSourceBackedDimArc()) {
17048 m_writeFailed = true;
17049 m_dwgW->markDwgWriteFailure();
17050 return;
17051 }
17052 if (!prepareDwgEntityWritePlans() || !admitDwgEntityWritePlans()) {
17053 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
17054 "RS_FilterDXFRW: DWG entity-frame plan admission failed");
17055 m_writeFailed = true;
17056 m_dwgW->markDwgWriteFailure();
17057 return;
17058 }
17059 const auto admitSourceBackedMetadataEntity =
17060 [this](const auto &records, std::uint16_t classNumber,
17061 DRW::Version minimumVersion) {
17062 if (m_dwgW->getVersion() < minimumVersion)
17063 return true;
17064 for (const auto &record : records) {
17065 if (record.replayState !=
17066 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17067 record.handle == 0)
17068 continue;
17069 if (isDwgWriteSourceAmbiguous(
17070 record.handle, RS_FilterDXFRW_DwgWriteIdentityRegistry::
17071 DwgWriteSourceKind::Entity))
17072 return false;
17073 const std::uint32_t outputHandle =
17074 resolveDwgWriteHandle(record.handle);
17075 if (outputHandle == 0 ||
17076 !admitDwgClassFrameDescriptor(
17077 RS_FilterDXFRW_DwgWriteIdentityRegistry::DwgWriteSourceKind::
17078 Entity,
17079 record.handle, outputHandle, classNumber, 0,
17080 m_dwgW->getVersion()) ||
17081 !m_dwgW->registerDwgEntityClassInstance(classNumber,
17082 outputHandle))
17083 return false;
17084 }
17085 return true;
17086 };
17087 if (!admitSourceBackedMetadataEntity(metadata.lights(),
17088 DRW_Light::kDwgClassNum, DRW::AC1021) ||
17089 !admitSourceBackedMetadataEntity(metadata.navisworksModels(),
17090 DRW_NavisworksModel::kDwgClassNum,
17091 DRW::AC1015) ||
17092 !admitSourceBackedMetadataEntity(metadata.cameras(),
17093 DRW_Camera::kDwgClassNum, DRW::AC1015) ||
17094 !admitSourceBackedMetadataEntity(metadata.sectionObjects(),
17095 DRW_SectionObject::kDwgClassNum,
17096 DRW::AC1021)) {
17097 m_writeFailed = true;
17098 m_dwgW->markDwgWriteFailure();
17099 return;
17100 }
17101 const auto remapClassInstanceHandle = [this](auto &object) {
17102 object.handle = resolveDwgWriteHandle(object.handle);
17103 };
17104 const auto noteObjectClassRegistration =
17105 [this](bool registered, std::set<std::uint32_t> &handles,
17106 std::uint32_t sourceHandle) {
17107 const auto *decision = dataStorageDecisionForOwner(sourceHandle);
17108 if (!registered) {
17109 if (decision != nullptr &&
17110 decision->decision == DwgDataStorageDecision::EmitRaw)
17111 return;
17112 m_writeFailed = true;
17113 return;
17114 }
17115 handles.insert(sourceHandle);
17116 if (decision != nullptr &&
17117 !recordDwgDataStorageClassAdmission(
17118 sourceHandle, DwgDataStorageCarrier::Kind::Typed)) {
17119 m_writeFailed = true;
17120 return;
17121 }
17122 const std::uint32_t outputHandle = resolveDwgWriteHandle(sourceHandle);
17123 std::uint16_t classNumber = 0;
17124 if (!m_dwgW->getDwgClassInstanceClass(outputHandle, classNumber) ||
17125 !admitDwgClassFrameDescriptor(
17126 DwgWriteSourceKind::Object, sourceHandle, outputHandle,
17127 classNumber, 0, m_dwgW->getVersion())) {
17128 m_writeFailed = true;
17129 }
17130 };
17131 const auto hasUnresolvedSortEntsReference = [this](
17132 const DRW_SortEntsTable &table) {
17133 const auto isLive = [](DwgWriteReferenceStatus status) {
17134 return status == DwgWriteReferenceStatus::Resolved ||
17135 status == DwgWriteReferenceStatus::LegacySpaceAlias ||
17136 status == DwgWriteReferenceStatus::Reserved ||
17137 status == DwgWriteReferenceStatus::Selected ||
17138 status == DwgWriteReferenceStatus::Committed;
17139 };
17140 return std::any_of(
17141 table.m_sortHandles.cbegin(), table.m_sortHandles.cend(),
17142 [this, &isLive](std::uint32_t handle) {
17143 if (handle == DRW::NoHandle)
17144 return false;
17145 std::uint32_t outputHandle = 0;
17146 DwgWriteSourceKind targetKind = DwgWriteSourceKind::Object;
17147 const DwgWriteReferenceStatus status =
17148 resolveDwgWriteCommonHandle(handle, &outputHandle, &targetKind);
17149 return !isLive(status) || outputHandle == 0;
17150 });
17151 };
17152 // The frozen admission plan is the sole carrier decision for this custom
17153 // object. Raw records stay authoritative only when the plan selected their
17154 // exact same-version frame; all other sidecars use the typed path.
17155 std::size_t nativePlotSettingsCount =
17156 metadata.plotSettings().empty() ? 1u : 0u;
17157 for (std::size_t index = 0; index < metadata.plotSettings().size(); ++index) {
17158 if (isDwgTypedPlotSettingsSelected(index))
17159 ++nativePlotSettingsCount;
17160 }
17161 const bool emitNativePlotSettings =
17162 canRegisterCustomClassObjects && nativePlotSettingsCount != 0;
17163 if (emitNativePlotSettings) {
17164 if (metadata.plotSettings().empty()) {
17165 if (!m_dwgW->registerPlotSettingsObjectClass())
17166 m_writeFailed = true;
17167 } else {
17168 for (std::size_t index = 0; index < metadata.plotSettings().size();
17169 ++index) {
17170 const auto &settings = metadata.plotSettings()[index];
17171 if (!isDwgTypedPlotSettingsSelected(index))
17172 continue;
17173 if (settings.handle == 0) {
17174 if (!m_dwgW->registerPlotSettingsObjectClass()) {
17175 m_writeFailed = true;
17176 break;
17177 }
17178 continue;
17179 }
17180 const std::uint32_t outputHandle =
17181 resolveDwgWriteHandle(settings.handle);
17182 if (!m_dwgW->registerPlotSettingsObjectClass(outputHandle)) {
17183 m_writeFailed = true;
17184 break;
17185 }
17186 std::uint16_t classNumber = 0;
17187 if (!m_dwgW->getDwgClassInstanceClass(outputHandle, classNumber) ||
17188 !admitDwgClassFrameDescriptor(
17189 DwgWriteSourceKind::Object, settings.handle, outputHandle,
17190 classNumber, 0, m_dwgW->getVersion())) {
17191 m_writeFailed = true;
17192 break;
17193 }
17194 }
17195 }
17196 }
17197 if (emitNativePlotSettings && !m_writeFailed) {
17198 const std::uint16_t classNumber =
17199 m_dwgW->getDwgTypedClassNumber("AcDbPlotSettings", "PLOTSETTINGS", 0);
17200 const auto admitGeneratedPlotSettings = [this,
17201 classNumber](std::size_t index) {
17202 if (index >= std::numeric_limits<std::uint32_t>::max())
17203 return false;
17204 return admitDwgClassFrameDescriptor(
17205 DwgWriteSourceKind::GeneratedChild, 0, 0, classNumber,
17206 static_cast<std::uint32_t>(index + 1), m_dwgW->getVersion());
17207 };
17208 if (classNumber < 500) {
17209 m_writeFailed = true;
17210 } else if (metadata.plotSettings().empty()) {
17211 if (!admitGeneratedPlotSettings(0))
17212 m_writeFailed = true;
17213 } else {
17214 for (std::size_t index = 0; index < metadata.plotSettings().size();
17215 ++index) {
17216 const auto &settings = metadata.plotSettings()[index];
17217 if (settings.handle == 0 && isDwgTypedPlotSettingsSelected(index) &&
17218 !admitGeneratedPlotSettings(index)) {
17219 m_writeFailed = true;
17220 break;
17221 }
17222 }
17223 }
17224 }
17225 const auto registerRawClass =
17226 [this](const DRW_UnsupportedObject &object,
17227 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
17228 const bool registered = m_dwgW->registerRawDwgObjectClass(&object);
17229 if (!registered) {
17230 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
17231 "RS_FilterDXFRW: cannot register raw DWG class %s "
17232 "for handle 0x%X",
17233 record.recordName.empty() ? "(unnamed)"
17234 : record.recordName.c_str(),
17235 record.handle);
17236 return false;
17237 }
17238 const std::uint32_t outputHandle = resolveDwgWriteHandle(record.handle);
17239 std::uint16_t classNumber = 0;
17240 const bool hasClassInstance =
17241 m_dwgW->getDwgClassInstanceClass(outputHandle, classNumber);
17242 if (object.m_isCustomClass &&
17243 (!hasClassInstance ||
17244 !admitDwgClassFrameDescriptor(
17245 DwgWriteSourceKind::RawCarrier, record.handle, outputHandle,
17246 classNumber, 0, m_dwgW->getVersion())))
17247 return false;
17248 return !record.hasDataStorage ||
17249 recordDwgDataStorageClassAdmission(
17250 record.handle, DwgDataStorageCarrier::Kind::Raw);
17251 };
17252 std::set<std::uint32_t> nativeSunHandles;
17253 std::set<std::uint32_t> nativeSunStudyHandles;
17254 std::set<std::uint32_t> nativeMotionPathHandles;
17255 std::set<std::uint32_t> nativeCurvePathHandles;
17256 std::set<std::uint32_t> nativePointPathHandles;
17257 std::set<std::uint32_t> nativeObjectPtrHandles;
17258 std::set<std::uint32_t> nativePartialViewingIndexHandles;
17259 std::set<std::uint32_t> nativeRenderSettingsHandles;
17260 std::set<std::uint32_t> nativeVisualStyleHandles;
17261 std::set<std::uint32_t> nativeTvDevicePropertiesHandles;
17262 std::set<std::uint32_t> nativeVxControlHandles;
17263 std::set<std::uint32_t> nativeVxTableRecordHandles;
17264 std::set<std::uint32_t> nativeMLeaderStyleHandles;
17265 std::set<std::uint32_t> nativeTableStyleHandles;
17266 std::set<std::uint32_t> nativeMaterialHandles;
17267 std::set<std::uint32_t> nativeLightListHandles;
17268 std::set<std::uint32_t> nativeBackgroundHandles;
17269 std::set<std::uint32_t> nativeDbColorHandles;
17270 std::set<std::uint32_t> nativeRasterVariablesHandles;
17271 std::set<std::uint32_t> nativeWipeoutVariablesHandles;
17272 std::set<std::uint32_t> nativeGeoDataHandles;
17273 std::set<std::uint32_t> nativeSpatialFilterHandles;
17274 std::set<std::uint32_t> nativeScaleHandles;
17275 std::set<std::uint32_t> nativeIDBufferHandles;
17276 std::set<std::uint32_t> nativeLayerIndexHandles;
17277 std::set<std::uint32_t> nativeSpatialIndexHandles;
17278 std::set<std::uint32_t> nativeDictionaryVarHandles;
17279 std::set<std::uint32_t> nativeDictionaryWithDefaultHandles;
17280 std::set<std::uint32_t> nativeSortEntsTableHandles;
17281 std::set<std::uint32_t> nativeFieldListHandles;
17282 std::set<std::uint32_t> nativeFieldHandles;
17283 std::set<std::uint32_t> nativeUnderlayDefinitionHandles;
17284 std::set<std::uint32_t> nativePointCloudDefinitionHandles;
17285 std::set<std::uint32_t> nativeNavisworksModelDefinitionHandles;
17286 std::set<std::uint32_t> nativePointCloudColorMapHandles;
17287 std::set<std::uint32_t> nativeDimensionAssociationHandles;
17288 std::set<std::uint32_t> nativeEvaluationGraphHandles;
17289 std::set<std::uint32_t> nativeBlockRepresentationHandles;
17290 std::set<std::uint32_t> nativeSectionHandles;
17291
17292 // BLOCKREPRESENTATIONDATA is a fixed DWG object type, so it needs no
17293 // CLASSES entry. Still publish its handle before other metadata objects
17294 // resolve references to it.
17295 if (canRegisterCustomClassObjects) {
17296 for (const auto &record : metadata.blockRepresentationData()) {
17297 if (record.replayState ==
17298 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
17299 record.handle != 0 &&
17300 (m_dwgW->getVersion() <= DRW::AC1024 || !record.hasDsData)) {
17301 DRW_BlockRepresentationData data =
17302 blockRepresentationFromMetadata(record);
17303 if (hasDxfTableAppDataUnsupportedByDwg(data)) {
17304 m_writeFailed = true;
17305 continue;
17306 }
17307 nativeBlockRepresentationHandles.insert(record.handle);
17308 }
17309 }
17310 }
17311
17312 if (canWriteModernObjects) {
17313 for (const auto &record : metadata.suns()) {
17314 if (record.replayState !=
17315 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17316 record.handle == 0) {
17317 continue;
17318 }
17319 DRW_Sun sun = sunFromMetadata(record);
17320 if (hasDxfTableAppDataUnsupportedByDwg(sun)) {
17321 m_writeFailed = true;
17322 continue;
17323 }
17324 remapClassInstanceHandle(sun);
17325 noteObjectClassRegistration(
17326 isDwgDataStorageTypedSelected(record.handle) &&
17327 m_dwgW->registerSunObjectClass(&sun),
17328 nativeSunHandles, record.handle);
17329 }
17330 for (const auto &record : metadata.dimensionAssociations()) {
17331 if (record.replayState !=
17332 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17333 record.handle == 0 || !canWriteAssociativeObjects) {
17334 continue;
17335 }
17336 DRW_DimensionAssociation association =
17337 dimensionAssociationFromMetadata(record);
17338 if (hasDxfTableAppDataUnsupportedByDwg(association)) {
17339 m_writeFailed = true;
17340 continue;
17341 }
17342 remapClassInstanceHandle(association);
17343 if (canWriteNativeDimensionAssociation(association)) {
17344 noteObjectClassRegistration(
17345 isDwgDataStorageTypedSelected(record.handle) &&
17346 m_dwgW->registerDimensionAssociationObjectClass(&association),
17347 nativeDimensionAssociationHandles, record.handle);
17348 }
17349 }
17350 for (const auto &record : metadata.evaluationGraphs()) {
17351 if (record.replayState !=
17352 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17353 record.handle == 0 || !canWriteAssociativeObjects) {
17354 continue;
17355 }
17356 DRW_EvaluationGraph graph = evaluationGraphFromMetadata(record);
17357 if (hasDxfTableAppDataUnsupportedByDwg(graph)) {
17358 m_writeFailed = true;
17359 continue;
17360 }
17361 remapClassInstanceHandle(graph);
17362 if (canWriteNativeEvaluationGraph(graph)) {
17363 noteObjectClassRegistration(
17364 isDwgDataStorageTypedSelected(record.handle) &&
17365 m_dwgW->registerEvaluationGraphObjectClass(&graph),
17366 nativeEvaluationGraphHandles, record.handle);
17367 }
17368 }
17369 for (const auto &record : metadata.sections()) {
17370 if (record.replayState !=
17371 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17372 record.handle == 0)
17373 continue;
17374 DRW_Section section = sectionFromMetadata(record);
17375 if (hasDxfTableAppDataUnsupportedByDwg(section)) {
17376 m_writeFailed = true;
17377 continue;
17378 }
17379 remapClassInstanceHandle(section);
17380 noteObjectClassRegistration(
17381 isDwgDataStorageTypedSelected(record.handle) &&
17382 m_dwgW->registerSectionObjectClass(&section),
17383 nativeSectionHandles, record.handle);
17384 }
17385 // RASTERVARIABLES / GEODATA / SPATIAL_FILTER registration moved
17386 // below to the `canRegisterCustomClassObjects` block (PR 13f).
17387 // PR 13g — SCALE / IDBUFFER / LAYER_INDEX / SPATIAL_INDEX /
17388 // DICTIONARYVAR registration moved below to the same block.
17389 // PR 13h — DICTIONARYWDFLT / SORTENTSTABLE / FIELDLIST / FIELD
17390 // registration moved below to the `canRegisterCustomClassObjects`
17391 // block.
17392 }
17393
17394 // PR 13f — custom-class families with version-clean encoders +
17395 // round-trip-grade Records register here on the broadened
17396 // `canRegisterCustomClassObjects` gate (≥AC1015) instead of the
17397 // AC1021+ `canWriteModernObjects` gate. The matching dispatch loop
17398 // in writeObjects uses the same gate so the CLASSES section entry and
17399 // the OBJECTS instance always land together.
17400 if (canRegisterCustomClassObjects) {
17401 for (const auto &record : metadata.mleaderStyles()) {
17402 if (record.replayState !=
17403 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17404 record.handle == 0 ||
17405 hasReplayableRawMLeaderStyle(metadata, record.handle,
17406 m_dwgW->getVersion())) {
17407 continue;
17408 }
17409 DRW_MLeaderStyle style = mleaderStyleFromMetadata(record);
17410 remapClassInstanceHandle(style);
17411 noteObjectClassRegistration(
17412 isDwgDataStorageTypedSelected(record.handle) &&
17413 m_dwgW->registerMLeaderStyleObjectClass(&style),
17414 nativeMLeaderStyleHandles, record.handle);
17415 }
17416 // MATERIAL has only the modelled name/description fields. Keep a
17417 // replayable source carrier authoritative so its modern opaque tail
17418 // is not discarded by the typed writer.
17419 for (const auto &record : metadata.materials()) {
17420 if (record.replayState !=
17421 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17422 record.handle == 0 ||
17423 hasReplayableRawMaterial(metadata, record.handle,
17424 m_dwgW->getVersion())) {
17425 continue;
17426 }
17427 DRW_Material material = materialFromMetadata(record);
17428 if (hasDxfTableAppDataUnsupportedByDwg(material)) {
17429 m_writeFailed = true;
17430 continue;
17431 }
17432 remapClassInstanceHandle(material);
17433 noteObjectClassRegistration(
17434 isDwgDataStorageTypedSelected(record.handle) &&
17435 m_dwgW->registerMaterialObjectClass(&material),
17436 nativeMaterialHandles, record.handle);
17437 }
17438 if (m_dwgW->getVersion() >= DRW::AC1018) {
17439 for (const auto &record : metadata.dbColors()) {
17440 if (record.replayState !=
17441 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17442 record.handle == 0) {
17443 continue;
17444 }
17445 DRW_DbColor color = dbColorFromMetadata(record);
17446 if (hasDxfTableAppDataUnsupportedByDwg(color)) {
17447 m_writeFailed = true;
17448 continue;
17449 }
17450 remapClassInstanceHandle(color);
17451 noteObjectClassRegistration(
17452 isDwgDataStorageTypedSelected(record.handle) &&
17453 m_dwgW->registerDbColorObjectClass(&color),
17454 nativeDbColorHandles, record.handle);
17455 }
17456 }
17457 for (const auto &record : metadata.lightLists()) {
17458 if (record.replayState !=
17459 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17460 record.handle == 0 ||
17461 hasReplayableRawLightList(
17462 metadata, record.handle, m_dwgW->getVersion(),
17463 [this](std::uint32_t sourceHandle) {
17464 return isDwgWriteOpaqueReferenceStable(sourceHandle);
17465 })) {
17466 continue;
17467 }
17468 DRW_LightList lightList = lightListFromMetadata(record);
17469 if (hasDxfTableAppDataUnsupportedByDwg(lightList)) {
17470 m_writeFailed = true;
17471 continue;
17472 }
17473 remapClassInstanceHandle(lightList);
17474 noteObjectClassRegistration(
17475 isDwgDataStorageTypedSelected(record.handle) &&
17476 m_dwgW->registerLightListObjectClass(&lightList),
17477 nativeLightListHandles, record.handle);
17478 }
17479 for (const auto &record : metadata.backgrounds()) {
17480 if (record.replayState !=
17481 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17482 record.handle == 0 ||
17483 hasReplayableRawBackground(metadata, record.handle,
17484 m_dwgW->getVersion())) {
17485 continue;
17486 }
17487 DRW_Background background = backgroundFromMetadata(record);
17488 if (hasDxfTableAppDataUnsupportedByDwg(background)) {
17489 m_writeFailed = true;
17490 continue;
17491 }
17492 remapClassInstanceHandle(background);
17493 noteObjectClassRegistration(
17494 isDwgDataStorageTypedSelected(record.handle) &&
17495 m_dwgW->registerBackgroundObjectClass(&background),
17496 nativeBackgroundHandles, record.handle);
17497 }
17498 for (const auto &record : metadata.sunStudies()) {
17499 if (record.replayState !=
17500 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17501 record.handle == 0 ||
17502 hasReplayableRawSunStudy(metadata, record.handle,
17503 m_dwgW->getVersion())) {
17504 continue;
17505 }
17506 DRW_SunStudy study = sunStudyFromMetadata(record);
17507 if (hasDxfTableAppDataUnsupportedByDwg(study)) {
17508 m_writeFailed = true;
17509 continue;
17510 }
17511 remapClassInstanceHandle(study);
17512 noteObjectClassRegistration(
17513 isDwgDataStorageTypedSelected(record.handle) &&
17514 m_dwgW->registerSunStudyObjectClass(&study),
17515 nativeSunStudyHandles, record.handle);
17516 }
17517 for (const auto &record : metadata.motionPaths()) {
17518 if (record.replayState !=
17519 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17520 record.handle == 0 ||
17521 hasReplayableRawMotionPath(metadata, record.handle,
17522 m_dwgW->getVersion())) {
17523 continue;
17524 }
17525 DRW_MotionPath path = motionPathFromMetadata(record);
17526 if (hasDxfTableAppDataUnsupportedByDwg(path)) {
17527 m_writeFailed = true;
17528 continue;
17529 }
17530 remapClassInstanceHandle(path);
17531 noteObjectClassRegistration(
17532 isDwgDataStorageTypedSelected(record.handle) &&
17533 m_dwgW->registerMotionPathObjectClass(&path),
17534 nativeMotionPathHandles, record.handle);
17535 }
17536 for (const auto &record : metadata.curvePaths()) {
17537 if (record.replayState !=
17538 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17539 record.handle == 0 ||
17540 hasReplayableRawCurvePath(metadata, record.handle,
17541 m_dwgW->getVersion())) {
17542 continue;
17543 }
17544 DRW_CurvePath path = curvePathFromMetadata(record);
17545 if (hasDxfTableAppDataUnsupportedByDwg(path)) {
17546 m_writeFailed = true;
17547 continue;
17548 }
17549 remapClassInstanceHandle(path);
17550 noteObjectClassRegistration(
17551 isDwgDataStorageTypedSelected(record.handle) &&
17552 m_dwgW->registerCurvePathObjectClass(&path),
17553 nativeCurvePathHandles, record.handle);
17554 }
17555 for (const auto &record : metadata.pointPaths()) {
17556 if (record.replayState !=
17557 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17558 record.handle == 0 ||
17559 hasReplayableRawPointPath(metadata, record.handle,
17560 m_dwgW->getVersion())) {
17561 continue;
17562 }
17563 DRW_PointPath path = pointPathFromMetadata(record);
17564 if (hasDxfTableAppDataUnsupportedByDwg(path)) {
17565 m_writeFailed = true;
17566 continue;
17567 }
17568 remapClassInstanceHandle(path);
17569 noteObjectClassRegistration(
17570 isDwgDataStorageTypedSelected(record.handle) &&
17571 m_dwgW->registerPointPathObjectClass(&path),
17572 nativePointPathHandles, record.handle);
17573 }
17574 for (const auto &record : metadata.objectPtrs()) {
17575 if (record.replayState !=
17576 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17577 record.handle == 0 ||
17578 hasReplayableRawObjectPtr(metadata, record.handle,
17579 m_dwgW->getVersion())) {
17580 continue;
17581 }
17582 DRW_ObjectPtr object = objectPtrFromMetadata(record);
17583 if (hasDxfTableAppDataUnsupportedByDwg(object)) {
17584 m_writeFailed = true;
17585 continue;
17586 }
17587 remapClassInstanceHandle(object);
17588 noteObjectClassRegistration(
17589 isDwgDataStorageTypedSelected(record.handle) &&
17590 m_dwgW->registerObjectPtrObjectClass(&object),
17591 nativeObjectPtrHandles, record.handle);
17592 }
17593 for (const auto &record : metadata.partialViewingIndexes()) {
17594 if (record.replayState !=
17595 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17596 record.handle == 0 ||
17597 hasReplayableRawPartialViewingIndex(metadata, record.handle,
17598 m_dwgW->getVersion())) {
17599 continue;
17600 }
17601 DRW_PartialViewingIndex index = partialViewingIndexFromMetadata(record);
17602 if (hasDxfTableAppDataUnsupportedByDwg(index)) {
17603 m_writeFailed = true;
17604 continue;
17605 }
17606 remapClassInstanceHandle(index);
17607 noteObjectClassRegistration(
17608 isDwgDataStorageTypedSelected(record.handle) &&
17609 m_dwgW->registerPartialViewingIndexObjectClass(&index),
17610 nativePartialViewingIndexHandles, record.handle);
17611 }
17612 for (const auto &record : metadata.renderSettings()) {
17613 if (record.replayState !=
17614 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17615 record.handle == 0 ||
17616 hasReplayableRawRenderSettings(metadata, record.handle, record.kind,
17617 m_dwgW->getVersion())) {
17618 continue;
17619 }
17620 DRW_RenderSettings settings = renderSettingsFromMetadata(record);
17621 if (hasDxfTableAppDataUnsupportedByDwg(settings)) {
17622 m_writeFailed = true;
17623 continue;
17624 }
17625 remapClassInstanceHandle(settings);
17626 noteObjectClassRegistration(
17627 isDwgDataStorageTypedSelected(record.handle) &&
17628 m_dwgW->registerRenderSettingsObjectClass(&settings),
17629 nativeRenderSettingsHandles, record.handle);
17630 }
17631 for (const auto &record : metadata.visualStyles()) {
17632 if (record.replayState !=
17633 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17634 record.handle == 0 ||
17635 hasReplayableRawVisualStyle(metadata, record.handle,
17636 m_dwgW->getVersion())) {
17637 continue;
17638 }
17639 DRW_VisualStyle style = visualStyleFromMetadata(record);
17640 if (hasDxfTableAppDataUnsupportedByDwg(style)) {
17641 m_writeFailed = true;
17642 continue;
17643 }
17644 remapClassInstanceHandle(style);
17645 noteObjectClassRegistration(
17646 isDwgDataStorageTypedSelected(record.handle) &&
17647 m_dwgW->registerVisualStyleObjectClass(&style),
17648 nativeVisualStyleHandles, record.handle);
17649 }
17650 for (const auto &record : metadata.tvDeviceProperties()) {
17651 if (record.replayState !=
17652 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17653 record.handle == 0)
17654 continue;
17655 DRW_TvDeviceProperties properties =
17656 tvDevicePropertiesFromMetadata(record);
17657 if (hasDxfTableAppDataUnsupportedByDwg(properties)) {
17658 m_writeFailed = true;
17659 continue;
17660 }
17661 remapClassInstanceHandle(properties);
17662 noteObjectClassRegistration(
17663 isDwgDataStorageTypedSelected(record.handle) &&
17664 m_dwgW->registerTvDevicePropertiesObjectClass(&properties),
17665 nativeTvDevicePropertiesHandles, record.handle);
17666 }
17667 for (const auto &record : metadata.vxControls()) {
17668 if (record.replayState !=
17669 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17670 record.handle == 0 ||
17671 hasReplayableRawVxControl(metadata, record.handle,
17672 m_dwgW->getVersion()))
17673 continue;
17674 DRW_VxControl control = vxControlFromMetadata(record);
17675 if (hasDxfTableAppDataUnsupportedByDwg(control)) {
17676 m_writeFailed = true;
17677 continue;
17678 }
17679 remapClassInstanceHandle(control);
17680 noteObjectClassRegistration(
17681 isDwgDataStorageTypedSelected(record.handle) &&
17682 m_dwgW->registerVxControlObjectClass(&control),
17683 nativeVxControlHandles, record.handle);
17684 }
17685 for (const auto &record : metadata.vxTableRecords()) {
17686 if (record.replayState !=
17687 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17688 record.handle == 0 ||
17689 hasReplayableRawVxTableRecord(metadata, record.handle,
17690 m_dwgW->getVersion()))
17691 continue;
17692 DRW_VxTableRecord tableRecord = vxTableRecordFromMetadata(record);
17693 if (hasDxfTableAppDataUnsupportedByDwg(tableRecord)) {
17694 m_writeFailed = true;
17695 continue;
17696 }
17697 remapClassInstanceHandle(tableRecord);
17698 noteObjectClassRegistration(
17699 isDwgDataStorageTypedSelected(record.handle) &&
17700 m_dwgW->registerVxTableRecordObjectClass(&tableRecord),
17701 nativeVxTableRecordHandles, record.handle);
17702 }
17703 // RASTERVARIABLES (AcDbRasterVariables, custom class 505) — must be
17704 // registered here, BEFORE writeDwgClasses() emits the CLASSES section.
17705 // PR 8d.1b + PR 13f.
17706 for (const auto &record : metadata.rasterVariables()) {
17707 if (record.replayState !=
17708 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17709 record.handle == 0) {
17710 continue;
17711 }
17712 DRW_RasterVariables rv = rasterVariablesFromMetadata(record);
17713 if (hasDxfTableAppDataUnsupportedByDwg(rv)) {
17714 m_writeFailed = true;
17715 continue;
17716 }
17717 remapClassInstanceHandle(rv);
17718 noteObjectClassRegistration(
17719 isDwgDataStorageTypedSelected(record.handle) &&
17720 m_dwgW->registerRasterVariablesObjectClass(&rv),
17721 nativeRasterVariablesHandles, record.handle);
17722 }
17723 // WIPEOUTVARIABLES (AcDbWipeoutVariables, custom class 529).
17724 for (const auto &record : metadata.wipeoutVariables()) {
17725 if (record.replayState !=
17726 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17727 record.handle == 0) {
17728 continue;
17729 }
17730 DRW_WipeoutVariables wv = wipeoutVariablesFromMetadata(record);
17731 if (hasDxfTableAppDataUnsupportedByDwg(wv)) {
17732 m_writeFailed = true;
17733 continue;
17734 }
17735 remapClassInstanceHandle(wv);
17736 noteObjectClassRegistration(
17737 isDwgDataStorageTypedSelected(record.handle) &&
17738 m_dwgW->registerWipeoutVariablesObjectClass(&wv),
17739 nativeWipeoutVariablesHandles, record.handle);
17740 }
17741 // GEODATA (AcDbGeoData, custom class 506) — PR 8d.1c + PR 13f.
17742 for (const auto &record : metadata.geoData()) {
17743 if (record.replayState !=
17744 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17745 record.handle == 0) {
17746 continue;
17747 }
17748 DRW_GeoData gd = geoDataFromMetadata(record);
17749 if (hasDxfTableAppDataUnsupportedByDwg(gd)) {
17750 m_writeFailed = true;
17751 continue;
17752 }
17753 remapClassInstanceHandle(gd);
17754 noteObjectClassRegistration(
17755 isDwgDataStorageTypedSelected(record.handle) &&
17756 m_dwgW->registerGeoDataObjectClass(&gd),
17757 nativeGeoDataHandles, record.handle);
17758 }
17759 // SPATIAL_FILTER (AcDbSpatialFilter, custom class 507) — PR 8d.1d +
17760 // PR 13f.
17761 for (const auto &record : metadata.spatialFilters()) {
17762 if (record.replayState !=
17763 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17764 record.handle == 0) {
17765 continue;
17766 }
17767 DRW_SpatialFilter sf = spatialFilterFromMetadata(record);
17768 if (hasDxfTableAppDataUnsupportedByDwg(sf)) {
17769 m_writeFailed = true;
17770 continue;
17771 }
17772 remapClassInstanceHandle(sf);
17773 noteObjectClassRegistration(
17774 isDwgDataStorageTypedSelected(record.handle) &&
17775 m_dwgW->registerSpatialFilterObjectClass(&sf),
17776 nativeSpatialFilterHandles, record.handle);
17777 }
17778 // PR 13g — SCALE / IDBUFFER / LAYER_INDEX / SPATIAL_INDEX /
17779 // DICTIONARYVAR (custom classes 508-512). Encoders are version-
17780 // clean; SPATIAL_INDEX additionally gates its common-handle prefix
17781 // on `version > AC1018` (mirrors its parser). All 5 default to
17782 // enabled in `isDwgClassEnabled` (no per-class gate needed in
17783 // dwgwriter.h).
17784 for (const auto &record : metadata.scales()) {
17785 if (record.replayState !=
17786 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17787 record.handle == 0) {
17788 continue;
17789 }
17790 DRW_Scale s = scaleFromMetadata(record);
17791 if (hasDxfTableAppDataUnsupportedByDwg(s)) {
17792 m_writeFailed = true;
17793 continue;
17794 }
17795 remapClassInstanceHandle(s);
17796 noteObjectClassRegistration(
17797 isDwgDataStorageTypedSelected(record.handle) &&
17798 m_dwgW->registerScaleObjectClass(&s),
17799 nativeScaleHandles, record.handle);
17800 }
17801 for (const auto &record : metadata.idBuffers()) {
17802 if (record.replayState !=
17803 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17804 record.handle == 0) {
17805 continue;
17806 }
17807 DRW_IDBuffer b = idBufferFromMetadata(record);
17808 if (hasDxfTableAppDataUnsupportedByDwg(b)) {
17809 m_writeFailed = true;
17810 continue;
17811 }
17812 remapClassInstanceHandle(b);
17813 noteObjectClassRegistration(
17814 isDwgDataStorageTypedSelected(record.handle) &&
17815 m_dwgW->registerIDBufferObjectClass(&b),
17816 nativeIDBufferHandles, record.handle);
17817 }
17818 for (const auto &record : metadata.layerIndexes()) {
17819 if (record.replayState !=
17820 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17821 record.handle == 0) {
17822 continue;
17823 }
17824 DRW_LayerIndex li = layerIndexFromMetadata(record);
17825 if (hasDxfTableAppDataUnsupportedByDwg(li)) {
17826 m_writeFailed = true;
17827 continue;
17828 }
17829 remapClassInstanceHandle(li);
17830 noteObjectClassRegistration(
17831 isDwgDataStorageTypedSelected(record.handle) &&
17832 m_dwgW->registerLayerIndexObjectClass(&li),
17833 nativeLayerIndexHandles, record.handle);
17834 }
17835 for (const auto &record : metadata.spatialIndexes()) {
17836 if (record.replayState !=
17837 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17838 record.handle == 0 ||
17839 hasReplayableRawSpatialIndex(metadata, record.handle,
17840 m_dwgW->getVersion())) {
17841 continue;
17842 }
17843 DRW_SpatialIndex si = spatialIndexFromMetadata(record);
17844 if (hasDxfTableAppDataUnsupportedByDwg(si)) {
17845 m_writeFailed = true;
17846 continue;
17847 }
17848 remapClassInstanceHandle(si);
17849 noteObjectClassRegistration(
17850 isDwgDataStorageTypedSelected(record.handle) &&
17851 m_dwgW->registerSpatialIndexObjectClass(&si),
17852 nativeSpatialIndexHandles, record.handle);
17853 }
17854 for (const auto &record : metadata.dictionaryVars()) {
17855 if (record.replayState !=
17856 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17857 record.handle == 0) {
17858 continue;
17859 }
17860 DRW_DictionaryVar dv = dictionaryVarFromMetadata(record);
17861 if (hasDxfTableAppDataUnsupportedByDwg(dv)) {
17862 m_writeFailed = true;
17863 continue;
17864 }
17865 remapClassInstanceHandle(dv);
17866 noteObjectClassRegistration(
17867 isDwgDataStorageTypedSelected(record.handle) &&
17868 m_dwgW->registerDictionaryVarObjectClass(&dv),
17869 nativeDictionaryVarHandles, record.handle);
17870 }
17871 // PR 13h — DICTIONARYWDFLT / SORTENTSTABLE / FIELDLIST / FIELD
17872 // (custom classes 513-516). Encoders are version-clean (FIELD
17873 // has a parser-mirrored `version < AC1021` branch for the
17874 // legacy m_formatString TV). All 4 default to enabled in
17875 // `isDwgClassEnabled`.
17876 for (const auto &record : metadata.dictionariesWithDefault()) {
17877 if (record.replayState !=
17878 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17879 record.handle == 0) {
17880 continue;
17881 }
17882 if (!canWriteDwgDictionaryWithDefaultRecord(metadata, record,
17883 m_dwgW->getVersion())) {
17884 if (!metadata.hasRawObjectForHandle(record.handle))
17885 m_writeFailed = true;
17886 continue;
17887 }
17888 DRW_DictionaryWithDefault dwd = dictionaryWithDefaultFromMetadata(record);
17889 remapClassInstanceHandle(dwd);
17890 noteObjectClassRegistration(
17891 isDwgDataStorageTypedSelected(record.handle) &&
17892 m_dwgW->registerDictionaryWithDefaultObjectClass(&dwd),
17893 nativeDictionaryWithDefaultHandles, record.handle);
17894 }
17895 for (const auto &record : metadata.sortEntsTables()) {
17896 if (record.replayState !=
17897 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17898 record.handle == 0) {
17899 continue;
17900 }
17901 DRW_SortEntsTable se = sortEntsTableFromMetadata(record);
17902 if (hasDxfTableAppDataUnsupportedByDwg(se)) {
17903 m_writeFailed = true;
17904 continue;
17905 }
17906 if (hasReplayableRawSortEntsTable(metadata, record.handle,
17907 m_dwgW->getVersion()) &&
17908 hasUnresolvedSortEntsReference(se)) {
17909 // Preserve a valid source frame when the typed view contains a
17910 // dangling sort key that cannot be remapped safely.
17911 continue;
17912 }
17913 remapClassInstanceHandle(se);
17914 noteObjectClassRegistration(
17915 isDwgDataStorageTypedSelected(record.handle) &&
17916 m_dwgW->registerSortEntsTableObjectClass(&se),
17917 nativeSortEntsTableHandles, record.handle);
17918 }
17919 for (auto &candidate : m_dwgFieldWritePlan.fieldCandidates) {
17920 DRW_Field f = candidate.payload;
17921 remapClassInstanceHandle(f);
17922 const bool registered = m_dwgW->registerFieldObjectClass(&f);
17923 noteObjectClassRegistration(registered, nativeFieldHandles,
17924 candidate.sourceHandle);
17925 candidate.classAdmitted =
17926 registered && nativeFieldHandles.count(candidate.sourceHandle) != 0;
17927 }
17928 for (auto &candidate : m_dwgFieldWritePlan.fieldListCandidates) {
17929 const bool fieldsAdmitted = std::all_of(
17930 candidate.fieldCandidateIndexes.cbegin(),
17931 candidate.fieldCandidateIndexes.cend(),
17932 [this](std::size_t fieldIndex) {
17933 return fieldIndex < m_dwgFieldWritePlan.fieldCandidates.size() &&
17934 m_dwgFieldWritePlan.fieldCandidates[fieldIndex]
17935 .classAdmitted;
17936 });
17937 if (!fieldsAdmitted) {
17938 m_writeFailed = true;
17939 continue;
17940 }
17941 DRW_FieldList fl = candidate.payload;
17942 remapClassInstanceHandle(fl);
17943 const bool registered = m_dwgW->registerFieldListObjectClass(&fl);
17944 noteObjectClassRegistration(registered, nativeFieldListHandles,
17945 candidate.sourceHandle);
17946 candidate.classAdmitted =
17947 registered && nativeFieldListHandles.count(candidate.sourceHandle) !=
17948 0;
17949 }
17950 for (const auto &record : metadata.underlayDefinitions()) {
17951 if (record.replayState !=
17952 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17953 record.handle == 0) {
17954 continue;
17955 }
17956 DRW_UnderlayDefinition definition =
17957 underlayDefinitionFromMetadata(record);
17958 if (hasDxfTableAppDataUnsupportedByDwg(definition)) {
17959 m_writeFailed = true;
17960 continue;
17961 }
17962 remapClassInstanceHandle(definition);
17963 noteObjectClassRegistration(
17964 isDwgDataStorageTypedSelected(record.handle) &&
17965 m_dwgW->registerUnderlayDefinitionObjectClass(&definition),
17966 nativeUnderlayDefinitionHandles, record.handle);
17967 }
17968 for (const auto &record : metadata.pointCloudDefinitions()) {
17969 if (record.replayState !=
17970 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17971 record.handle == 0)
17972 continue;
17973 DRW_PointCloudDef definition = pointCloudDefinitionFromMetadata(record);
17974 if (hasDxfTableAppDataUnsupportedByDwg(definition)) {
17975 m_writeFailed = true;
17976 continue;
17977 }
17978 remapClassInstanceHandle(definition);
17979 noteObjectClassRegistration(
17980 isDwgDataStorageTypedSelected(record.handle) &&
17981 m_dwgW->registerPointCloudDefObjectClass(&definition),
17982 nativePointCloudDefinitionHandles, record.handle);
17983 }
17984 for (const auto &record : metadata.navisworksModelDefinitions()) {
17985 if (record.replayState !=
17986 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17987 record.handle == 0)
17988 continue;
17989 DRW_NavisworksModelDef definition =
17990 navisworksModelDefinitionFromMetadata(record);
17991 if (hasDxfTableAppDataUnsupportedByDwg(definition)) {
17992 m_writeFailed = true;
17993 continue;
17994 }
17995 remapClassInstanceHandle(definition);
17996 noteObjectClassRegistration(
17997 isDwgDataStorageTypedSelected(record.handle) &&
17998 m_dwgW->registerNavisworksModelDefObjectClass(&definition),
17999 nativeNavisworksModelDefinitionHandles, record.handle);
18000 }
18001 for (const auto &record : metadata.pointCloudColorMaps()) {
18002 if (record.replayState !=
18003 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
18004 record.handle == 0)
18005 continue;
18006 DRW_PointCloudColorMap colorMap = pointCloudColorMapFromMetadata(record);
18007 if (hasDxfTableAppDataUnsupportedByDwg(colorMap)) {
18008 m_writeFailed = true;
18009 continue;
18010 }
18011 remapClassInstanceHandle(colorMap);
18012 noteObjectClassRegistration(
18013 isDwgDataStorageTypedSelected(record.handle) &&
18014 m_dwgW->registerPointCloudColorMapObjectClass(&colorMap),
18015 nativePointCloudColorMapHandles, record.handle);
18016 }
18017 }
18018
18019 // TABLESTYLE has a complete positional writer through AC1021. Newer
18020 // versions retain the opaque carrier until their extended layout is
18021 // implemented.
18022 if (canRegisterCustomClassObjects && m_dwgW->getVersion() <= DRW::AC1021) {
18023 for (const auto &record : metadata.tables()) {
18024 if (record.replayState !=
18025 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
18026 record.handle == 0 || !record.nativeTableStyle ||
18027 !isDwgDataStorageTypedSelected(record.handle))
18028 continue;
18029 DRW_TableStyle style = *record.nativeTableStyle;
18030 if (hasDxfTableAppDataUnsupportedByDwg(style) ||
18031 !style.validateDwgPayload(m_dwgW->getVersion())) {
18032 m_writeFailed = true;
18033 continue;
18034 }
18035 remapClassInstanceHandle(style);
18036 noteObjectClassRegistration(
18037 isDwgDataStorageTypedSelected(record.handle) &&
18038 m_dwgW->registerTableStyleObjectClass(&style),
18039 nativeTableStyleHandles, record.handle);
18040 }
18041 }
18042
18043 // Publish the same native-object set used by the later OBJECTS
18044 // dispatch before views and entities emit references to it. The
18045 // writeObjects() copy remains a defensive refresh for records selected
18046 // by that callback's version gates.
18047 auto markNativeHandles = [this](const auto &handles) {
18048 m_dwgWriteKnownHandles.insert(handles.begin(), handles.end());
18049 };
18050 markNativeHandles(nativeSunHandles);
18051 markNativeHandles(nativeSunStudyHandles);
18052 markNativeHandles(nativeMotionPathHandles);
18053 markNativeHandles(nativeCurvePathHandles);
18054 markNativeHandles(nativePointPathHandles);
18055 markNativeHandles(nativeObjectPtrHandles);
18056 markNativeHandles(nativePartialViewingIndexHandles);
18057 markNativeHandles(nativeRenderSettingsHandles);
18058 markNativeHandles(nativeVisualStyleHandles);
18059 markNativeHandles(nativeTvDevicePropertiesHandles);
18060 markNativeHandles(nativeVxControlHandles);
18061 markNativeHandles(nativeVxTableRecordHandles);
18062 markNativeHandles(nativeMLeaderStyleHandles);
18063 markNativeHandles(nativeTableStyleHandles);
18064 markNativeHandles(nativeMaterialHandles);
18065 markNativeHandles(nativeLightListHandles);
18066 markNativeHandles(nativeBackgroundHandles);
18067 markNativeHandles(nativeRasterVariablesHandles);
18068 markNativeHandles(nativeWipeoutVariablesHandles);
18069 markNativeHandles(nativeGeoDataHandles);
18070 markNativeHandles(nativeSpatialFilterHandles);
18071 markNativeHandles(nativeScaleHandles);
18072 markNativeHandles(nativeIDBufferHandles);
18073 markNativeHandles(nativeLayerIndexHandles);
18074 markNativeHandles(nativeSpatialIndexHandles);
18075 markNativeHandles(nativeDictionaryVarHandles);
18076 markNativeHandles(nativeDictionaryWithDefaultHandles);
18077 markNativeHandles(nativeSortEntsTableHandles);
18078 markNativeHandles(nativeFieldListHandles);
18079 markNativeHandles(nativeFieldHandles);
18080 markNativeHandles(nativeUnderlayDefinitionHandles);
18081 markNativeHandles(nativePointCloudDefinitionHandles);
18082 markNativeHandles(nativeNavisworksModelDefinitionHandles);
18083 markNativeHandles(nativePointCloudColorMapHandles);
18084 markNativeHandles(nativeDbColorHandles);
18085 markNativeHandles(nativeDimensionAssociationHandles);
18086 markNativeHandles(nativeEvaluationGraphHandles);
18087 markNativeHandles(nativeBlockRepresentationHandles);
18088 markNativeHandles(nativeSectionHandles);
18089
18090 // CLASSES is serialized immediately after this callback. If a raw
18091 // DataStorage decision has nevertheless admitted a typed class, there is
18092 // no safe way to retract that class later; fail the staged export rather
18093 // than letting CLASSES and OBJECTS choose different carriers.
18094 for (const auto &decision : m_dwgDataStorageReplayPlan.decisions) {
18095 if (decision.decision == DwgDataStorageDecision::EmitRaw &&
18096 hasDwgDataStorageClassAdmission(decision.ownerHandle,
18097 DwgDataStorageCarrier::Kind::Typed)) {
18098 m_writeFailed = true;
18099 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
18100 "RS_FilterDXFRW: raw DataStorage decision 0x%X has a "
18101 "typed CLASSES admission",
18102 decision.ownerHandle);
18103 }
18104 }
18105
18106 for (std::size_t rawIndex = 0; rawIndex < metadata.rawObjects().size();
18107 ++rawIndex) {
18108 const auto &record = metadata.rawObjects()[rawIndex];
18109 // Never register a CLASSES entry for raw bytes that cannot be replayed
18110 // into this exact target version. This mirrors the emit-loop guard so
18111 // a rejected carrier cannot leave an orphan CLASSES entry behind.
18112 if (!sameRawObjectEncodingFamily(record.version, m_dwgW->getVersion())) {
18113 continue;
18114 }
18115 if (isPlotSettingsRawObject(record) &&
18116 !isDwgRawPlotSettingsSelected(record.handle)) {
18117 continue;
18118 }
18119 if (requiresDwgOpaqueTableAdmission(record) &&
18120 !isDwgOpaqueTableRawSelected(rawIndex)) {
18121 continue;
18122 }
18123 if (record.replayState !=
18124 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
18125 continue;
18126 }
18127 if (record.hasDataStorage && m_dwgW->getVersion() > DRW::AC1024 &&
18128 !isDwgDataStorageRawSelected(record.handle)) {
18129 // A typed DataStorage decision owns this source handle. Do not
18130 // admit a second raw class before CLASSES is serialized.
18131 continue;
18132 }
18133 if (isSectionObjectRawEntity(record) &&
18134 m_dwgW->getVersion() >= DRW::AC1021 &&
18135 hasReplayableSectionObject(metadata, record.handle)) {
18136 continue;
18137 }
18138 // LIGHT, CAMERA, NAVISWORKSMODEL, and SECTIONOBJECT retain a raw
18139 // source frame for fallback, but their typed entity routes already
18140 // own the class instance and final frame. Registering both would
18141 // compete for one handle under different source class ordinals.
18142 if (isNativeMetadataEntityRawObject(metadata, record))
18143 continue;
18144 if (isRawViewportEntityHeaderControl(record)) {
18145 if (record.rawBytes.empty() ||
18146 LC_DwgAdvancedMetadata::rawReplayBlocker(record) !=
18147 LC_DwgAdvancedMetadata::ReplayBlocker::None) {
18148 continue;
18149 }
18150 DRW_UnsupportedObject object = rawObjectFromMetadata(record);
18151 if (!registerRawClass(object, record))
18152 m_writeFailed = true;
18153 continue;
18154 }
18155 if (!record.isCustomClass) {
18156 continue;
18157 }
18158 if (isFixedStructuralDwgHandle(record.handle) ||
18159 LC_DwgAdvancedMetadata::rawReplayBlocker(record) !=
18160 LC_DwgAdvancedMetadata::ReplayBlocker::None) {
18161 continue;
18162 }
18163 const DRW_UnsupportedObject ownerProbe = rawObjectFromMetadata(record);
18164 if (!rawCustomEntityOwnerFieldsAreReplayable(ownerProbe,
18165 m_dwgW->getVersion())) {
18166 continue;
18167 }
18168 const std::uint32_t sourceBlockOwner =
18169 record.blockOwnerHandle != DRW::NoHandle ? record.blockOwnerHandle
18170 : record.parentHandle;
18171 if (record.isEntity && sourceBlockOwner != DRW::NoHandle) {
18172 const auto blockIt = m_dwgWriteBlockHandleRemap.find(sourceBlockOwner);
18173 if (blockIt != m_dwgWriteBlockHandleRemap.end() &&
18174 blockIt->second != sourceBlockOwner &&
18175 (record.version <= DRW::AC1018 ||
18176 record.parentHandle == DRW::NoHandle)) {
18177 // R2000/R2004 opaque bodies have no validated owner boundary.
18178 // A block-owner-only carrier has no body owner to probe in any
18179 // version. AC1021+ entities with a common parent are patched
18180 // by dwgWriter15 after that owner is validated.
18181 continue;
18182 }
18183 }
18184 if (record.hasDataStorage &&
18185 !allowsDwgDataStoragePresenceBit(record.handle)) {
18186 continue;
18187 }
18188 if (nativeSunHandles.count(record.handle) != 0 && isSunRawObject(record))
18189 continue;
18190 if (nativeMLeaderStyleHandles.count(record.handle) != 0 &&
18191 isMLeaderStyleRawObject(record))
18192 continue;
18193 if (nativeTableStyleHandles.count(record.handle) != 0 &&
18194 isTableStyleRawObject(record))
18195 continue;
18196 if (nativeMaterialHandles.count(record.handle) != 0 &&
18197 isMaterialRawObject(record))
18198 continue;
18199 if (nativeLightListHandles.count(record.handle) != 0 &&
18200 isLightListRawObject(record))
18201 continue;
18202 if (nativeBackgroundHandles.count(record.handle) != 0 &&
18203 isBackgroundRawObject(record))
18204 continue;
18205 if (nativeSunStudyHandles.count(record.handle) != 0 &&
18206 isSunStudyRawObject(record))
18207 continue;
18208 if (nativeMotionPathHandles.count(record.handle) != 0 &&
18209 isMotionPathRawObject(record))
18210 continue;
18211 if (nativeCurvePathHandles.count(record.handle) != 0 &&
18212 isCurvePathRawObject(record))
18213 continue;
18214 if (nativePointPathHandles.count(record.handle) != 0 &&
18215 isPointPathRawObject(record))
18216 continue;
18217 if (nativeObjectPtrHandles.count(record.handle) != 0 &&
18218 isObjectPtrRawObject(record))
18219 continue;
18220 if (nativePartialViewingIndexHandles.count(record.handle) != 0 &&
18221 isPartialViewingIndexRawObject(record))
18222 continue;
18223 if (nativeRenderSettingsHandles.count(record.handle) != 0 &&
18224 isRenderSettingsRawObject(record))
18225 continue;
18226 if (nativeVisualStyleHandles.count(record.handle) != 0 &&
18227 isVisualStyleRawObject(record))
18228 continue;
18229 if (nativeTvDevicePropertiesHandles.count(record.handle) != 0 &&
18230 isTvDevicePropertiesRawObject(record))
18231 continue;
18232 if (nativeVxControlHandles.count(record.handle) != 0 &&
18233 isVxControlRawObject(record))
18234 continue;
18235 if (nativeVxTableRecordHandles.count(record.handle) != 0 &&
18236 isVxTableRecordRawObject(record))
18237 continue;
18238 if (nativeRasterVariablesHandles.count(record.handle) != 0 &&
18239 isRasterVariablesRawObject(record))
18240 continue;
18241 if (nativeWipeoutVariablesHandles.count(record.handle) != 0 &&
18242 isWipeoutVariablesRawObject(record))
18243 continue;
18244 if (nativeGeoDataHandles.count(record.handle) != 0 &&
18245 isGeoDataRawObject(record))
18246 continue;
18247 if (nativeSpatialFilterHandles.count(record.handle) != 0 &&
18248 isSpatialFilterRawObject(record))
18249 continue;
18250 // PR 8d.2a — five small no-storage OBJECTS families.
18251 if (nativeScaleHandles.count(record.handle) != 0 &&
18252 isScaleRawObject(record))
18253 continue;
18254 if (nativeIDBufferHandles.count(record.handle) != 0 &&
18255 isIDBufferRawObject(record))
18256 continue;
18257 if (nativeLayerIndexHandles.count(record.handle) != 0 &&
18258 isLayerIndexRawObject(record))
18259 continue;
18260 if (nativeSpatialIndexHandles.count(record.handle) != 0 &&
18261 isSpatialIndexRawObject(record))
18262 continue;
18263 if (nativeDictionaryVarHandles.count(record.handle) != 0 &&
18264 isDictionaryVarRawObject(record))
18265 continue;
18266 // PR 8d.2b — four larger no-storage OBJECTS families.
18267 if (nativeDictionaryWithDefaultHandles.count(record.handle) != 0 &&
18268 isDictionaryWithDefaultRawObject(record))
18269 continue;
18270 if (nativeSortEntsTableHandles.count(record.handle) != 0 &&
18271 isSortEntsTableRawObject(record))
18272 continue;
18273 if (nativeFieldListHandles.count(record.handle) != 0 &&
18274 isFieldListRawObject(record))
18275 continue;
18276 if (nativeFieldHandles.count(record.handle) != 0 &&
18277 isFieldRawObject(record))
18278 continue;
18279 if (nativeUnderlayDefinitionHandles.count(record.handle) != 0 &&
18280 isUnderlayDefinitionRawObject(record))
18281 continue;
18282 if (nativePointCloudDefinitionHandles.count(record.handle) != 0 &&
18283 isPointCloudDefinitionRawObject(record))
18284 continue;
18285 if (nativeNavisworksModelDefinitionHandles.count(record.handle) != 0 &&
18286 isNavisworksModelDefinitionRawObject(record))
18287 continue;
18288 if (nativePointCloudColorMapHandles.count(record.handle) != 0 &&
18289 isPointCloudColorMapRawObject(record))
18290 continue;
18291 if (nativeDimensionAssociationHandles.count(record.handle) != 0 &&
18292 isDimensionAssociationRawObject(record))
18293 continue;
18294 if (nativeEvaluationGraphHandles.count(record.handle) != 0 &&
18295 isEvaluationGraphRawObject(record))
18296 continue;
18297 if (nativeBlockRepresentationHandles.count(record.handle) != 0 &&
18298 isBlockRepresentationDataRawObject(record))
18299 continue;
18300 if (nativeSectionHandles.count(record.handle) != 0 &&
18301 isSectionRawObject(record))
18302 continue;
18303 DRW_UnsupportedObject object = rawObjectFromMetadata(record);
18304 if (!registerRawClass(object, record))
18305 m_writeFailed = true;
18306 }
18307 m_dwgFieldWritePlan.classesAdmitted = !m_writeFailed;
18308 if (!validateDwgDataStorageAdmissionSummary(
18309 /*requireClassAdmission=*/true)) {
18310 m_writeFailed = true;
18311 RS_DEBUGRS_Debug::instance()->print(
18312 RS_Debug::D_WARNING,
18313 "RS_FilterDXFRW: DataStorage admission diverged before CLASSES");
18314 }
18315 if (m_dwgPostClassesTestHook)
18316 m_dwgPostClassesTestHook();
18317 // CLASSES is the next low-level section. A preflight/class admission
18318 // failure must stop the writer before it serializes any file section.
18319 if (m_writeFailed)
18320 m_dwgW->markDwgWriteFailure();
18321#endif
18322}
18323
18324void RS_FilterDXFRW::writeLType(const UTF8STRINGstd::string &lTypeName,
18325 const UTF8STRINGstd::string &ltDescription, int ltSize,
18326 double ltLength,
18327 const std::vector<double> &ltPath) {
18328 DRW_LType ltype;
18329 ltype.updateValues(lTypeName, ltDescription, ltSize, ltLength, ltPath);
18330 if (const auto *source =
18331 m_graphic->dwgAdvancedMetadata().findLineTypeTableEntryByName(
18332 lTypeName)) {
18333 ltype = *source;
18334 }
18335 m_builtinLTypeNames.insert(normalizeDwgTableName(lTypeName));
18336 (void)writeLTypeRecord(ltype);
18337}
18338
18339bool RS_FilterDXFRW::writeLTypeRecord(DRW_LType &ltype) {
18340 if (m_dwgW) {
18341 if (hasDxfTableAppDataUnsupportedByDwg(ltype)) {
18342 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
18343 "RS_FilterDXFRW: cannot convert DXF LTYPE "
18344 "application data to DWG");
18345 m_writeFailed = true;
18346 return false;
18347 }
18348 const std::uint32_t sourceHandle = ltype.handle;
18349 if (m_dwgW->addLType(&ltype) && ltype.handle != 0 &&
18350 stageDwgTableRecordIdentity(RS_FilterDXFRW_DwgWriteIdentityRegistry::
18351 DwgWriteSourceKind::TableRecord,
18352 sourceHandle, ltype.handle)) {
18353 m_dwgWriteLTypeHandleByName[normalizeDwgTableName(ltype.name)] =
18354 ltype.handle;
18355 return true;
18356 }
18357 m_writeFailed = true;
18358 return false;
18359 }
18360 return noteDxfWrite(m_dxfW->writeLineType(&ltype));
18361}
18362
18363void RS_FilterDXFRW::writeLTypes() {
18364 m_builtinLTypeNames.clear();
18365 writeLType("CONTINUOUS", "Solid line", 0, 0, {});
18366 writeLType("ByLayer", "", 0, 0, {});
18367 writeLType("ByBlock", "", 0, 0, {});
18368 writeLType("DOT", "Dot . . . . . . . . . . . . . . . . . . . . . .", 2, 6.35,
18369 {0.0, -6.35});
18370 writeLType("DOTTINY", "Dot (.15x) .....................................", 2,
18371 0.9525, {0.0, -0.9525});
18372 writeLType("DOT2", "Dot (.5x) .....................................", 2,
18373 3.175, {0.0, -3.175});
18374 writeLType("DOTX2", "Dot (2x) . . . . . . . . . . . . .", 2, 12.7,
18375 {0.0, -12.7});
18376 writeLType("DASHED", "Dashed _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _", 2, 19.05,
18377 {12.7, -6.35});
18378 writeLType("DASHEDTINY", "Dashed (.15x) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _", 2,
18379 2.8575, {1.905, -0.9525});
18380 writeLType("DASHED2", "Dashed (.5x) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _", 2,
18381 9.525, {6.35, -3.175});
18382 writeLType("DASHEDX2", "Dashed (2x) ____ ____ ____ ____ ____ ___", 2,
18383 38.1, {25.4, -12.7});
18384 // acad.lin: HIDDEN A,.25,-.125 / HIDDEN2 A,.125,-.0625 / HIDDENX2 A,.5,-.25
18385 writeLType("HIDDEN", "Hidden __ __ __ __ __ __ __ __ __ __ __ __ __ __", 2,
18386 9.525, {6.35, -3.175});
18387 writeLType("HIDDENTINY", "Hidden (.15x) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _", 2,
18388 1.42875, {0.9525, -0.47625});
18389 writeLType("HIDDEN2", "Hidden (.5x) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _", 2,
18390 4.7625, {3.175, -1.5875});
18391 writeLType("HIDDENX2", "Hidden (2x) ____ ____ ____ ____ ____ ____ ____", 2,
18392 19.05, {12.7, -6.35});
18393 writeLType("DASHDOT", "Dash dot __ . __ . __ . __ . __ . __ . __ . __", 4,
18394 25.4, {12.7, -6.35, 0.0, -6.35});
18395 writeLType("DASHDOTTINY", "Dash dot (.15x) _._._._._._._._._._._._._._._.", 4,
18396 3.81, {1.905, -0.9525, 0.0, -0.9525});
18397 writeLType("DASHDOT2", "Dash dot (.5x) _._._._._._._._._._._._._._._.", 4,
18398 12.7, {6.35, -3.175, 0.0, -3.175});
18399 writeLType("DASHDOTX2", "Dash dot (2x) ____ . ____ . ____ . ___", 4,
18400 50.8, {25.4, -12.7, 0.0, -12.7});
18401 writeLType("DIVIDE", "Divide ____ . . ____ . . ____ . . ____ . . ____", 6,
18402 31.75, {12.7, -6.35, 0.0, -6.35, 0.0, -6.35});
18403 writeLType("DIVIDETINY", "Divide (.15x) __..__..__..__..__..__..__..__.._", 6,
18404 4.7625, {1.905, -0.9525, 0.0, -0.9525, 0.0, -0.9525});
18405 writeLType("DIVIDE2", "Divide (.5x) __..__..__..__..__..__..__..__.._", 6,
18406 15.875, {6.35, -3.175, 0.0, -3.175, 0.0, -3.175});
18407 writeLType("DIVIDEX2", "Divide (2x) ________ . . ________ . . _", 6,
18408 63.5, {25.4, -12.7, 0.0, -12.7, 0.0, -12.7});
18409 writeLType("BORDER", "Border __ __ . __ __ . __ __ . __ __ . __ __ .", 6,
18410 44.45, {12.7, -6.35, 12.7, -6.35, 0.0, -6.35});
18411 writeLType("BORDERTINY", "Border (.15x) __.__.__.__.__.__.__.__.__.__.__.", 6,
18412 6.6675, {1.905, -0.9525, 1.905, -0.9525, 0.0, -0.9525});
18413 writeLType("BORDER2", "Border (.5x) __.__.__.__.__.__.__.__.__.__.__.", 6,
18414 22.225, {6.35, -3.175, 6.35, -3.175, 0.0, -3.175});
18415 writeLType("BORDERX2", "Border (2x) ____ ____ . ____ ____ . ___", 6,
18416 88.9, {25.4, -12.7, 25.4, -12.7, 0.0, -12.7});
18417 writeLType("CENTER", "Center ____ _ ____ _ ____ _ ____ _ ____ _ ____", 4,
18418 50.8, {31.75, -6.35, 6.35, -6.35});
18419 writeLType("CENTERTINY", "Center (.15x) ___ _ ___ _ ___ _ ___ _ ___ _ ___", 4,
18420 7.62, {4.7625, -0.9525, 0.9525, -0.9525});
18421 writeLType("CENTER2", "Center (.5x) ___ _ ___ _ ___ _ ___ _ ___ _ ___", 4,
18422 28.575, {19.05, -3.175, 3.175, -3.175});
18423 writeLType("CENTERX2", "Center (2x) ________ __ ________ __ _____", 4,
18424 101.6, {63.5, -12.7, 12.7, -12.7});
18425 // acad.lin: PHANTOM A,1.25,-.25,.25,-.25,.25,-.25; PHANTOM2 and PHANTOMX2
18426 // are its .5x and 2x.
18427 writeLType("PHANTOM", "Phantom ______ __ __ ______ __ __ ______", 6,
18428 63.5, {31.75, -6.35, 6.35, -6.35, 6.35, -6.35});
18429 writeLType("PHANTOMTINY", "Phantom (.15x) ___ _ _ ___ _ _ ___ _ _ ___ _ _", 6,
18430 9.525, {4.7625, -0.9525, 0.9525, -0.9525, 0.9525, -0.9525});
18431 writeLType("PHANTOM2", "Phantom (.5x) ___ _ _ ___ _ _ ___ _ _ ___ _ _", 6,
18432 31.75, {15.875, -3.175, 3.175, -3.175, 3.175, -3.175});
18433 writeLType("PHANTOMX2", "Phantom (2x) ____________ ____ ____ _", 6,
18434 127.0, {63.5, -12.7, 12.7, -12.7, 12.7, -12.7});
18435 // Imported LTYPE records that are not part of the built-in table above are
18436 // re-emitted as they came in; writeLType() records the built-in names so a
18437 // new built-in type cannot end up written twice.
18438 std::set<std::string> emittedNames = m_builtinLTypeNames;
18439 for (const auto &entry :
18440 m_graphic->dwgAdvancedMetadata().lineTypeTableEntries()) {
18441 if (!emittedNames.insert(normalizeDwgTableName(entry.first)).second)
18442 continue;
18443 DRW_LType ltype = entry.second;
18444 (void)writeLTypeRecord(ltype);
18445 }
18446}
18447
18448void RS_FilterDXFRW::writeLayers() {
18449 DRW_Layer lay;
18450 RS_LayerList *ll = m_graphic->getLayerList();
18451 int exact_rgb;
18452 for (unsigned int i = 0; i < ll->count(); i++) {
18453 lay.reset();
18454 RS_Layer *l = ll->at(i);
18455 RS_Pen pen = l->getPen();
18456 lay.name = l->getName().toUtf8().data();
18457 lay.color = colorToNumber(pen.getColor(), &exact_rgb);
18458 lay.color24 = exact_rgb;
18459 lay.lWeight = widthToNumber(pen.getWidth());
18460 lay.lineType = lineTypeToName(pen.getLineType()).toStdString();
18461 lay.flags = l->isFrozen() ? 0x01 : 0x00;
18462 if (l->isLocked()) {
18463 lay.flags |= 0x04;
18464 }
18465 lay.plotF = l->isPrint();
18466 if (const auto *sourceLayer =
18467 m_graphic->dwgAdvancedMetadata().findLayerTableEntryByName(
18468 lay.name)) {
18469 if (m_dwgW != nullptr &&
18470 hasDxfTableAppDataUnsupportedByDwg(*sourceLayer)) {
18471 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
18472 "RS_FilterDXFRW: cannot convert DXF LAYER "
18473 "application data to DWG");
18474 m_writeFailed = true;
18475 continue;
18476 }
18477 if (!restoreTableEntryExtData(lay, sourceLayer->extData)) {
18478 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
18479 "RS_FilterDXFRW: cannot restore DXF LAYER "
18480 "XDATA");
18481 m_writeFailed = true;
18482 continue;
18483 }
18484 lay.appData = sourceLayer->appData;
18485 lay.reactorHandles = sourceLayer->reactorHandles;
18486 lay.xDictHandle = sourceLayer->xDictHandle;
18487 lay.handle = sourceLayer->handle;
18488 }
18489 if (l->isConstruction() && classifyLayerConstructionMarker(lay.extData) ==
18490 LayerConstructionMarkerStatus::Absent) {
18491 try {
18492 if (!lay.addExtData(std::make_unique<DRW_Variant>(1001, "LibreCad")) ||
18493 !lay.addExtData(std::make_unique<DRW_Variant>(1070, 1))) {
18494 m_writeFailed = true;
18495 }
18496 } catch (...) {
18497 m_writeFailed = true;
18498 }
18499 // RS_DEBUG->print(RS_Debug::D_WARNING, "RS_FilterDXF::writeLayers: layer
18500 // %s saved as construction layer", lay.name.c_str());
18501 }
18502 if (m_dwgW) {
18503 const std::uint32_t sourceHandle = lay.handle;
18504 if (!m_dwgW->addLayer(&lay) ||
18505 !stageDwgTableRecordIdentity(DwgWriteSourceKind::TableRecord,
18506 sourceHandle, lay.handle))
18507 m_writeFailed = true;
18508 } else {
18509 noteDxfWrite(m_dxfW->writeLayer(&lay));
18510 }
18511 }
18512}
18513
18514void RS_FilterDXFRW::writeUCSs() {
18515 LC_UCSList *vl = m_graphic->getUCSList();
18516 DRW_UCS ucs;
18517 for (unsigned int i = 1; i < vl->count(); i++) {
18518 ucs.reset();
18519
18520 const LC_UCS *u = vl->at(i);
18521 if (u->isTemporary()) {
18522 // temporary ucs without name are not persistent
18523 continue;
18524 }
18525 ucs.name = u->getName().toUtf8().data();
18526 ucs.origin.x = u->getOrigin().x;
18527 ucs.origin.y = u->getOrigin().y;
18528 ucs.origin.z = u->getOrigin().z;
18529
18530 ucs.xAxisDirection.x = u->getXAxis().x;
18531 ucs.xAxisDirection.y = u->getXAxis().y;
18532 ucs.xAxisDirection.z = u->getXAxis().z;
18533
18534 ucs.yAxisDirection.x = u->getYAxis().x;
18535 ucs.yAxisDirection.y = u->getYAxis().y;
18536 ucs.yAxisDirection.z = u->getYAxis().z;
18537
18538 ucs.orthoOrigin.x = u->getOrthoOrigin().x;
18539 ucs.orthoOrigin.y = u->getOrthoOrigin().y;
18540 ucs.orthoOrigin.z = u->getOrthoOrigin().z;
18541
18542 ucs.orthoType = u->getOrthoType();
18543 ucs.elevation = u->getElevation();
18544
18545 const auto *ucsMetadata =
18546 m_graphic->dwgAdvancedMetadata().findUcsByName(ucs.name);
18547 if (ucsMetadata != nullptr) {
18548 if (m_dwgW != nullptr &&
18549 (hasCustomDxfApplicationGroups(ucsMetadata->appData) ||
18550 !ucsMetadata->extData.empty())) {
18551 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
18552 "RS_FilterDXFRW: cannot convert DXF UCS "
18553 "application data or XDATA to DWG");
18554 m_writeFailed = true;
18555 continue;
18556 }
18557 if (!restoreTableEntryExtData(ucs, ucsMetadata->extData)) {
18558 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
18559 "RS_FilterDXFRW: cannot restore DXF UCS "
18560 "XDATA");
18561 m_writeFailed = true;
18562 continue;
18563 }
18564 ucs.appData = ucsMetadata->appData;
18565 ucs.reactorHandles = ucsMetadata->reactorHandles;
18566 ucs.xDictHandle = ucsMetadata->xDictHandle;
18567 }
18568
18569 if (m_dwgW) {
18570 if (ucsMetadata != nullptr)
18571 ucs.handle = ucsMetadata->handle;
18572 const std::uint32_t sourceHandle = ucs.handle;
18573 if (!m_dwgW->addUCS(&ucs)) {
18574 m_writeFailed = true;
18575 continue;
18576 }
18577 if (sourceHandle != 0 && ucs.handle != 0) {
18578 const auto [it, inserted] =
18579 m_dwgWriteUcsHandleRemap.emplace(sourceHandle, ucs.handle);
18580 if (!inserted && it->second != ucs.handle) {
18581 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
18582 "RS_FilterDXFRW: duplicate DWG UCS handle 0x%X",
18583 sourceHandle);
18584 m_writeFailed = true;
18585 }
18586 }
18587 if (!stageDwgTableRecordIdentity(DwgWriteSourceKind::TableRecord,
18588 sourceHandle, ucs.handle))
18589 m_writeFailed = true;
18590 } else {
18591 noteDxfWrite(m_dxfW->writeUCS(&ucs));
18592 }
18593 }
18594}
18595
18596void RS_FilterDXFRW::writeViews() {
18597 LC_ViewList *vl = m_graphic->getViewList();
18598 DRW_View vie;
18599#ifdef DWGSUPPORT1
18600 struct PendingOptionalDrop {
18601 std::uint32_t targetSourceHandle = 0;
18602 DwgWriteReferenceStatus targetStatus = DwgWriteReferenceStatus::Missing;
18603 DwgWriteSourceKind targetKind = DwgWriteSourceKind::Object;
18604 const char *referenceName = nullptr;
18605 std::uint32_t referenceOrdinal = 0;
18606 const char *reason = nullptr;
18607 };
18608 const auto isDropped = [](DwgWriteReferenceStatus status) {
18609 return status == DwgWriteReferenceStatus::Missing ||
18610 status == DwgWriteReferenceStatus::Ambiguous ||
18611 status == DwgWriteReferenceStatus::Suppressed;
18612 };
18613 auto remapDwgObjectReference = [this, &isDropped](
18614 std::uint32_t sourceHandle,
18615 const char *referenceKind,
18616 std::vector<PendingOptionalDrop> &drops) {
18617 std::uint32_t outputHandle = 0;
18618 const DwgWriteReferenceStatus status =
18619 resolveDwgWriteObjectHandle(sourceHandle, &outputHandle);
18620 if (isDropped(status)) {
18621 RS_DEBUGRS_Debug::instance()->print(
18622 RS_Debug::D_WARNING,
18623 status == DwgWriteReferenceStatus::Ambiguous
18624 ? "RS_FilterDXFRW: dropping ambiguous DWG %s target 0x%X"
18625 : "RS_FilterDXFRW: dropping dangling DWG %s target 0x%X",
18626 referenceKind, sourceHandle);
18627 drops.push_back(
18628 {sourceHandle, status, DwgWriteSourceKind::Object, referenceKind, 0,
18629 status == DwgWriteReferenceStatus::Ambiguous ? "ambiguous target"
18630 : "missing target"});
18631 return std::uint32_t{0};
18632 }
18633 return outputHandle;
18634 };
18635 auto remapDwgUcsReference =
18636 [this, &isDropped](std::uint32_t sourceHandle, const char *referenceKind,
18637 std::vector<PendingOptionalDrop> &drops) {
18638 std::uint32_t outputHandle = 0;
18639 const DwgWriteReferenceStatus status = resolveDwgWriteTableRecordHandle(
18640 sourceHandle, m_dwgWriteUcsHandleRemap, &outputHandle);
18641 if (status == DwgWriteReferenceStatus::Reserved) {
18642 return outputHandle;
18643 }
18644 if (isDropped(status)) {
18645 RS_DEBUGRS_Debug::instance()->print(
18646 RS_Debug::D_WARNING,
18647 status == DwgWriteReferenceStatus::Ambiguous
18648 ? "RS_FilterDXFRW: dropping ambiguous DWG %s target 0x%X"
18649 : "RS_FilterDXFRW: dropping dangling DWG %s target 0x%X",
18650 referenceKind, sourceHandle);
18651 drops.push_back({sourceHandle, status,
18652 DwgWriteSourceKind::TableRecord, referenceKind, 0,
18653 status == DwgWriteReferenceStatus::Ambiguous
18654 ? "ambiguous target"
18655 : "missing target"});
18656 return std::uint32_t{0};
18657 }
18658 return outputHandle;
18659 };
18660#endif
18661 for (unsigned int i = 0; i < vl->count(); i++) {
18662 vie.reset();
18663#ifdef DWGSUPPORT1
18664 std::vector<PendingOptionalDrop> optionalDrops;
18665#endif
18666 LC_View *view = vl->at(i);
18667 vie.name = view->getName().toUtf8().data();
18668 vie.center.x = view->getCenter().x;
18669 vie.center.y = view->getCenter().y;
18670 vie.center.z = view->getCenter().z;
18671
18672 vie.targetPoint.x = view->getTargetPoint().x;
18673 vie.targetPoint.y = view->getTargetPoint().y;
18674 vie.targetPoint.z = view->getTargetPoint().z;
18675
18676 vie.size.x = view->getSize().x;
18677 vie.size.y = view->getSize().y;
18678 vie.size.z = view->getSize().z;
18679
18680 vie.frontClippingPlaneOffset = view->getFrontClippingPlaneOffset();
18681 vie.backClippingPlaneOffset = view->getBackClippingPlaneOffset();
18682 vie.lensLen = view->getLensLen();
18683 vie.flags = view->getFlags();
18684 vie.viewMode = view->getViewMode();
18685
18686 vie.viewDirectionFromTarget.x = view->getViewDirection().x;
18687 vie.viewDirectionFromTarget.y = view->getViewDirection().y;
18688 vie.viewDirectionFromTarget.z = view->getViewDirection().z;
18689
18690 vie.cameraPlottable = view->isCameraPlottable();
18691 vie.renderMode = view->getRenderMode();
18692
18693 vie.twistAngle = view->getTwistAngle();
18694
18695 if (view->isHasUCS()) {
18696 vie.hasUCS = true;
18697 LC_UCS *ucs = view->getUCS();
18698 vie.ucsOrigin.x = ucs->getOrigin().x;
18699 vie.ucsOrigin.y = ucs->getOrigin().y;
18700 vie.ucsOrigin.z = ucs->getOrigin().z;
18701
18702 vie.ucsOrthoType = ucs->getOrthoType();
18703 vie.ucsElevation = ucs->getElevation();
18704
18705 vie.ucsXAxis.x = ucs->getXAxis().x;
18706 vie.ucsXAxis.y = ucs->getXAxis().y;
18707 vie.ucsXAxis.z = ucs->getXAxis().z;
18708
18709 vie.ucsYAxis.x = ucs->getYAxis().x;
18710 vie.ucsYAxis.y = ucs->getYAxis().y;
18711 vie.ucsYAxis.z = ucs->getYAxis().z;
18712
18713 // fixme - complete - base UCS_ID and Named UCS_ID support. That's might
18714 // be necessary to support views/UCS created outside of LibreCAD. Return
18715 // to this after normal support of UCS.
18716 // vie.namedUCS_ID = ucs.
18717 // vie.baseUCS_ID = ucs.
18718 }
18719 const auto *viewMetadata =
18720 m_graphic->dwgAdvancedMetadata().findViewByName(vie.name);
18721 if (viewMetadata) {
18722 if (m_dwgW != nullptr &&
18723 (hasCustomDxfApplicationGroups(viewMetadata->appData) ||
18724 !viewMetadata->extData.empty())) {
18725 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
18726 "RS_FilterDXFRW: cannot convert DXF VIEW "
18727 "application data or XDATA to DWG");
18728 m_writeFailed = true;
18729 continue;
18730 }
18731 if (!restoreTableEntryExtData(vie, viewMetadata->extData)) {
18732 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
18733 "RS_FilterDXFRW: cannot restore DXF VIEW "
18734 "XDATA");
18735 m_writeFailed = true;
18736 continue;
18737 }
18738 vie.handle = viewMetadata->handle;
18739 vie.appData = viewMetadata->appData;
18740 vie.reactorHandles = viewMetadata->reactorHandles;
18741 vie.xDictHandle = viewMetadata->xDictHandle;
18742 vie.namedUCS_ID = viewMetadata->namedUcsHandle;
18743 vie.baseUCS_ID = viewMetadata->baseUcsHandle;
18744 vie.m_useDefaultLights = viewMetadata->useDefaultLights;
18745 vie.m_defaultLightingType = viewMetadata->defaultLightingType;
18746 vie.m_brightness = viewMetadata->brightness;
18747 vie.m_contrast = viewMetadata->contrast;
18748 vie.m_ambientColor = viewMetadata->ambientColor;
18749 vie.m_backgroundHandle = viewMetadata->backgroundHandle;
18750 vie.m_visualStyleHandle = viewMetadata->visualStyleHandle;
18751 vie.m_sunHandle = viewMetadata->sunHandle;
18752 vie.m_liveSectionHandle = viewMetadata->liveSectionHandle;
18753#ifdef DWGSUPPORT1
18754 if (m_dwgW != nullptr) {
18755 vie.namedUCS_ID = remapDwgUcsReference(vie.namedUCS_ID,
18756 "VIEW named UCS", optionalDrops);
18757 vie.baseUCS_ID = remapDwgUcsReference(vie.baseUCS_ID, "VIEW base UCS",
18758 optionalDrops);
18759 vie.m_backgroundHandle = remapDwgObjectReference(
18760 vie.m_backgroundHandle, "VIEW background", optionalDrops);
18761 vie.m_visualStyleHandle = remapDwgObjectReference(
18762 vie.m_visualStyleHandle, "VIEW visual style", optionalDrops);
18763 vie.m_sunHandle =
18764 remapDwgObjectReference(vie.m_sunHandle, "VIEW sun", optionalDrops);
18765 vie.m_liveSectionHandle = remapDwgObjectReference(
18766 vie.m_liveSectionHandle, "VIEW live section", optionalDrops);
18767 }
18768#endif
18769 }
18770 if (m_dwgW != nullptr) {
18771 const std::uint32_t sourceHandle = vie.handle;
18772 if (!m_dwgW->addView(&vie)) {
18773 m_writeFailed = true;
18774 continue;
18775 }
18776 if (sourceHandle != 0 && vie.handle != 0) {
18777 const auto [it, inserted] =
18778 m_dwgWriteViewHandleRemap.emplace(sourceHandle, vie.handle);
18779 if (!inserted && it->second != vie.handle) {
18780 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
18781 "RS_FilterDXFRW: duplicate DWG VIEW handle 0x%X",
18782 sourceHandle);
18783 m_writeFailed = true;
18784 }
18785 }
18786 if (!stageDwgTableRecordIdentity(DwgWriteSourceKind::TableRecord,
18787 sourceHandle, vie.handle)) {
18788 m_writeFailed = true;
18789 } else {
18790 for (const PendingOptionalDrop &drop : optionalDrops) {
18791 if (!recordDwgWriteOptionalDrop(
18792 DwgWriteSourceKind::TableRecord, sourceHandle, vie.handle,
18793 drop.referenceName, drop.referenceOrdinal, drop.targetKind,
18794 drop.targetSourceHandle, drop.targetStatus,
18795 m_dwgW->getVersion(), "DWG VIEWS", drop.reason)) {
18796 m_writeFailed = true;
18797 break;
18798 }
18799 }
18800 }
18801 } else {
18802 noteDxfWrite(m_dxfW->writeView(&vie));
18803 }
18804 }
18805}
18806
18807void RS_FilterDXFRW::writeTextstyles() {
18808 QHash<QString, QString> styles;
18809 QString sty;
18810 // Find fonts used by text entities in drawing
18811 for (RS_Entity *e :
18812 lc::LC_ContainerTraverser{*m_graphic, RS2::ResolveNone}.entities()) {
18813 if (!e->isDeleted()) {
18814 auto rtti = e->rtti();
18815 switch (rtti) {
18816 case RS2::EntityMText:
18817 sty = static_cast<RS_MText *>(e)->getStyle();
18818 break;
18819 case RS2::EntityText:
18820 sty = static_cast<RS_Text *>(e)->getStyle();
18821 break;
18822 default:
18823 if (RS2::isDimensionalEntity(rtti)) {
18824 auto dim = dynamic_cast<RS_Dimension *>(e);
18825 if (dim != nullptr) {
18826 auto styleOverride = dim->getDimStyleOverride();
18827 if (styleOverride != nullptr) {
18828 auto dimTextStyle = styleOverride->text()->style();
18829 sty = dimTextStyle;
18830 }
18831 }
18832 } else {
18833 sty.clear();
18834 }
18835 break;
18836 }
18837 if (!sty.isEmpty() && !styles.contains(sty)) {
18838 styles.insert(sty, sty);
18839 }
18840 }
18841 }
18842 // Find fonts used by text entities in blocks
18843 RS_Block *blk;
18844 for (unsigned i = 0; i < m_graphic->countBlocks(); i++) {
18845 blk = m_graphic->blockAt(i);
18846 for (RS_Entity *e :
18847 lc::LC_ContainerTraverser{*blk, RS2::ResolveNone}.entities()) {
18848 if (!e->isDeleted()) {
18849 RS2::EntityType rtti = e->rtti();
18850 switch (rtti) {
18851 case RS2::EntityMText:
18852 sty = static_cast<RS_MText *>(e)->getStyle();
18853 break;
18854 case RS2::EntityText:
18855 sty = static_cast<RS_Text *>(e)->getStyle();
18856 break;
18857 default:
18858 if (RS2::isDimensionalEntity(rtti)) {
18859 auto dim = dynamic_cast<RS_Dimension *>(e);
18860 if (dim != nullptr) {
18861 auto styleOverride = dim->getDimStyleOverride();
18862 if (styleOverride != nullptr) {
18863 auto dimTextStyle = styleOverride->text()->style();
18864 sty = dimTextStyle;
18865 }
18866 }
18867 } else {
18868 sty.clear();
18869 }
18870
18871 break;
18872 }
18873 if (!sty.isEmpty() && !styles.contains(sty)) {
18874 styles.insert(sty, sty);
18875 }
18876 }
18877 }
18878 }
18879
18880 auto dimStyleList = m_graphic->getDimStyleList();
18881
18882 for (const auto ds : *dimStyleList->getStylesList()) {
18883 sty = ds->text()->style();
18884 if (!sty.isEmpty() && !styles.contains(sty)) {
18885 styles.insert(sty, sty);
18886 }
18887 }
18888
18889 const auto &metadata = m_graphic->dwgAdvancedMetadata();
18890 std::set<std::string> emittedNames;
18891 DRW_Textstyle ts;
18892 if (const auto *source = metadata.findTextStyleTableEntryByName("STANDARD")) {
18893 ts = *source;
18894 } else {
18895 ts.name = "STANDARD";
18896 ts.font = "txt";
18897 }
18898 emittedNames.insert(normalizeDwgTableName(ts.name));
18899 (void)writeTextStyleRecord(ts);
18900
18901 for (const auto &entry : metadata.textStyleTableEntries()) {
18902 if (!emittedNames.insert(normalizeDwgTableName(entry.first)).second)
18903 continue;
18904 ts = entry.second;
18905 (void)writeTextStyleRecord(ts);
18906 }
18907 QHash<QString, QString>::const_iterator it = styles.constBegin();
18908 while (it != styles.constEnd()) {
18909 if (!emittedNames.insert(normalizeDwgTableName(it.key().toStdString()))
18910 .second) {
18911 ++it;
18912 continue;
18913 }
18914 ts.reset();
18915 ts.name = (it.key()).toStdString();
18916 ts.font = it.value().toStdString();
18917 (void)writeTextStyleRecord(ts);
18918 ++it;
18919 }
18920}
18921
18922bool RS_FilterDXFRW::writeTextStyleRecord(DRW_Textstyle &textStyle) {
18923 if (m_dwgW != nullptr) {
18924 if (hasDxfTableAppDataUnsupportedByDwg(textStyle)) {
18925 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
18926 "RS_FilterDXFRW: cannot convert DXF STYLE "
18927 "application data to DWG");
18928 m_writeFailed = true;
18929 return false;
18930 }
18931 const std::uint32_t sourceHandle = textStyle.handle;
18932 if (m_dwgW->addTextstyle(&textStyle) && textStyle.handle != 0 &&
18933 stageDwgTableRecordIdentity(RS_FilterDXFRW_DwgWriteIdentityRegistry::
18934 DwgWriteSourceKind::TableRecord,
18935 sourceHandle, textStyle.handle)) {
18936 m_dwgWriteTextStyleHandleByName[normalizeDwgTableName(textStyle.name)] =
18937 textStyle.handle;
18938 return true;
18939 }
18940 m_writeFailed = true;
18941 return false;
18942 }
18943 return noteDxfWrite(m_dxfW->writeTextstyle(&textStyle));
18944}
18945
18946void RS_FilterDXFRW::writeVports() {
18947 DRW_Vport vp;
18948 vp.name = "*Active";
18949 m_graphic->isGridOn() ? vp.grid = 1 : vp.grid = 0;
18950 RS_Vector spacing =
18951 m_graphic->getVariableVector("$GRIDUNIT", RS_Vector(0.0, 0.0));
18952 vp.gridBehavior = 3;
18953 vp.gridSpacing.x = spacing.x;
18954 vp.gridSpacing.y = spacing.y;
18955 vp.snapStyle = m_graphic->isIsometricGrid();
18956 vp.snapIsopair = m_graphic->getIsoView();
18957 if (vp.snapIsopair > 2) {
18958 vp.snapIsopair = 0;
18959 }
18960 if (fabs(spacing.x) < 1.0e-6) {
18961 vp.gridBehavior = 7; // auto
18962 vp.gridSpacing.x = 10;
18963 }
18964 if (fabs(spacing.y) < 1.0e-6) {
18965 vp.gridBehavior = 7; // auto
18966 vp.gridSpacing.y = 10;
18967 }
18968 RS_GraphicView *gv = m_graphic->getGraphicView();
18969 if (gv) {
18970 LC_GraphicViewport *viewport = gv->getViewPort();
18971 RS_Vector fac = viewport->getFactor();
18972 vp.height = gv->getHeight() / fac.y;
18973 vp.ratio = (double)gv->getWidth() / (double)gv->getHeight();
18974 vp.center.x = (gv->getWidth() - viewport->getOffsetX()) / (fac.x * 2.0);
18975 vp.center.y = (gv->getHeight() - viewport->getOffsetY()) / (fac.y * 2.0);
18976 }
18977 const auto &metadata = m_graphic->dwgAdvancedMetadata();
18978 if (const auto *activeVport = metadata.findVportTableEntryByName(vp.name)) {
18979 const bool hasUnsupportedAppData =
18980 hasCustomDxfApplicationGroups(activeVport->appData);
18981 if (m_dwgW != nullptr && hasUnsupportedAppData) {
18982 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
18983 "RS_FilterDXFRW: dropping unsupported DXF VPORT "
18984 "application data while writing DWG");
18985 }
18986 if (!restoreTableEntryExtData(vp, activeVport->extData)) {
18987 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
18988 "RS_FilterDXFRW: cannot restore DXF VPORT "
18989 "XDATA");
18990 m_writeFailed = true;
18991 return;
18992 }
18993 if (!hasUnsupportedAppData)
18994 vp.appData = activeVport->appData;
18995 vp.reactorHandles = activeVport->reactorHandles;
18996 vp.xDictHandle = activeVport->xDictHandle;
18997 vp.handle = activeVport->handle;
18998 }
18999 if (m_dwgW) {
19000 struct PendingOptionalDrop {
19001 std::uint32_t targetSourceHandle = 0;
19002 DwgWriteReferenceStatus targetStatus = DwgWriteReferenceStatus::Missing;
19003 DwgWriteSourceKind targetKind = DwgWriteSourceKind::Object;
19004 const char *referenceName = nullptr;
19005 std::uint32_t referenceOrdinal = 0;
19006 const char *reason = nullptr;
19007 };
19008 const auto isDropped = [](DwgWriteReferenceStatus status) {
19009 return status == DwgWriteReferenceStatus::Missing ||
19010 status == DwgWriteReferenceStatus::Ambiguous ||
19011 status == DwgWriteReferenceStatus::Suppressed;
19012 };
19013 const auto dropReason = [](DwgWriteReferenceStatus status) {
19014 return status == DwgWriteReferenceStatus::Ambiguous ? "ambiguous target"
19015 : status == DwgWriteReferenceStatus::Suppressed
19016 ? "suppressed target"
19017 : "missing target";
19018 };
19019 const bool hasViewportEntityReferences = std::any_of(
19020 metadata.viewportEntityHeaders().cbegin(),
19021 metadata.viewportEntityHeaders().cend(), [](const auto &record) {
19022 return record.replayState ==
19023 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
19024 record.viewportEntityHandle != 0;
19025 });
19026 // Entity handles are allocated while blocks are written, but VPH
19027 // records are queued from this earlier table callback. Precompute the
19028 // entity remap only when a VPH actually refers to an entity, so the
19029 // reference is stable without changing ordinary save allocation.
19030 if (m_dwgW->getVersion() >= DRW::AC1015 && hasViewportEntityReferences) {
19031 prepareDwgEntityHandleMap();
19032 }
19033 const std::uint32_t activeSourceHandle = vp.handle;
19034 const bool importedActiveSource =
19035 activeSourceHandle != 0 &&
19036 activeSourceHandle != DRW_Vport::kDwgActiveHandle &&
19037 metadata.isImportedVportSource(activeSourceHandle, vp.name);
19038 const std::uint32_t sourceHandle = activeSourceHandle;
19039 if (!m_dwgW->addVport(&vp)) {
19040 m_writeFailed = true;
19041 return;
19042 }
19043 m_dwgWriteVportHandleRemap[DRW_Vport::kDwgActiveHandle] = vp.handle;
19044 if (sourceHandle != 0) {
19045 const auto [it, inserted] =
19046 m_dwgWriteVportHandleRemap.emplace(sourceHandle, vp.handle);
19047 if (!inserted && it->second != vp.handle) {
19048 RS_DEBUGRS_Debug::instance()->print(
19049 RS_Debug::D_WARNING,
19050 "RS_FilterDXFRW: duplicate DWG active VPORT handle 0x%X",
19051 sourceHandle);
19052 m_writeFailed = true;
19053 }
19054 }
19055 if (!stageDwgTableRecordIdentity(DwgWriteSourceKind::TableRecord,
19056 sourceHandle, vp.handle))
19057 m_writeFailed = true;
19058 auto remapHandle = [this](std::uint32_t handle) {
19059 const auto objectIt = m_dwgWriteHandleRemap.find(handle);
19060 if (objectIt != m_dwgWriteHandleRemap.end())
19061 return objectIt->second;
19062 const auto entityIt = m_dwgWriteEntityHandleRemap.find(handle);
19063 if (entityIt != m_dwgWriteEntityHandleRemap.end())
19064 return entityIt->second;
19065 return handle;
19066 };
19067 auto remapObjectReference = [this, &isDropped, &dropReason](
19068 std::uint32_t sourceHandle,
19069 const char *referenceKind,
19070 std::vector<PendingOptionalDrop> &drops,
19071 std::uint32_t referenceOrdinal = 0) {
19072 std::uint32_t outputHandle = 0;
19073 const DwgWriteReferenceStatus status =
19074 resolveDwgWriteObjectHandle(sourceHandle, &outputHandle);
19075 if (isDropped(status)) {
19076 RS_DEBUGRS_Debug::instance()->print(
19077 RS_Debug::D_WARNING,
19078 status == DwgWriteReferenceStatus::Ambiguous
19079 ? "RS_FilterDXFRW: dropping ambiguous DWG %s target "
19080 "0x%X"
19081 : "RS_FilterDXFRW: dropping dangling DWG %s target "
19082 "0x%X",
19083 referenceKind, sourceHandle);
19084 drops.push_back({sourceHandle, status, DwgWriteSourceKind::Object,
19085 referenceKind, referenceOrdinal, dropReason(status)});
19086 return std::uint32_t{0};
19087 }
19088 return outputHandle;
19089 };
19090 auto remapUcsReference = [this, &isDropped, &dropReason](
19091 std::uint32_t sourceHandle,
19092 const char *referenceKind,
19093 std::vector<PendingOptionalDrop> &drops) {
19094 std::uint32_t outputHandle = 0;
19095 const DwgWriteReferenceStatus status = resolveDwgWriteTableRecordHandle(
19096 sourceHandle, m_dwgWriteUcsHandleRemap, &outputHandle);
19097 if (status == DwgWriteReferenceStatus::Reserved)
19098 return outputHandle;
19099 if (isDropped(status)) {
19100 RS_DEBUGRS_Debug::instance()->print(
19101 RS_Debug::D_WARNING,
19102 status == DwgWriteReferenceStatus::Ambiguous
19103 ? "RS_FilterDXFRW: dropping ambiguous DWG %s target "
19104 "0x%X"
19105 : "RS_FilterDXFRW: dropping dangling DWG %s target "
19106 "0x%X",
19107 referenceKind, sourceHandle);
19108 drops.push_back({sourceHandle, status, DwgWriteSourceKind::TableRecord,
19109 referenceKind, 0, dropReason(status)});
19110 return std::uint32_t{0};
19111 }
19112 return outputHandle;
19113 };
19114 std::set<std::string> emittedVportNames;
19115 emittedVportNames.insert("*ACTIVE");
19116 for (const auto &record : metadata.vports()) {
19117 if (record.replayState !=
19118 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
19119 record.handle == 0)
19120 continue;
19121 std::string normalizedName = record.name;
19122 std::transform(
19123 normalizedName.begin(), normalizedName.end(), normalizedName.begin(),
19124 [](unsigned char c) { return static_cast<char>(std::toupper(c)); });
19125 if (normalizedName == "*ACTIVE") {
19126 // The writer always emits the canonical active record. An
19127 // imported copy with its fixed source identity is therefore
19128 // already represented; any other active-name record is an
19129 // ambiguous second table identity.
19130 if (record.handle == DRW_Vport::kDwgActiveHandle ||
19131 (importedActiveSource && record.handle == activeSourceHandle))
19132 continue;
19133 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
19134 "RS_FilterDXFRW: duplicate generated DWG VPORT "
19135 "'*ACTIVE' record");
19136 m_writeFailed = true;
19137 continue;
19138 }
19139 if (record.handle == DRW_Vport::kDwgActiveHandle) {
19140 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
19141 "RS_FilterDXFRW: DWG VPORT handle 0x%X is reserved "
19142 "for '*ACTIVE'",
19143 record.handle);
19144 m_writeFailed = true;
19145 continue;
19146 }
19147 if (!emittedVportNames.insert(normalizedName).second) {
19148 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
19149 "RS_FilterDXFRW: duplicate DWG VPORT name '%s'",
19150 record.name.c_str());
19151 m_writeFailed = true;
19152 continue;
19153 }
19154 if (hasCustomDxfApplicationGroups(record.appData)) {
19155 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
19156 "RS_FilterDXFRW: dropping unsupported DXF "
19157 "VPORT application data while writing DWG");
19158 }
19159 DRW_Vport named;
19160 named.handle = remapHandle(record.handle);
19161 // VPORT entries are owned by the fixed VPORT control object;
19162 // the source parent is not a remappable object reference.
19163 named.parentHandle = DRW_Vport::kDwgControlHandle;
19164 named.name = record.name;
19165 named.center = record.center;
19166 named.viewTarget = record.viewTarget;
19167 named.viewDir = record.viewDirection;
19168 named.height = record.height;
19169 named.ratio = record.ratio;
19170 named.viewMode = record.viewMode;
19171 named.grid = record.grid;
19172 named.snap = record.snap;
19173 named.gridBehavior = record.gridBehavior;
19174 if (!restoreTableEntryExtData(named, record.extData)) {
19175 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
19176 "RS_FilterDXFRW: cannot restore DXF VPORT XDATA");
19177 m_writeFailed = true;
19178 continue;
19179 }
19180 named.reactorHandles = record.reactorHandles;
19181 named.xDictHandle = record.xDictHandle;
19182 std::vector<PendingOptionalDrop> optionalDrops;
19183 named.backgroundHandle = remapObjectReference(
19184 record.backgroundHandle, "VPORT background", optionalDrops);
19185 named.visualStyleHandle = remapObjectReference(
19186 record.visualStyleHandle, "VPORT visual style", optionalDrops);
19187 named.m_sunHandle =
19188 remapObjectReference(record.sunHandle, "VPORT sun", optionalDrops);
19189 named.namedUcsHandle = remapUcsReference(
19190 record.namedUcsHandle, "VPORT named UCS", optionalDrops);
19191 named.baseUcsHandle = remapUcsReference(record.baseUcsHandle,
19192 "VPORT base UCS", optionalDrops);
19193 const std::uint32_t sourceHandle = record.handle;
19194 if (!m_dwgW->addVport(&named)) {
19195 m_writeFailed = true;
19196 continue;
19197 }
19198 if (sourceHandle != 0 && named.handle != 0) {
19199 const auto [it, inserted] =
19200 m_dwgWriteVportHandleRemap.emplace(sourceHandle, named.handle);
19201 if (!inserted && it->second != named.handle) {
19202 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
19203 "RS_FilterDXFRW: duplicate DWG VPORT handle 0x%X",
19204 sourceHandle);
19205 m_writeFailed = true;
19206 }
19207 }
19208 const bool identityCommitted = stageDwgTableRecordIdentity(
19209 DwgWriteSourceKind::TableRecord, sourceHandle, named.handle);
19210 if (!identityCommitted) {
19211 m_writeFailed = true;
19212 } else {
19213 for (const PendingOptionalDrop &drop : optionalDrops) {
19214 if (!recordDwgWriteOptionalDrop(
19215 DwgWriteSourceKind::TableRecord, sourceHandle, named.handle,
19216 drop.referenceName, drop.referenceOrdinal, drop.targetKind,
19217 drop.targetSourceHandle, drop.targetStatus,
19218 m_dwgW->getVersion(), "DWG VPORTS", drop.reason)) {
19219 m_writeFailed = true;
19220 break;
19221 }
19222 }
19223 }
19224 }
19225 std::set<std::uint32_t> viewportHeaderHandles;
19226 for (const auto &record : metadata.viewportEntityHeaders()) {
19227 if (record.replayState ==
19228 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
19229 record.handle != 0) {
19230 if (!viewportHeaderHandles.insert(record.handle).second) {
19231 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
19232 "RS_FilterDXFRW: duplicate DWG viewport-header "
19233 "handle 0x%X",
19234 record.handle);
19235 m_writeFailed = true;
19236 }
19237 }
19238 }
19239 auto remapBlockReference = [this, &isDropped, &dropReason](
19240 std::uint32_t sourceHandle,
19241 const char *referenceKind,
19242 std::vector<PendingOptionalDrop> &drops) {
19243 if (sourceHandle == DRW::NoHandle)
19244 return std::uint32_t{0};
19245 std::uint32_t outputHandle = 0;
19246 const DwgWriteReferenceStatus status =
19247 resolveDwgWriteBlockOwner(sourceHandle, &outputHandle);
19248 if (isDropped(status)) {
19249 RS_DEBUGRS_Debug::instance()->print(
19250 RS_Debug::D_WARNING,
19251 status == DwgWriteReferenceStatus::Ambiguous
19252 ? "RS_FilterDXFRW: dropping ambiguous DWG %s target "
19253 "0x%X"
19254 : "RS_FilterDXFRW: dropping dangling DWG %s target "
19255 "0x%X",
19256 referenceKind, sourceHandle);
19257 drops.push_back({sourceHandle, status, DwgWriteSourceKind::Block,
19258 referenceKind, 0, dropReason(status)});
19259 return std::uint32_t{0};
19260 }
19261 return outputHandle;
19262 };
19263 auto remapEntityReference = [this, &isDropped, &dropReason](
19264 std::uint32_t sourceHandle,
19265 const char *referenceKind,
19266 std::vector<PendingOptionalDrop> &drops) {
19267 std::uint32_t outputHandle = 0;
19268 const DwgWriteReferenceStatus status =
19269 resolveDwgWriteEntityHandle(sourceHandle, &outputHandle);
19270 if (isDropped(status)) {
19271 RS_DEBUGRS_Debug::instance()->print(
19272 RS_Debug::D_WARNING,
19273 status == DwgWriteReferenceStatus::Ambiguous
19274 ? "RS_FilterDXFRW: dropping ambiguous DWG %s target "
19275 "0x%X"
19276 : "RS_FilterDXFRW: dropping dangling DWG %s target "
19277 "0x%X",
19278 referenceKind, sourceHandle);
19279 drops.push_back({sourceHandle, status, DwgWriteSourceKind::Entity,
19280 referenceKind, 0, dropReason(status)});
19281 return std::uint32_t{0};
19282 }
19283 return outputHandle;
19284 };
19285 auto remapViewportHeaderReference =
19286 [this, &remapHandle, &viewportHeaderHandles,
19287 &dropReason](std::uint32_t sourceHandle, const char *referenceKind,
19288 std::vector<PendingOptionalDrop> &drops) {
19289 if (sourceHandle == DRW::NoHandle)
19290 return std::uint32_t{0};
19291 if (viewportHeaderHandles.count(sourceHandle) == 0) {
19292 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
19293 "RS_FilterDXFRW: dropping dangling DWG %s target "
19294 "0x%X",
19295 referenceKind, sourceHandle);
19296 drops.push_back({sourceHandle, DwgWriteReferenceStatus::Missing,
19297 DwgWriteSourceKind::TableRecord, referenceKind, 0,
19298 dropReason(DwgWriteReferenceStatus::Missing)});
19299 return std::uint32_t{0};
19300 }
19301 return remapHandle(sourceHandle);
19302 };
19303 for (const auto &record : metadata.viewportEntityHeaders()) {
19304 if (record.replayState !=
19305 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
19306 record.handle == 0) {
19307 continue;
19308 }
19309 DRW_ViewportEntityHeader header;
19310 header.handle = remapHandle(record.handle);
19311 header.parentHandle = static_cast<int>(remapHandle(record.parentHandle));
19312 header.name = record.name;
19313 header.flags = record.flags;
19314 header.isOn = record.isOn;
19315 std::vector<PendingOptionalDrop> optionalDrops;
19316 header.xrefBlockHandle = remapBlockReference(
19317 record.xrefBlockHandle, "VPEHEADER block", optionalDrops);
19318 header.viewportEntityHandle = remapEntityReference(
19319 record.viewportEntityHandle, "VPEHEADER entity", optionalDrops);
19320 header.previousViewportEntityHeaderHandle = remapViewportHeaderReference(
19321 record.previousViewportEntityHeaderHandle, "VPEHEADER previous",
19322 optionalDrops);
19323 for (std::size_t ordinal = 0; ordinal < record.reactorHandles.size();
19324 ++ordinal) {
19325 const std::uint32_t handle = record.reactorHandles[ordinal];
19326 const std::uint32_t remapped =
19327 remapObjectReference(handle, "VPEHEADER reactor", optionalDrops,
19328 static_cast<std::uint32_t>(ordinal));
19329 if (remapped != 0)
19330 header.reactorHandles.push_back(remapped);
19331 }
19332 header.xDictHandle = remapObjectReference(
19333 record.xDictHandle, "VPEHEADER extension dictionary", optionalDrops);
19334 header.setDwgCommonObjectState(
19335 static_cast<std::int32_t>(header.reactorHandles.size()),
19336 header.xDictHandle != 0 ? record.xDictFlag : 0,
19337 encodedDwgDataStoragePresenceBit(record.handle, record.hasDsData));
19338 if (!m_dwgW->addViewportEntityHeader(&header)) {
19339 m_writeFailed = true;
19340 continue;
19341 }
19342 const bool identityCommitted = stageDwgTableRecordIdentity(
19343 DwgWriteSourceKind::TableRecord, record.handle, header.handle);
19344 if (!identityCommitted) {
19345 m_writeFailed = true;
19346 continue;
19347 }
19348 for (const PendingOptionalDrop &drop : optionalDrops) {
19349 if (!recordDwgWriteOptionalDrop(
19350 DwgWriteSourceKind::TableRecord, record.handle, header.handle,
19351 drop.referenceName, drop.referenceOrdinal, drop.targetKind,
19352 drop.targetSourceHandle, drop.targetStatus,
19353 m_dwgW->getVersion(), "DWG VPORT_ENTITY_HEADERS",
19354 drop.reason)) {
19355 m_writeFailed = true;
19356 break;
19357 }
19358 }
19359 }
19360 return;
19361 }
19362 noteDxfWrite(m_dxfW->writeVport(&vp));
19363 if (m_writeFailed)
19364 return;
19365 for (const auto &entry : metadata.vportTableEntries()) {
19366 if (normalizeDwgTableName(entry.first) == "*ACTIVE")
19367 continue;
19368 DRW_Vport named = entry.second;
19369 noteDxfWrite(m_dxfW->writeVport(&named));
19370 if (m_writeFailed)
19371 return;
19372 }
19373}
19374
19375void RS_FilterDXFRW::writeDimstyles() {
19376 LC_DimStylesList *dimStylesList = m_graphic->getDimStyleList();
19377 auto stylesList = dimStylesList->getStylesList();
19378 for (auto ds : *stylesList) {
19379 DRW_Dimstyle dst;
19380 prepareDRWDimStyle(dst, ds);
19381 if (const auto *sourceStyle =
19382 m_graphic->dwgAdvancedMetadata().findDimStyleTableEntryByName(
19383 dst.name)) {
19384 if (m_dwgW != nullptr &&
19385 hasDxfTableAppDataUnsupportedByDwg(*sourceStyle)) {
19386 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
19387 "RS_FilterDXFRW: cannot convert DXF DIMSTYLE "
19388 "application data to DWG");
19389 m_writeFailed = true;
19390 continue;
19391 }
19392 if (!restoreTableEntryExtData(dst, sourceStyle->extData)) {
19393 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
19394 "RS_FilterDXFRW: cannot restore DXF "
19395 "DIMSTYLE XDATA");
19396 m_writeFailed = true;
19397 continue;
19398 }
19399 dst.appData = sourceStyle->appData;
19400 dst.reactorHandles = sourceStyle->reactorHandles;
19401 dst.xDictHandle = sourceStyle->xDictHandle;
19402 dst.handle = sourceStyle->handle;
19403 }
19404 if (m_dwgW) {
19405 const std::uint32_t sourceHandle = dst.handle;
19406 if (!m_dwgW->addDimstyle(&dst) ||
19407 !stageDwgTableRecordIdentity(DwgWriteSourceKind::TableRecord,
19408 sourceHandle, dst.handle))
19409 m_writeFailed = true;
19410 } else {
19411 noteDxfWrite(m_dxfW->writeDimstyle(&dst));
19412 }
19413 }
19414}
19415
19416void RS_FilterDXFRW::prepareDRWDimStyleZerosSuppression(DRW_Dimstyle &d,
19417 const LC_DimStyle *ds) {
19418 auto zeros = ds->zerosSuppression();
19419 if (zeros->checkModifyState(LC_DimStyle::ZerosSuppression::$DIMZIN)) {
19420 d.add("$DIMZIN", 78, zeros->linearRaw());
19421 }
19422 if (zeros->checkModifyState(LC_DimStyle::ZerosSuppression::$DIMAZIN)) {
19423 d.add("$DIMAZIN", 79, zeros->angularRaw());
19424 }
19425
19426 if (zeros->checkModifyState(LC_DimStyle::ZerosSuppression::$DIMTZIN)) {
19427 d.add("$DIMTZIN", 284, zeros->toleranceRaw());
19428 }
19429 if (zeros->checkModifyState(LC_DimStyle::ZerosSuppression::$DIMALTZ)) {
19430 d.add("$DIMALTZ", 285, zeros->altLinearRaw());
19431 }
19432 if (zeros->checkModifyState(LC_DimStyle::ZerosSuppression::$DIMALTTZ)) {
19433 d.add("$DIMALTTZ", 286, zeros->altToleranceRaw());
19434 }
19435}
19436
19437void RS_FilterDXFRW::prepareDRWDimStyleArrows(DRW_Dimstyle &d,
19438 const LC_DimStyle *ds) {
19439 auto arrow = ds->arrowhead();
19440 if (arrow->checkModifyState(LC_DimStyle::Arrowhead::$DIMBLK)) {
19441 QString blockName = arrow->sameBlockName();
19442 if (!blockName.isEmpty()) {
19443 auto blkName = blockName.toStdString();
19444 std::uint32_t blkHandle =
19445 m_dxfW != nullptr ? m_dxfW->getBlockRecordHandleToWrite(blkName)
19446 : DRW::NoHandle;
19447 if (blkHandle != DRW::NoHandle) {
19448 d.add("_$DIMBLK", 342, toHexStr(blkHandle).toStdString());
19449 }
19450 d.add("$DIMBLK", 5, blkName);
19451 }
19452 }
19453 if (arrow->checkModifyState(LC_DimStyle::Arrowhead::$DIMBLK1)) {
19454 QString blockName = arrow->arrowHeadBlockNameFirst();
19455 if (!blockName.isEmpty()) {
19456 auto blkName = blockName.toStdString();
19457 std::uint32_t blkHandle =
19458 m_dxfW != nullptr ? m_dxfW->getBlockRecordHandleToWrite(blkName)
19459 : DRW::NoHandle;
19460 if (blkHandle != DRW::NoHandle) {
19461 d.add("_$DIMBLK1", 343, toHexStr(blkHandle).toStdString());
19462 }
19463 d.add("$DIMBLK1", 6, blkName);
19464 }
19465 }
19466 if (arrow->checkModifyState(LC_DimStyle::Arrowhead::$DIMBLK2)) {
19467 QString blockName = arrow->arrowHeadBlockNameSecond();
19468 if (!blockName.isEmpty()) {
19469 auto blkName = blockName.toStdString();
19470 std::uint32_t blkHandle =
19471 m_dxfW != nullptr ? m_dxfW->getBlockRecordHandleToWrite(blkName)
19472 : DRW::NoHandle;
19473 if (blkHandle != DRW::NoHandle) {
19474 d.add("_$DIMBLK2", 344, toHexStr(blkHandle).toStdString());
19475 }
19476 d.add("$DIMBLK2", 7, blkName);
19477 }
19478 }
19479 if (arrow->checkModifyState(LC_DimStyle::Arrowhead::$DIMASZ)) {
19480 d.add("$DIMASZ", 41, arrow->size());
19481 }
19482 if (arrow->checkModifyState(LC_DimStyle::Arrowhead::$DIMTSZ)) {
19483 d.add("$DIMTSZ", 142, arrow->tickSize());
19484 }
19485 if (arrow->checkModifyState(LC_DimStyle::Arrowhead::$DIMSAH)) {
19486 d.add("$DIMSAH", 173, arrow->isUseSeparateArrowHeads() ? 1 : 0);
19487 }
19488 if (arrow->checkModifyState(LC_DimStyle::Arrowhead::$DIMSOXD)) {
19489 d.add("$DIMSOXD", 175, arrow->suppression());
19490 }
19491}
19492
19493void RS_FilterDXFRW::prepareDRWDimStyleScaling(DRW_Dimstyle &d,
19494 const LC_DimStyle *ds) {
19495 auto scale = ds->scaling();
19496 if (scale->checkModifyState(LC_DimStyle::Scaling::$DIMSCALE)) {
19497 d.add("$DIMSCALE", 40, scale->scale());
19498 }
19499 if (scale->checkModifyState(LC_DimStyle::Scaling::$DIMLFAC)) {
19500 d.add("$DIMLFAC", 144, scale->linearFactor());
19501 }
19502}
19503
19504void RS_FilterDXFRW::prepareDRWDimStyleExtLine(DRW_Dimstyle &d,
19505 const LC_DimStyle *ds) {
19506 auto extLine = ds->extensionLine();
19507 if (extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMEXO)) {
19508 d.add("$DIMEXO", 42, extLine->distanceFromOriginPoint());
19509 }
19510 if (extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMEXE)) {
19511 d.add("$DIMEXE", 44, extLine->distanceBeyondDimLine());
19512 }
19513 if (extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMFXL)) {
19514 d.add("$DIMFXL", 49, extLine->fixedLength());
19515 }
19516 if (extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMFXLON)) {
19517 d.add("$DIMFXLON", 290, extLine->hasFixedLength() ? 1 : 0);
19518 }
19519 if (extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMLWE)) {
19520 auto lineWidth = extLine->lineWidth();
19521 int lw = RS2::lineWidth2dxfInt(lineWidth);
19522 d.add("$DIMLWE", 372, lw);
19523 }
19524 if (extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMCLRE)) {
19525 auto lineColor = extLine->color();
19526 int colRGB;
19527 int colNum = colorToNumber(lineColor, &colRGB);
19528 d.add("$DIMCLRE", 177, colNum);
19529 }
19530 if (extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMSE1)) {
19531 d.add("$DIMSE1", 75, extLine->suppressFirstLine());
19532 }
19533 if (extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMSE2)) {
19534 d.add("$DIMSE2", 76, extLine->suppressSecondLine());
19535 }
19536 if (m_dxfW != nullptr && m_dxfW->getVersion() > DRW::AC1018 &&
19537 extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMLTEX1)) {
19538 std::uint32_t lineTypeHandle =
19539 findLineTypeHandleToWrite(extLine->lineTypeFirstRaw());
19540 if (lineTypeHandle != DRW::NoHandle) {
19541 auto handleStr = toHexStr(lineTypeHandle);
19542 d.add("$DIMLTEX1", 346, handleStr.toStdString());
19543 }
19544 }
19545 if (m_dxfW != nullptr && m_dxfW->getVersion() > DRW::AC1018 &&
19546 extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMLTEX2)) {
19547 std::uint32_t lineTypeHandle =
19548 findLineTypeHandleToWrite(extLine->lineTypeSecondRaw());
19549 if (lineTypeHandle != DRW::NoHandle) {
19550 auto handleStr = toHexStr(lineTypeHandle);
19551 d.add("$DIMLTEX2", 347, handleStr.toStdString());
19552 }
19553 }
19554}
19555
19556void RS_FilterDXFRW::prepareDRWDimStyleDimLine(DRW_Dimstyle &d,
19557 const LC_DimStyle *ds) {
19558 auto dimLine = ds->dimensionLine();
19559 if (dimLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMLWD)) {
19560 auto lineWidth = dimLine->lineWidth();
19561 int lw = RS2::lineWidth2dxfInt(lineWidth);
19562 d.add("$DIMLWD", 371, lw);
19563 }
19564 if (dimLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMDLE)) {
19565 d.add("$DIMDLE", 46, dimLine->distanceBeyondExtLinesForObliqueStroke());
19566 }
19567 if (dimLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMDLI)) {
19568 d.add("$DIMDLI", 43, dimLine->baseLineDimLinesSpacing());
19569 }
19570 if (dimLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMGAP)) {
19571 d.add("$DIMGAP", 147, dimLine->lineGap());
19572 }
19573 if (dimLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMCLRD)) {
19574 auto lineColor = dimLine->color();
19575 int colRGB;
19576 int colNum = colorToNumber(lineColor, &colRGB);
19577 d.add("$DIMCLRD", 176, colNum);
19578 }
19579 if (dimLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMSD1)) {
19580 d.add("$DIMSD1", 281, dimLine->suppressFirstLine());
19581 }
19582 if (dimLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMSD2)) {
19583 d.add("$DIMSD2", 281, dimLine->suppressSecondLine());
19584 }
19585 if (dimLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMTOFL)) {
19586 d.add("$DIMTOFL", 172, dimLine->drawPolicyForOutsideText());
19587 }
19588 if (m_dxfW != nullptr && m_dxfW->getVersion() > DRW::AC1018 &&
19589 dimLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMLTYPE)) {
19590 std::uint32_t lineTypeHandle =
19591 findLineTypeHandleToWrite(dimLine->lineTypeName());
19592 if (lineTypeHandle != DRW::NoHandle) {
19593 auto handleStr = toHexStr(lineTypeHandle);
19594 d.add("$DIMLTYPE", 345, handleStr.toStdString());
19595 }
19596 }
19597}
19598
19599std::uint32_t
19600RS_FilterDXFRW::findLineTypeHandleToWrite(const QString &name) const {
19601 if (m_dxfW == nullptr) {
19602 return DRW::NoHandle;
19603 }
19604 std::string lineName = name.toUpper().toStdString();
19605 for (auto p : m_dxfW->getWritingContext()->lineTypesMap) {
19606 if (p.first.compare(lineName) == 0) {
19607 return p.second;
19608 }
19609 }
19610 return DRW::NoHandle;
19611}
19612
19613DRW::Version RS_FilterDXFRW::dimStyleTargetVersion() const {
19614 if (m_dxfW != nullptr)
19615 return m_dxfW->getVersion();
19616 if (m_dwgW != nullptr)
19617 return m_dwgW->getVersion();
19618 return DRW::UNKNOWNV;
19619}
19620
19621void RS_FilterDXFRW::prepareDRWDimStyleText(DRW_Dimstyle &d,
19622 const LC_DimStyle *ds) {
19623 auto text = ds->text();
19624 if (text->checkModifyState(LC_DimStyle::Text::$DIMTXT)) {
19625 d.add("$DIMTXT", 140, text->height());
19626 }
19627
19628 if (text->checkModifyState(LC_DimStyle::Text::$DIMTXSTY)) {
19629 auto styleName = text->style().toStdString();
19630 std::uint32_t styleHandle = m_dxfW != nullptr
19631 ? m_dxfW->getTextStyleHandle(styleName)
19632 : DRW::NoHandle;
19633 if (styleHandle != DRW::NoHandle) {
19634 d.add("$DIMTXSTY", 340, toHexStr(styleHandle).toStdString());
19635 }
19636 }
19637 if (text->checkModifyState(LC_DimStyle::Text::$DIMTOH)) {
19638 d.add("$DIMTOH", 74, text->orientationOutside());
19639 }
19640 if (text->checkModifyState(LC_DimStyle::Text::$DIMTIH)) {
19641 d.add("$DIMTIH", 73, text->orientationInside());
19642 }
19643 if (text->checkModifyState(LC_DimStyle::Text::$DIMJUST)) {
19644 d.add("$DIMJUST", 280, text->horizontalPositioning());
19645 }
19646 if (text->checkModifyState(LC_DimStyle::Text::$DIMCLRT)) {
19647 auto lineColor = text->color();
19648 int colRGB;
19649 int colNum = colorToNumber(lineColor, &colRGB);
19650 d.add("$DIMCLRT", 178, colNum);
19651 }
19652 if (text->checkModifyState(LC_DimStyle::Text::$DIMTAD)) {
19653 d.add("$DIMTAD", 77, text->verticalPositioning());
19654 }
19655 if (text->checkModifyState(LC_DimStyle::Text::$DIMTIX)) {
19656 d.add("$DIMTIX", 174, text->extLinesRelativePlacement());
19657 }
19658 if (text->checkModifyState(LC_DimStyle::Text::$DIMTFILL)) {
19659 d.add("$DIMTFILL", 69, text->backgroundFillMode());
19660 }
19661 if (text->checkModifyState(LC_DimStyle::Text::$DIMTFILLCLR)) {
19662 auto lineColor = text->explicitBackgroundFillColor();
19663 int colRGB;
19664 int colNum = colorToNumber(lineColor, &colRGB);
19665 d.add("$DIMTFILLCLR", 70, colNum);
19666 }
19667 if (dimStyleTargetVersion() >= DRW::AC1024 &&
19668 text->checkModifyState(LC_DimStyle::Text::$DIMTXTDIRECTION)) {
19669 d.dimtxtdirection = text->readingDirection();
19670 d.add("$DIMTXTDIRECTION", 295, d.dimtxtdirection);
19671 }
19672 if (text->checkModifyState(LC_DimStyle::Text::$DIMTVP)) {
19673 d.add("$DIMTVP", 145, text->verticalDistanceToDimLine());
19674 }
19675 if (text->checkModifyState(LC_DimStyle::Text::$DIMUPT)) {
19676 d.add("$DIMUPT", 288, text->cursorControlPolicy());
19677 }
19678 if (text->checkModifyState(LC_DimStyle::Text::$DIMTMOVE)) {
19679 d.add("$DIMTMOVE", 279, text->positionMovementPolicy());
19680 }
19681 if (text->checkModifyState(LC_DimStyle::Text::$DIMATFIT)) {
19682 d.add("$DIMATFIT", 289, text->unsufficientSpacePolicy());
19683 }
19684}
19685
19686void RS_FilterDXFRW::prepareDRWDimStyleLinearFormat(DRW_Dimstyle &d,
19687 const LC_DimStyle *ds) {
19688 auto linear = ds->linearFormat();
19689 if (linear->checkModifyState(LC_DimStyle::LinearFormat::$DIMLUNIT)) {
19690 d.add("$DIMLUNIT", 277, linear->formatRaw());
19691 }
19692 if (linear->checkModifyState(LC_DimStyle::LinearFormat::$DIMSEP)) {
19693 d.add("$DIMDSEP", 278, linear->decimalFormatSeparatorChar());
19694 }
19695 if (linear->checkModifyState(LC_DimStyle::LinearFormat::$DIMDEC)) {
19696 d.add("$DIMDEC", 271, linear->decimalPlaces());
19697 }
19698 if (linear->checkModifyState(LC_DimStyle::LinearFormat::$DIMPOST)) {
19699 d.add("$DIMPOST", 3, linear->prefixOrSuffix().toStdString());
19700 }
19701 if (linear->checkModifyState(LC_DimStyle::LinearFormat::$DIMALT)) {
19702 d.add("$DIMALT", 170, linear->alternateUnits());
19703 }
19704 if (linear->checkModifyState(LC_DimStyle::LinearFormat::$DIMALTU)) {
19705 d.add("$DIMALTU", 273, linear->altFormatRaw());
19706 }
19707 if (linear->checkModifyState(LC_DimStyle::LinearFormat::$DIMALTD)) {
19708 d.add("$DIMALTD", 171, linear->altDecimalPlaces());
19709 }
19710 if (linear->checkModifyState(LC_DimStyle::LinearFormat::$DIMALTF)) {
19711 d.add("$DIMALTF", 143, linear->altUnitsMultiplier());
19712 }
19713 if (linear->checkModifyState(LC_DimStyle::LinearFormat::$DIMAPOST)) {
19714 d.add("$DIMAPOST", 4, linear->altPrefixOrSuffix().toStdString());
19715 }
19716}
19717
19718void RS_FilterDXFRW::prepareDRWDimStyleFractions(DRW_Dimstyle &d,
19719 const LC_DimStyle *ds) {
19720 auto fraction = ds->fractions();
19721 if (fraction->checkModifyState(LC_DimStyle::Fractions::$DIMFRAC)) {
19722 d.add("$DIMFRAC", 276, fraction->style());
19723 }
19724}
19725
19726void RS_FilterDXFRW::prepareDRWDimStyleAngularFormat(DRW_Dimstyle &d,
19727 const LC_DimStyle *ds) {
19728 auto angular = ds->angularFormat();
19729 if (angular->checkModifyState(LC_DimStyle::AngularFormat::$DIMAUNIT)) {
19730 d.add("$DIMAUNIT", 275, angular->format());
19731 }
19732 if (angular->checkModifyState(LC_DimStyle::AngularFormat::$DIMADEC)) {
19733 d.add("$DIMADEC", 179, angular->decimalPlaces());
19734 }
19735
19736 auto round = ds->roundOff();
19737 if (round->checkModifyState(LC_DimStyle::LinearRoundOff::$DIMRND)) {
19738 d.add("$DIMRND", 45, round->roundTo());
19739 }
19740 if (round->checkModifyState(LC_DimStyle::LinearRoundOff::$DIMALTRND)) {
19741 d.add("$DIMALTRND", 148, round->altRoundTo());
19742 }
19743}
19744
19745void RS_FilterDXFRW::prepareDRWDimStyleRadial(DRW_Dimstyle &d,
19746 const LC_DimStyle *ds) {
19747 auto radial = ds->radial();
19748 if (radial->checkModifyState(LC_DimStyle::Radial::$DIMCEN)) {
19749 d.add("$DIMCEN", 141, radial->centerCenterMarkOrLineSize());
19750 }
19751}
19752
19753void RS_FilterDXFRW::prepareDRWDimStyleTolerance(DRW_Dimstyle &d,
19754 const LC_DimStyle *ds) {
19755 auto tolerance = ds->latteralTolerance();
19756 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTDEC)) {
19757 d.add("$DIMTDEC", 272, tolerance->decimalPlaces());
19758 }
19759 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMALTTD)) {
19760 d.add("$DIMALTTD", 274, tolerance->decimalPlacesAltDim());
19761 }
19762 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTOL)) {
19763 d.add("$DIMTOL", 71, tolerance->isAppendTolerancesToDimText() ? 1 : 0);
19764 }
19765 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTOLJ)) {
19766 d.add("$DIMTOLJ", 283, tolerance->verticalJustification());
19767 }
19768 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTM)) {
19769 d.add("$DIMTM", 48, tolerance->lowerToleranceLimit());
19770 }
19771 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTM)) {
19772 d.add("$DIMTP", 47, tolerance->upperToleranceLimit());
19773 }
19774 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTFAC)) {
19775 d.add("$DIMTFAC", 146, tolerance->heightScaleFactorToDimText());
19776 }
19777 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMLIM)) {
19778 d.add("$DIMLIM", 72, tolerance->isLimitsGeneratedAsDefaultText() ? 1 : 0);
19779 }
19780}
19781
19782void RS_FilterDXFRW::prepareDRWDimStyleArc(DRW_Dimstyle &d,
19783 const LC_DimStyle *ds) {
19784 auto arc = ds->arc();
19785 if (arc->checkModifyState(LC_DimStyle::Arc::$DIMARCSYM)) {
19786 d.add("$DIMARCSYM", 90, arc->arcSymbolPosition());
19787 }
19788}
19789
19790void RS_FilterDXFRW::prepareDRWDimStyleLeader(DRW_Dimstyle &d,
19791 const LC_DimStyle *ds) {
19792 auto leader = ds->leader();
19793 if (leader->checkModifyState(LC_DimStyle::Leader::$DIMLDRBLK)) {
19794 QString blockName = leader->arrowBlockName();
19795 if (!blockName.isEmpty()) {
19796 auto blkName = blockName.toStdString();
19797 std::uint32_t blkHandle =
19798 m_dxfW != nullptr ? m_dxfW->getBlockRecordHandleToWrite(blkName)
19799 : DRW::NoHandle;
19800 if (blkHandle != DRW::NoHandle) {
19801 d.add("_$DIMLDRBLK", 341, toHexStr(blkHandle).toStdString());
19802 }
19803 }
19804 }
19805}
19806
19807void RS_FilterDXFRW::prepareDRWDimStyleExtData(DRW_Dimstyle &d,
19808 const LC_DimStyle *ds) {
19809 auto tolerance = ds->latteralTolerance();
19810 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTALN)) {
19811 try {
19812 if (!d.addExtData(
19813 std::make_unique<DRW_Variant>(1001, "ACAD_DSTYLE_DIMTALN")) ||
19814 !d.addExtData(std::make_unique<DRW_Variant>(1070, 392)) ||
19815 !d.addExtData(
19816 std::make_unique<DRW_Variant>(1070, tolerance->adjustment()))) {
19817 m_writeFailed = true;
19818 }
19819 } catch (...) {
19820 m_writeFailed = true;
19821 }
19822 }
19823 // todo - add support of ACAD_DIMSTYLE_DIMBREAK
19824 // todo - add support of ACAD_DIMSTYLE_DIMJAG
19825}
19826
19827void RS_FilterDXFRW::prepareDRWDimStyle(DRW_Dimstyle &d,
19828 const LC_DimStyle *ds) {
19829 d.name = ds->getName().toStdString();
19830
19831 auto savedMode = ds->getModifyCheckMode();
19832 if (ds->isBaseStyle()) {
19833 // base styles are written completely, regardless of fields modification
19834 // state!
19835 ds->setModifyCheckMode(LC_DimStyle::ModificationAware::ALL);
19836 }
19837
19838 // in AutoCAD, dimstyle that is specific for the type of dimension will
19839 // contain only values that are really overriden for the type. Other
19840 // properties will be extracted from the base style. therefore, have need to
19841 // check for properties that are modified and write only one that has modified
19842 // flag set...
19843
19844 prepareDRWDimStyleArrows(d, ds);
19845 prepareDRWDimStyleScaling(d, ds);
19846 prepareDRWDimStyleExtLine(d, ds);
19847 prepareDRWDimStyleDimLine(d, ds);
19848 prepareDRWDimStyleText(d, ds);
19849 prepareDRWDimStyleZerosSuppression(d, ds);
19850 prepareDRWDimStyleLinearFormat(d, ds);
19851 prepareDRWDimStyleAngularFormat(d, ds);
19852 prepareDRWDimStyleFractions(d, ds);
19853 prepareDRWDimStyleRadial(d, ds);
19854 prepareDRWDimStyleTolerance(d, ds);
19855 prepareDRWDimStyleArc(d, ds);
19856 prepareDRWDimStyleLeader(d, ds);
19857 prepareDRWDimStyleExtData(d, ds);
19858 // result->setDimunit(s.dimunit); // $DIMLUNIT
19859
19860 ds->setModifyCheckMode(savedMode); // restore modification flags
19861
19862 // fixme - return to this later, move to MLeaderStyle
19863 // auto mleader = ds->mleader();
19864 // var = s.get("$MLEADERSCALE");
19865 // if (var != nullptr) {
19866 // mleaderStyle->setScale(var->d_val());
19867 // }
19868}
19869
19870void RS_FilterDXFRW::writeObjects() {
19871 if (m_dwgW) {
19872 if (!validateDwgDataStorageAdmissionSummary(
19873 /*requireClassAdmission=*/true)) {
19874 m_writeFailed = true;
19875 m_dwgW->markDwgWriteFailure();
19876 RS_DEBUGRS_Debug::instance()->print(
19877 RS_Debug::D_WARNING,
19878 "RS_FilterDXFRW: DataStorage admission diverged before OBJECTS");
19879 return;
19880 }
19881 if (!validateDwgExportAdmissionPlan(DwgOpaqueTableFailurePhase::Objects)) {
19882 m_writeFailed = true;
19883 m_dwgW->markDwgWriteFailure();
19884 RS_DEBUGRS_Debug::instance()->print(
19885 RS_Debug::D_WARNING,
19886 "RS_FilterDXFRW: DWG export admission changed before OBJECTS");
19887 return;
19888 }
19889 if (!validateDwgFieldWritePlan(/*requireClassAdmission=*/true)) {
19890 m_writeFailed = true;
19891 m_dwgW->markDwgWriteFailure();
19892 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
19893 "RS_FilterDXFRW: DWG FIELD admission changed before "
19894 "OBJECTS");
19895 return;
19896 }
19897 const auto &metadata = m_graphic->dwgAdvancedMetadata();
19898 const auto &rawObjects = metadata.rawObjects();
19899 // P3 #2: structural-collision remap built by fileExport — rewrite the
19900 // handle (and typed references to it) of preserved objects that
19901 // collide with the writer's fixed structural handles.
19902 auto remapHandle = [this](std::uint32_t h) -> std::uint32_t {
19903 return resolveDwgWriteHandle(h);
19904 };
19905 struct DwgWriteReferenceContext {
19906 DwgWriteSourceKind producerKind = DwgWriteSourceKind::Object;
19907 std::uint32_t producerSourceHandle = 0;
19908 std::uint32_t producerOutputHandle = 0;
19909 } referenceContext;
19910 const auto recordOptionalDrop =
19911 [this, &referenceContext](
19912 std::uint32_t targetSourceHandle,
19913 DwgWriteReferenceStatus targetStatus, const char *referenceName,
19914 std::uint32_t referenceOrdinal, const char *reason,
19915 DwgWriteSourceKind targetKind = DwgWriteSourceKind::Object) {
19916 if (referenceContext.producerOutputHandle == 0 ||
19917 targetSourceHandle == 0 || m_dwgW == nullptr ||
19918 referenceName == nullptr || reason == nullptr) {
19919 m_writeFailed = true;
19920 return false;
19921 }
19922 if (!recordDwgWriteOptionalDrop(
19923 referenceContext.producerKind,
19924 referenceContext.producerSourceHandle,
19925 referenceContext.producerOutputHandle, referenceName,
19926 referenceOrdinal, targetKind, targetSourceHandle,
19927 targetStatus, m_dwgW->getVersion(), "DWG OBJECTS", reason)) {
19928 m_writeFailed = true;
19929 return false;
19930 }
19931 return true;
19932 };
19933 const auto recordRetainedReference =
19934 [this, &referenceContext](
19935 std::uint32_t targetSourceHandle, std::uint32_t targetOutputHandle,
19936 DwgWriteSourceKind targetKind, const char *referenceName,
19937 std::uint32_t referenceOrdinal, std::uint8_t wireHandleCode,
19938 bool required) {
19939 if (referenceContext.producerOutputHandle == 0 ||
19940 (targetSourceHandle == 0 &&
19941 targetKind != DwgWriteSourceKind::FixedStructural) ||
19942 targetOutputHandle == 0 || m_dwgW == nullptr ||
19943 referenceName == nullptr) {
19944 m_writeFailed = true;
19945 return false;
19946 }
19947 const DwgWriteReferenceReceipt receipt{
19948 referenceContext.producerKind,
19949 referenceContext.producerSourceHandle,
19950 referenceContext.producerOutputHandle,
19951 referenceName,
19952 referenceOrdinal,
19953 targetKind,
19954 targetKind == DwgWriteSourceKind::FixedStructural
19955 ? 0
19956 : targetSourceHandle,
19957 targetOutputHandle,
19958 required,
19959 wireHandleCode};
19960 if (!recordDwgWriteReferenceReceipt(receipt)) {
19961 RS_DEBUGRS_Debug::instance()->print(
19962 RS_Debug::D_WARNING,
19963 "RS_FilterDXFRW: rejected DWG reference receipt "
19964 "producer=0x%X target=0x%X kind=%d name=%s ordinal=%u",
19965 referenceContext.producerSourceHandle, targetSourceHandle,
19966 static_cast<int>(targetKind), referenceName, referenceOrdinal);
19967 m_writeFailed = true;
19968 return false;
19969 }
19970 return true;
19971 };
19972 const auto isLiveDwgWriteReference = [](DwgWriteReferenceStatus status) {
19973 return status == DwgWriteReferenceStatus::Resolved ||
19974 status == DwgWriteReferenceStatus::LegacySpaceAlias ||
19975 status == DwgWriteReferenceStatus::Reserved ||
19976 status == DwgWriteReferenceStatus::Selected ||
19977 status == DwgWriteReferenceStatus::Committed;
19978 };
19979 auto sanitizeOptionalCommonHandle =
19980 [this, &recordOptionalDrop, &recordRetainedReference,
19981 &isLiveDwgWriteReference](std::uint32_t &handle, const char *kind,
19982 std::uint32_t referenceOrdinal = 0,
19983 std::uint8_t wireHandleCode =
19984 DRW::DwgSoftPointer) {
19985 if (handle == 0)
19986 return;
19987 const std::uint32_t sourceHandle = handle;
19988 std::uint32_t outputHandle = 0;
19989 DwgWriteSourceKind targetKind = DwgWriteSourceKind::Object;
19990 const DwgWriteReferenceStatus status =
19991 resolveDwgWriteCommonHandle(handle, &outputHandle, &targetKind);
19992 if (isLiveDwgWriteReference(status)) {
19993 if (!recordRetainedReference(sourceHandle, outputHandle, targetKind,
19994 kind, referenceOrdinal, wireHandleCode,
19995 false)) {
19996 handle = 0;
19997 return;
19998 }
19999 handle = outputHandle;
20000 return;
20001 }
20002 const char *reason = status == DwgWriteReferenceStatus::Ambiguous
20003 ? "ambiguous target"
20004 : status == DwgWriteReferenceStatus::Suppressed
20005 ? "suppressed target"
20006 : "missing target";
20007 if (!recordOptionalDrop(handle, status, kind, referenceOrdinal,
20008 reason, targetKind)) {
20009 handle = 0;
20010 return;
20011 }
20012 RS_DEBUGRS_Debug::instance()->print(
20013 RS_Debug::D_WARNING,
20014 "RS_FilterDXFRW: dropping unresolved DWG %s target 0x%X", kind,
20015 handle);
20016 handle = 0;
20017 };
20018 auto sanitizeOptionalEntityHandle = [this, &recordOptionalDrop,
20019 &recordRetainedReference,
20020 &isLiveDwgWriteReference](
20021 std::uint32_t &handle,
20022 const char *kind,
20023 std::uint32_t referenceOrdinal = 0,
20024 std::uint8_t wireHandleCode =
20025 DRW::DwgSoftPointer) {
20026 if (handle == 0)
20027 return;
20028 const std::uint32_t sourceHandle = handle;
20029 std::uint32_t outputHandle = 0;
20030 const DwgWriteReferenceStatus status =
20031 resolveDwgWriteEntityHandle(sourceHandle, &outputHandle);
20032 if (isLiveDwgWriteReference(status)) {
20033 if (!recordRetainedReference(sourceHandle, outputHandle,
20034 DwgWriteSourceKind::Entity, kind,
20035 referenceOrdinal, wireHandleCode, false)) {
20036 handle = 0;
20037 return;
20038 }
20039 handle = outputHandle;
20040 return;
20041 }
20042 const char *reason =
20043 status == DwgWriteReferenceStatus::Ambiguous ? "ambiguous target"
20044 : status == DwgWriteReferenceStatus::Suppressed ? "suppressed target"
20045 : "missing target";
20046 if (!recordOptionalDrop(sourceHandle, status, kind, referenceOrdinal,
20047 reason, DwgWriteSourceKind::Entity)) {
20048 handle = 0;
20049 return;
20050 }
20051 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20052 "RS_FilterDXFRW: dropping unresolved DWG %s target 0x%X",
20053 kind, sourceHandle);
20054 handle = 0;
20055 };
20056 const auto resolveRequiredEntityHandle = [this](std::uint32_t sourceHandle,
20057 const char *referenceKind) {
20058 if (sourceHandle == 0) {
20059 m_writeFailed = true;
20060 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20061 "RS_FilterDXFRW: zero required DWG %s target",
20062 referenceKind == nullptr ? "entity" : referenceKind);
20063 return std::uint32_t{0};
20064 }
20065 std::uint32_t outputHandle = 0;
20066 const DwgWriteReferenceStatus status =
20067 resolveDwgWriteEntityHandle(sourceHandle, &outputHandle);
20068 if (status != DwgWriteReferenceStatus::Committed || outputHandle == 0) {
20069 m_writeFailed = true;
20070 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20071 "RS_FilterDXFRW: unresolved required DWG %s target "
20072 "0x%X",
20073 referenceKind, sourceHandle);
20074 return std::uint32_t{0};
20075 }
20076 return outputHandle;
20077 };
20078 // Raw custom entities are written through the opaque-carrier identity
20079 // path, but they remain valid SORTENTSTABLE entity targets. Resolve that
20080 // identity only after the raw frame has committed; a missing or
20081 // uncommitted entity still fails the required-reference contract.
20082 const auto resolveRequiredEntityReference = [this, &rawObjects](
20083 std::uint32_t sourceHandle, const char *referenceKind)
20084 -> std::optional<std::pair<std::uint32_t, DwgWriteSourceKind>> {
20085 if (sourceHandle == 0) {
20086 m_writeFailed = true;
20087 RS_DEBUGRS_Debug::instance()->print(
20088 RS_Debug::D_WARNING, "RS_FilterDXFRW: zero required DWG %s target",
20089 referenceKind == nullptr ? "entity" : referenceKind);
20090 return std::nullopt;
20091 }
20092
20093 std::uint32_t outputHandle = 0;
20094 if (resolveDwgWriteEntityHandle(sourceHandle, &outputHandle) ==
20095 DwgWriteReferenceStatus::Committed &&
20096 outputHandle != 0) {
20097 return std::make_pair(outputHandle, DwgWriteSourceKind::Entity);
20098 }
20099
20100 const auto rawIt = std::find_if(
20101 rawObjects.cbegin(), rawObjects.cend(),
20102 [sourceHandle](const auto &raw) {
20103 return raw.isEntity && raw.handle == sourceHandle;
20104 });
20105 const DwgWriteIdentityLookup rawIdentityStatus =
20106 lookupDwgWriteIdentity(DwgWriteSourceKind::RawCarrier, sourceHandle,
20107 &outputHandle);
20108 if (rawIt != rawObjects.cend() &&
20109 rawIdentityStatus == DwgWriteIdentityLookup::Committed &&
20110 outputHandle != 0) {
20111 return std::make_pair(outputHandle, DwgWriteSourceKind::RawCarrier);
20112 }
20113
20114 m_writeFailed = true;
20115 RS_DEBUGRS_Debug::instance()->print(
20116 RS_Debug::D_WARNING,
20117 "RS_FilterDXFRW: unresolved required DWG %s target 0x%X",
20118 referenceKind, sourceHandle);
20119 return std::nullopt;
20120 };
20121 const auto remapRequiredEntityHandle =
20122 [&resolveRequiredEntityHandle, &recordRetainedReference](
20123 std::uint32_t sourceHandle, const char *referenceKind,
20124 std::uint32_t referenceOrdinal = 0,
20125 std::uint8_t wireHandleCode = DRW::DwgHardPointer) {
20126 const std::uint32_t outputHandle =
20127 resolveRequiredEntityHandle(sourceHandle, referenceKind);
20128 if (outputHandle == 0 ||
20129 !recordRetainedReference(
20130 sourceHandle, outputHandle, DwgWriteSourceKind::Entity,
20131 referenceKind, referenceOrdinal, wireHandleCode, true))
20132 return std::uint32_t{0};
20133 return outputHandle;
20134 };
20135 const auto remapRequiredObjectHandle =
20136 [this, &recordRetainedReference](
20137 std::uint32_t sourceHandle, const char *referenceKind,
20138 std::uint32_t referenceOrdinal = 0,
20139 std::uint8_t wireHandleCode = DRW::DwgHardPointer) {
20140 if (sourceHandle == 0)
20141 return std::uint32_t{0};
20142 std::uint32_t outputHandle = 0;
20143 const DwgWriteReferenceStatus status =
20144 resolveDwgWriteObjectHandle(sourceHandle, &outputHandle);
20145 if ((status != DwgWriteReferenceStatus::Resolved &&
20146 status != DwgWriteReferenceStatus::Selected &&
20147 status != DwgWriteReferenceStatus::Committed) ||
20148 outputHandle == 0) {
20149 m_writeFailed = true;
20150 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20151 "RS_FilterDXFRW: unresolved required DWG %s target "
20152 "0x%X",
20153 referenceKind, sourceHandle);
20154 return std::uint32_t{0};
20155 }
20156 if (!recordRetainedReference(
20157 sourceHandle, outputHandle, DwgWriteSourceKind::Object,
20158 referenceKind, referenceOrdinal, wireHandleCode, true))
20159 return std::uint32_t{0};
20160 return outputHandle;
20161 };
20162 const auto remapRequiredEntityList =
20163 [&remapRequiredEntityHandle](std::vector<std::uint32_t> &handles,
20164 const char *referenceKind) {
20165 std::vector<std::uint32_t> remapped;
20166 remapped.reserve(handles.size());
20167 for (std::size_t ordinal = 0; ordinal < handles.size(); ++ordinal) {
20168 const std::uint32_t sourceHandle = handles[ordinal];
20169 if (sourceHandle == 0)
20170 return false;
20171 const std::uint32_t outputHandle = remapRequiredEntityHandle(
20172 sourceHandle, referenceKind,
20173 static_cast<std::uint32_t>(ordinal), DRW::DwgHardPointer);
20174 if (outputHandle == 0)
20175 return false;
20176 remapped.push_back(outputHandle);
20177 }
20178 handles = std::move(remapped);
20179 return true;
20180 };
20181 const auto sanitizeOptionalCommonList =
20182 [&sanitizeOptionalCommonHandle](std::vector<std::uint32_t> &handles,
20183 const char *kind) {
20184 std::vector<std::uint32_t> kept;
20185 kept.reserve(handles.size());
20186 for (std::size_t ordinal = 0; ordinal < handles.size(); ++ordinal) {
20187 std::uint32_t handle = handles[ordinal];
20188 sanitizeOptionalCommonHandle(handle, kind,
20189 static_cast<std::uint32_t>(ordinal));
20190 if (handle != 0)
20191 kept.push_back(handle);
20192 }
20193 handles = std::move(kept);
20194 };
20195 auto remapEntityHandle = [this](std::uint32_t sourceHandle,
20196 const char *referenceKind) {
20197 std::uint32_t outputHandle = 0;
20198 const DwgWriteReferenceStatus status =
20199 resolveDwgWriteEntityHandle(sourceHandle, &outputHandle);
20200 if (status != DwgWriteReferenceStatus::Committed) {
20201 const bool ambiguous = status == DwgWriteReferenceStatus::Ambiguous;
20202 RS_DEBUGRS_Debug::instance()->print(
20203 RS_Debug::D_WARNING,
20204 ambiguous ? "RS_FilterDXFRW: dropping ambiguous DWG %s target "
20205 "0x%X"
20206 : "RS_FilterDXFRW: dropping dangling DWG %s target "
20207 "0x%X",
20208 referenceKind, sourceHandle);
20209 return std::uint32_t{0};
20210 }
20211 return outputHandle;
20212 };
20213 auto remapSectionObjectHandle =
20214 [this, &remapHandle, &remapEntityHandle](std::uint32_t sourceHandle,
20215 const char *referenceKind) {
20216 if (sourceHandle == 0)
20217 return std::uint32_t{0};
20218
20219 const auto &sectionObjects =
20220 m_graphic->dwgAdvancedMetadata().sectionObjects();
20221 const auto sectionIt = std::find_if(
20222 sectionObjects.cbegin(), sectionObjects.cend(),
20223 [sourceHandle](const auto &record) {
20224 return record.handle == sourceHandle &&
20225 record.replayState ==
20226 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed;
20227 });
20228 if (sectionIt != sectionObjects.cend())
20229 return remapHandle(sourceHandle);
20230
20231 return remapEntityHandle(sourceHandle, referenceKind);
20232 };
20233 auto remapBlockOwnerHandle = [this, &referenceContext, &recordOptionalDrop,
20234 &recordRetainedReference](
20235 std::uint32_t sourceHandle,
20236 const char *referenceKind =
20237 "DWG block owner",
20238 std::uint32_t referenceOrdinal = 0,
20239 std::uint8_t wireHandleCode =
20240 DRW::DwgSoftPointer,
20241 bool recordReceipt = true) {
20242 if (sourceHandle == DRW::NoHandle)
20243 return std::uint32_t{0};
20244 std::uint32_t outputHandle = 0;
20245 const DwgWriteReferenceStatus status =
20246 resolveDwgWriteBlockOwner(sourceHandle, &outputHandle);
20247 if (status == DwgWriteReferenceStatus::LegacySpaceAlias ||
20248 status == DwgWriteReferenceStatus::Selected ||
20249 status == DwgWriteReferenceStatus::Committed) {
20250 const DwgWriteSourceKind targetKind =
20251 status == DwgWriteReferenceStatus::LegacySpaceAlias
20252 ? DwgWriteSourceKind::FixedStructural
20253 : DwgWriteSourceKind::Block;
20254 const std::uint32_t targetSourceHandle =
20255 status == DwgWriteReferenceStatus::LegacySpaceAlias ? 0
20256 : sourceHandle;
20257 if (recordReceipt && referenceContext.producerOutputHandle != 0 &&
20258 !recordRetainedReference(targetSourceHandle, outputHandle,
20259 targetKind, referenceKind,
20260 referenceOrdinal, wireHandleCode, false))
20261 return std::uint32_t{0};
20262 return outputHandle;
20263 }
20264 const char *reason = nullptr;
20265 if (status == DwgWriteReferenceStatus::Ambiguous)
20266 reason = "ambiguous target";
20267 else if (status == DwgWriteReferenceStatus::Suppressed)
20268 reason = "suppressed target";
20269 else if (status == DwgWriteReferenceStatus::Missing)
20270 reason = "missing target";
20271 if (reason != nullptr && referenceContext.producerOutputHandle != 0 &&
20272 !recordOptionalDrop(sourceHandle, status, referenceKind,
20273 referenceOrdinal, reason,
20274 DwgWriteSourceKind::Block))
20275 return std::uint32_t{0};
20276 if (status == DwgWriteReferenceStatus::Ambiguous) {
20277 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20278 "RS_FilterDXFRW: dropping ambiguous DWG %s 0x%X",
20279 referenceKind, sourceHandle);
20280 return std::uint32_t{0};
20281 }
20282 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20283 "RS_FilterDXFRW: dropping dangling DWG %s 0x%X",
20284 referenceKind, sourceHandle);
20285 return std::uint32_t{0};
20286 };
20287 const auto remapRequiredSortBlockOwnerHandle =
20288 [this, &recordRetainedReference](std::uint32_t sourceHandle) {
20289 if (sourceHandle == 0) {
20290 m_writeFailed = true;
20291 return std::uint32_t{0};
20292 }
20293 std::uint32_t outputHandle = 0;
20294 const DwgWriteReferenceStatus status =
20295 resolveDwgWriteBlockOwner(sourceHandle, &outputHandle);
20296 if ((status != DwgWriteReferenceStatus::LegacySpaceAlias &&
20297 status != DwgWriteReferenceStatus::Selected &&
20298 status != DwgWriteReferenceStatus::Committed) ||
20299 outputHandle == 0) {
20300 m_writeFailed = true;
20301 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20302 "RS_FilterDXFRW: unresolved required DWG "
20303 "SORTENTSTABLE block owner 0x%X",
20304 sourceHandle);
20305 return std::uint32_t{0};
20306 }
20307 const DwgWriteSourceKind targetKind =
20308 status == DwgWriteReferenceStatus::LegacySpaceAlias
20309 ? DwgWriteSourceKind::FixedStructural
20310 : DwgWriteSourceKind::Block;
20311 const std::uint32_t targetSourceHandle =
20312 status == DwgWriteReferenceStatus::LegacySpaceAlias
20313 ? 0
20314 : sourceHandle;
20315 if (!recordRetainedReference(targetSourceHandle, outputHandle,
20316 targetKind, "SORTENTSTABLE block owner",
20317 0, DRW::DwgSoftPointer, true))
20318 return std::uint32_t{0};
20319 return outputHandle;
20320 };
20321 auto remapObjectOwnerHandle =
20322 [this, &referenceContext, &recordOptionalDrop,
20323 &recordRetainedReference, &remapHandle](
20324 std::uint32_t sourceHandle,
20325 const char *referenceKind = "DWG object owner",
20326 std::uint32_t referenceOrdinal = 0,
20327 std::uint8_t wireHandleCode = DRW::DwgSoftPointer) {
20328 if (sourceHandle == DRW::NoHandle)
20329 return std::uint32_t{0};
20330 // A source handle can collide with a fixed block record while also
20331 // identifying a preserved dictionary. For OBJECTS owners, the
20332 // dictionary identity is authoritative; block-owned entities use
20333 // remapBlockOwnerHandle separately.
20334 const auto &dictionaries =
20335 m_graphic->dwgAdvancedMetadata().dictionaries();
20336 const bool isRemappedDictionary =
20337 hasDwgWriteHandleRemap(sourceHandle) &&
20338 std::any_of(dictionaries.cbegin(), dictionaries.cend(),
20339 [sourceHandle](const auto &record) {
20340 return record.handle == sourceHandle &&
20341 record.handle !=
20342 DRW::DwgNamedObjectsDictionaryHandle &&
20343 record.handle !=
20344 DRW::DwgAcadGroupDictionaryHandle;
20345 });
20346 if (isRemappedDictionary) {
20347 const std::uint32_t outputHandle = remapHandle(sourceHandle);
20348 if (referenceContext.producerOutputHandle != 0 &&
20349 !recordRetainedReference(
20350 sourceHandle, outputHandle, DwgWriteSourceKind::Object,
20351 referenceKind, referenceOrdinal, wireHandleCode, false))
20352 return std::uint32_t{0};
20353 return outputHandle;
20354 }
20355 std::uint32_t outputHandle = 0;
20356 const DwgWriteReferenceStatus status =
20357 resolveDwgWriteOwnerHandle(sourceHandle, &outputHandle);
20358 if (status == DwgWriteReferenceStatus::Resolved ||
20359 status == DwgWriteReferenceStatus::LegacySpaceAlias ||
20360 status == DwgWriteReferenceStatus::Selected ||
20361 status == DwgWriteReferenceStatus::Committed) {
20362 DwgWriteSourceKind targetKind = DwgWriteSourceKind::Object;
20363 std::uint32_t blockOutputHandle = 0;
20364 const DwgWriteReferenceStatus blockStatus =
20365 resolveDwgWriteBlockOwner(sourceHandle, &blockOutputHandle);
20366 const DwgWriteIdentityLookup rawIdentityStatus =
20367 lookupDwgWriteIdentity(DwgWriteSourceKind::RawCarrier,
20368 sourceHandle);
20369 if (status == DwgWriteReferenceStatus::LegacySpaceAlias) {
20370 targetKind = DwgWriteSourceKind::FixedStructural;
20371 sourceHandle = 0;
20372 } else if (blockStatus == DwgWriteReferenceStatus::Reserved ||
20373 blockStatus == DwgWriteReferenceStatus::Selected ||
20374 blockStatus == DwgWriteReferenceStatus::Committed) {
20375 targetKind = DwgWriteSourceKind::Block;
20376 } else if (rawIdentityStatus == DwgWriteIdentityLookup::Reserved ||
20377 rawIdentityStatus == DwgWriteIdentityLookup::Selected ||
20378 rawIdentityStatus == DwgWriteIdentityLookup::Committed) {
20379 targetKind = DwgWriteSourceKind::RawCarrier;
20380 }
20381 if (referenceContext.producerOutputHandle != 0 &&
20382 !recordRetainedReference(sourceHandle, outputHandle, targetKind,
20383 referenceKind, referenceOrdinal,
20384 wireHandleCode, false))
20385 return std::uint32_t{0};
20386 return outputHandle;
20387 }
20388 const char *reason = nullptr;
20389 if (status == DwgWriteReferenceStatus::Ambiguous)
20390 reason = "ambiguous target";
20391 else if (status == DwgWriteReferenceStatus::Suppressed)
20392 reason = "suppressed target";
20393 else if (status == DwgWriteReferenceStatus::Missing)
20394 reason = "missing target";
20395 if (reason != nullptr && referenceContext.producerOutputHandle != 0 &&
20396 !recordOptionalDrop(sourceHandle, status, referenceKind,
20397 referenceOrdinal, reason,
20398 DwgWriteSourceKind::Object))
20399 return std::uint32_t{0};
20400 if (status == DwgWriteReferenceStatus::Ambiguous) {
20401 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20402 "RS_FilterDXFRW: dropping ambiguous DWG %s 0x%X",
20403 referenceKind, sourceHandle);
20404 return std::uint32_t{0};
20405 }
20406 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20407 "RS_FilterDXFRW: dropping dangling DWG %s 0x%X",
20408 referenceKind, sourceHandle);
20409 return std::uint32_t{0};
20410 };
20411 const auto remapNamedTableHandle =
20412 [this, &referenceContext, &recordOptionalDrop, &recordRetainedReference,
20413 &isLiveDwgWriteReference](
20414 std::uint32_t sourceHandle,
20415 const std::map<std::uint32_t, std::uint32_t> &remap,
20416 const char *referenceKind, std::uint32_t referenceOrdinal = 0,
20417 std::uint8_t wireHandleCode = DRW::DwgHardPointer) {
20418 if (sourceHandle == DRW::NoHandle)
20419 return std::uint32_t{0};
20420 std::uint32_t outputHandle = 0;
20421 const DwgWriteReferenceStatus status =
20422 resolveDwgWriteTableRecordHandle(sourceHandle, remap,
20423 &outputHandle);
20424 if (!isLiveDwgWriteReference(status) || outputHandle == 0) {
20425 if (status == DwgWriteReferenceStatus::Missing ||
20426 status == DwgWriteReferenceStatus::Ambiguous ||
20427 status == DwgWriteReferenceStatus::Suppressed) {
20428 if (!recordOptionalDrop(
20429 sourceHandle, status, referenceKind, referenceOrdinal,
20430 status == DwgWriteReferenceStatus::Suppressed
20431 ? "suppressed target"
20432 : status == DwgWriteReferenceStatus::Ambiguous
20433 ? "ambiguous target"
20434 : "missing target",
20435 DwgWriteSourceKind::TableRecord))
20436 return std::uint32_t{0};
20437 }
20438 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20439 "RS_FilterDXFRW: dropping dangling DWG %s target "
20440 "0x%X",
20441 referenceKind, sourceHandle);
20442 return std::uint32_t{0};
20443 }
20444 // Plot settings may be emitted without a source-backed OBJECT
20445 // identity. In that case there is no producer receipt to
20446 // attach; source-backed metadata objects always have context.
20447 if (referenceContext.producerOutputHandle != 0 &&
20448 !recordRetainedReference(
20449 sourceHandle, outputHandle, DwgWriteSourceKind::TableRecord,
20450 referenceKind, referenceOrdinal, wireHandleCode, false))
20451 return std::uint32_t{0};
20452 return outputHandle;
20453 };
20454 auto remapViewHandle =
20455 [this, &remapNamedTableHandle](std::uint32_t sourceHandle,
20456 const char *referenceKind,
20457 std::uint32_t referenceOrdinal = 0) {
20458 return remapNamedTableHandle(sourceHandle, m_dwgWriteViewHandleRemap,
20459 referenceKind, referenceOrdinal,
20460 DRW::DwgHardPointer);
20461 };
20462 auto remapUcsHandle =
20463 [this, &remapNamedTableHandle](std::uint32_t sourceHandle,
20464 const char *referenceKind,
20465 std::uint32_t referenceOrdinal = 0) {
20466 return remapNamedTableHandle(sourceHandle, m_dwgWriteUcsHandleRemap,
20467 referenceKind, referenceOrdinal,
20468 DRW::DwgHardPointer);
20469 };
20470 auto remapVportHandle =
20471 [this, &remapNamedTableHandle](std::uint32_t sourceHandle,
20472 const char *referenceKind,
20473 std::uint32_t referenceOrdinal = 0) {
20474 return remapNamedTableHandle(sourceHandle, m_dwgWriteVportHandleRemap,
20475 referenceKind, referenceOrdinal,
20476 DRW::DwgSoftPointer);
20477 };
20478 auto remapTableHandle =
20479 [this, &referenceContext, &recordOptionalDrop,
20480 &recordRetainedReference](
20481 std::uint32_t sourceHandle, const std::string &sourceName,
20482 const std::map<std::string, std::uint32_t> &outputHandles,
20483 const char *referenceKind, std::uint32_t referenceOrdinal = 0,
20484 std::uint8_t wireHandleCode = DRW::DwgHardPointer) {
20485 if (sourceHandle == DRW::NoHandle)
20486 return std::uint32_t{0};
20487 if (sourceName.empty()) {
20488 (void)recordOptionalDrop(
20489 sourceHandle, DwgWriteReferenceStatus::Missing, referenceKind,
20490 referenceOrdinal, "missing source name",
20491 DwgWriteSourceKind::TableRecord);
20492 RS_DEBUGRS_Debug::instance()->print(
20493 RS_Debug::D_WARNING,
20494 "RS_FilterDXFRW: dropping DWG %s 0x%X without a source name",
20495 referenceKind, sourceHandle);
20496 return std::uint32_t{0};
20497 }
20498 const auto it = outputHandles.find(normalizeDwgTableName(sourceName));
20499 if (it == outputHandles.end()) {
20500 (void)recordOptionalDrop(
20501 sourceHandle, DwgWriteReferenceStatus::Missing, referenceKind,
20502 referenceOrdinal, "missing target",
20503 DwgWriteSourceKind::TableRecord);
20504 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20505 "RS_FilterDXFRW: dropping unresolved DWG %s '%s'",
20506 referenceKind, sourceName.c_str());
20507 return std::uint32_t{0};
20508 }
20509 if (referenceContext.producerOutputHandle != 0 &&
20510 !recordRetainedReference(
20511 sourceHandle, it->second, DwgWriteSourceKind::TableRecord,
20512 referenceKind, referenceOrdinal, wireHandleCode, false))
20513 return std::uint32_t{0};
20514 return it->second;
20515 };
20516 auto remapEntryHandle = [&remapHandle,
20517 &referenceContext](DRW_TableEntry &e) {
20518 const std::uint32_t sourceHandle = e.handle;
20519 e.handle = remapHandle(e.handle);
20520 referenceContext.producerKind = DwgWriteSourceKind::Object;
20521 referenceContext.producerSourceHandle = sourceHandle;
20522 referenceContext.producerOutputHandle = e.handle;
20523 };
20524 auto remapEntry = [&remapEntryHandle,
20525 &remapObjectOwnerHandle](DRW_TableEntry &e) {
20526 remapEntryHandle(e);
20527 if (e.parentHandle != 0)
20528 e.parentHandle = remapObjectOwnerHandle(e.parentHandle);
20529 };
20530 auto sanitizeDictionaryOwner = [this,
20531 &remapHandle](std::uint32_t &parentHandle,
20532 const char *kind) {
20533 if (parentHandle == 0)
20534 return; // The root named-object dictionary intentionally has no owner.
20535 const std::uint32_t sourceHandle = parentHandle;
20536 if (!hasDwgWriteKnownHandle(sourceHandle)) {
20537 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20538 "RS_FilterDXFRW: reparenting dangling DWG %s owner "
20539 "0x%X to root 0x%X",
20540 kind, sourceHandle,
20541 DRW::DwgNamedObjectsDictionaryHandle);
20542 parentHandle = DRW::DwgNamedObjectsDictionaryHandle;
20543 return;
20544 }
20545 parentHandle = remapHandle(sourceHandle);
20546 };
20547 const bool canWriteModernObjects = m_dwgW->getVersion() >= DRW::AC1021;
20548 // PR 13a/b/c/d — DICTIONARY (ODA fixed type 42), XRECORD (type 79),
20549 // GROUP (type 72), LAYOUT (type 82), and ACDBPLACEHOLDER (type 80)
20550 // are universally available since R2000. Their encoders gate
20551 // AC1018+-only fields (e.g., LAYOUT shadePlot* + viewportCount
20552 // RawLong32 + plotViewHandle) on `version >= DRW::AC1018`, and
20553 // the AC1015-only plotViewName branch on `version < DRW::AC1018`.
20554 // ACDBPLACEHOLDER's encoder has no version-gated body fields —
20555 // only the standard string/handle split-buffer routing on
20556 // `version > AC1018`. Encoder smoke tests cover
20557 // AC1015/AC1018/AC1024/AC1027/AC1032 round-trip (see
20558 // `[dictionary]` / `[xrecord]` / `[group]` / `[layout]` /
20559 // `[placeholder]` cases). Broaden the dispatch gate for these
20560 // families from AC1021+ to AC1015+ ahead of the long-tail
20561 // families which still need their own validation.
20562 const bool canWriteFixedTypeObjects = m_dwgW->getVersion() >= DRW::AC1015;
20563 const bool canWriteDbColor = m_dwgW->getVersion() >= DRW::AC1018;
20564 const bool canWriteAssociativeObjects = m_dwgW->getVersion() >= DRW::AC1021;
20565 auto sanitizeDictionaryEntries =
20566 [this, &metadata, &recordOptionalDrop, &recordRetainedReference,
20567 &isLiveDwgWriteReference](DRW_Dictionary &dictionary) {
20568 std::vector<DRW_Dictionary::Entry> remapped;
20569 remapped.reserve(dictionary.m_entries.size());
20570 for (std::size_t ordinal = 0; ordinal < dictionary.m_entries.size();
20571 ++ordinal) {
20572 const DRW_Dictionary::Entry &entry = dictionary.m_entries[ordinal];
20573 if (entry.m_name.empty() || entry.m_handle == 0) {
20574 m_writeFailed = true;
20575 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20576 "RS_FilterDXFRW: invalid required DWG dictionary "
20577 "entry at index %d",
20578 static_cast<int>(ordinal));
20579 return false;
20580 }
20581 std::uint32_t outputHandle = 0;
20582 const DwgWriteReferenceStatus status =
20583 resolveDwgWriteObjectHandle(entry.m_handle, &outputHandle);
20584 if (!isLiveDwgWriteReference(status) || outputHandle == 0) {
20585 // A declared raw source object that is unavailable in the target
20586 // version is different from a genuinely dangling member. Do
20587 // not silently sever that ownership edge.
20588 if (metadata.hasRawObjectForHandle(entry.m_handle)) {
20589 m_writeFailed = true;
20590 RS_DEBUGRS_Debug::instance()->print(
20591 RS_Debug::D_WARNING,
20592 "RS_FilterDXFRW: unresolved DWG DICTIONARY "
20593 "entry target 0x%X",
20594 entry.m_handle);
20595 return false;
20596 }
20597 if (status != DwgWriteReferenceStatus::Missing &&
20598 status != DwgWriteReferenceStatus::Suppressed &&
20599 status != DwgWriteReferenceStatus::Ambiguous) {
20600 m_writeFailed = true;
20601 RS_DEBUGRS_Debug::instance()->print(
20602 RS_Debug::D_WARNING,
20603 "RS_FilterDXFRW: unresolved DWG DICTIONARY "
20604 "entry target 0x%X",
20605 entry.m_handle);
20606 return false;
20607 }
20608 if (!recordOptionalDrop(
20609 entry.m_handle, status, "DICTIONARY entry",
20610 static_cast<std::uint32_t>(ordinal),
20611 status == DwgWriteReferenceStatus::Ambiguous
20612 ? "ambiguous target"
20613 : status == DwgWriteReferenceStatus::Suppressed
20614 ? "suppressed target"
20615 : "missing target"))
20616 return false;
20617 RS_DEBUGRS_Debug::instance()->print(
20618 RS_Debug::D_WARNING,
20619 "RS_FilterDXFRW: dropping dangling DWG DICTIONARY entry "
20620 "target 0x%X",
20621 entry.m_handle);
20622 continue;
20623 }
20624 if (!recordRetainedReference(
20625 entry.m_handle, outputHandle,
20626 DwgWriteSourceKind::Object, "DICTIONARY entry",
20627 static_cast<std::uint32_t>(ordinal),
20628 dictionary.dwgItemHandleCode(), true))
20629 return false;
20630 remapped.push_back({entry.m_name, outputHandle});
20631 }
20632 dictionary.m_entries = std::move(remapped);
20633 return true;
20634 };
20635 auto sanitizeObjectHandle = [this, &remapHandle, &recordOptionalDrop,
20636 &recordRetainedReference](
20637 std::uint32_t &handle, const char *kind,
20638 std::uint32_t referenceOrdinal = 0,
20639 std::uint8_t wireHandleCode =
20640 DRW::DwgSoftPointer) {
20641 if (handle == 0)
20642 return;
20643 const std::uint32_t sourceHandle = handle;
20644 std::uint32_t outputHandle = 0;
20645 DwgWriteSourceKind targetKind = DwgWriteSourceKind::Object;
20646 const DwgWriteReferenceStatus status =
20647 resolveDwgWriteCommonHandle(handle, &outputHandle, &targetKind);
20648 if (status == DwgWriteReferenceStatus::Ambiguous) {
20649 if (!recordOptionalDrop(handle, status, kind, referenceOrdinal,
20650 "ambiguous target", targetKind)) {
20651 handle = 0;
20652 return;
20653 }
20654 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20655 "RS_FilterDXFRW: dropping ambiguous DWG %s target 0x%X",
20656 kind, handle);
20657 handle = 0;
20658 return;
20659 }
20660 if (status == DwgWriteReferenceStatus::Missing ||
20661 status == DwgWriteReferenceStatus::Suppressed) {
20662 if (!recordOptionalDrop(handle, status, kind, referenceOrdinal,
20663 status == DwgWriteReferenceStatus::Suppressed
20664 ? "suppressed target"
20665 : "missing target",
20666 targetKind)) {
20667 handle = 0;
20668 return;
20669 }
20670 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20671 "RS_FilterDXFRW: dropping dangling DWG %s target 0x%X",
20672 kind, handle);
20673 handle = 0;
20674 return;
20675 }
20676 if (!recordRetainedReference(sourceHandle, outputHandle, targetKind, kind,
20677 referenceOrdinal, wireHandleCode, false)) {
20678 handle = 0;
20679 return;
20680 }
20681 handle = outputHandle != 0 ? outputHandle : remapHandle(handle);
20682 };
20683 auto sanitizeCommonObjectReferences = [&sanitizeObjectHandle](
20684 DRW_TableEntry &entry,
20685 const char *kind) {
20686 for (std::size_t index = 0; index < entry.reactorHandles.size();
20687 ++index) {
20688 sanitizeObjectHandle(entry.reactorHandles[index], kind,
20689 static_cast<std::uint32_t>(index),
20690 DRW::DwgSoftPointer);
20691 }
20692 entry.reactorHandles.erase(std::remove(entry.reactorHandles.begin(),
20693 entry.reactorHandles.end(), 0),
20694 entry.reactorHandles.end());
20695 sanitizeObjectHandle(entry.xDictHandle, kind,
20696 std::numeric_limits<std::uint32_t>::max(),
20697 DRW::DwgHardOwnership);
20698 const std::uint8_t xDictFlag =
20699 entry.xDictHandle != 0 ? entry.extensionDictionaryFlag() : 0;
20700 if (entry.reactorHandles.size() >
20701 static_cast<std::size_t>(std::numeric_limits<std::int32_t>::max())) {
20702 return false;
20703 }
20704 entry.setDwgCommonObjectState(
20705 static_cast<std::int32_t>(entry.reactorHandles.size()), xDictFlag,
20706 entry.hasDataStorageBinaryData());
20707 return true;
20708 };
20709 // Select an OBJECT carrier only for the duration of its actual writer
20710 // call. Dependent receipts must be admitted before this transaction
20711 // commits, otherwise a late validation failure leaves no retry point.
20712 const auto writeDwgObjectWithCompletion =
20713 [this, &remapHandle](
20714 RS_FilterDXFRW_DwgWriteIdentityRegistry::DwgWriteSourceKind kind,
20715 std::uint32_t sourceHandle, const auto &write, const auto &complete,
20716 std::uint32_t sourceOrdinal = 0) {
20717 const bool isGenerated = sourceHandle == 0;
20718 if (m_writeFailed ||
20719 (isGenerated ? sourceOrdinal == 0 : sourceOrdinal != 0))
20720 return false;
20721 if (!isGenerated && isDwgWriteSourceAmbiguous(sourceHandle, kind)) {
20722 m_writeFailed = true;
20723 return false;
20724 }
20725 const auto identityCheckpoint = checkpointDwgWriteIdentity();
20726 const std::uint32_t outputHandle =
20727 isGenerated ? 0 : remapHandle(sourceHandle);
20728 if (!isGenerated &&
20729 !selectDwgWriteIdentity(kind, sourceHandle, outputHandle)) {
20730 m_writeFailed = true;
20731 return false;
20732 }
20733 std::uint16_t admittedClassNumber = 0;
20734 bool classAdmissionCommitted = false;
20735 const bool hasClassAdmission = findDwgClassFrameAdmission(
20736 kind, sourceHandle, outputHandle, admittedClassNumber,
20737 classAdmissionCommitted, sourceOrdinal);
20738 std::uint16_t writerClassNumber = 0;
20739 const bool hasWriterClass =
20740 !isGenerated &&
20741 m_dwgW->getDwgClassInstanceClass(outputHandle, writerClassNumber);
20742 const bool classAdmissionMismatch =
20743 hasClassAdmission &&
20744 (classAdmissionCommitted ||
20745 (!isGenerated && admittedClassNumber != writerClassNumber));
20746 if ((!isGenerated && hasClassAdmission != hasWriterClass) ||
20747 (isGenerated && !hasClassAdmission) || classAdmissionMismatch) {
20748 restoreDwgWriteIdentity(identityCheckpoint);
20749 m_writeFailed = true;
20750 return false;
20751 }
20752 const std::uint64_t transaction =
20753 m_dwgW->beginDwgWriteTransaction(false, false, false);
20754 if (transaction == 0) {
20755 restoreDwgWriteIdentity(identityCheckpoint);
20756 m_writeFailed = true;
20757 return false;
20758 }
20759 std::uint32_t dataStorageCommitHandle = 0;
20760 std::uint32_t recordedOutputHandle = 0;
20761 bool objectOccurrencesRecorded = false;
20762 const auto rollback = [&]() {
20763 m_dwgW->clearDwgObjectFrameProvenance();
20764 (void)m_dwgW->rollbackDwgWriteTransaction(transaction);
20765 if (objectOccurrencesRecorded)
20766 (void)rollbackDwgWriteObjectOccurrences(recordedOutputHandle);
20767 if (dataStorageCommitHandle != 0) {
20768 m_dwgDataStorageCommittedKinds.erase(dataStorageCommitHandle);
20769 failDwgDataStorageCommit(dataStorageCommitHandle);
20770 }
20771 restoreDwgWriteIdentity(identityCheckpoint);
20772 m_writeFailed = true;
20773 };
20774 const DwgDataStorageCarrier::Kind actualKind =
20775 kind == DwgWriteSourceKind::RawCarrier
20776 ? DwgDataStorageCarrier::Kind::Raw
20777 : DwgDataStorageCarrier::Kind::Typed;
20778 if (!bindDwgDataStorageFrame(sourceHandle, actualKind)) {
20779 rollback();
20780 return false;
20781 }
20782 // Class-backed ordinary OBJECTS registrations are admitted by the
20783 // low-level writer before CLASSES is serialized. Bind that class
20784 // to this frame too; DataStorage bindings above already include
20785 // their token and must not be replaced.
20786 if (hasClassAdmission &&
20787 dataStorageDecisionForOwner(sourceHandle) == nullptr) {
20788 if (!m_dwgW->setDwgObjectFrameProvenance(admittedClassNumber, 0,
20789 0)) {
20790 rollback();
20791 return false;
20792 }
20793 }
20794 if (!write()) {
20795 rollback();
20796 return false;
20797 }
20798 DRW::DwgObjectFrameReceipt frame;
20799 if (!m_dwgW->getLastDwgObjectFrame(frame) || !frame.valid ||
20800 frame.objectHandle == 0) {
20801 rollback();
20802 return false;
20803 }
20804 recordedOutputHandle =
20805 isGenerated ? frame.objectHandle : outputHandle;
20806 if (isGenerated &&
20807 !bindDwgClassFrameOutput(kind, sourceHandle, sourceOrdinal,
20808 recordedOutputHandle)) {
20809 rollback();
20810 return false;
20811 }
20812 if (!recordDwgWriteObjectOccurrences(recordedOutputHandle, frame)) {
20813 rollback();
20814 return false;
20815 }
20816 if (hasClassAdmission && frame.classNumber != admittedClassNumber) {
20817 rollback();
20818 return false;
20819 }
20820 objectOccurrencesRecorded = true;
20821 if (!complete() || m_writeFailed) {
20822 rollback();
20823 return false;
20824 }
20825 if (m_dwgW->getVersion() > DRW::AC1024) {
20826 const auto decisionIt =
20827 m_dwgDataStorageReplayPlan.decisionByOwner.find(sourceHandle);
20828 if (decisionIt !=
20829 m_dwgDataStorageReplayPlan.decisionByOwner.end() &&
20830 decisionIt->second <
20831 m_dwgDataStorageReplayPlan.decisions.size()) {
20832 const auto &decision =
20833 m_dwgDataStorageReplayPlan.decisions[decisionIt->second];
20834 const DwgDataStorageCarrier::WriterCapability actualCapability =
20835 kind == DwgWriteSourceKind::RawCarrier
20836 ? DwgDataStorageCarrier::WriterCapability::Raw
20837 : DwgDataStorageCarrier::WriterCapability::Typed;
20838 const std::size_t actualCarrierIndex =
20839 kind == DwgWriteSourceKind::RawCarrier
20840 ? decision.rawCarrierIndex
20841 : decision.typedCarrierIndex;
20842 const DwgDataStorageCarrier::Kind expectedKind =
20843 decision.decision == DwgDataStorageDecision::EmitRaw
20844 ? DwgDataStorageCarrier::Kind::Raw
20845 : DwgDataStorageCarrier::Kind::Typed;
20846 dataStorageCommitHandle = sourceHandle;
20847 DwgWriteSourceKind ledgerSourceKind = DwgWriteSourceKind::Object;
20848 if (kind == DwgWriteSourceKind::RawCarrier &&
20849 decision.rawCarrierIndex <
20850 m_dwgDataStorageReplayPlan.carriers.size()) {
20851 const auto &rawCarrier =
20852 m_dwgDataStorageReplayPlan
20853 .carriers[decision.rawCarrierIndex];
20854 const auto &metadata = m_graphic->dwgAdvancedMetadata();
20855 if (rawCarrier.rawObjectIndex < metadata.rawObjects().size()) {
20856 ledgerSourceKind =
20857 metadata.rawObjects()[rawCarrier.rawObjectIndex].isEntity
20858 ? DwgWriteSourceKind::Entity
20859 : DwgWriteSourceKind::Object;
20860 }
20861 }
20862 if (actualKind != expectedKind ||
20863 actualCarrierIndex != decision.selectedCarrierIndex ||
20864 decision.selectedPhase !=
20865 DwgDataStorageCarrier::EmissionPhase::Objects ||
20866 decision.selectedCapability != actualCapability ||
20867 !recordDwgDataStorageObjectCommit(
20868 sourceHandle, actualKind, ledgerSourceKind,
20869 recordedOutputHandle, frame)) {
20870 rollback();
20871 return false;
20872 }
20873 }
20874 }
20875 if (hasClassAdmission &&
20876 !commitDwgClassFrame(kind, sourceHandle, recordedOutputHandle,
20877 admittedClassNumber, sourceOrdinal)) {
20878 rollback();
20879 return false;
20880 }
20881 if (!isGenerated &&
20882 !commitDwgWriteIdentity(kind, recordedOutputHandle)) {
20883 rollback();
20884 return false;
20885 }
20886 if (!m_dwgW->commitDwgWriteTransaction(transaction)) {
20887 rollback();
20888 return false;
20889 }
20890 return true;
20891 };
20892 const auto writeDwgObject =
20893 [&writeDwgObjectWithCompletion](DwgWriteSourceKind kind,
20894 std::uint32_t sourceHandle,
20895 const auto &write) {
20896 return writeDwgObjectWithCompletion(kind, sourceHandle, write,
20897 [] { return true; });
20898 };
20899 const auto captureDwgFieldFrame = [this](auto &candidate) {
20900 std::uint32_t outputHandle = 0;
20901 if (lookupDwgWriteIdentity(DwgWriteSourceKind::Object,
20902 candidate.sourceHandle, &outputHandle) !=
20903 DwgWriteIdentityLookup::Committed ||
20904 outputHandle == 0) {
20905 return false;
20906 }
20907 const DRW::DwgObjectFrameReceipt *frame =
20908 findDwgWriteObjectOccurrences(outputHandle);
20909 if (frame == nullptr || !frame->valid || frame->generation == 0 ||
20910 frame->objectHandle != outputHandle ||
20911 frame->version != m_dwgW->getVersion()) {
20912 return false;
20913 }
20914 candidate.outputHandle = outputHandle;
20915 candidate.frame = *frame;
20916 candidate.objectEmitted = true;
20917 return true;
20918 };
20919 auto sanitizeIndexedObjectHandle =
20920 [this, &sanitizeObjectHandle, &sanitizeOptionalEntityHandle](
20921 std::uint32_t &handle, const char *kind,
20922 std::uint32_t referenceOrdinal = 0,
20923 std::uint8_t wireHandleCode = DRW::DwgSoftPointer) {
20924 if (handle == 0)
20925 return;
20926 if (hasDwgWriteKnownHandle(handle)) {
20927 sanitizeObjectHandle(handle, kind, referenceOrdinal,
20928 wireHandleCode);
20929 return;
20930 }
20931 sanitizeOptionalEntityHandle(handle, kind, referenceOrdinal,
20932 wireHandleCode);
20933 };
20934 auto sanitizeIndexedObjectList =
20935 [&sanitizeIndexedObjectHandle](
20936 std::vector<std::uint32_t> &handles, const char *kind,
20937 std::uint8_t wireHandleCode = DRW::DwgSoftPointer) {
20938 std::vector<std::uint32_t> kept;
20939 kept.reserve(handles.size());
20940 for (std::size_t ordinal = 0; ordinal < handles.size(); ++ordinal) {
20941 std::uint32_t handle = handles[ordinal];
20942 sanitizeIndexedObjectHandle(handle, kind,
20943 static_cast<std::uint32_t>(ordinal),
20944 wireHandleCode);
20945 if (handle != 0)
20946 kept.push_back(handle);
20947 }
20948 handles = std::move(kept);
20949 };
20950 // PR 13f — custom-class families with version-clean encoders +
20951 // round-trip-grade Records dispatch on the broadened gate
20952 // (≥AC1015). Matches the same gate used in writeDwgClasses for
20953 // the CLASSES section entry registration; the entry and the
20954 // OBJECTS instance must land together or the reader's class
20955 // lookup fails and the object is silently dropped (see plan's
20956 // "Discovered during PR 13f" notes).
20957 const bool canRegisterCustomClassObjects =
20958 m_dwgW->getVersion() >= DRW::AC1015;
20959 std::size_t nativePlotSettingsCount =
20960 metadata.plotSettings().empty() ? 1u : 0u;
20961 for (std::size_t index = 0; index < metadata.plotSettings().size();
20962 ++index) {
20963 if (isDwgTypedPlotSettingsSelected(index))
20964 ++nativePlotSettingsCount;
20965 }
20966 const bool emitNativePlotSettings =
20967 canRegisterCustomClassObjects && nativePlotSettingsCount != 0;
20968 std::set<std::uint32_t> nativeSunHandles;
20969 std::set<std::uint32_t> nativeSunStudyHandles;
20970 std::set<std::uint32_t> nativeMotionPathHandles;
20971 std::set<std::uint32_t> nativeCurvePathHandles;
20972 std::set<std::uint32_t> nativePointPathHandles;
20973 std::set<std::uint32_t> nativeObjectPtrHandles;
20974 std::set<std::uint32_t> nativePartialViewingIndexHandles;
20975 std::set<std::uint32_t> nativeRenderSettingsHandles;
20976 std::set<std::uint32_t> nativeVisualStyleHandles;
20977 std::set<std::uint32_t> nativeTvDevicePropertiesHandles;
20978 std::set<std::uint32_t> nativeViewportEntityHeaderHandles;
20979 std::set<std::uint32_t> nativeVxControlHandles;
20980 std::set<std::uint32_t> nativeVxTableRecordHandles;
20981 std::set<std::uint32_t> nativePlaceholderHandles;
20982 std::set<std::uint32_t> nativeVbaProjectHandles;
20983 std::set<std::uint32_t> nativeMLeaderStyleHandles;
20984 std::set<std::uint32_t> nativeMaterialHandles;
20985 std::set<std::uint32_t> nativeLightListHandles;
20986 std::set<std::uint32_t> nativeBackgroundHandles;
20987 std::set<std::uint32_t> nativeDictionaryHandles;
20988 std::set<std::size_t> ownerGraphDictionaryIndexes;
20989 std::set<std::uint32_t> nativeXRecordHandles;
20990 std::set<std::uint32_t> nativeLayoutHandles;
20991 std::set<std::uint32_t> nativeGroupHandles;
20992 std::set<std::uint32_t> nativeMLineStyleHandles;
20993 std::set<std::uint32_t> nativeTableStyleHandles;
20994 std::set<std::uint32_t> nativeDbColorHandles;
20995 std::set<std::uint32_t> nativeDimensionAssociationHandles;
20996 std::set<std::uint32_t> nativeEvaluationGraphHandles;
20997 std::set<std::uint32_t> nativeBlockRepresentationHandles;
20998 std::set<std::uint32_t> nativeRasterVariablesHandles;
20999 std::set<std::uint32_t> nativeWipeoutVariablesHandles;
21000 std::set<std::uint32_t> nativeGeoDataHandles;
21001 std::set<std::uint32_t> nativeSpatialFilterHandles;
21002 // PR 8d.2a — five small no-storage OBJECTS families.
21003 std::set<std::uint32_t> nativeScaleHandles;
21004 std::set<std::uint32_t> nativeIDBufferHandles;
21005 std::set<std::uint32_t> nativeLayerIndexHandles;
21006 std::set<std::uint32_t> nativeSpatialIndexHandles;
21007 std::set<std::uint32_t> nativeDictionaryVarHandles;
21008 // PR 8d.2b — four larger no-storage OBJECTS families.
21009 std::set<std::uint32_t> nativeDictionaryWithDefaultHandles;
21010 std::set<std::uint32_t> nativeSortEntsTableHandles;
21011 std::set<std::uint32_t> nativeFieldListHandles;
21012 std::set<std::uint32_t> nativeFieldHandles;
21013 const auto remapRequiredFieldChildren =
21014 [this, &isLiveDwgWriteReference, &nativeFieldHandles,
21015 &recordRetainedReference](std::vector<std::uint32_t> &handles) {
21016 const DRW_Field::DwgReferencePolicy policy =
21017 DRW_Field::dwgReferencePolicy(
21018 m_dwgW->getVersion(),
21019 DRW_Field::DwgReferenceKind::ChildField);
21020 if (!policy.hasHandleStreamSlot) {
21021 m_writeFailed = true;
21022 return false;
21023 }
21024 std::vector<std::uint32_t> remapped;
21025 remapped.reserve(handles.size());
21026 for (std::size_t ordinal = 0; ordinal < handles.size(); ++ordinal) {
21027 const std::uint32_t sourceHandle = handles[ordinal];
21028 std::uint32_t outputHandle = 0;
21029 if (sourceHandle == DRW::NoHandle ||
21030 nativeFieldHandles.count(sourceHandle) == 0 ||
21031 !isLiveDwgWriteReference(
21032 resolveDwgWriteObjectHandle(sourceHandle, &outputHandle)) ||
21033 outputHandle == 0 ||
21034 !recordRetainedReference(
21035 sourceHandle, outputHandle, DwgWriteSourceKind::Object,
21036 "FIELD child", static_cast<std::uint32_t>(ordinal),
21037 policy.wireHandleCode, true)) {
21038 m_writeFailed = true;
21039 return false;
21040 }
21041 remapped.push_back(outputHandle);
21042 }
21043 handles = std::move(remapped);
21044 return true;
21045 };
21046 const auto remapRequiredFieldObjects =
21047 [this, &isLiveDwgWriteReference,
21048 &recordRetainedReference](std::vector<std::uint32_t> &handles) {
21049 const DRW_Field::DwgReferencePolicy policy =
21050 DRW_Field::dwgReferencePolicy(m_dwgW->getVersion(),
21051 DRW_Field::DwgReferenceKind::Object);
21052 if (!policy.hasHandleStreamSlot) {
21053 m_writeFailed = true;
21054 return false;
21055 }
21056 std::vector<std::uint32_t> remapped;
21057 remapped.reserve(handles.size());
21058 for (std::size_t ordinal = 0; ordinal < handles.size(); ++ordinal) {
21059 const std::uint32_t sourceHandle = handles[ordinal];
21060 std::uint32_t outputHandle = 0;
21061 DwgWriteSourceKind targetKind = DwgWriteSourceKind::Object;
21062 if (sourceHandle == DRW::NoHandle ||
21063 !isLiveDwgWriteReference(resolveDwgWriteCommonHandle(
21064 sourceHandle, &outputHandle, &targetKind)) ||
21065 outputHandle == 0 ||
21066 !recordRetainedReference(sourceHandle, outputHandle, targetKind,
21067 "FIELD object",
21068 static_cast<std::uint32_t>(ordinal),
21069 policy.wireHandleCode, true)) {
21070 m_writeFailed = true;
21071 return false;
21072 }
21073 remapped.push_back(outputHandle);
21074 }
21075 handles = std::move(remapped);
21076 return true;
21077 };
21078 const auto remapRequiredFieldCadValue = [this, &isLiveDwgWriteReference,
21079 &recordRetainedReference](
21080 DRW_CadValue &value,
21081 const char *kind,
21082 std::uint32_t ordinal) {
21083 const DRW_Field::DwgReferencePolicy policy =
21084 DRW_Field::dwgReferencePolicy(
21085 m_dwgW->getVersion(),
21086 DRW_Field::DwgReferenceKind::CadValueObjectId, &value);
21087 if (!policy.hasHandleStreamSlot)
21088 return true;
21089 const std::uint32_t sourceHandle = value.m_handle;
21090 std::uint32_t outputHandle = 0;
21091 DwgWriteSourceKind targetKind = DwgWriteSourceKind::Object;
21092 if (sourceHandle == DRW::NoHandle ||
21093 !isLiveDwgWriteReference(resolveDwgWriteCommonHandle(
21094 sourceHandle, &outputHandle, &targetKind)) ||
21095 outputHandle == 0 ||
21096 !recordRetainedReference(sourceHandle, outputHandle, targetKind, kind,
21097 ordinal, policy.wireHandleCode, true)) {
21098 m_writeFailed = true;
21099 return false;
21100 }
21101 value.m_handle = outputHandle;
21102 return true;
21103 };
21104 std::set<std::uint32_t> nativeUnderlayDefinitionHandles;
21105 std::set<std::uint32_t> nativePointCloudDefinitionHandles;
21106 std::set<std::uint32_t> nativeNavisworksModelDefinitionHandles;
21107 std::set<std::uint32_t> nativePointCloudColorMapHandles;
21108 std::set<std::uint32_t> nativeSectionHandles;
21109 std::set<std::uint32_t> nativeObjectHandles;
21110 int nativeSunObjects = 0;
21111 int nativeSunStudyObjects = 0;
21112 int nativeMotionPathObjects = 0;
21113 int nativeCurvePathObjects = 0;
21114 int nativePointPathObjects = 0;
21115 int nativeObjectPtrObjects = 0;
21116 int nativePartialViewingIndexObjects = 0;
21117 int nativeRenderSettingsObjects = 0;
21118 int nativeVisualStyleObjects = 0;
21119 int nativeTvDevicePropertiesObjects = 0;
21120 int nativeVxControlObjects = 0;
21121 int nativeVxTableRecordObjects = 0;
21122 int nativePlaceholderObjects = 0;
21123 int nativeVbaProjectObjects = 0;
21124 int nativeMLeaderStyleObjects = 0;
21125 int nativeMaterialObjects = 0;
21126 int nativeLightListObjects = 0;
21127 int nativeBackgroundObjects = 0;
21128 int nativeDictionaryObjects = 0;
21129 int nativeXRecordObjects = 0;
21130 int nativeLayoutObjects = 0;
21131 int nativeGroupObjects = 0;
21132 int nativeMLineStyleObjects = 0;
21133 int nativeTableStyleObjects = 0;
21134 int nativeDbColorObjects = 0;
21135 int nativeDimensionAssociationObjects = 0;
21136 int nativeEvaluationGraphObjects = 0;
21137 int nativeBlockRepresentationObjects = 0;
21138 int nativeRasterVariablesObjects = 0;
21139 int nativeWipeoutVariablesObjects = 0;
21140 int nativeGeoDataObjects = 0;
21141 int nativeSpatialFilterObjects = 0;
21142 int nativeScaleObjects = 0;
21143 int nativeIDBufferObjects = 0;
21144 int nativeLayerIndexObjects = 0;
21145 int nativeSpatialIndexObjects = 0;
21146 int nativeDictionaryVarObjects = 0;
21147 // PR 8d.2b — four larger no-storage OBJECTS families.
21148 int nativeDictionaryWithDefaultObjects = 0;
21149 int nativeSortEntsTableObjects = 0;
21150 int nativeFieldListObjects = 0;
21151 int nativeFieldObjects = 0;
21152 int nativeUnderlayDefinitionObjects = 0;
21153 int nativePointCloudDefinitionObjects = 0;
21154 int nativeNavisworksModelDefinitionObjects = 0;
21155 int nativePointCloudColorMapObjects = 0;
21156 int nativeSectionObjects = 0;
21157 if (emitNativePlotSettings) {
21158 LC_PlotSettings *graphicPlotSettings = m_graphic->getPlotSettings();
21159 const auto writePlotSettings = [&](DRW_PlotSettings plotSettings,
21160 bool overlayActiveSettings,
21161 std::uint32_t generatedOrdinal) {
21162 const std::uint32_t sourceHandle = plotSettings.handle;
21163 remapEntry(plotSettings);
21164 const bool plotSettingsValid = sanitizeCommonObjectReferences(
21165 plotSettings, "PLOTSETTINGS common reference");
21166 if (!plotSettingsValid)
21167 m_writeFailed = true;
21168 if (m_dwgW->getVersion() >= DRW::AC1018) {
21169 plotSettings.plotViewHandle.ref = remapViewHandle(
21170 plotSettings.plotViewHandle.ref, "PLOTSETTINGS plot VIEW");
21171 } else {
21172 plotSettings.plotViewHandle.ref = 0;
21173 }
21174 if (m_dwgW->getVersion() > DRW::AC1018) {
21175 sanitizeObjectHandle(plotSettings.shadePlotHandle.ref,
21176 "PLOTSETTINGS shade plot");
21177 } else {
21178 plotSettings.shadePlotHandle.ref = 0;
21179 }
21180 if (plotSettingsValid && overlayActiveSettings &&
21181 graphicPlotSettings != nullptr) {
21182 overlayActivePlotSettings(plotSettings, *graphicPlotSettings);
21183 }
21184 const bool wrote =
21185 plotSettingsValid &&
21186 (sourceHandle == 0
21187 ? writeDwgObjectWithCompletion(
21188 RS_FilterDXFRW_DwgWriteIdentityRegistry::
21189 DwgWriteSourceKind::GeneratedChild,
21190 0,
21191 [&plotSettings, this] {
21192 return m_dwgW->writePlotSettings(&plotSettings);
21193 },
21194 [] { return true; }, generatedOrdinal)
21195 : writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
21196 DwgWriteSourceKind::Object,
21197 sourceHandle, [&plotSettings, this] {
21198 return m_dwgW->writePlotSettings(
21199 &plotSettings);
21200 }));
21201 if (plotSettingsValid && !wrote) {
21202 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
21203 "RS_FilterDXFRW: failed to write DWG "
21204 "PLOTSETTINGS");
21205 m_writeFailed = true;
21206 }
21207 };
21208 if (metadata.plotSettings().empty()) {
21209 writePlotSettings(DRW_PlotSettings{}, true, 1);
21210 } else {
21211 for (std::size_t index = 0; index < metadata.plotSettings().size();
21212 ++index) {
21213 const DRW_PlotSettings &source = metadata.plotSettings()[index];
21214 if (!isDwgTypedPlotSettingsSelected(index))
21215 continue;
21216 const std::uint32_t generatedOrdinal =
21217 source.handle == 0 &&
21218 index < std::numeric_limits<std::uint32_t>::max()
21219 ? static_cast<std::uint32_t>(index + 1)
21220 : 0;
21221 writePlotSettings(source, index == 0, generatedOrdinal);
21222 }
21223 }
21224 }
21225 if (canWriteModernObjects) {
21226 for (const auto &record : metadata.suns()) {
21227 if (record.replayState ==
21228 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21229 record.handle != 0) {
21230 DRW_Sun sun = sunFromMetadata(record);
21231 if (hasDxfTableAppDataUnsupportedByDwg(sun)) {
21232 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
21233 "RS_FilterDXFRW: cannot convert DXF SUN "
21234 "application data to DWG");
21235 m_writeFailed = true;
21236 continue;
21237 }
21238 nativeSunHandles.insert(record.handle);
21239 }
21240 }
21241 for (const auto &record : metadata.sections()) {
21242 if (record.replayState ==
21243 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21244 record.handle != 0) {
21245 DRW_Section section = sectionFromMetadata(record);
21246 if (hasDxfTableAppDataUnsupportedByDwg(section)) {
21247 m_writeFailed = true;
21248 continue;
21249 }
21250 nativeSectionHandles.insert(record.handle);
21251 }
21252 }
21253 // ACDBPLACEHOLDER handle-set construction moved below to the
21254 // `canWriteFixedTypeObjects` block (PR 13d) — fixed type 80
21255 // is available since R2000 and the encoder is version-clean.
21256 // DICTIONARY / XRECORD / LAYOUT / GROUP handle-set construction
21257 // moved below to the `canWriteFixedTypeObjects` block
21258 // (PR 13a/b/c) so the broadened gate applies even when
21259 // canWriteModernObjects is false (AC1015/AC1018 export path).
21260 // RASTERVARIABLES / GEODATA / SPATIAL_FILTER handle-set
21261 // construction moved to the `canRegisterCustomClassObjects`
21262 // block (PR 13f).
21263 // PR 8d.2a — five small no-storage OBJECTS families.
21264 // Handle-set construction moved to the
21265 // `canRegisterCustomClassObjects` block (PR 13g).
21266 // PR 8d.2b — four larger no-storage OBJECTS families.
21267 // Handle-set construction moved to the
21268 // `canRegisterCustomClassObjects` block (PR 13h).
21269 }
21270 // PR 13f — RASTERVARIABLES / GEODATA / SPATIAL_FILTER handle-set
21271 // construction sits in its own broadened block (≥AC1015) so the
21272 // raw-replay blocker (below) skips raw bytes for these handles at
21273 // AC1015/AC1018 too.
21274 if (canRegisterCustomClassObjects) {
21275 for (const auto &record : metadata.materials()) {
21276 if (record.replayState ==
21277 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21278 record.handle != 0 &&
21279 !hasReplayableRawMaterial(metadata, record.handle,
21280 m_dwgW->getVersion())) {
21281 DRW_Material material = materialFromMetadata(record);
21282 if (hasDxfTableAppDataUnsupportedByDwg(material)) {
21283 m_writeFailed = true;
21284 continue;
21285 }
21286 nativeMaterialHandles.insert(record.handle);
21287 }
21288 }
21289 for (const auto &record : metadata.lightLists()) {
21290 if (record.replayState ==
21291 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21292 record.handle != 0 &&
21293 !hasReplayableRawLightList(
21294 metadata, record.handle, m_dwgW->getVersion(),
21295 [this](std::uint32_t handle) {
21296 return isDwgWriteOpaqueReferenceStable(handle);
21297 })) {
21298 DRW_LightList lightList = lightListFromMetadata(record);
21299 if (hasDxfTableAppDataUnsupportedByDwg(lightList)) {
21300 m_writeFailed = true;
21301 continue;
21302 }
21303 nativeLightListHandles.insert(record.handle);
21304 }
21305 }
21306 for (const auto &record : metadata.backgrounds()) {
21307 if (record.replayState ==
21308 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21309 record.handle != 0 &&
21310 !hasReplayableRawBackground(metadata, record.handle,
21311 m_dwgW->getVersion())) {
21312 DRW_Background background = backgroundFromMetadata(record);
21313 if (hasDxfTableAppDataUnsupportedByDwg(background)) {
21314 m_writeFailed = true;
21315 continue;
21316 }
21317 nativeBackgroundHandles.insert(record.handle);
21318 }
21319 }
21320 for (const auto &record : metadata.sunStudies()) {
21321 if (record.replayState ==
21322 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21323 record.handle != 0 &&
21324 !hasReplayableRawSunStudy(metadata, record.handle,
21325 m_dwgW->getVersion())) {
21326 DRW_SunStudy study = sunStudyFromMetadata(record);
21327 if (hasDxfTableAppDataUnsupportedByDwg(study)) {
21328 m_writeFailed = true;
21329 continue;
21330 }
21331 nativeSunStudyHandles.insert(record.handle);
21332 }
21333 }
21334 for (const auto &record : metadata.motionPaths()) {
21335 if (record.replayState ==
21336 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21337 record.handle != 0 &&
21338 !hasReplayableRawMotionPath(metadata, record.handle,
21339 m_dwgW->getVersion())) {
21340 DRW_MotionPath path = motionPathFromMetadata(record);
21341 if (hasDxfTableAppDataUnsupportedByDwg(path)) {
21342 m_writeFailed = true;
21343 continue;
21344 }
21345 nativeMotionPathHandles.insert(record.handle);
21346 }
21347 }
21348 for (const auto &record : metadata.curvePaths()) {
21349 if (record.replayState ==
21350 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21351 record.handle != 0 &&
21352 !hasReplayableRawCurvePath(metadata, record.handle,
21353 m_dwgW->getVersion())) {
21354 DRW_CurvePath path = curvePathFromMetadata(record);
21355 if (hasDxfTableAppDataUnsupportedByDwg(path)) {
21356 m_writeFailed = true;
21357 continue;
21358 }
21359 nativeCurvePathHandles.insert(record.handle);
21360 }
21361 }
21362 for (const auto &record : metadata.pointPaths()) {
21363 if (record.replayState ==
21364 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21365 record.handle != 0 &&
21366 !hasReplayableRawPointPath(metadata, record.handle,
21367 m_dwgW->getVersion())) {
21368 DRW_PointPath path = pointPathFromMetadata(record);
21369 if (hasDxfTableAppDataUnsupportedByDwg(path)) {
21370 m_writeFailed = true;
21371 continue;
21372 }
21373 nativePointPathHandles.insert(record.handle);
21374 }
21375 }
21376 for (const auto &record : metadata.objectPtrs()) {
21377 if (record.replayState ==
21378 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21379 record.handle != 0 &&
21380 !hasReplayableRawObjectPtr(metadata, record.handle,
21381 m_dwgW->getVersion())) {
21382 DRW_ObjectPtr object = objectPtrFromMetadata(record);
21383 if (hasDxfTableAppDataUnsupportedByDwg(object)) {
21384 m_writeFailed = true;
21385 continue;
21386 }
21387 nativeObjectPtrHandles.insert(record.handle);
21388 }
21389 }
21390 for (const auto &record : metadata.partialViewingIndexes()) {
21391 if (record.replayState ==
21392 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21393 record.handle != 0 &&
21394 !hasReplayableRawPartialViewingIndex(metadata, record.handle,
21395 m_dwgW->getVersion())) {
21396 DRW_PartialViewingIndex index =
21397 partialViewingIndexFromMetadata(record);
21398 if (hasDxfTableAppDataUnsupportedByDwg(index)) {
21399 m_writeFailed = true;
21400 continue;
21401 }
21402 nativePartialViewingIndexHandles.insert(record.handle);
21403 }
21404 }
21405 for (const auto &record : metadata.renderSettings()) {
21406 if (record.replayState ==
21407 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21408 record.handle != 0 &&
21409 !hasReplayableRawRenderSettings(
21410 metadata, record.handle, record.kind, m_dwgW->getVersion())) {
21411 DRW_RenderSettings settings = renderSettingsFromMetadata(record);
21412 if (hasDxfTableAppDataUnsupportedByDwg(settings)) {
21413 m_writeFailed = true;
21414 continue;
21415 }
21416 nativeRenderSettingsHandles.insert(record.handle);
21417 }
21418 }
21419 for (const auto &record : metadata.visualStyles()) {
21420 if (record.replayState ==
21421 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21422 record.handle != 0 &&
21423 !hasReplayableRawVisualStyle(metadata, record.handle,
21424 m_dwgW->getVersion())) {
21425 DRW_VisualStyle style = visualStyleFromMetadata(record);
21426 if (hasDxfTableAppDataUnsupportedByDwg(style)) {
21427 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
21428 "RS_FilterDXFRW: cannot convert DXF VISUALSTYLE "
21429 "application data to DWG");
21430 m_writeFailed = true;
21431 continue;
21432 }
21433 nativeVisualStyleHandles.insert(record.handle);
21434 }
21435 }
21436 for (const auto &record : metadata.tvDeviceProperties()) {
21437 if (record.replayState ==
21438 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21439 record.handle != 0) {
21440 DRW_TvDeviceProperties properties =
21441 tvDevicePropertiesFromMetadata(record);
21442 if (hasDxfTableAppDataUnsupportedByDwg(properties)) {
21443 m_writeFailed = true;
21444 continue;
21445 }
21446 nativeTvDevicePropertiesHandles.insert(record.handle);
21447 }
21448 }
21449 for (const auto &record : metadata.vxControls()) {
21450 if (record.replayState ==
21451 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21452 record.handle != 0 &&
21453 !hasReplayableRawVxControl(metadata, record.handle,
21454 m_dwgW->getVersion())) {
21455 DRW_VxControl control = vxControlFromMetadata(record);
21456 if (hasDxfTableAppDataUnsupportedByDwg(control)) {
21457 m_writeFailed = true;
21458 continue;
21459 }
21460 nativeVxControlHandles.insert(record.handle);
21461 }
21462 }
21463 for (const auto &record : metadata.vxTableRecords()) {
21464 if (record.replayState ==
21465 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21466 record.handle != 0 &&
21467 !hasReplayableRawVxTableRecord(metadata, record.handle,
21468 m_dwgW->getVersion())) {
21469 DRW_VxTableRecord tableRecord = vxTableRecordFromMetadata(record);
21470 if (hasDxfTableAppDataUnsupportedByDwg(tableRecord)) {
21471 m_writeFailed = true;
21472 continue;
21473 }
21474 nativeVxTableRecordHandles.insert(record.handle);
21475 }
21476 }
21477 // RASTERVARIABLES (AcDbRasterVariables, custom class 505) —
21478 // round-trip-grade RasterVariablesRecord captures every encoder
21479 // field (classVersion, imageFrame, imageQuality, units). PR 8d.1.
21480 for (const auto &record : metadata.rasterVariables()) {
21481 if (record.replayState ==
21482 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21483 record.handle != 0) {
21484 DRW_RasterVariables rv = rasterVariablesFromMetadata(record);
21485 if (hasDxfTableAppDataUnsupportedByDwg(rv)) {
21486 m_writeFailed = true;
21487 continue;
21488 }
21489 nativeRasterVariablesHandles.insert(record.handle);
21490 }
21491 }
21492 // WIPEOUTVARIABLES (AcDbWipeoutVariables, custom class 529).
21493 for (const auto &record : metadata.wipeoutVariables()) {
21494 if (record.replayState ==
21495 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21496 record.handle != 0) {
21497 DRW_WipeoutVariables wv = wipeoutVariablesFromMetadata(record);
21498 if (hasDxfTableAppDataUnsupportedByDwg(wv)) {
21499 m_writeFailed = true;
21500 continue;
21501 }
21502 nativeWipeoutVariablesHandles.insert(record.handle);
21503 }
21504 }
21505 // GEODATA (AcDbGeoData, custom class 506) — round-trip-grade
21506 // GeoDataRecord extended in PR 8d.1c to capture all encoder
21507 // fields (designPoint, referencePoint, up/north direction,
21508 // scale estimation, sea-level correction, observation tags,
21509 // mesh points + faces).
21510 for (const auto &record : metadata.geoData()) {
21511 if (record.replayState ==
21512 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21513 record.handle != 0) {
21514 DRW_GeoData gd = geoDataFromMetadata(record);
21515 if (hasDxfTableAppDataUnsupportedByDwg(gd)) {
21516 m_writeFailed = true;
21517 continue;
21518 }
21519 nativeGeoDataHandles.insert(record.handle);
21520 }
21521 }
21522 // SPATIAL_FILTER (AcDbSpatialFilter, custom class 507) —
21523 // round-trip-grade SpatialFilterRecord extended in PR 8d.1d to
21524 // capture boundary points + 4x3 clip transform matrices.
21525 for (const auto &record : metadata.spatialFilters()) {
21526 if (record.replayState ==
21527 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21528 record.handle != 0) {
21529 DRW_SpatialFilter sf = spatialFilterFromMetadata(record);
21530 if (hasDxfTableAppDataUnsupportedByDwg(sf)) {
21531 m_writeFailed = true;
21532 continue;
21533 }
21534 nativeSpatialFilterHandles.insert(record.handle);
21535 }
21536 }
21537 // PR 13g — SCALE / IDBUFFER / LAYER_INDEX / SPATIAL_INDEX /
21538 // DICTIONARYVAR (custom classes 508-512) handle-set
21539 // construction.
21540 for (const auto &record : metadata.scales()) {
21541 if (record.replayState ==
21542 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21543 record.handle != 0) {
21544 DRW_Scale scale = scaleFromMetadata(record);
21545 if (hasDxfTableAppDataUnsupportedByDwg(scale)) {
21546 m_writeFailed = true;
21547 continue;
21548 }
21549 nativeScaleHandles.insert(record.handle);
21550 }
21551 }
21552 for (const auto &record : metadata.idBuffers()) {
21553 if (record.replayState ==
21554 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21555 record.handle != 0) {
21556 DRW_IDBuffer idBuffer = idBufferFromMetadata(record);
21557 if (hasDxfTableAppDataUnsupportedByDwg(idBuffer)) {
21558 m_writeFailed = true;
21559 continue;
21560 }
21561 nativeIDBufferHandles.insert(record.handle);
21562 }
21563 }
21564 for (const auto &record : metadata.layerIndexes()) {
21565 if (record.replayState ==
21566 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21567 record.handle != 0) {
21568 DRW_LayerIndex layerIndex = layerIndexFromMetadata(record);
21569 if (hasDxfTableAppDataUnsupportedByDwg(layerIndex)) {
21570 m_writeFailed = true;
21571 continue;
21572 }
21573 nativeLayerIndexHandles.insert(record.handle);
21574 }
21575 }
21576 for (const auto &record : metadata.spatialIndexes()) {
21577 if (record.replayState ==
21578 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21579 record.handle != 0 &&
21580 !hasReplayableRawSpatialIndex(metadata, record.handle,
21581 m_dwgW->getVersion())) {
21582 DRW_SpatialIndex spatialIndex = spatialIndexFromMetadata(record);
21583 if (hasDxfTableAppDataUnsupportedByDwg(spatialIndex)) {
21584 m_writeFailed = true;
21585 continue;
21586 }
21587 nativeSpatialIndexHandles.insert(record.handle);
21588 }
21589 }
21590 for (const auto &record : metadata.dictionaryVars()) {
21591 if (record.replayState ==
21592 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21593 record.handle != 0) {
21594 DRW_DictionaryVar dictionaryVar = dictionaryVarFromMetadata(record);
21595 if (hasDxfTableAppDataUnsupportedByDwg(dictionaryVar)) {
21596 m_writeFailed = true;
21597 continue;
21598 }
21599 nativeDictionaryVarHandles.insert(record.handle);
21600 }
21601 }
21602 // PR 13h — DICTIONARYWDFLT / SORTENTSTABLE / FIELDLIST / FIELD
21603 // (custom classes 513-516) handle-set construction.
21604 for (const auto &record : metadata.dictionariesWithDefault()) {
21605 if (record.replayState ==
21606 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21607 record.handle != 0) {
21608 if (!canWriteDwgDictionaryWithDefaultRecord(metadata, record,
21609 m_dwgW->getVersion())) {
21610 if (!metadata.hasRawObjectForHandle(record.handle))
21611 m_writeFailed = true;
21612 continue;
21613 }
21614 nativeDictionaryWithDefaultHandles.insert(record.handle);
21615 }
21616 }
21617 for (const auto &record : metadata.sortEntsTables()) {
21618 if (record.replayState ==
21619 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21620 record.handle != 0) {
21621 DRW_SortEntsTable sortEntsTable = sortEntsTableFromMetadata(record);
21622 if (hasDxfTableAppDataUnsupportedByDwg(sortEntsTable)) {
21623 m_writeFailed = true;
21624 continue;
21625 }
21626 const bool hasUnresolvedSortKey = std::any_of(
21627 sortEntsTable.m_sortHandles.cbegin(),
21628 sortEntsTable.m_sortHandles.cend(),
21629 [this, &isLiveDwgWriteReference](std::uint32_t handle) {
21630 if (handle == DRW::NoHandle)
21631 return false;
21632 std::uint32_t outputHandle = 0;
21633 DwgWriteSourceKind targetKind = DwgWriteSourceKind::Object;
21634 const DwgWriteReferenceStatus status =
21635 resolveDwgWriteCommonHandle(handle, &outputHandle,
21636 &targetKind);
21637 return !isLiveDwgWriteReference(status) || outputHandle == 0;
21638 });
21639 if (hasReplayableRawSortEntsTable(metadata, record.handle,
21640 m_dwgW->getVersion()) &&
21641 hasUnresolvedSortKey) {
21642 // Keep the validated source frame authoritative when the typed
21643 // view contains a dangling sort key.
21644 continue;
21645 }
21646 nativeSortEntsTableHandles.insert(record.handle);
21647 }
21648 }
21649 for (const auto &candidate : m_dwgFieldWritePlan.fieldCandidates) {
21650 if (candidate.classAdmitted)
21651 nativeFieldHandles.insert(candidate.sourceHandle);
21652 }
21653 for (const auto &candidate : m_dwgFieldWritePlan.fieldListCandidates) {
21654 if (candidate.classAdmitted)
21655 nativeFieldListHandles.insert(candidate.sourceHandle);
21656 }
21657 for (const auto &record : metadata.underlayDefinitions()) {
21658 if (record.replayState ==
21659 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21660 record.handle != 0) {
21661 DRW_UnderlayDefinition definition =
21662 underlayDefinitionFromMetadata(record);
21663 if (hasDxfTableAppDataUnsupportedByDwg(definition)) {
21664 RS_DEBUGRS_Debug::instance()->print(
21665 RS_Debug::D_WARNING,
21666 "RS_FilterDXFRW: cannot convert DXF UNDERLAYDEFINITION "
21667 "application data to DWG");
21668 m_writeFailed = true;
21669 continue;
21670 }
21671 nativeUnderlayDefinitionHandles.insert(record.handle);
21672 }
21673 }
21674 for (const auto &record : metadata.pointCloudDefinitions()) {
21675 if (record.replayState ==
21676 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21677 record.handle != 0) {
21678 DRW_PointCloudDef definition =
21679 pointCloudDefinitionFromMetadata(record);
21680 if (hasDxfTableAppDataUnsupportedByDwg(definition)) {
21681 RS_DEBUGRS_Debug::instance()->print(
21682 RS_Debug::D_WARNING,
21683 "RS_FilterDXFRW: cannot convert DXF POINTCLOUDDEFINITION "
21684 "application data to DWG");
21685 m_writeFailed = true;
21686 continue;
21687 }
21688 nativePointCloudDefinitionHandles.insert(record.handle);
21689 }
21690 }
21691 for (const auto &record : metadata.navisworksModelDefinitions()) {
21692 if (record.replayState ==
21693 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21694 record.handle != 0) {
21695 DRW_NavisworksModelDef definition =
21696 navisworksModelDefinitionFromMetadata(record);
21697 if (hasDxfTableAppDataUnsupportedByDwg(definition)) {
21698 RS_DEBUGRS_Debug::instance()->print(
21699 RS_Debug::D_WARNING,
21700 "RS_FilterDXFRW: cannot convert DXF NAVISWORKSMODELDEF "
21701 "application data to DWG");
21702 m_writeFailed = true;
21703 continue;
21704 }
21705 nativeNavisworksModelDefinitionHandles.insert(record.handle);
21706 }
21707 }
21708 for (const auto &record : metadata.pointCloudColorMaps()) {
21709 if (record.replayState ==
21710 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21711 record.handle != 0) {
21712 DRW_PointCloudColorMap colorMap =
21713 pointCloudColorMapFromMetadata(record);
21714 if (hasDxfTableAppDataUnsupportedByDwg(colorMap)) {
21715 RS_DEBUGRS_Debug::instance()->print(
21716 RS_Debug::D_WARNING,
21717 "RS_FilterDXFRW: cannot convert DXF POINTCLOUDCOLORMAP "
21718 "application data to DWG");
21719 m_writeFailed = true;
21720 continue;
21721 }
21722 nativePointCloudColorMapHandles.insert(record.handle);
21723 }
21724 }
21725 }
21726 // PR 13a/b/c — DICTIONARY / XRECORD / GROUP / LAYOUT handle-set
21727 // construction sits in its own broadened block (≥ AC1015) so the
21728 // raw-replay blocker (below) skips raw bytes for these handles at
21729 // AC1015/AC1018 too.
21730 if (canWriteFixedTypeObjects) {
21731 for (const auto &record : metadata.viewportEntityHeaders()) {
21732 if (record.replayState ==
21733 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21734 record.handle != 0) {
21735 nativeViewportEntityHeaderHandles.insert(record.handle);
21736 }
21737 }
21738 for (const auto &record : metadata.placeholders()) {
21739 if (record.replayState ==
21740 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21741 record.handle != 0) {
21742 DRW_AcDbPlaceholder placeholder = placeholderFromMetadata(record);
21743 if (hasDxfTableAppDataUnsupportedByDwg(placeholder)) {
21744 m_writeFailed = true;
21745 continue;
21746 }
21747 nativePlaceholderHandles.insert(record.handle);
21748 }
21749 }
21750 for (const auto &project : metadata.vbaProjects()) {
21751 if (project.handle != 0 &&
21752 !hasReplayableRawVbaProject(metadata, project.handle,
21753 m_dwgW->getVersion())) {
21754 if (project.hasDataStorageBinaryData() ||
21755 hasDxfTableAppDataUnsupportedByDwg(project)) {
21756 m_writeFailed = true;
21757 continue;
21758 }
21759 nativeVbaProjectHandles.insert(project.handle);
21760 }
21761 }
21762 for (const auto &record : metadata.dictionaries()) {
21763 if (record.replayState ==
21764 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21765 record.handle != 0 &&
21766 record.handle != DRW::DwgNamedObjectsDictionaryHandle &&
21767 record.handle != DRW::DwgAcadGroupDictionaryHandle) {
21768 if (!canWriteDwgDictionaryRecord(metadata, record,
21769 m_dwgW->getVersion())) {
21770 if (!metadata.hasRawObjectForHandle(record.handle))
21771 m_writeFailed = true;
21772 continue;
21773 }
21774 nativeDictionaryHandles.insert(record.handle);
21775 }
21776 }
21777 for (const auto &record : metadata.xrecords()) {
21778 if (record.replayState ==
21779 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21780 record.handle != 0) {
21781 nativeXRecordHandles.insert(record.handle);
21782 }
21783 }
21784 for (const auto &record : metadata.groups()) {
21785 if (record.replayState ==
21786 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21787 record.handle != 0) {
21788 nativeGroupHandles.insert(record.handle);
21789 }
21790 }
21791 for (const auto &record : metadata.mlineStyles()) {
21792 if (record.replayState ==
21793 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21794 record.handle != 0) {
21795 nativeMLineStyleHandles.insert(record.handle);
21796 }
21797 }
21798 for (const auto &record : metadata.layouts()) {
21799 if (record.replayState ==
21800 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21801 record.handle != 0) {
21802 nativeLayoutHandles.insert(record.handle);
21803 }
21804 }
21805 }
21806 if (canRegisterCustomClassObjects && m_dwgW->getVersion() <= DRW::AC1021) {
21807 for (const auto &record : metadata.tables()) {
21808 if (record.replayState !=
21809 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
21810 record.handle == 0 || !record.nativeTableStyle ||
21811 !isDwgDataStorageTypedSelected(record.handle))
21812 continue;
21813 DRW_TableStyle style = *record.nativeTableStyle;
21814 if (hasDxfTableAppDataUnsupportedByDwg(style) ||
21815 !style.validateDwgPayload(m_dwgW->getVersion())) {
21816 m_writeFailed = true;
21817 continue;
21818 }
21819 // The class was admitted before CLASSES was emitted. The
21820 // OBJECTS pass only selects the matching typed carrier.
21821 nativeTableStyleHandles.insert(record.handle);
21822 }
21823 }
21824 if (canRegisterCustomClassObjects) {
21825 for (const auto &record : metadata.mleaderStyles()) {
21826 if (record.replayState ==
21827 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21828 record.handle != 0 &&
21829 !hasReplayableRawMLeaderStyle(metadata, record.handle,
21830 m_dwgW->getVersion())) {
21831 nativeMLeaderStyleHandles.insert(record.handle);
21832 }
21833 }
21834 }
21835 if (canWriteDbColor) {
21836 for (const auto &record : metadata.dbColors()) {
21837 if (record.replayState ==
21838 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21839 record.handle != 0) {
21840 DRW_DbColor color = dbColorFromMetadata(record);
21841 if (hasDxfTableAppDataUnsupportedByDwg(color)) {
21842 m_writeFailed = true;
21843 continue;
21844 }
21845 nativeDbColorHandles.insert(record.handle);
21846 }
21847 }
21848 }
21849 if (canWriteAssociativeObjects) {
21850 for (const auto &record : metadata.dimensionAssociations()) {
21851 if (record.replayState ==
21852 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21853 record.handle != 0) {
21854 const DRW_DimensionAssociation association =
21855 dimensionAssociationFromMetadata(record);
21856 if (hasDxfTableAppDataUnsupportedByDwg(association) ||
21857 !canWriteNativeDimensionAssociation(association)) {
21858 if (hasDxfTableAppDataUnsupportedByDwg(association))
21859 m_writeFailed = true;
21860 continue;
21861 }
21862 nativeDimensionAssociationHandles.insert(record.handle);
21863 }
21864 }
21865 for (const auto &record : metadata.evaluationGraphs()) {
21866 if (record.replayState ==
21867 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21868 record.handle != 0) {
21869 const DRW_EvaluationGraph graph = evaluationGraphFromMetadata(record);
21870 if (hasDxfTableAppDataUnsupportedByDwg(graph) ||
21871 !canWriteNativeEvaluationGraph(graph)) {
21872 if (hasDxfTableAppDataUnsupportedByDwg(graph))
21873 m_writeFailed = true;
21874 continue;
21875 }
21876 nativeEvaluationGraphHandles.insert(record.handle);
21877 }
21878 }
21879 }
21880
21881 if (canWriteFixedTypeObjects) {
21882 for (const auto &record : metadata.blockRepresentationData()) {
21883 if (record.replayState ==
21884 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
21885 record.handle != 0 &&
21886 (m_dwgW->getVersion() <= DRW::AC1024 || !record.hasDsData)) {
21887 DRW_BlockRepresentationData data =
21888 blockRepresentationFromMetadata(record);
21889 if (hasDxfTableAppDataUnsupportedByDwg(data)) {
21890 m_writeFailed = true;
21891 continue;
21892 }
21893 nativeBlockRepresentationHandles.insert(record.handle);
21894 }
21895 }
21896 }
21897
21898 // Handles reserved above are not necessarily emitted: version gates,
21899 // replay state, and native/raw replacement policy can skip a record.
21900 // Only records selected by the matching writer paths are valid future
21901 // OBJECTS targets; entity references are outside this object-only set.
21902 auto markNativeHandles = [this, &nativeObjectHandles](auto &handles) {
21903 // A raw DataStorage decision replaces its typed metadata carrier.
21904 // Remove it before publishing the native set, otherwise the raw
21905 // replay loop sees an artificial same-handle collision and the
21906 // typed OBJECTS loop can emit a second instance.
21907 for (auto it = handles.begin(); it != handles.end();) {
21908 if (isDwgDataStorageRawSelected(*it))
21909 it = handles.erase(it);
21910 else
21911 ++it;
21912 }
21913 m_dwgWriteKnownHandles.insert(handles.begin(), handles.end());
21914 nativeObjectHandles.insert(handles.begin(), handles.end());
21915 };
21916 markNativeHandles(nativeSunHandles);
21917 markNativeHandles(nativeSunStudyHandles);
21918 markNativeHandles(nativeMotionPathHandles);
21919 markNativeHandles(nativeCurvePathHandles);
21920 markNativeHandles(nativePointPathHandles);
21921 markNativeHandles(nativeObjectPtrHandles);
21922 markNativeHandles(nativePartialViewingIndexHandles);
21923 markNativeHandles(nativeRenderSettingsHandles);
21924 markNativeHandles(nativeVisualStyleHandles);
21925 markNativeHandles(nativeTvDevicePropertiesHandles);
21926 markNativeHandles(nativeViewportEntityHeaderHandles);
21927 markNativeHandles(nativeVxControlHandles);
21928 markNativeHandles(nativeVxTableRecordHandles);
21929 markNativeHandles(nativePlaceholderHandles);
21930 markNativeHandles(nativeVbaProjectHandles);
21931 markNativeHandles(nativeMLeaderStyleHandles);
21932 markNativeHandles(nativeMaterialHandles);
21933 markNativeHandles(nativeLightListHandles);
21934 markNativeHandles(nativeDictionaryHandles);
21935 markNativeHandles(nativeXRecordHandles);
21936 markNativeHandles(nativeLayoutHandles);
21937 markNativeHandles(nativeGroupHandles);
21938 markNativeHandles(nativeMLineStyleHandles);
21939 markNativeHandles(nativeTableStyleHandles);
21940 markNativeHandles(nativeDbColorHandles);
21941 markNativeHandles(nativeDimensionAssociationHandles);
21942 markNativeHandles(nativeEvaluationGraphHandles);
21943 markNativeHandles(nativeBlockRepresentationHandles);
21944 markNativeHandles(nativeRasterVariablesHandles);
21945 markNativeHandles(nativeWipeoutVariablesHandles);
21946 markNativeHandles(nativeGeoDataHandles);
21947 markNativeHandles(nativeSpatialFilterHandles);
21948 markNativeHandles(nativeScaleHandles);
21949 markNativeHandles(nativeIDBufferHandles);
21950 markNativeHandles(nativeLayerIndexHandles);
21951 markNativeHandles(nativeSpatialIndexHandles);
21952 markNativeHandles(nativeDictionaryVarHandles);
21953 markNativeHandles(nativeDictionaryWithDefaultHandles);
21954 markNativeHandles(nativeSortEntsTableHandles);
21955 markNativeHandles(nativeFieldListHandles);
21956 markNativeHandles(nativeFieldHandles);
21957 markNativeHandles(nativeUnderlayDefinitionHandles);
21958 markNativeHandles(nativePointCloudDefinitionHandles);
21959 markNativeHandles(nativeNavisworksModelDefinitionHandles);
21960 markNativeHandles(nativePointCloudColorMapHandles);
21961 markNativeHandles(nativeSectionHandles);
21962
21963 for (const auto &entry :
21964 m_dwgExportAdmissionPlan.opaqueTableRecordIndexes) {
21965 const auto &graph = entry.second.ownerGraph;
21966 if (!graph.has_value())
21967 continue;
21968 if (graph->rootDictionaryIndex >= metadata.dictionaries().size() ||
21969 graph->childDictionaryIndex >= metadata.dictionaries().size() ||
21970 graph->rootDictionaryIndex == graph->childDictionaryIndex ||
21971 nativeDictionaryHandles.count(graph->childOutputHandle) == 0 ||
21972 !ownerGraphDictionaryIndexes.insert(graph->rootDictionaryIndex)
21973 .second ||
21974 !ownerGraphDictionaryIndexes.insert(graph->childDictionaryIndex)
21975 .second) {
21976 m_writeFailed = true;
21977 m_dwgW->markDwgWriteFailure();
21978 return;
21979 }
21980 }
21981
21982 bool hasBlockedReplay = false;
21983 int blockedInvalidated = 0;
21984 int blockedReplaced = 0;
21985 int blockedEntityReplay = 0;
21986 int blockedMissingRawBytes = 0;
21987 int blockedMissingClassMetadata = 0;
21988 int blockedWriterRejected = 0;
21989 int blockedVersionMismatch = 0;
21990 int blockedFixedHandle = 0;
21991 int blockedDataStorage = 0;
21992 int replayedObjects = 0;
21993 int replayedSections = 0;
21994 std::set<std::uint32_t> replayedRawObjectHandles;
21995 const LC_DwgAdvancedMetadata::RawObjectFamilyCounts rawFamilyCounts =
21996 metadata.rawObjectFamilyCounts();
21997 const LC_DwgAdvancedMetadata::TableNativeWriterBlockerCounts tableBlockers =
21998 metadata.tableNativeWriterBlockerCounts(m_dwgW->getVersion());
21999 const LC_DwgAdvancedMetadata::MLeaderWriterBlockerCounts mleaderBlockers =
22000 metadata.mleaderWriterBlockerCounts();
22001 const LC_DwgAdvancedMetadata::AdvancedEntityWriterBlockerCounts
22002 advancedEntityBlockers =
22003 metadata.advancedEntityWriterBlockerCounts(m_dwgW->getVersion());
22004 const LC_DwgAdvancedMetadata::ModelerPayloadCounts modelerPayloads =
22005 metadata.modelerPayloadCounts();
22006 const LC_DwgAdvancedMetadata::MeshWriterBlockerCounts meshBlockers =
22007 metadata.meshWriterBlockerCounts();
22008 const LC_DwgAdvancedMetadata::ExternalReferenceCounts externalRefs =
22009 metadata.externalReferenceCounts();
22010 const LC_DwgAdvancedMetadata::ShapeOleWriterBlockerCounts shapeOleBlockers =
22011 metadata.shapeOleWriterBlockerCounts();
22012 const LC_DwgAdvancedMetadata::VisualMetadataWriterBlockerCounts
22013 visualBlockers =
22014 metadata.visualMetadataWriterBlockerCounts(m_dwgW->getVersion());
22015 const LC_DwgAdvancedMetadata::AssociativeShellCounts associativeShells =
22016 metadata.associativeShellCounts();
22017 const LC_DwgAdvancedMetadata::AssociativePrefixCounts associativePrefixes =
22018 metadata.associativePrefixCounts();
22019 LC_DwgAdvancedMetadata::GraphReplayPolicyCounts graphReplayPolicy =
22020 metadata.graphReplayPolicyCounts();
22021 const bool replaceViewportEntityHeaderControl =
22022 m_dwgW->getVersion() < DRW::AC1018 &&
22023 !nativeViewportEntityHeaderHandles.empty();
22024 using DataStorageCarrier = DwgDataStorageCarrier;
22025 const auto &dataStorageCarriers = m_dwgDataStorageReplayPlan.carriers;
22026 const auto rawCarrier = [&metadata](const DataStorageCarrier &carrier)
22027 -> const LC_DwgAdvancedMetadata::RawObjectRecord * {
22028 if (carrier.capability != DataStorageCarrier::WriterCapability::Raw ||
22029 carrier.rawObjectIndex >= metadata.rawObjects().size())
22030 return nullptr;
22031 return &metadata.rawObjects()[carrier.rawObjectIndex];
22032 };
22033 auto isParsedDataStorageSection = [&metadata](const auto &section) {
22034 return std::any_of(metadata.dataStorages().cbegin(),
22035 metadata.dataStorages().cend(),
22036 [&section](const auto &storage) {
22037 return normalizeDwgTableName(storage.name) ==
22038 normalizeDwgTableName(section.name) &&
22039 storage.version == section.version;
22040 });
22041 };
22042
22043 const auto &replayableDataStorageSectionIndexes =
22044 m_dwgDataStorageReplayPlan.rawSectionIndexes;
22045 std::set<std::uint32_t> replayableRawDataStorageHandles;
22046 bool dataStorageLedgerValid = m_dwgDataStorageReplayPlan.valid;
22047 const auto targetDwgVersion = m_dwgW->getVersion();
22048 const bool targetSupportsDataStorage = targetDwgVersion > DRW::AC1024;
22049 const auto hasPlannedTypedClass =
22050 [this](const DwgDataStorageCarrier &carrier) {
22051 return carrier.className.empty() ||
22052 hasDwgDataStorageClassAdmission(
22053 carrier.ownerHandle, DwgDataStorageCarrier::Kind::Typed);
22054 };
22055 const auto hasPlannedRawClass =
22056 [this](const DwgDataStorageCarrier &carrier,
22057 const LC_DwgAdvancedMetadata::RawObjectRecord *raw) {
22058 return raw == nullptr || !raw->isCustomClass ||
22059 hasDwgDataStorageClassAdmission(
22060 carrier.ownerHandle, DwgDataStorageCarrier::Kind::Raw);
22061 };
22062 if (targetSupportsDataStorage) {
22063 // The frozen plan owns identity and preferred-record selection.
22064 // OBJECTS only chooses between the already-approved typed and raw
22065 // carriers after native class admission is known.
22066 for (const auto &decision : m_dwgDataStorageReplayPlan.decisions) {
22067 bool nativeTyped = false;
22068 bool replayableRaw = false;
22069 if (decision.typedCarrierIndex !=
22070 std::numeric_limits<std::size_t>::max()) {
22071 const auto *carrierDecision =
22072 dataStorageDecisionForCarrier(decision.typedCarrierIndex);
22073 if (carrierDecision == &decision &&
22074 decision.selectedCarrierIndex == decision.typedCarrierIndex) {
22075 const auto &carrier =
22076 dataStorageCarriers[decision.typedCarrierIndex];
22077 nativeTyped =
22078 carrier.ownerHandle != 0 &&
22079 m_dwgWriteHandleRemap.count(carrier.ownerHandle) == 0 &&
22080 m_dwgWriteEntityHandleRemap.count(carrier.ownerHandle) == 0 &&
22081 m_dwgWriteDuplicateEntityHandles.count(carrier.ownerHandle) ==
22082 0 &&
22083 nativeObjectHandles.count(carrier.ownerHandle) != 0
22084 // Custom classes are recorded explicitly by the
22085 // CLASSES phase. Fixed object types have no class
22086 // token, so they do not require class admission.
22087 && hasPlannedTypedClass(carrier);
22088 }
22089 }
22090 if (decision.rawCarrierIndex !=
22091 std::numeric_limits<std::size_t>::max()) {
22092 const auto *carrierDecision =
22093 dataStorageDecisionForCarrier(decision.rawCarrierIndex);
22094 if (carrierDecision == &decision &&
22095 decision.selectedCarrierIndex == decision.rawCarrierIndex) {
22096 const auto &carrier = dataStorageCarriers[decision.rawCarrierIndex];
22097 const auto *raw = rawCarrier(carrier);
22098 replayableRaw =
22099 carrier.ownerHandle != 0 &&
22100 m_dwgWriteHandleRemap.count(carrier.ownerHandle) == 0 &&
22101 m_dwgWriteEntityHandleRemap.count(carrier.ownerHandle) == 0 &&
22102 m_dwgWriteDuplicateEntityHandles.count(carrier.ownerHandle) ==
22103 0 &&
22104 raw != nullptr && raw->handle != 0 && !raw->rawBytes.empty() &&
22105 sameRawObjectEncodingFamily(raw->version, targetDwgVersion) &&
22106 !isFixedStructuralDwgHandle(raw->handle) &&
22107 LC_DwgAdvancedMetadata::rawReplayBlocker(*raw) ==
22108 LC_DwgAdvancedMetadata::ReplayBlocker::None &&
22109 hasPlannedRawClass(carrier, raw);
22110 if (replayableRaw && !nativeTyped)
22111 replayableRawDataStorageHandles.insert(raw->handle);
22112 }
22113 }
22114 if (!nativeTyped && !replayableRaw)
22115 dataStorageLedgerValid = false;
22116 }
22117 }
22118 if (targetSupportsDataStorage &&
22119 ((!dataStorageCarriers.empty() && metadata.dataStorages().empty()) ||
22120 (!dataStorageLedgerValid && !metadata.dataStorages().empty()))) {
22121 hasBlockedReplay = true;
22122 ++blockedDataStorage;
22123 }
22124
22125 // BLOCKREPRESENTATIONDATA has a typed prefix writer, but its AC1027+
22126 // DataStorage payload is still opaque. Keep the complete raw object
22127 // for same-version replay; reject an export that has no such carrier.
22128 for (const auto &record : metadata.blockRepresentationData()) {
22129 if (record.replayState !=
22130 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
22131 !record.hasDsData)
22132 continue;
22133 if (!hasReplayableRawBlockRepresentationData(metadata, record.handle,
22134 m_dwgW->getVersion())) {
22135 hasBlockedReplay = true;
22136 ++blockedDataStorage;
22137 }
22138 }
22139
22140 for (std::size_t rawSectionIndex = 0;
22141 rawSectionIndex < metadata.rawDwgSections().size();
22142 ++rawSectionIndex) {
22143 const auto &record = metadata.rawDwgSections()[rawSectionIndex];
22144 const bool isDataStorage = isParsedDataStorageSection(record);
22145 if (record.replayState !=
22146 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
22147 hasBlockedReplay = true;
22148 ++blockedInvalidated;
22149 if (isDataStorage)
22150 ++blockedDataStorage;
22151 continue;
22152 }
22153 if (metadata.sourceDwgVersion() != DRW::UNKNOWNV &&
22154 metadata.sourceDwgVersion() != m_dwgW->getVersion()) {
22155 hasBlockedReplay = true;
22156 ++blockedVersionMismatch;
22157 if (isDataStorage)
22158 ++blockedDataStorage;
22159 continue;
22160 }
22161 if (isDataStorage &&
22162 (!dataStorageLedgerValid ||
22163 replayableDataStorageSectionIndexes.count(rawSectionIndex) == 0)) {
22164 hasBlockedReplay = true;
22165 ++blockedInvalidated;
22166 ++blockedDataStorage;
22167 continue;
22168 }
22169 DRW_RawDwgSection section;
22170 section.m_name = record.name;
22171 section.m_version = record.version;
22172 section.m_data = record.data;
22173 section.m_encoding = record.encoding;
22174 section.m_encrypted = record.encrypted;
22175 section.m_maxSize = record.maxSize;
22176 if (m_dwgW->writeRawDwgSection(&section)) {
22177 if (isDataStorage &&
22178 replayableDataStorageSectionIndexes.count(rawSectionIndex) != 0) {
22179 std::set<std::size_t> recordIndexes;
22180 for (const auto &storage : metadata.dataStorages()) {
22181 if (storage.version != record.version ||
22182 normalizeDwgTableName(storage.name) !=
22183 normalizeDwgTableName(record.name))
22184 continue;
22185 for (std::size_t index = 0; index < storage.full.records.size();
22186 ++index)
22187 recordIndexes.insert(index);
22188 break;
22189 }
22190 if (!recordDwgDataStorageSectionCommit(rawSectionIndex,
22191 recordIndexes)) {
22192 dataStorageLedgerValid = false;
22193 hasBlockedReplay = true;
22194 ++blockedWriterRejected;
22195 ++blockedDataStorage;
22196 }
22197 }
22198 ++replayedSections;
22199 } else {
22200 hasBlockedReplay = true;
22201 ++blockedWriterRejected;
22202 if (isDataStorage)
22203 ++blockedDataStorage;
22204 }
22205 }
22206 for (const auto &entry :
22207 m_dwgExportAdmissionPlan.opaqueTableRecordIndexes) {
22208 const auto &graph = entry.second.ownerGraph;
22209 if (!graph.has_value())
22210 continue;
22211 if (graph->rawObjectIndex >= metadata.rawObjects().size() ||
22212 graph->rootDictionaryIndex >= metadata.dictionaries().size() ||
22213 graph->childDictionaryIndex >= metadata.dictionaries().size()) {
22214 m_writeFailed = true;
22215 m_dwgW->markDwgWriteFailure();
22216 return;
22217 }
22218 const auto &raw = metadata.rawObjects()[graph->rawObjectIndex];
22219 const auto &root = metadata.dictionaries()[graph->rootDictionaryIndex];
22220 const auto &source = metadata.dictionaries()[graph->childDictionaryIndex];
22221 if (raw.handle != graph->rawSourceHandle ||
22222 raw.handle != graph->rawOutputHandle ||
22223 raw.parentHandle != graph->rawOwnerHandle ||
22224 root.handle != graph->rootSourceHandle ||
22225 root.parentHandle != DRW::NoHandle || root.handle == source.handle ||
22226 root.handle == raw.handle ||
22227 graph->rootEntryOrdinal >= root.entries.size() ||
22228 root.entries[graph->rootEntryOrdinal].name != graph->rootEntryName ||
22229 root.entries[graph->rootEntryOrdinal].handle !=
22230 graph->childOutputHandle ||
22231 source.handle != graph->childSourceHandle ||
22232 source.handle != graph->childOutputHandle ||
22233 source.parentHandle != graph->rootSourceHandle ||
22234 graph->childEntryOrdinal >= source.entries.size() ||
22235 source.entries[graph->childEntryOrdinal].name !=
22236 graph->childEntryName ||
22237 source.entries[graph->childEntryOrdinal].handle !=
22238 graph->rawOutputHandle ||
22239 dwgOpaqueTableDictionaryDigest(root) != graph->rootIdentityDigest ||
22240 dwgOpaqueTableDictionaryDigest(source) !=
22241 graph->childIdentityDigest ||
22242 !canWriteDwgDictionaryRecord(metadata, source,
22243 m_dwgW->getVersion()) ||
22244 hasDxfTableAppDataUnsupportedByDwg(dictionaryFromMetadata(source))) {
22245 m_writeFailed = true;
22246 m_dwgW->markDwgWriteFailure();
22247 return;
22248 }
22249
22250 DRW_Dictionary dictionary = dictionaryFromMetadata(source);
22251 dictionary.parentHandle = graph->rootOutputHandle;
22252 referenceContext = {DwgWriteSourceKind::Object, graph->childSourceHandle,
22253 graph->childOutputHandle};
22254 if (!sanitizeDictionaryEntries(dictionary) ||
22255 !sanitizeCommonObjectReferences(dictionary,
22256 "DICTIONARY common reference") ||
22257 !writeDwgObject(DwgWriteSourceKind::Object, graph->childSourceHandle,
22258 [&dictionary, this] {
22259 return m_dwgW->writeDictionary(&dictionary);
22260 })) {
22261 m_writeFailed = true;
22262 m_dwgW->markDwgWriteFailure();
22263 return;
22264 }
22265 ++nativeDictionaryObjects;
22266 }
22267 for (std::size_t rawIndex = 0; rawIndex < metadata.rawObjects().size();
22268 ++rawIndex) {
22269 const auto &record = metadata.rawObjects()[rawIndex];
22270 // Typed metadata entities are emitted during ENTITIES. Their raw
22271 // frame is a read-side fallback, not a second object instance.
22272 if (isNativeMetadataEntityRawObject(metadata, record))
22273 continue;
22274 if (isPlotSettingsRawObject(record) &&
22275 !isDwgRawPlotSettingsSelected(record.handle)) {
22276 continue;
22277 }
22278 if (requiresDwgOpaqueTableAdmission(record) &&
22279 !isDwgOpaqueTableRawSelected(rawIndex)) {
22280 continue;
22281 }
22282 if (replaceViewportEntityHeaderControl &&
22283 isViewportEntityHeaderControlRawObject(record)) {
22284 hasBlockedReplay = true;
22285 ++blockedReplaced;
22286 continue;
22287 }
22288 if (nativeViewportEntityHeaderHandles.count(record.handle) != 0 &&
22289 isViewportEntityHeaderRawObject(record)) {
22290 hasBlockedReplay = true;
22291 ++blockedReplaced;
22292 continue;
22293 }
22294 if (nativeSunHandles.count(record.handle) != 0 &&
22295 isSunRawObject(record)) {
22296 hasBlockedReplay = true;
22297 ++blockedReplaced;
22298 continue;
22299 }
22300 if (nativeTvDevicePropertiesHandles.count(record.handle) != 0 &&
22301 isTvDevicePropertiesRawObject(record)) {
22302 hasBlockedReplay = true;
22303 ++blockedReplaced;
22304 continue;
22305 }
22306 if (nativeVxControlHandles.count(record.handle) != 0 &&
22307 isVxControlRawObject(record)) {
22308 hasBlockedReplay = true;
22309 ++blockedReplaced;
22310 continue;
22311 }
22312 if (nativeVxTableRecordHandles.count(record.handle) != 0 &&
22313 isVxTableRecordRawObject(record)) {
22314 hasBlockedReplay = true;
22315 ++blockedReplaced;
22316 continue;
22317 }
22318 if (nativePlaceholderHandles.count(record.handle) != 0 &&
22319 isAcDbPlaceholderRawObject(record)) {
22320 hasBlockedReplay = true;
22321 ++blockedReplaced;
22322 continue;
22323 }
22324 if (nativeVbaProjectHandles.count(record.handle) != 0 &&
22325 isVbaProjectRawObject(record)) {
22326 hasBlockedReplay = true;
22327 ++blockedReplaced;
22328 continue;
22329 }
22330 if (nativeMLeaderStyleHandles.count(record.handle) != 0 &&
22331 isMLeaderStyleRawObject(record)) {
22332 hasBlockedReplay = true;
22333 ++blockedReplaced;
22334 continue;
22335 }
22336 if (nativeMaterialHandles.count(record.handle) != 0 &&
22337 isMaterialRawObject(record)) {
22338 hasBlockedReplay = true;
22339 ++blockedReplaced;
22340 continue;
22341 }
22342 if (nativeLightListHandles.count(record.handle) != 0 &&
22343 isLightListRawObject(record)) {
22344 hasBlockedReplay = true;
22345 ++blockedReplaced;
22346 continue;
22347 }
22348 if (nativeBackgroundHandles.count(record.handle) != 0 &&
22349 isBackgroundRawObject(record)) {
22350 hasBlockedReplay = true;
22351 ++blockedReplaced;
22352 continue;
22353 }
22354 if (nativeSunStudyHandles.count(record.handle) != 0 &&
22355 isSunStudyRawObject(record)) {
22356 hasBlockedReplay = true;
22357 ++blockedReplaced;
22358 continue;
22359 }
22360 if (nativeMotionPathHandles.count(record.handle) != 0 &&
22361 isMotionPathRawObject(record)) {
22362 hasBlockedReplay = true;
22363 ++blockedReplaced;
22364 continue;
22365 }
22366 if (nativeCurvePathHandles.count(record.handle) != 0 &&
22367 isCurvePathRawObject(record)) {
22368 hasBlockedReplay = true;
22369 ++blockedReplaced;
22370 continue;
22371 }
22372 if (nativePointPathHandles.count(record.handle) != 0 &&
22373 isPointPathRawObject(record)) {
22374 hasBlockedReplay = true;
22375 ++blockedReplaced;
22376 continue;
22377 }
22378 if (nativeObjectPtrHandles.count(record.handle) != 0 &&
22379 isObjectPtrRawObject(record)) {
22380 hasBlockedReplay = true;
22381 ++blockedReplaced;
22382 continue;
22383 }
22384 if (nativePartialViewingIndexHandles.count(record.handle) != 0 &&
22385 isPartialViewingIndexRawObject(record)) {
22386 hasBlockedReplay = true;
22387 ++blockedReplaced;
22388 continue;
22389 }
22390 if (nativeRenderSettingsHandles.count(record.handle) != 0 &&
22391 isRenderSettingsRawObject(record)) {
22392 hasBlockedReplay = true;
22393 ++blockedReplaced;
22394 continue;
22395 }
22396 if (nativeVisualStyleHandles.count(record.handle) != 0 &&
22397 isVisualStyleRawObject(record)) {
22398 hasBlockedReplay = true;
22399 ++blockedReplaced;
22400 continue;
22401 }
22402 if (nativeDictionaryHandles.count(record.handle) != 0 &&
22403 isDictionaryRawObject(record)) {
22404 hasBlockedReplay = true;
22405 ++blockedReplaced;
22406 continue;
22407 }
22408 if (nativeXRecordHandles.count(record.handle) != 0 &&
22409 isXRecordRawObject(record)) {
22410 hasBlockedReplay = true;
22411 ++blockedReplaced;
22412 continue;
22413 }
22414 if (nativeLayoutHandles.count(record.handle) != 0 &&
22415 isLayoutRawObject(record)) {
22416 hasBlockedReplay = true;
22417 ++blockedReplaced;
22418 continue;
22419 }
22420 if (nativeGroupHandles.count(record.handle) != 0 &&
22421 isGroupRawObject(record)) {
22422 hasBlockedReplay = true;
22423 ++blockedReplaced;
22424 continue;
22425 }
22426 if (nativeMLineStyleHandles.count(record.handle) != 0 &&
22427 isMLineStyleRawObject(record)) {
22428 hasBlockedReplay = true;
22429 ++blockedReplaced;
22430 continue;
22431 }
22432 if (nativeTableStyleHandles.count(record.handle) != 0 &&
22433 isTableStyleRawObject(record)) {
22434 hasBlockedReplay = true;
22435 ++blockedReplaced;
22436 continue;
22437 }
22438 if (nativeDbColorHandles.count(record.handle) != 0 &&
22439 isDbColorRawObject(record)) {
22440 hasBlockedReplay = true;
22441 ++blockedReplaced;
22442 continue;
22443 }
22444 if (nativeDimensionAssociationHandles.count(record.handle) != 0 &&
22445 isDimensionAssociationRawObject(record)) {
22446 hasBlockedReplay = true;
22447 ++blockedReplaced;
22448 continue;
22449 }
22450 if (nativeEvaluationGraphHandles.count(record.handle) != 0 &&
22451 isEvaluationGraphRawObject(record)) {
22452 hasBlockedReplay = true;
22453 ++blockedReplaced;
22454 continue;
22455 }
22456 if (nativeSectionHandles.count(record.handle) != 0 &&
22457 isSectionRawObject(record)) {
22458 hasBlockedReplay = true;
22459 ++blockedReplaced;
22460 continue;
22461 }
22462 if (isSectionObjectRawEntity(record) &&
22463 m_dwgW->getVersion() >= DRW::AC1021 &&
22464 hasReplayableSectionObject(metadata, record.handle)) {
22465 hasBlockedReplay = true;
22466 ++blockedReplaced;
22467 continue;
22468 }
22469 if (nativeRasterVariablesHandles.count(record.handle) != 0 &&
22470 isRasterVariablesRawObject(record)) {
22471 hasBlockedReplay = true;
22472 ++blockedReplaced;
22473 continue;
22474 }
22475 if (nativeWipeoutVariablesHandles.count(record.handle) != 0 &&
22476 isWipeoutVariablesRawObject(record)) {
22477 hasBlockedReplay = true;
22478 ++blockedReplaced;
22479 continue;
22480 }
22481 if (nativeGeoDataHandles.count(record.handle) != 0 &&
22482 isGeoDataRawObject(record)) {
22483 hasBlockedReplay = true;
22484 ++blockedReplaced;
22485 continue;
22486 }
22487 if (nativeSpatialFilterHandles.count(record.handle) != 0 &&
22488 isSpatialFilterRawObject(record)) {
22489 hasBlockedReplay = true;
22490 ++blockedReplaced;
22491 continue;
22492 }
22493 // PR 8d.2a — five small no-storage OBJECTS families.
22494 if (nativeScaleHandles.count(record.handle) != 0 &&
22495 isScaleRawObject(record)) {
22496 hasBlockedReplay = true;
22497 ++blockedReplaced;
22498 continue;
22499 }
22500 if (nativeIDBufferHandles.count(record.handle) != 0 &&
22501 isIDBufferRawObject(record)) {
22502 hasBlockedReplay = true;
22503 ++blockedReplaced;
22504 continue;
22505 }
22506 if (nativeLayerIndexHandles.count(record.handle) != 0 &&
22507 isLayerIndexRawObject(record)) {
22508 hasBlockedReplay = true;
22509 ++blockedReplaced;
22510 continue;
22511 }
22512 if (nativeSpatialIndexHandles.count(record.handle) != 0 &&
22513 isSpatialIndexRawObject(record)) {
22514 hasBlockedReplay = true;
22515 ++blockedReplaced;
22516 continue;
22517 }
22518 if (nativeDictionaryVarHandles.count(record.handle) != 0 &&
22519 isDictionaryVarRawObject(record)) {
22520 hasBlockedReplay = true;
22521 ++blockedReplaced;
22522 continue;
22523 }
22524 // PR 8d.2b — four larger no-storage OBJECTS families.
22525 if (nativeDictionaryWithDefaultHandles.count(record.handle) != 0 &&
22526 isDictionaryWithDefaultRawObject(record)) {
22527 hasBlockedReplay = true;
22528 ++blockedReplaced;
22529 continue;
22530 }
22531 if (nativeSortEntsTableHandles.count(record.handle) != 0 &&
22532 isSortEntsTableRawObject(record)) {
22533 hasBlockedReplay = true;
22534 ++blockedReplaced;
22535 continue;
22536 }
22537 if (nativeFieldListHandles.count(record.handle) != 0 &&
22538 isFieldListRawObject(record)) {
22539 hasBlockedReplay = true;
22540 ++blockedReplaced;
22541 continue;
22542 }
22543 if (nativeFieldHandles.count(record.handle) != 0 &&
22544 isFieldRawObject(record)) {
22545 hasBlockedReplay = true;
22546 ++blockedReplaced;
22547 continue;
22548 }
22549 if (nativeUnderlayDefinitionHandles.count(record.handle) != 0 &&
22550 isUnderlayDefinitionRawObject(record)) {
22551 hasBlockedReplay = true;
22552 ++blockedReplaced;
22553 continue;
22554 }
22555 if (nativePointCloudDefinitionHandles.count(record.handle) != 0 &&
22556 isPointCloudDefinitionRawObject(record)) {
22557 hasBlockedReplay = true;
22558 ++blockedReplaced;
22559 continue;
22560 }
22561 if (nativeNavisworksModelDefinitionHandles.count(record.handle) != 0 &&
22562 isNavisworksModelDefinitionRawObject(record)) {
22563 hasBlockedReplay = true;
22564 ++blockedReplaced;
22565 continue;
22566 }
22567 if (nativePointCloudColorMapHandles.count(record.handle) != 0 &&
22568 isPointCloudColorMapRawObject(record)) {
22569 hasBlockedReplay = true;
22570 ++blockedReplaced;
22571 continue;
22572 }
22573 // A raw record with the same handle as a typed object from a
22574 // different family would create two OBJECTS identities. The
22575 // family-specific checks above intentionally allow a raw carrier
22576 // to be replaced by its typed counterpart; every other collision
22577 // is structurally ambiguous and must fail the export.
22578 if (nativeObjectHandles.count(record.handle) != 0) {
22579 hasBlockedReplay = true;
22580 ++blockedWriterRejected;
22581 m_writeFailed = true;
22582 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
22583 "RS_FilterDXFRW: raw DWG object %s collides with a "
22584 "typed OBJECT handle 0x%X; export rejected",
22585 record.recordName.empty() ? "(unknown)"
22586 : record.recordName.c_str(),
22587 record.handle);
22588 continue;
22589 }
22590 // A raw object whose handle collides with the writer's fixed
22591 // structural handles can never be replayed: its verbatim bytes
22592 // cannot be rewritten to a fresh handle, and double emission
22593 // duplicates the object-map entry, failing the whole save in
22594 // writeDwgHandles(). Typed counterparts of these records are
22595 // remapped and emitted instead (see fileExport / P3 #2).
22596 if (isFixedStructuralDwgHandle(record.handle) &&
22597 !isRawViewportEntityHeaderControl(record)) {
22598 hasBlockedReplay = true;
22599 ++blockedFixedHandle;
22600 continue;
22601 }
22602 // Raw object bytes must remain in their exact source DWG version.
22603 // The matching writeDwgClasses guard prevents orphan CLASSES data.
22604 if (!sameRawObjectEncodingFamily(record.version, m_dwgW->getVersion())) {
22605 hasBlockedReplay = true;
22606 ++blockedVersionMismatch;
22607 continue;
22608 }
22609 if (targetSupportsDataStorage && record.hasDataStorage &&
22610 (!dataStorageLedgerValid ||
22611 replayableRawDataStorageHandles.count(record.handle) == 0)) {
22612 hasBlockedReplay = true;
22613 ++blockedInvalidated;
22614 ++blockedDataStorage;
22615 continue;
22616 }
22617 if (!record.isEntity && record.parentHandle != DRW::NoHandle &&
22618 !isDwgWriteOpaqueReferenceStable(record.parentHandle)) {
22619 hasBlockedReplay = true;
22620 ++blockedWriterRejected;
22621 m_writeFailed = true;
22622 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
22623 "RS_FilterDXFRW: opaque DWG OBJECT %s has a remapped "
22624 "owner 0x%X; export rejected",
22625 record.recordName.empty() ? "(unknown)"
22626 : record.recordName.c_str(),
22627 record.parentHandle);
22628 continue;
22629 }
22630 const LC_DwgAdvancedMetadata::ReplayBlocker blocker =
22631 LC_DwgAdvancedMetadata::rawReplayBlocker(record);
22632 if (blocker != LC_DwgAdvancedMetadata::ReplayBlocker::None) {
22633 hasBlockedReplay = true;
22634 if (blocker == LC_DwgAdvancedMetadata::ReplayBlocker::Invalidated)
22635 ++blockedInvalidated;
22636 else if (blocker == LC_DwgAdvancedMetadata::ReplayBlocker::Replaced)
22637 ++blockedReplaced;
22638 else if (blocker ==
22639 LC_DwgAdvancedMetadata::ReplayBlocker::EntityReplayUnsupported)
22640 ++blockedEntityReplay;
22641 else if (blocker ==
22642 LC_DwgAdvancedMetadata::ReplayBlocker::MissingRawBytes)
22643 ++blockedMissingRawBytes;
22644 else if (blocker ==
22645 LC_DwgAdvancedMetadata::ReplayBlocker::MissingClassMetadata)
22646 ++blockedMissingClassMetadata;
22647 else if (blocker ==
22648 LC_DwgAdvancedMetadata::ReplayBlocker::WriterRejected) {
22649 // Keep writer-side skip accounting authoritative for a
22650 // carrier rejected by the same validation used by replay.
22651 DRW_UnsupportedObject object = rawObjectFromMetadata(record);
22652 (void)m_dwgW->writeRawDwgObject(&object);
22653 ++blockedWriterRejected;
22654 }
22655 continue;
22656 }
22657 if (record.handle == 0) {
22658 hasBlockedReplay = true;
22659 ++blockedWriterRejected;
22660 m_writeFailed = true;
22661 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
22662 "RS_FilterDXFRW: raw DWG object %s has no self-handle; "
22663 "export rejected",
22664 record.recordName.empty() ? "(unknown)"
22665 : record.recordName.c_str());
22666 continue;
22667 }
22668 if (!replayedRawObjectHandles.insert(record.handle).second) {
22669 hasBlockedReplay = true;
22670 ++blockedWriterRejected;
22671 m_writeFailed = true;
22672 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
22673 "RS_FilterDXFRW: duplicate raw DWG self-handle 0x%X; "
22674 "export rejected",
22675 record.handle);
22676 continue;
22677 }
22678 DRW_UnsupportedObject object = rawObjectFromMetadata(record);
22679 if (!rawCustomEntityOwnerFieldsAreReplayable(object,
22680 m_dwgW->getVersion())) {
22681 hasBlockedReplay = true;
22682 ++blockedWriterRejected;
22683 m_writeFailed = true;
22684 continue;
22685 }
22686 const std::uint32_t sourceOwner =
22687 object.m_blockOwnerHandle != DRW::NoHandle ? object.m_blockOwnerHandle
22688 : object.m_parentHandle;
22689 if (object.m_isEntity && object.m_isCustomClass &&
22690 sourceOwner != DRW::NoHandle) {
22691 const std::uint32_t targetOwner =
22692 remapBlockOwnerHandle(sourceOwner, "raw custom entity owner", 0,
22693 DRW::DwgSoftPointer, false);
22694 if (targetOwner == 0) {
22695 hasBlockedReplay = true;
22696 ++blockedWriterRejected;
22697 m_writeFailed = true;
22698 continue;
22699 }
22700 // R2000/R2004 owner bytes are not safely locatable in an
22701 // opaque body. Modern entities are patched in the writer.
22702 if (targetOwner != sourceOwner && !isDwgSpaceBlockOwner(sourceOwner) &&
22703 (object.m_version <= DRW::AC1018 ||
22704 object.m_parentHandle == DRW::NoHandle)) {
22705 hasBlockedReplay = true;
22706 ++blockedWriterRejected;
22707 m_writeFailed = true;
22708 continue;
22709 }
22710 object.m_blockOwnerHandle = targetOwner;
22711 }
22712 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
22713 DwgWriteSourceKind::RawCarrier,
22714 record.handle, [&object, this] {
22715 return m_dwgW->writeRawDwgObject(&object);
22716 })) {
22717 ++replayedObjects;
22718 } else {
22719 hasBlockedReplay = true;
22720 ++blockedWriterRejected;
22721 if (object.m_isEntity && object.m_isCustomClass)
22722 m_writeFailed = true;
22723 }
22724 }
22725 if (!validateDwgOpaqueTableReferenceReceipts(
22726 DwgOpaqueTableReceiptStage::RawObjectsEmitted,
22727 DwgOpaqueTableFailurePhase::Objects)) {
22728 m_writeFailed = true;
22729 m_dwgW->markDwgWriteFailure();
22730 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
22731 "RS_FilterDXFRW: opaque DWG table relation receipts are "
22732 "missing after OBJECTS replay");
22733 return;
22734 }
22735 if (canWriteModernObjects) {
22736 // ACDBPLACEHOLDER dispatch moved below to the
22737 // `canWriteFixedTypeObjects` block (PR 13d).
22738 // PR 13d — SUN stays gated AC1021+ — custom class 503 was
22739 // introduced in R2007 and the encoder explicitly rejects
22740 // below AC1021 (see DRW_Sun::encodeDwg at drw_objects.cpp:
22741 // `if (buf == nullptr || version < DRW::AC1021) return
22742 // false`). Do not move into canWriteFixedTypeObjects.
22743 for (const auto &record : metadata.suns()) {
22744 if (record.replayState !=
22745 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
22746 record.handle == 0) {
22747 continue;
22748 }
22749 DRW_Sun sun = sunFromMetadata(record);
22750 if (hasDxfTableAppDataUnsupportedByDwg(sun)) {
22751 hasBlockedReplay = true;
22752 ++blockedWriterRejected;
22753 continue;
22754 }
22755 remapEntry(sun);
22756 if (!sanitizeCommonObjectReferences(sun, "SUN common reference")) {
22757 hasBlockedReplay = true;
22758 ++blockedWriterRejected;
22759 continue;
22760 }
22761 sun.setDwgCommonObjectState(
22762 static_cast<std::int32_t>(sun.reactorHandles.size()),
22763 sun.extensionDictionaryFlag(),
22764 encodedDwgDataStoragePresenceBit(record.handle,
22765 sun.hasDataStorageBinaryData()));
22766 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
22767 DwgWriteSourceKind::Object,
22768 record.handle,
22769 [&sun, this] { return m_dwgW->writeSun(&sun); })) {
22770 ++nativeSunObjects;
22771 } else {
22772 hasBlockedReplay = true;
22773 ++blockedWriterRejected;
22774 }
22775 }
22776 // DICTIONARY / XRECORD / LAYOUT / GROUP dispatch moved below
22777 // to the `canWriteFixedTypeObjects` block (PR 13a/b/c).
22778 // RASTERVARIABLES / GEODATA / SPATIAL_FILTER dispatch moved
22779 // to the `canRegisterCustomClassObjects` block (PR 13f).
22780 // PR 8d.2a — SCALE / IDBUFFER / LAYER_INDEX / SPATIAL_INDEX /
22781 // DICTIONARYVAR dispatch moved to the
22782 // `canRegisterCustomClassObjects` block (PR 13g).
22783 // PR 8d.2b — four larger no-storage OBJECTS families.
22784 // Dispatch moved to the `canRegisterCustomClassObjects` block
22785 // (PR 13h).
22786 }
22787 // PR 13a/b/c/d — DICTIONARY / XRECORD / GROUP / LAYOUT /
22788 // ACDBPLACEHOLDER native dispatch. Gate broadened from AC1021+ to
22789 // AC1015+ now that the encoder smoke tests cover the full
22790 // AC1015/AC1018/AC1024/AC1027/AC1032 range (see
22791 // `[dwg-write][dictionary]`, `[dwg-write][xrecord]`,
22792 // `[dwg-write][group]`, `[dwg-write][layout]`,
22793 // `[dwg-write][placeholder]` cases). Sits outside the `if
22794 // (canWriteModernObjects)` block so the broadened gate also
22795 // applies at AC1015/AC1018.
22796 if (canWriteFixedTypeObjects) {
22797 for (const auto &project : metadata.vbaProjects()) {
22798 if (nativeVbaProjectHandles.count(project.handle) == 0)
22799 continue;
22800 DRW_VbaProject object = project;
22801 remapEntry(object);
22802 if (object.hasDataStorageBinaryData() ||
22803 hasDxfTableAppDataUnsupportedByDwg(object)) {
22804 hasBlockedReplay = true;
22805 ++blockedWriterRejected;
22806 continue;
22807 }
22808 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
22809 DwgWriteSourceKind::Object,
22810 project.handle, [&object, this] {
22811 return m_dwgW->writeVbaProject(&object);
22812 })) {
22813 ++nativeVbaProjectObjects;
22814 } else {
22815 hasBlockedReplay = true;
22816 ++blockedWriterRejected;
22817 }
22818 }
22819 for (const auto &record : metadata.placeholders()) {
22820 if (record.replayState !=
22821 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
22822 record.handle == 0) {
22823 continue;
22824 }
22825 DRW_AcDbPlaceholder placeholder = placeholderFromMetadata(record);
22826 if (hasDxfTableAppDataUnsupportedByDwg(placeholder)) {
22827 hasBlockedReplay = true;
22828 ++blockedWriterRejected;
22829 continue;
22830 }
22831 remapEntry(placeholder);
22832 if (!sanitizeCommonObjectReferences(
22833 placeholder, "ACDBPLACEHOLDER common reference")) {
22834 hasBlockedReplay = true;
22835 ++blockedWriterRejected;
22836 continue;
22837 }
22838 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
22839 DwgWriteSourceKind::Object,
22840 record.handle, [&placeholder, this] {
22841 return m_dwgW->writeAcDbPlaceholder(&placeholder);
22842 })) {
22843 ++nativePlaceholderObjects;
22844 } else {
22845 hasBlockedReplay = true;
22846 ++blockedWriterRejected;
22847 }
22848 }
22849 for (const auto &record : metadata.blockRepresentationData()) {
22850 if (nativeBlockRepresentationHandles.count(record.handle) == 0)
22851 continue;
22852 DRW_BlockRepresentationData data =
22853 blockRepresentationFromMetadata(record);
22854 if (hasDxfTableAppDataUnsupportedByDwg(data)) {
22855 hasBlockedReplay = true;
22856 ++blockedWriterRejected;
22857 continue;
22858 }
22859 remapEntry(data);
22860 if (!sanitizeCommonObjectReferences(
22861 data, "BLOCKREPRESENTATIONDATA common reference")) {
22862 hasBlockedReplay = true;
22863 ++blockedWriterRejected;
22864 continue;
22865 }
22866 if (m_dwgW->getVersion() <= DRW::AC1024 &&
22867 data.hasDataStorageBinaryData()) {
22868 data.setDwgCommonObjectState(data.reactorCount(),
22869 data.extensionDictionaryFlag(), false);
22870 }
22871 const std::uint32_t sourceBlock = data.m_blockHandle;
22872 data.m_blockHandle =
22873 remapBlockOwnerHandle(sourceBlock, "BLOCKREPRESENTATIONDATA block",
22874 0, DRW::DwgHardOwnership);
22875 if (sourceBlock != 0 && data.m_blockHandle == 0) {
22876 hasBlockedReplay = true;
22877 ++blockedWriterRejected;
22878 continue;
22879 }
22880 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
22881 DwgWriteSourceKind::Object,
22882 record.handle, [&data, this] {
22883 return m_dwgW->writeBlockRepresentationData(&data);
22884 })) {
22885 ++nativeBlockRepresentationObjects;
22886 } else {
22887 hasBlockedReplay = true;
22888 ++blockedWriterRejected;
22889 }
22890 }
22891 for (std::size_t index = 0; index < metadata.dictionaries().size();
22892 ++index) {
22893 if (ownerGraphDictionaryIndexes.count(index) != 0)
22894 continue;
22895 const auto &record = metadata.dictionaries()[index];
22896 if (nativeDictionaryHandles.count(record.handle) == 0)
22897 continue;
22898 DRW_Dictionary dictionary = dictionaryFromMetadata(record);
22899 if (hasDxfTableAppDataUnsupportedByDwg(dictionary)) {
22900 hasBlockedReplay = true;
22901 ++blockedWriterRejected;
22902 continue;
22903 }
22904 sanitizeDictionaryOwner(dictionary.parentHandle, "dictionary");
22905 remapEntryHandle(dictionary);
22906 if (!sanitizeDictionaryEntries(dictionary) ||
22907 !sanitizeCommonObjectReferences(dictionary,
22908 "DICTIONARY common reference")) {
22909 hasBlockedReplay = true;
22910 ++blockedWriterRejected;
22911 continue;
22912 }
22913 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
22914 DwgWriteSourceKind::Object,
22915 record.handle, [&dictionary, this] {
22916 return m_dwgW->writeDictionary(&dictionary);
22917 })) {
22918 ++nativeDictionaryObjects;
22919 } else {
22920 hasBlockedReplay = true;
22921 ++blockedWriterRejected;
22922 }
22923 }
22924 for (const auto &record : metadata.xrecords()) {
22925 if (nativeXRecordHandles.count(record.handle) == 0)
22926 continue;
22927 DRW_XRecord xrecord = xrecordFromMetadata(record);
22928 if (hasDxfTableAppDataUnsupportedByDwg(xrecord)) {
22929 hasBlockedReplay = true;
22930 ++blockedWriterRejected;
22931 continue;
22932 }
22933 remapEntry(xrecord);
22934 for (std::size_t ordinal = 0; ordinal < xrecord.m_handleValues.size();
22935 ++ordinal)
22936 sanitizeObjectHandle(xrecord.m_handleValues[ordinal].second,
22937 "XRECORD", static_cast<std::uint32_t>(ordinal));
22938 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
22939 DwgWriteSourceKind::Object,
22940 record.handle, [&xrecord, this] {
22941 return m_dwgW->writeXRecord(&xrecord);
22942 })) {
22943 ++nativeXRecordObjects;
22944 } else {
22945 hasBlockedReplay = true;
22946 ++blockedWriterRejected;
22947 }
22948 }
22949 for (const auto &record : metadata.groups()) {
22950 if (nativeGroupHandles.count(record.handle) == 0)
22951 continue;
22952 DRW_Group group = groupFromMetadata(record);
22953 if (hasDxfTableAppDataUnsupportedByDwg(group)) {
22954 hasBlockedReplay = true;
22955 ++blockedWriterRejected;
22956 continue;
22957 }
22958 remapEntry(group);
22959 if (!remapRequiredEntityList(group.m_entityHandles, "GROUP member")) {
22960 hasBlockedReplay = true;
22961 ++blockedWriterRejected;
22962 continue;
22963 }
22964 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
22965 DwgWriteSourceKind::Object,
22966 record.handle, [&group, this] {
22967 return m_dwgW->writeGroup(&group);
22968 })) {
22969 ++nativeGroupObjects;
22970 } else {
22971 hasBlockedReplay = true;
22972 ++blockedWriterRejected;
22973 }
22974 }
22975 for (const auto &record : metadata.mlineStyles()) {
22976 if (nativeMLineStyleHandles.count(record.handle) == 0)
22977 continue;
22978 DRW_MLineStyle style = mlineStyleFromMetadata(record);
22979 if (hasDxfTableAppDataUnsupportedByDwg(style)) {
22980 hasBlockedReplay = true;
22981 ++blockedWriterRejected;
22982 continue;
22983 }
22984 remapEntry(style);
22985 if (m_dwgW->getVersion() >= DRW::AC1032) {
22986 for (std::size_t i = 0; i < style.elements.size(); ++i) {
22987 const auto &element = style.elements[i];
22988 const std::string sourceName =
22989 !element.linetype.empty()
22990 ? element.linetype
22991 : metadata.lineTypeNameForHandle(element.linetypeHandle);
22992 const auto outputIt = m_dwgWriteLTypeHandleByName.find(
22993 normalizeDwgTableName(sourceName));
22994 if (outputIt != m_dwgWriteLTypeHandleByName.end()) {
22995 style.elements[i].linetypeHandle = outputIt->second;
22996 continue;
22997 }
22998 if (element.linetypeHandle != 0) {
22999 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
23000 "RS_FilterDXFRW: dropping unresolved DWG "
23001 "MLINESTYLE element LTYPE 0x%X",
23002 element.linetypeHandle);
23003 style.elements[i].linetypeHandle = 0;
23004 }
23005 }
23006 }
23007 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
23008 DwgWriteSourceKind::Object,
23009 record.handle, [&style, this] {
23010 return m_dwgW->writeMLineStyle(&style);
23011 })) {
23012 ++nativeMLineStyleObjects;
23013 } else {
23014 hasBlockedReplay = true;
23015 ++blockedWriterRejected;
23016 }
23017 }
23018 for (const auto &record : metadata.layouts()) {
23019 if (nativeLayoutHandles.count(record.handle) == 0)
23020 continue;
23021 DRW_Layout layout = layoutFromMetadata(record);
23022 if (hasDxfTableAppDataUnsupportedByDwg(layout)) {
23023 hasBlockedReplay = true;
23024 ++blockedWriterRejected;
23025 continue;
23026 }
23027 remapEntry(layout);
23028 if (m_dwgW->getVersion() < DRW::AC1018) {
23029 // R2000 stores the plot view by name; later versions use
23030 // a handle in the object tail. Resolve the source handle
23031 // before dropping the unavailable legacy tail field.
23032 if (layout.plotViewName.empty() &&
23033 record.plotViewHandle != DRW::NoHandle) {
23034 const auto *view = metadata.findViewByHandle(record.plotViewHandle);
23035 if (view != nullptr)
23036 layout.plotViewName = view->name;
23037 }
23038 layout.plotViewHandle.ref = 0;
23039 } else {
23040 layout.plotViewHandle.ref =
23041 remapViewHandle(record.plotViewHandle, "LAYOUT plot VIEW");
23042 }
23043 layout.shadePlotHandle.ref = 0;
23044 sanitizeObjectHandle(layout.shadePlotHandle.ref, "LAYOUT shade plot");
23045 layout.paperSpaceBlockRecordHandle.ref =
23046 remapBlockOwnerHandle(record.paperSpaceBlockRecordHandle);
23047 layout.lastActiveViewportHandle.ref = remapVportHandle(
23048 record.lastActiveViewportHandle, "LAYOUT active VPORT");
23049 layout.namedUcsHandle.ref =
23050 remapUcsHandle(record.namedUcsHandle, "LAYOUT named UCS");
23051 layout.baseUcsHandle.ref =
23052 remapUcsHandle(record.baseUcsHandle, "LAYOUT base UCS");
23053 layout.viewportHandles.clear();
23054 layout.viewportHandles.reserve(record.viewportHandles.size());
23055 for (const std::uint32_t sourceHandle : record.viewportHandles) {
23056 const std::uint32_t remapped =
23057 remapVportHandle(sourceHandle, "LAYOUT VPORT");
23058 if (remapped != 0)
23059 layout.viewportHandles.push_back(remapped);
23060 }
23061 layout.viewportCount =
23062 static_cast<std::int32_t>(layout.viewportHandles.size());
23063 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
23064 DwgWriteSourceKind::Object,
23065 record.handle, [&layout, this] {
23066 return m_dwgW->writeLayout(&layout);
23067 })) {
23068 ++nativeLayoutObjects;
23069 } else {
23070 hasBlockedReplay = true;
23071 ++blockedWriterRejected;
23072 }
23073 }
23074 }
23075 if (canWriteDbColor) {
23076 for (const auto &record : metadata.dbColors()) {
23077 if (nativeDbColorHandles.count(record.handle) == 0)
23078 continue;
23079 DRW_DbColor color = dbColorFromMetadata(record);
23080 if (hasDxfTableAppDataUnsupportedByDwg(color)) {
23081 hasBlockedReplay = true;
23082 ++blockedWriterRejected;
23083 continue;
23084 }
23085 remapEntry(color);
23086 if (!sanitizeCommonObjectReferences(color,
23087 "DBCOLOR common reference")) {
23088 hasBlockedReplay = true;
23089 ++blockedWriterRejected;
23090 continue;
23091 }
23092 color.setDwgCommonObjectState(
23093 static_cast<std::int32_t>(color.reactorHandles.size()),
23094 color.extensionDictionaryFlag(),
23095 encodedDwgDataStoragePresenceBit(record.handle,
23096 color.hasDataStorageBinaryData()));
23097 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
23098 DwgWriteSourceKind::Object,
23099 record.handle, [&color, this] {
23100 return m_dwgW->writeDbColor(&color);
23101 })) {
23102 ++nativeDbColorObjects;
23103 } else {
23104 hasBlockedReplay = true;
23105 ++blockedWriterRejected;
23106 }
23107 }
23108 }
23109 if (canWriteAssociativeObjects) {
23110 for (const auto &record : metadata.dimensionAssociations()) {
23111 if (nativeDimensionAssociationHandles.count(record.handle) == 0)
23112 continue;
23113 DRW_DimensionAssociation association =
23114 dimensionAssociationFromMetadata(record);
23115 if (hasDxfTableAppDataUnsupportedByDwg(association)) {
23116 hasBlockedReplay = true;
23117 ++blockedWriterRejected;
23118 continue;
23119 }
23120 remapEntry(association);
23121 sanitizeOptionalEntityHandle(association.m_dimensionHandle,
23122 "DIMASSOC dimension");
23123 if (m_writeFailed) {
23124 hasBlockedReplay = true;
23125 ++blockedWriterRejected;
23126 continue;
23127 }
23128 for (std::size_t ordinal = 0; ordinal < association.m_osnapRefs.size();
23129 ++ordinal)
23130 sanitizeOptionalCommonHandle(
23131 association.m_osnapRefs[ordinal].m_objectHandle, "DIMASSOC osnap",
23132 static_cast<std::uint32_t>(ordinal));
23133 if (m_writeFailed) {
23134 hasBlockedReplay = true;
23135 ++blockedWriterRejected;
23136 continue;
23137 }
23138 if (!sanitizeCommonObjectReferences(association,
23139 "DIMASSOC common reference")) {
23140 hasBlockedReplay = true;
23141 ++blockedWriterRejected;
23142 continue;
23143 }
23144 association.setDwgCommonObjectState(
23145 static_cast<std::int32_t>(association.reactorHandles.size()),
23146 association.extensionDictionaryFlag(),
23147 encodedDwgDataStoragePresenceBit(
23148 record.handle, association.hasDataStorageBinaryData()));
23149 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
23150 DwgWriteSourceKind::Object,
23151 record.handle, [&association, this] {
23152 return m_dwgW->writeDimensionAssociation(
23153 &association);
23154 })) {
23155 ++nativeDimensionAssociationObjects;
23156 } else {
23157 hasBlockedReplay = true;
23158 ++blockedWriterRejected;
23159 }
23160 }
23161 for (const auto &record : metadata.evaluationGraphs()) {
23162 if (nativeEvaluationGraphHandles.count(record.handle) == 0)
23163 continue;
23164 DRW_EvaluationGraph graph = evaluationGraphFromMetadata(record);
23165 if (hasDxfTableAppDataUnsupportedByDwg(graph)) {
23166 hasBlockedReplay = true;
23167 ++blockedWriterRejected;
23168 continue;
23169 }
23170 remapEntry(graph);
23171 for (std::size_t ordinal = 0; ordinal < graph.m_nodes.size(); ++ordinal)
23172 sanitizeObjectHandle(graph.m_nodes[ordinal].m_expressionHandle,
23173 "evaluation-graph",
23174 static_cast<std::uint32_t>(ordinal));
23175 if (!sanitizeCommonObjectReferences(
23176 graph, "EVALUATION_GRAPH common reference")) {
23177 hasBlockedReplay = true;
23178 ++blockedWriterRejected;
23179 continue;
23180 }
23181 graph.setDwgCommonObjectState(
23182 static_cast<std::int32_t>(graph.reactorHandles.size()),
23183 graph.extensionDictionaryFlag(),
23184 encodedDwgDataStoragePresenceBit(record.handle,
23185 graph.hasDataStorageBinaryData()));
23186 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
23187 DwgWriteSourceKind::Object,
23188 record.handle, [&graph, this] {
23189 return m_dwgW->writeEvaluationGraph(&graph);
23190 })) {
23191 ++nativeEvaluationGraphObjects;
23192 } else {
23193 hasBlockedReplay = true;
23194 ++blockedWriterRejected;
23195 }
23196 }
23197 }
23198 for (const auto &record : metadata.sections()) {
23199 if (nativeSectionHandles.count(record.handle) == 0)
23200 continue;
23201 DRW_Section section = sectionFromMetadata(record);
23202 if (hasDxfTableAppDataUnsupportedByDwg(section)) {
23203 hasBlockedReplay = true;
23204 ++blockedWriterRejected;
23205 continue;
23206 }
23207 remapEntry(section);
23208 if (!sanitizeCommonObjectReferences(section,
23209 "SECTION common reference")) {
23210 hasBlockedReplay = true;
23211 ++blockedWriterRejected;
23212 continue;
23213 }
23214 section.setDwgCommonObjectState(
23215 static_cast<std::int32_t>(section.reactorHandles.size()),
23216 section.extensionDictionaryFlag(),
23217 encodedDwgDataStoragePresenceBit(record.handle,
23218 section.hasDataStorageBinaryData()));
23219 if (section.m_kind == DRW_Section::Manager) {
23220 section.m_sectionHandles.erase(
23221 std::remove_if(section.m_sectionHandles.begin(),
23222 section.m_sectionHandles.end(),
23223 [&remapSectionObjectHandle](std::uint32_t &handle) {
23224 handle = remapSectionObjectHandle(
23225 handle, "SECTION manager");
23226 return handle == 0;
23227 }),
23228 section.m_sectionHandles.end());
23229 section.m_sectionCount =
23230 static_cast<std::int32_t>(section.m_sectionHandles.size());
23231 } else {
23232 for (auto &type : section.m_types) {
23233 type.m_sourceHandles.erase(
23234 std::remove_if(
23235 type.m_sourceHandles.begin(), type.m_sourceHandles.end(),
23236 [&remapSectionObjectHandle](std::uint32_t &handle) {
23237 handle = remapSectionObjectHandle(handle, "SECTION source");
23238 return handle == 0;
23239 }),
23240 type.m_sourceHandles.end());
23241 type.m_numSources =
23242 static_cast<std::int32_t>(type.m_sourceHandles.size());
23243 type.m_destinationBlockHandle =
23244 remapBlockOwnerHandle(type.m_destinationBlockHandle);
23245 }
23246 section.m_typeCount = static_cast<std::int32_t>(section.m_types.size());
23247 }
23248 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
23249 DwgWriteSourceKind::Object,
23250 record.handle, [&section, this] {
23251 return m_dwgW->writeSection(&section);
23252 })) {
23253 ++nativeSectionObjects;
23254 } else {
23255 hasBlockedReplay = true;
23256 ++blockedWriterRejected;
23257 }
23258 }
23259 // PR 13f — RASTERVARIABLES / GEODATA / SPATIAL_FILTER / BACKGROUND native
23260 // dispatch. Gate broadened from AC1021+ to AC1015+ now that the
23261 // smoke tests cover AC1015/AC1018/AC1024/AC1027/AC1032 (see
23262 // `[dwg-write][rastervariables]`, `[dwg-write][geodata]`,
23263 // `[dwg-write][spatial-filter]`). Matches the
23264 // `canRegisterCustomClassObjects` gate used in writeDwgClasses so
23265 // the CLASSES section entry and the OBJECTS instance always land
23266 // together.
23267 if (canRegisterCustomClassObjects) {
23268 for (const auto &record : metadata.mleaderStyles()) {
23269 if (nativeMLeaderStyleHandles.count(record.handle) == 0)
23270 continue;
23271 DRW_MLeaderStyle style = mleaderStyleFromMetadata(record);
23272 if (hasDxfTableAppDataUnsupportedByDwg(style)) {
23273 hasBlockedReplay = true;
23274 ++blockedWriterRejected;
23275 continue;
23276 }
23277 remapEntry(style);
23278 style.leaderLineTypeHandle.ref = remapTableHandle(
23279 record.leaderLineTypeHandle,
23280 metadata.lineTypeNameForHandle(record.leaderLineTypeHandle),
23281 m_dwgWriteLTypeHandleByName, "MLEADERSTYLE LTYPE");
23282 style.textStyleHandle.ref = remapTableHandle(
23283 record.textStyleHandle,
23284 metadata.textStyleNameForHandle(record.textStyleHandle),
23285 m_dwgWriteTextStyleHandleByName, "MLEADERSTYLE STYLE");
23286 style.arrowHeadBlockHandle.ref = remapBlockOwnerHandle(
23287 record.arrowHeadBlockHandle, "MLEADERSTYLE arrow block", 0,
23288 DRW::DwgHardPointer);
23289 style.blockHandle.ref = remapBlockOwnerHandle(
23290 record.blockHandle, "MLEADERSTYLE block", 0, DRW::DwgHardPointer);
23291 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
23292 DwgWriteSourceKind::Object,
23293 record.handle, [&style, this] {
23294 return m_dwgW->writeMLeaderStyle(&style);
23295 })) {
23296 ++nativeMLeaderStyleObjects;
23297 } else {
23298 hasBlockedReplay = true;
23299 ++blockedWriterRejected;
23300 }
23301 }
23302 if (m_dwgW->getVersion() <= DRW::AC1021) {
23303 for (const auto &record : metadata.tables()) {
23304 if (nativeTableStyleHandles.count(record.handle) == 0 ||
23305 !record.nativeTableStyle)
23306 continue;
23307 DRW_TableStyle style = *record.nativeTableStyle;
23308 if (hasDxfTableAppDataUnsupportedByDwg(style) ||
23309 !style.validateDwgPayload(m_dwgW->getVersion())) {
23310 hasBlockedReplay = true;
23311 ++blockedWriterRejected;
23312 continue;
23313 }
23314 remapEntry(style);
23315 if (!sanitizeCommonObjectReferences(style,
23316 "TABLESTYLE common reference")) {
23317 hasBlockedReplay = true;
23318 ++blockedWriterRejected;
23319 continue;
23320 }
23321 for (std::size_t rowIndex = 0; rowIndex < style.m_rowStyles.size();
23322 ++rowIndex) {
23323 DRW_TableStyleRowStyle &row = style.m_rowStyles[rowIndex];
23324 row.m_textStyleHandle = remapTableHandle(
23325 row.m_textStyleHandle,
23326 metadata.textStyleNameForHandle(row.m_textStyleHandle),
23327 m_dwgWriteTextStyleHandleByName, "TABLESTYLE STYLE",
23328 static_cast<std::uint32_t>(rowIndex));
23329 }
23330 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
23331 DwgWriteSourceKind::Object,
23332 record.handle, [&style, this] {
23333 return m_dwgW->writeTableStyle(&style);
23334 })) {
23335 ++nativeTableStyleObjects;
23336 } else {
23337 hasBlockedReplay = true;
23338 ++blockedWriterRejected;
23339 }
23340 }
23341 }
23342 for (const auto &record : metadata.materials()) {
23343 if (nativeMaterialHandles.count(record.handle) == 0)
23344 continue;
23345 DRW_Material material = materialFromMetadata(record);
23346 if (hasDxfTableAppDataUnsupportedByDwg(material)) {
23347 hasBlockedReplay = true;
23348 ++blockedWriterRejected;
23349 continue;
23350 }
23351 remapEntry(material);
23352 if (!sanitizeCommonObjectReferences(material,
23353 "MATERIAL common reference")) {
23354 hasBlockedReplay = true;
23355 ++blockedWriterRejected;
23356 continue;
23357 }
23358 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
23359 DwgWriteSourceKind::Object,
23360 record.handle, [&material, this] {
23361 return m_dwgW->writeMaterial(&material);
23362 })) {
23363 ++nativeMaterialObjects;
23364 } else {
23365 hasBlockedReplay = true;
23366 ++blockedWriterRejected;
23367 }
23368 }
23369 for (const auto &record : metadata.lightLists()) {
23370 if (nativeLightListHandles.count(record.handle) == 0)
23371 continue;
23372 DRW_LightList lightList = lightListFromMetadata(record);
23373 if (hasDxfTableAppDataUnsupportedByDwg(lightList)) {
23374 hasBlockedReplay = true;
23375 ++blockedWriterRejected;
23376 continue;
23377 }
23378 remapEntry(lightList);
23379 if (!sanitizeCommonObjectReferences(lightList,
23380 "LIGHTLIST common reference")) {
23381 hasBlockedReplay = true;
23382 ++blockedWriterRejected;
23383 continue;
23384 }
23385 struct PendingLightReference {
23386 std::uint32_t sourceHandle = 0;
23387 std::uint32_t outputHandle = 0;
23388 std::uint32_t ordinal = 0;
23389 DwgWriteReferenceStatus targetStatus =
23390 DwgWriteReferenceStatus::Missing;
23391 bool required = false;
23392 };
23393 std::vector<PendingLightReference> pendingReferences;
23394 std::vector<DRW_LightListEntry> retainedLights;
23395 pendingReferences.reserve(lightList.m_lights.size());
23396 retainedLights.reserve(lightList.m_lights.size());
23397 for (std::uint32_t ordinal = 0; ordinal < lightList.m_lights.size();
23398 ++ordinal) {
23399 DRW_LightListEntry light = lightList.m_lights[ordinal];
23400 const std::uint32_t sourceLightHandle = light.m_handle;
23401 if (sourceLightHandle == 0)
23402 continue;
23403 std::uint32_t outputLightHandle = 0;
23404 const DwgWriteReferenceStatus lightStatus =
23405 resolveDwgWriteEntityHandle(sourceLightHandle,
23406 &outputLightHandle);
23407 if (lightStatus != DwgWriteReferenceStatus::Committed ||
23408 outputLightHandle == 0) {
23409 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
23410 "RS_FilterDXFRW: dropping dangling DWG "
23411 "LIGHTLIST light target 0x%X",
23412 sourceLightHandle);
23413 const DwgWriteReferenceStatus dropStatus =
23414 lightStatus == DwgWriteReferenceStatus::Ambiguous
23415 ? DwgWriteReferenceStatus::Ambiguous
23416 : lightStatus == DwgWriteReferenceStatus::Suppressed
23417 ? DwgWriteReferenceStatus::Suppressed
23418 : DwgWriteReferenceStatus::Missing;
23419 pendingReferences.push_back(
23420 {sourceLightHandle, 0, ordinal, dropStatus, false});
23421 continue;
23422 }
23423 light.m_handle = outputLightHandle;
23424 retainedLights.push_back(light);
23425 pendingReferences.push_back(
23426 {sourceLightHandle, light.m_handle, ordinal,
23427 DwgWriteReferenceStatus::Committed, true});
23428 }
23429 lightList.m_lights = std::move(retainedLights);
23430 lightList.m_lightCount =
23431 static_cast<std::uint32_t>(lightList.m_lights.size());
23432 const bool wroteLightList = writeDwgObjectWithCompletion(
23433 RS_FilterDXFRW_DwgWriteIdentityRegistry::DwgWriteSourceKind::Object,
23434 record.handle,
23435 [&lightList, this] { return m_dwgW->writeLightList(&lightList); },
23436 [&pendingReferences, &lightList, &recordOptionalDrop, this,
23437 &record]() {
23438 const std::uint32_t outputLightListHandle = lightList.handle;
23439 for (const PendingLightReference &reference : pendingReferences) {
23440 const DwgWriteReferenceReceipt receipt{
23441 DwgWriteSourceKind::Object, record.handle,
23442 outputLightListHandle, "LIGHTLIST member",
23443 reference.ordinal, DwgWriteSourceKind::Entity,
23444 reference.sourceHandle, reference.outputHandle,
23445 reference.required, 5};
23446 const bool recorded =
23447 reference.required
23448 ? recordDwgWriteReferenceReceipt(receipt)
23449 : recordOptionalDrop(
23450 reference.sourceHandle, reference.targetStatus,
23451 "LIGHTLIST member", reference.ordinal,
23452 reference.targetStatus ==
23453 DwgWriteReferenceStatus::Ambiguous
23454 ? "ambiguous target"
23455 : reference.targetStatus ==
23456 DwgWriteReferenceStatus::Suppressed
23457 ? "suppressed target"
23458 : "missing target",
23459 DwgWriteSourceKind::Entity);
23460 if (!recorded) {
23461 return false;
23462 }
23463 }
23464 return true;
23465 });
23466 if (!wroteLightList) {
23467 hasBlockedReplay = true;
23468 ++blockedWriterRejected;
23469 } else {
23470 ++nativeLightListObjects;
23471 }
23472 }
23473 for (const auto &record : metadata.backgrounds()) {
23474 if (nativeBackgroundHandles.count(record.handle) == 0)
23475 continue;
23476 DRW_Background background = backgroundFromMetadata(record);
23477 if (hasDxfTableAppDataUnsupportedByDwg(background)) {
23478 hasBlockedReplay = true;
23479 ++blockedWriterRejected;
23480 continue;
23481 }
23482 remapEntry(background);
23483 if (!sanitizeCommonObjectReferences(background,
23484 "BACKGROUND common reference")) {
23485 hasBlockedReplay = true;
23486 ++blockedWriterRejected;
23487 continue;
23488 }
23489 if (background.m_kind == DRW_Background::Ibl)
23490 sanitizeObjectHandle(background.m_secondaryBackgroundHandle,
23491 "BACKGROUND secondary", 0, DRW::DwgHardPointer);
23492 else if (background.m_kind == DRW_Background::Skylight)
23493 sanitizeObjectHandle(background.m_sunHandle, "BACKGROUND sun", 0,
23494 DRW::DwgHardPointer);
23495 background.setDwgCommonObjectState(
23496 static_cast<std::int32_t>(background.reactorHandles.size()),
23497 background.extensionDictionaryFlag(),
23498 encodedDwgDataStoragePresenceBit(
23499 record.handle, background.hasDataStorageBinaryData()));
23500 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
23501 DwgWriteSourceKind::Object,
23502 record.handle, [&background, this] {
23503 return m_dwgW->writeBackground(&background);
23504 })) {
23505 ++nativeBackgroundObjects;
23506 } else {
23507 hasBlockedReplay = true;
23508 ++blockedWriterRejected;
23509 }
23510 }
23511 for (const auto &record : metadata.sunStudies()) {
23512 if (nativeSunStudyHandles.count(record.handle) == 0)
23513 continue;
23514 DRW_SunStudy study = sunStudyFromMetadata(record);
23515 if (hasDxfTableAppDataUnsupportedByDwg(study)) {
23516 hasBlockedReplay = true;
23517 ++blockedWriterRejected;
23518 continue;
23519 }
23520 remapEntry(study);
23521 if (!sanitizeCommonObjectReferences(study,
23522 "SUNSTUDY common reference")) {
23523 hasBlockedReplay = true;
23524 ++blockedWriterRejected;
23525 continue;
23526 }
23527 sanitizeObjectHandle(study.m_pageSetupWizardHandle,
23528 "SUNSTUDY page setup", 0, DRW::DwgHardPointer);
23529 study.m_viewHandle =
23530 remapViewHandle(record.viewHandle, "SUNSTUDY view");
23531 sanitizeObjectHandle(study.m_visualStyleHandle, "SUNSTUDY visual style",
23532 0, DRW::DwgHardPointer);
23533 study.m_textStyleHandle = remapTableHandle(
23534 record.textStyleHandle,
23535 metadata.textStyleNameForHandle(record.textStyleHandle),
23536 m_dwgWriteTextStyleHandleByName, "SUNSTUDY STYLE", 0,
23537 DRW::DwgHardPointer);
23538 study.setDwgCommonObjectState(
23539 static_cast<std::int32_t>(study.reactorHandles.size()),
23540 study.extensionDictionaryFlag(),
23541 encodedDwgDataStoragePresenceBit(record.handle,
23542 study.hasDataStorageBinaryData()));
23543 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
23544 DwgWriteSourceKind::Object,
23545 record.handle, [&study, this] {
23546 return m_dwgW->writeSunStudy(&study);
23547 })) {
23548 ++nativeSunStudyObjects;
23549 } else {
23550 hasBlockedReplay = true;
23551 ++blockedWriterRejected;
23552 }
23553 }
23554 for (const auto &record : metadata.motionPaths()) {
23555 if (nativeMotionPathHandles.count(record.handle) == 0)
23556 continue;
23557 DRW_MotionPath path = motionPathFromMetadata(record);
23558 if (hasDxfTableAppDataUnsupportedByDwg(path)) {
23559 hasBlockedReplay = true;
23560 ++blockedWriterRejected;
23561 continue;
23562 }
23563 remapEntry(path);
23564 if (!sanitizeCommonObjectReferences(path,
23565 "MOTIONPATH common reference")) {
23566 hasBlockedReplay = true;
23567 ++blockedWriterRejected;
23568 continue;
23569 }
23570 sanitizeObjectHandle(path.m_cameraPathHandle, "MOTIONPATH camera path",
23571 0, DRW::DwgHardPointer);
23572 sanitizeObjectHandle(path.m_targetPathHandle, "MOTIONPATH target path",
23573 0, DRW::DwgHardPointer);
23574 path.m_viewTableHandle =
23575 remapViewHandle(record.viewTableHandle, "MOTIONPATH view table");
23576 path.setDwgCommonObjectState(
23577 static_cast<std::int32_t>(path.reactorHandles.size()),
23578 path.extensionDictionaryFlag(),
23579 encodedDwgDataStoragePresenceBit(record.handle,
23580 path.hasDataStorageBinaryData()));
23581 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
23582 DwgWriteSourceKind::Object,
23583 record.handle, [&path, this] {
23584 return m_dwgW->writeMotionPath(&path);
23585 })) {
23586 ++nativeMotionPathObjects;
23587 } else {
23588 hasBlockedReplay = true;
23589 ++blockedWriterRejected;
23590 }
23591 }
23592 for (const auto &record : metadata.curvePaths()) {
23593 if (nativeCurvePathHandles.count(record.handle) == 0)
23594 continue;
23595 DRW_CurvePath path = curvePathFromMetadata(record);
23596 if (hasDxfTableAppDataUnsupportedByDwg(path)) {
23597 hasBlockedReplay = true;
23598 ++blockedWriterRejected;
23599 continue;
23600 }
23601 remapEntry(path);
23602 path.m_entityHandle =
23603 remapRequiredEntityHandle(record.entityHandle, "CURVEPATH entity");
23604 if (m_writeFailed) {
23605 hasBlockedReplay = true;
23606 ++blockedWriterRejected;
23607 continue;
23608 }
23609 if (!sanitizeCommonObjectReferences(path,
23610 "CURVEPATH common reference")) {
23611 hasBlockedReplay = true;
23612 ++blockedWriterRejected;
23613 continue;
23614 }
23615 path.setDwgCommonObjectState(
23616 static_cast<std::int32_t>(path.reactorHandles.size()),
23617 path.extensionDictionaryFlag(),
23618 encodedDwgDataStoragePresenceBit(record.handle,
23619 path.hasDataStorageBinaryData()));
23620 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
23621 DwgWriteSourceKind::Object,
23622 record.handle, [&path, this] {
23623 return m_dwgW->writeCurvePath(&path);
23624 })) {
23625 ++nativeCurvePathObjects;
23626 } else {
23627 hasBlockedReplay = true;
23628 ++blockedWriterRejected;
23629 }
23630 }
23631 for (const auto &record : metadata.pointPaths()) {
23632 if (nativePointPathHandles.count(record.handle) == 0)
23633 continue;
23634 DRW_PointPath path = pointPathFromMetadata(record);
23635 if (hasDxfTableAppDataUnsupportedByDwg(path)) {
23636 hasBlockedReplay = true;
23637 ++blockedWriterRejected;
23638 continue;
23639 }
23640 remapEntry(path);
23641 if (!sanitizeCommonObjectReferences(path,
23642 "POINTPATH common reference")) {
23643 hasBlockedReplay = true;
23644 ++blockedWriterRejected;
23645 continue;
23646 }
23647 path.setDwgCommonObjectState(
23648 static_cast<std::int32_t>(path.reactorHandles.size()),
23649 path.extensionDictionaryFlag(),
23650 encodedDwgDataStoragePresenceBit(record.handle,
23651 path.hasDataStorageBinaryData()));
23652 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
23653 DwgWriteSourceKind::Object,
23654 record.handle, [&path, this] {
23655 return m_dwgW->writePointPath(&path);
23656 })) {
23657 ++nativePointPathObjects;
23658 } else {
23659 hasBlockedReplay = true;
23660 ++blockedWriterRejected;
23661 }
23662 }
23663 for (const auto &record : metadata.objectPtrs()) {
23664 if (nativeObjectPtrHandles.count(record.handle) == 0)
23665 continue;
23666 DRW_ObjectPtr object = objectPtrFromMetadata(record);
23667 if (hasDxfTableAppDataUnsupportedByDwg(object)) {
23668 hasBlockedReplay = true;
23669 ++blockedWriterRejected;
23670 continue;
23671 }
23672 remapEntry(object);
23673 if (!sanitizeCommonObjectReferences(object,
23674 "OBJECT_PTR common reference")) {
23675 hasBlockedReplay = true;
23676 ++blockedWriterRejected;
23677 continue;
23678 }
23679 object.setDwgCommonObjectState(
23680 static_cast<std::int32_t>(object.reactorHandles.size()),
23681 object.extensionDictionaryFlag(),
23682 encodedDwgDataStoragePresenceBit(
23683 record.handle, object.hasDataStorageBinaryData()));
23684 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
23685 DwgWriteSourceKind::Object,
23686 record.handle, [&object, this] {
23687 return m_dwgW->writeObjectPtr(&object);
23688 })) {
23689 ++nativeObjectPtrObjects;
23690 } else {
23691 hasBlockedReplay = true;
23692 ++blockedWriterRejected;
23693 }
23694 }
23695 for (const auto &record : metadata.partialViewingIndexes()) {
23696 if (nativePartialViewingIndexHandles.count(record.handle) == 0)
23697 continue;
23698 DRW_PartialViewingIndex index = partialViewingIndexFromMetadata(record);
23699 if (hasDxfTableAppDataUnsupportedByDwg(index)) {
23700 hasBlockedReplay = true;
23701 ++blockedWriterRejected;
23702 continue;
23703 }
23704 remapEntry(index);
23705 if (!sanitizeCommonObjectReferences(
23706 index, "PARTIAL_VIEWING_INDEX common reference")) {
23707 hasBlockedReplay = true;
23708 ++blockedWriterRejected;
23709 continue;
23710 }
23711 for (std::size_t ordinal = 0; ordinal < index.m_entries.size();
23712 ++ordinal) {
23713 auto &entry = index.m_entries[ordinal];
23714 sanitizeIndexedObjectHandle(
23715 entry.objectHandle, "PARTIAL_VIEWING_INDEX entry",
23716 static_cast<std::uint32_t>(ordinal), DRW::DwgHardPointer);
23717 }
23718 index.m_entryCount = static_cast<std::uint32_t>(index.m_entries.size());
23719 index.setDwgCommonObjectState(
23720 static_cast<std::int32_t>(index.reactorHandles.size()),
23721 index.extensionDictionaryFlag(),
23722 encodedDwgDataStoragePresenceBit(record.handle,
23723 index.hasDataStorageBinaryData()));
23724 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
23725 DwgWriteSourceKind::Object,
23726 record.handle, [&index, this] {
23727 return m_dwgW->writePartialViewingIndex(&index);
23728 })) {
23729 ++nativePartialViewingIndexObjects;
23730 } else {
23731 hasBlockedReplay = true;
23732 ++blockedWriterRejected;
23733 }
23734 }
23735 for (const auto &record : metadata.renderSettings()) {
23736 if (nativeRenderSettingsHandles.count(record.handle) == 0)
23737 continue;
23738 DRW_RenderSettings settings = renderSettingsFromMetadata(record);
23739 if (hasDxfTableAppDataUnsupportedByDwg(settings)) {
23740 hasBlockedReplay = true;
23741 ++blockedWriterRejected;
23742 continue;
23743 }
23744 remapEntry(settings);
23745 if (!sanitizeCommonObjectReferences(
23746 settings, "RENDERSETTINGS common reference")) {
23747 hasBlockedReplay = true;
23748 ++blockedWriterRejected;
23749 continue;
23750 }
23751 settings.setDwgCommonObjectState(
23752 static_cast<std::int32_t>(settings.reactorHandles.size()),
23753 settings.extensionDictionaryFlag(),
23754 encodedDwgDataStoragePresenceBit(
23755 record.handle, settings.hasDataStorageBinaryData()));
23756 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
23757 DwgWriteSourceKind::Object,
23758 record.handle, [&settings, this] {
23759 return m_dwgW->writeRenderSettings(&settings);
23760 })) {
23761 ++nativeRenderSettingsObjects;
23762 } else {
23763 hasBlockedReplay = true;
23764 ++blockedWriterRejected;
23765 }
23766 }
23767 for (const auto &record : metadata.visualStyles()) {
23768 if (nativeVisualStyleHandles.count(record.handle) == 0)
23769 continue;
23770 DRW_VisualStyle style = visualStyleFromMetadata(record);
23771 if (hasDxfTableAppDataUnsupportedByDwg(style)) {
23772 hasBlockedReplay = true;
23773 ++blockedWriterRejected;
23774 continue;
23775 }
23776 remapEntry(style);
23777 if (!sanitizeCommonObjectReferences(style,
23778 "VISUALSTYLE common reference")) {
23779 hasBlockedReplay = true;
23780 ++blockedWriterRejected;
23781 continue;
23782 }
23783 style.setDwgCommonObjectState(
23784 static_cast<std::int32_t>(style.reactorHandles.size()),
23785 style.extensionDictionaryFlag(),
23786 encodedDwgDataStoragePresenceBit(record.handle,
23787 style.hasDataStorageBinaryData()));
23788 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
23789 DwgWriteSourceKind::Object,
23790 record.handle, [&style, this] {
23791 return m_dwgW->writeVisualStyle(&style);
23792 })) {
23793 ++nativeVisualStyleObjects;
23794 } else {
23795 hasBlockedReplay = true;
23796 ++blockedWriterRejected;
23797 }
23798 }
23799 for (const auto &record : metadata.tvDeviceProperties()) {
23800 if (nativeTvDevicePropertiesHandles.count(record.handle) == 0)
23801 continue;
23802 DRW_TvDeviceProperties properties =
23803 tvDevicePropertiesFromMetadata(record);
23804 if (hasDxfTableAppDataUnsupportedByDwg(properties)) {
23805 hasBlockedReplay = true;
23806 ++blockedWriterRejected;
23807 continue;
23808 }
23809 remapEntry(properties);
23810 properties.parentHandle = remapObjectOwnerHandle(record.parentHandle);
23811 if (!sanitizeCommonObjectReferences(
23812 properties, "TVDEVICEPROPERTIES common reference")) {
23813 hasBlockedReplay = true;
23814 ++blockedWriterRejected;
23815 continue;
23816 }
23817 properties.setDwgCommonObjectState(
23818 static_cast<std::int32_t>(properties.reactorHandles.size()),
23819 properties.extensionDictionaryFlag(),
23820 encodedDwgDataStoragePresenceBit(
23821 record.handle, properties.hasDataStorageBinaryData()));
23822 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
23823 DwgWriteSourceKind::Object,
23824 record.handle, [&properties, this] {
23825 return m_dwgW->writeTvDeviceProperties(
23826 &properties);
23827 })) {
23828 ++nativeTvDevicePropertiesObjects;
23829 } else {
23830 hasBlockedReplay = true;
23831 ++blockedWriterRejected;
23832 }
23833 }
23834 for (const auto &record : metadata.vxControls()) {
23835 if (nativeVxControlHandles.count(record.handle) == 0)
23836 continue;
23837 DRW_VxControl control = vxControlFromMetadata(record);
23838 if (hasDxfTableAppDataUnsupportedByDwg(control)) {
23839 hasBlockedReplay = true;
23840 ++blockedWriterRejected;
23841 continue;
23842 }
23843 remapEntry(control);
23844 control.parentHandle = remapObjectOwnerHandle(record.parentHandle);
23845 if (!sanitizeCommonObjectReferences(control,
23846 "VXCONTROL common reference")) {
23847 hasBlockedReplay = true;
23848 ++blockedWriterRejected;
23849 continue;
23850 }
23851 sanitizeIndexedObjectList(control.recordHandles, "VXCONTROL record",
23852 DRW::DwgSoftOwnership);
23853 sanitizeObjectHandle(control.xDictHandle,
23854 "VXCONTROL extension dictionary");
23855 control.setDwgCommonObjectState(
23856 static_cast<std::int32_t>(control.reactorHandles.size()),
23857 control.extensionDictionaryFlag(),
23858 encodedDwgDataStoragePresenceBit(
23859 record.handle, control.hasDataStorageBinaryData()));
23860 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
23861 DwgWriteSourceKind::Object,
23862 record.handle, [&control, this] {
23863 return m_dwgW->writeVxControl(&control);
23864 })) {
23865 ++nativeVxControlObjects;
23866 } else {
23867 hasBlockedReplay = true;
23868 ++blockedWriterRejected;
23869 }
23870 }
23871 for (const auto &record : metadata.vxTableRecords()) {
23872 if (nativeVxTableRecordHandles.count(record.handle) == 0)
23873 continue;
23874 DRW_VxTableRecord tableRecord = vxTableRecordFromMetadata(record);
23875 if (hasDxfTableAppDataUnsupportedByDwg(tableRecord)) {
23876 hasBlockedReplay = true;
23877 ++blockedWriterRejected;
23878 continue;
23879 }
23880 remapEntry(tableRecord);
23881 tableRecord.parentHandle = remapObjectOwnerHandle(record.parentHandle);
23882 if (!sanitizeCommonObjectReferences(tableRecord,
23883 "VXTABLERECORD common reference")) {
23884 hasBlockedReplay = true;
23885 ++blockedWriterRejected;
23886 continue;
23887 }
23888 tableRecord.setDwgCommonObjectState(
23889 static_cast<std::int32_t>(tableRecord.reactorHandles.size()),
23890 tableRecord.extensionDictionaryFlag(),
23891 encodedDwgDataStoragePresenceBit(
23892 record.handle, tableRecord.hasDataStorageBinaryData()));
23893 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
23894 DwgWriteSourceKind::Object,
23895 record.handle, [&tableRecord, this] {
23896 return m_dwgW->writeVxTableRecord(&tableRecord);
23897 })) {
23898 ++nativeVxTableRecordObjects;
23899 } else {
23900 hasBlockedReplay = true;
23901 ++blockedWriterRejected;
23902 }
23903 }
23904 for (const auto &record : metadata.rasterVariables()) {
23905 if (nativeRasterVariablesHandles.count(record.handle) == 0)
23906 continue;
23907 DRW_RasterVariables rv = rasterVariablesFromMetadata(record);
23908 if (hasDxfTableAppDataUnsupportedByDwg(rv)) {
23909 hasBlockedReplay = true;
23910 ++blockedWriterRejected;
23911 continue;
23912 }
23913 remapEntry(rv);
23914 if (!sanitizeCommonObjectReferences(
23915 rv, "RASTERVARIABLES common reference")) {
23916 hasBlockedReplay = true;
23917 ++blockedWriterRejected;
23918 continue;
23919 }
23920 rv.setDwgCommonObjectState(
23921 static_cast<std::int32_t>(rv.reactorHandles.size()),
23922 rv.extensionDictionaryFlag(),
23923 encodedDwgDataStoragePresenceBit(record.handle,
23924 rv.hasDataStorageBinaryData()));
23925 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
23926 DwgWriteSourceKind::Object,
23927 record.handle, [&rv, this] {
23928 return m_dwgW->writeRasterVariables(&rv);
23929 })) {
23930 ++nativeRasterVariablesObjects;
23931 } else {
23932 hasBlockedReplay = true;
23933 ++blockedWriterRejected;
23934 }
23935 }
23936 for (const auto &record : metadata.wipeoutVariables()) {
23937 if (nativeWipeoutVariablesHandles.count(record.handle) == 0)
23938 continue;
23939 DRW_WipeoutVariables wv = wipeoutVariablesFromMetadata(record);
23940 if (hasDxfTableAppDataUnsupportedByDwg(wv)) {
23941 hasBlockedReplay = true;
23942 ++blockedWriterRejected;
23943 continue;
23944 }
23945 remapEntry(wv);
23946 if (!sanitizeCommonObjectReferences(
23947 wv, "WIPEOUTVARIABLES common reference")) {
23948 hasBlockedReplay = true;
23949 ++blockedWriterRejected;
23950 continue;
23951 }
23952 wv.setDwgCommonObjectState(
23953 static_cast<std::int32_t>(wv.reactorHandles.size()),
23954 wv.extensionDictionaryFlag(),
23955 encodedDwgDataStoragePresenceBit(record.handle,
23956 wv.hasDataStorageBinaryData()));
23957 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
23958 DwgWriteSourceKind::Object,
23959 record.handle, [&wv, this] {
23960 return m_dwgW->writeWipeoutVariables(&wv);
23961 })) {
23962 ++nativeWipeoutVariablesObjects;
23963 } else {
23964 hasBlockedReplay = true;
23965 ++blockedWriterRejected;
23966 }
23967 }
23968 for (const auto &record : metadata.geoData()) {
23969 if (nativeGeoDataHandles.count(record.handle) == 0)
23970 continue;
23971 DRW_GeoData gd = geoDataFromMetadata(record);
23972 if (hasDxfTableAppDataUnsupportedByDwg(gd)) {
23973 hasBlockedReplay = true;
23974 ++blockedWriterRejected;
23975 continue;
23976 }
23977 remapEntry(gd);
23978 gd.m_hostBlockHandle = remapBlockOwnerHandle(record.hostBlockHandle);
23979 if (!sanitizeCommonObjectReferences(gd, "GEODATA common reference")) {
23980 hasBlockedReplay = true;
23981 ++blockedWriterRejected;
23982 continue;
23983 }
23984 gd.setDwgCommonObjectState(
23985 static_cast<std::int32_t>(gd.reactorHandles.size()),
23986 gd.extensionDictionaryFlag(),
23987 encodedDwgDataStoragePresenceBit(record.handle,
23988 gd.hasDataStorageBinaryData()));
23989 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
23990 DwgWriteSourceKind::Object,
23991 record.handle,
23992 [&gd, this] { return m_dwgW->writeGeoData(&gd); })) {
23993 ++nativeGeoDataObjects;
23994 } else {
23995 hasBlockedReplay = true;
23996 ++blockedWriterRejected;
23997 }
23998 }
23999 for (const auto &record : metadata.spatialFilters()) {
24000 if (nativeSpatialFilterHandles.count(record.handle) == 0)
24001 continue;
24002 DRW_SpatialFilter sf = spatialFilterFromMetadata(record);
24003 if (hasDxfTableAppDataUnsupportedByDwg(sf)) {
24004 hasBlockedReplay = true;
24005 ++blockedWriterRejected;
24006 continue;
24007 }
24008 remapEntry(sf);
24009 if (!sanitizeCommonObjectReferences(
24010 sf, "SPATIAL_FILTER common reference")) {
24011 hasBlockedReplay = true;
24012 ++blockedWriterRejected;
24013 continue;
24014 }
24015 sf.setDwgCommonObjectState(
24016 static_cast<std::int32_t>(sf.reactorHandles.size()),
24017 sf.extensionDictionaryFlag(),
24018 encodedDwgDataStoragePresenceBit(record.handle,
24019 sf.hasDataStorageBinaryData()));
24020 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
24021 DwgWriteSourceKind::Object,
24022 record.handle, [&sf, this] {
24023 return m_dwgW->writeSpatialFilter(&sf);
24024 })) {
24025 ++nativeSpatialFilterObjects;
24026 } else {
24027 hasBlockedReplay = true;
24028 ++blockedWriterRejected;
24029 }
24030 }
24031 // PR 13g — SCALE / IDBUFFER / LAYER_INDEX / SPATIAL_INDEX /
24032 // DICTIONARYVAR (custom classes 508-512) dispatch. Smoke
24033 // tests at AC1015/AC1018 confirm the encoders + parsers
24034 // round-trip cleanly; SPATIAL_INDEX's pre-R2007 opaque body
24035 // path is exercised at AC1015/AC1018.
24036 for (const auto &record : metadata.scales()) {
24037 if (nativeScaleHandles.count(record.handle) == 0)
24038 continue;
24039 DRW_Scale s = scaleFromMetadata(record);
24040 if (hasDxfTableAppDataUnsupportedByDwg(s)) {
24041 hasBlockedReplay = true;
24042 ++blockedWriterRejected;
24043 continue;
24044 }
24045 remapEntry(s);
24046 if (!sanitizeCommonObjectReferences(s, "SCALE common reference")) {
24047 hasBlockedReplay = true;
24048 ++blockedWriterRejected;
24049 continue;
24050 }
24051 s.setDwgCommonObjectState(
24052 static_cast<std::int32_t>(s.reactorHandles.size()),
24053 s.extensionDictionaryFlag(),
24054 encodedDwgDataStoragePresenceBit(record.handle,
24055 s.hasDataStorageBinaryData()));
24056 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
24057 DwgWriteSourceKind::Object,
24058 record.handle,
24059 [&s, this] { return m_dwgW->writeScale(&s); })) {
24060 ++nativeScaleObjects;
24061 } else {
24062 hasBlockedReplay = true;
24063 ++blockedWriterRejected;
24064 }
24065 }
24066 for (const auto &record : metadata.idBuffers()) {
24067 if (nativeIDBufferHandles.count(record.handle) == 0)
24068 continue;
24069 DRW_IDBuffer b = idBufferFromMetadata(record);
24070 if (hasDxfTableAppDataUnsupportedByDwg(b)) {
24071 hasBlockedReplay = true;
24072 ++blockedWriterRejected;
24073 continue;
24074 }
24075 remapEntry(b);
24076 sanitizeIndexedObjectList(b.objIds, "IDBUFFER");
24077 if (!sanitizeCommonObjectReferences(b, "IDBUFFER common reference")) {
24078 hasBlockedReplay = true;
24079 ++blockedWriterRejected;
24080 continue;
24081 }
24082 b.setDwgCommonObjectState(
24083 static_cast<std::int32_t>(b.reactorHandles.size()),
24084 b.extensionDictionaryFlag(),
24085 encodedDwgDataStoragePresenceBit(record.handle,
24086 b.hasDataStorageBinaryData()));
24087 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
24088 DwgWriteSourceKind::Object,
24089 record.handle,
24090 [&b, this] { return m_dwgW->writeIDBuffer(&b); })) {
24091 ++nativeIDBufferObjects;
24092 } else {
24093 hasBlockedReplay = true;
24094 ++blockedWriterRejected;
24095 }
24096 }
24097 for (const auto &record : metadata.layerIndexes()) {
24098 if (nativeLayerIndexHandles.count(record.handle) == 0)
24099 continue;
24100 DRW_LayerIndex li = layerIndexFromMetadata(record);
24101 if (hasDxfTableAppDataUnsupportedByDwg(li)) {
24102 hasBlockedReplay = true;
24103 ++blockedWriterRejected;
24104 continue;
24105 }
24106 remapEntry(li);
24107 std::vector<DRW_LayerIndexEntry> keptEntries;
24108 keptEntries.reserve(li.entries.size());
24109 for (std::size_t ordinal = 0; ordinal < li.entries.size(); ++ordinal) {
24110 DRW_LayerIndexEntry entry = li.entries[ordinal];
24111 sanitizeObjectHandle(entry.entryHandle, "LAYER_INDEX",
24112 static_cast<std::uint32_t>(ordinal),
24113 DRW::DwgHardPointer);
24114 if (entry.entryHandle != 0)
24115 keptEntries.push_back(std::move(entry));
24116 }
24117 li.entries = std::move(keptEntries);
24118 if (!sanitizeCommonObjectReferences(li,
24119 "LAYER_INDEX common reference")) {
24120 hasBlockedReplay = true;
24121 ++blockedWriterRejected;
24122 continue;
24123 }
24124 li.setDwgCommonObjectState(
24125 static_cast<std::int32_t>(li.reactorHandles.size()),
24126 li.extensionDictionaryFlag(),
24127 encodedDwgDataStoragePresenceBit(record.handle,
24128 li.hasDataStorageBinaryData()));
24129 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
24130 DwgWriteSourceKind::Object,
24131 record.handle, [&li, this] {
24132 return m_dwgW->writeLayerIndex(&li);
24133 })) {
24134 ++nativeLayerIndexObjects;
24135 } else {
24136 hasBlockedReplay = true;
24137 ++blockedWriterRejected;
24138 }
24139 }
24140 for (const auto &record : metadata.spatialIndexes()) {
24141 if (nativeSpatialIndexHandles.count(record.handle) == 0)
24142 continue;
24143 DRW_SpatialIndex si = spatialIndexFromMetadata(record);
24144 if (hasDxfTableAppDataUnsupportedByDwg(si)) {
24145 hasBlockedReplay = true;
24146 ++blockedWriterRejected;
24147 continue;
24148 }
24149 remapEntry(si);
24150 if (!sanitizeCommonObjectReferences(si,
24151 "SPATIAL_INDEX common reference")) {
24152 hasBlockedReplay = true;
24153 ++blockedWriterRejected;
24154 continue;
24155 }
24156 si.setDwgCommonObjectState(
24157 static_cast<std::int32_t>(si.reactorHandles.size()),
24158 si.extensionDictionaryFlag(),
24159 encodedDwgDataStoragePresenceBit(record.handle,
24160 si.hasDataStorageBinaryData()));
24161 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
24162 DwgWriteSourceKind::Object,
24163 record.handle, [&si, this] {
24164 return m_dwgW->writeSpatialIndex(&si);
24165 })) {
24166 ++nativeSpatialIndexObjects;
24167 } else {
24168 hasBlockedReplay = true;
24169 ++blockedWriterRejected;
24170 }
24171 }
24172 for (const auto &record : metadata.dictionaryVars()) {
24173 if (nativeDictionaryVarHandles.count(record.handle) == 0)
24174 continue;
24175 DRW_DictionaryVar dv = dictionaryVarFromMetadata(record);
24176 if (hasDxfTableAppDataUnsupportedByDwg(dv)) {
24177 hasBlockedReplay = true;
24178 ++blockedWriterRejected;
24179 continue;
24180 }
24181 remapEntry(dv);
24182 if (!sanitizeCommonObjectReferences(dv,
24183 "DICTIONARYVAR common reference")) {
24184 hasBlockedReplay = true;
24185 ++blockedWriterRejected;
24186 continue;
24187 }
24188 dv.setDwgCommonObjectState(
24189 static_cast<std::int32_t>(dv.reactorHandles.size()),
24190 dv.extensionDictionaryFlag(),
24191 encodedDwgDataStoragePresenceBit(record.handle,
24192 dv.hasDataStorageBinaryData()));
24193 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
24194 DwgWriteSourceKind::Object,
24195 record.handle, [&dv, this] {
24196 return m_dwgW->writeDictionaryVar(&dv);
24197 })) {
24198 ++nativeDictionaryVarObjects;
24199 } else {
24200 hasBlockedReplay = true;
24201 ++blockedWriterRejected;
24202 }
24203 }
24204 // PR 13h — DICTIONARYWDFLT / SORTENTSTABLE / FIELDLIST / FIELD
24205 // (custom classes 513-516) dispatch. Smoke tests at AC1015/
24206 // AC1018 confirm the encoders + parsers round-trip cleanly;
24207 // FIELD exercises the parser-mirrored `version < AC1021`
24208 // m_formatString branch.
24209 for (const auto &record : metadata.dictionariesWithDefault()) {
24210 if (nativeDictionaryWithDefaultHandles.count(record.handle) == 0)
24211 continue;
24212 DRW_DictionaryWithDefault dwd =
24213 dictionaryWithDefaultFromMetadata(record);
24214 if (hasDxfTableAppDataUnsupportedByDwg(dwd)) {
24215 hasBlockedReplay = true;
24216 ++blockedWriterRejected;
24217 continue;
24218 }
24219 sanitizeDictionaryOwner(dwd.parentHandle, "dictionary-with-default");
24220 remapEntryHandle(dwd);
24221 if (!sanitizeDictionaryEntries(dwd)) {
24222 hasBlockedReplay = true;
24223 ++blockedWriterRejected;
24224 continue;
24225 }
24226 dwd.m_defaultEntryHandle = remapRequiredObjectHandle(
24227 dwd.m_defaultEntryHandle, "DICTIONARYWDFLT default", 0,
24228 DRW::DwgHardPointer);
24229 if (m_writeFailed) {
24230 hasBlockedReplay = true;
24231 ++blockedWriterRejected;
24232 continue;
24233 }
24234 if (!sanitizeCommonObjectReferences(
24235 dwd, "DICTIONARYWDFLT common reference")) {
24236 hasBlockedReplay = true;
24237 ++blockedWriterRejected;
24238 continue;
24239 }
24240 dwd.setDwgCommonObjectState(
24241 static_cast<std::int32_t>(dwd.reactorHandles.size()),
24242 dwd.extensionDictionaryFlag(),
24243 encodedDwgDataStoragePresenceBit(record.handle,
24244 dwd.hasDataStorageBinaryData()));
24245 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
24246 DwgWriteSourceKind::Object,
24247 record.handle, [&dwd, this] {
24248 return m_dwgW->writeDictionaryWithDefault(&dwd);
24249 })) {
24250 ++nativeDictionaryWithDefaultObjects;
24251 } else {
24252 hasBlockedReplay = true;
24253 ++blockedWriterRejected;
24254 }
24255 }
24256 for (const auto &record : metadata.sortEntsTables()) {
24257 if (nativeSortEntsTableHandles.count(record.handle) == 0)
24258 continue;
24259 DRW_SortEntsTable se = sortEntsTableFromMetadata(record);
24260 if (hasDxfTableAppDataUnsupportedByDwg(se)) {
24261 hasBlockedReplay = true;
24262 ++blockedWriterRejected;
24263 continue;
24264 }
24265 remapEntry(se);
24266 se.m_blockOwnerHandle =
24267 remapRequiredSortBlockOwnerHandle(se.m_blockOwnerHandle);
24268 if (se.m_entityHandles.size() != se.m_sortHandles.size() ||
24269 se.m_entityHandles.size() > DRW_SortEntsTable::kMaxEntries) {
24270 m_writeFailed = true;
24271 hasBlockedReplay = true;
24272 ++blockedWriterRejected;
24273 continue;
24274 }
24275 std::vector<std::uint32_t> entityHandles;
24276 std::vector<std::uint32_t> sortHandles;
24277 struct PendingSortReference {
24278 bool retained = false;
24279 std::uint32_t sourceHandle = 0;
24280 std::uint32_t outputHandle = 0;
24281 DwgWriteSourceKind targetKind = DwgWriteSourceKind::Object;
24282 DwgWriteReferenceStatus targetStatus =
24283 DwgWriteReferenceStatus::Missing;
24284 const char *referenceName = nullptr;
24285 std::uint32_t ordinal = 0;
24286 std::uint8_t wireHandleCode = DRW::DwgNullReference;
24287 bool required = false;
24288 };
24289 std::vector<PendingSortReference> pendingReferences;
24290 entityHandles.reserve(se.m_entityHandles.size());
24291 sortHandles.reserve(se.m_entityHandles.size());
24292 pendingReferences.reserve(se.m_entityHandles.size() * 2u);
24293 for (std::size_t i = 0; i < se.m_entityHandles.size(); ++i) {
24294 const std::uint32_t sourceEntityHandle = se.m_entityHandles[i];
24295 const auto entityReference = resolveRequiredEntityReference(
24296 sourceEntityHandle, "SORTENTSTABLE entity");
24297 if (!entityReference.has_value())
24298 break;
24299 const std::uint32_t entityHandle = entityReference->first;
24300 const DwgWriteSourceKind entityTargetKind =
24301 entityReference->second;
24302 const std::uint32_t sourceSortHandle = se.m_sortHandles[i];
24303 if (sourceSortHandle == DRW::NoHandle) {
24304 entityHandles.push_back(entityHandle);
24305 sortHandles.push_back(DRW::NoHandle);
24306 pendingReferences.push_back(
24307 {true, sourceEntityHandle, entityHandle,
24308 entityTargetKind, DwgWriteReferenceStatus::Committed,
24309 "SORTENTSTABLE entity", static_cast<std::uint32_t>(i),
24310 DRW::DwgSoftPointer, true});
24311 continue;
24312 }
24313 std::uint32_t sortHandle = 0;
24314 DwgWriteSourceKind sortTargetKind = DwgWriteSourceKind::Object;
24315 const DwgWriteReferenceStatus sortStatus =
24316 resolveDwgWriteCommonHandle(sourceSortHandle, &sortHandle,
24317 &sortTargetKind);
24318 DwgWriteReferenceStatus effectiveSortStatus = sortStatus;
24319 if (!isLiveDwgWriteReference(sortStatus) || sortHandle == 0) {
24320 // A SORTENTSTABLE sort handle may repeat its entity handle. If
24321 // that entity is retained only as an opaque carrier, the common
24322 // resolver cannot select it because the rendered entity map is
24323 // intentionally ambiguous. Use the committed raw identity in
24324 // that case; the receipt still verifies the exact carrier.
24325 const auto rawSortIt = std::find_if(
24326 rawObjects.cbegin(), rawObjects.cend(),
24327 [sourceSortHandle](const auto &raw) {
24328 return raw.isEntity && raw.handle == sourceSortHandle;
24329 });
24330 std::uint32_t rawSortOutputHandle = 0;
24331 const DwgWriteIdentityLookup rawSortIdentityStatus =
24332 lookupDwgWriteIdentity(DwgWriteSourceKind::RawCarrier,
24333 sourceSortHandle,
24334 &rawSortOutputHandle);
24335 if (rawSortIt != rawObjects.cend() &&
24336 rawSortIdentityStatus == DwgWriteIdentityLookup::Committed &&
24337 rawSortOutputHandle != 0) {
24338 sortHandle = rawSortOutputHandle;
24339 sortTargetKind = DwgWriteSourceKind::RawCarrier;
24340 effectiveSortStatus = DwgWriteReferenceStatus::Committed;
24341 }
24342 }
24343 if (!isLiveDwgWriteReference(effectiveSortStatus) ||
24344 sortHandle == 0) {
24345 const DwgWriteReferenceStatus dropStatus =
24346 effectiveSortStatus == DwgWriteReferenceStatus::Ambiguous
24347 ? DwgWriteReferenceStatus::Ambiguous
24348 : effectiveSortStatus == DwgWriteReferenceStatus::Suppressed
24349 ? DwgWriteReferenceStatus::Suppressed
24350 : DwgWriteReferenceStatus::Missing;
24351 pendingReferences.push_back({false, sourceSortHandle, 0,
24352 sortTargetKind, dropStatus,
24353 "SORTENTSTABLE sort",
24354 static_cast<std::uint32_t>(i), 0,
24355 false});
24356 m_writeFailed = true;
24357 break;
24358 }
24359 entityHandles.push_back(entityHandle);
24360 sortHandles.push_back(sortHandle);
24361 pendingReferences.push_back(
24362 {true, sourceEntityHandle, entityHandle,
24363 entityTargetKind, DwgWriteReferenceStatus::Committed,
24364 "SORTENTSTABLE entity", static_cast<std::uint32_t>(i),
24365 DRW::DwgSoftPointer, true});
24366 pendingReferences.push_back(
24367 {true, sourceSortHandle, sortHandle, sortTargetKind,
24368 DwgWriteReferenceStatus::Committed, "SORTENTSTABLE sort",
24369 static_cast<std::uint32_t>(i), DRW::DwgNullReference, false});
24370 }
24371 if (m_writeFailed) {
24372 hasBlockedReplay = true;
24373 ++blockedWriterRejected;
24374 continue;
24375 }
24376 se.m_entityHandles = std::move(entityHandles);
24377 se.m_sortHandles = std::move(sortHandles);
24378 if (!sanitizeCommonObjectReferences(se,
24379 "SORTENTSTABLE common reference")) {
24380 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
24381 "RS_FilterDXFRW: SORTENTSTABLE 0x%X common "
24382 "reference sanitization failed",
24383 record.handle);
24384 hasBlockedReplay = true;
24385 ++blockedWriterRejected;
24386 continue;
24387 }
24388 se.setDwgCommonObjectState(
24389 static_cast<std::int32_t>(se.reactorHandles.size()),
24390 se.extensionDictionaryFlag(),
24391 encodedDwgDataStoragePresenceBit(record.handle,
24392 se.hasDataStorageBinaryData()));
24393 const bool wroteSortEntsTable = writeDwgObjectWithCompletion(
24394 RS_FilterDXFRW_DwgWriteIdentityRegistry::DwgWriteSourceKind::Object,
24395 record.handle,
24396 [&se, this] { return m_dwgW->writeSortEntsTable(&se); },
24397 [&pendingReferences, &recordRetainedReference,
24398 &recordOptionalDrop]() {
24399 for (const PendingSortReference &reference : pendingReferences) {
24400 if (reference.retained) {
24401 if (!recordRetainedReference(
24402 reference.sourceHandle, reference.outputHandle,
24403 reference.targetKind, reference.referenceName,
24404 reference.ordinal, reference.wireHandleCode,
24405 reference.required)) {
24406 return false;
24407 }
24408 } else if (!recordOptionalDrop(
24409 reference.sourceHandle, reference.targetStatus,
24410 reference.referenceName, reference.ordinal,
24411 reference.targetStatus ==
24412 DwgWriteReferenceStatus::Ambiguous
24413 ? "ambiguous target"
24414 : reference.targetStatus ==
24415 DwgWriteReferenceStatus::Suppressed
24416 ? "suppressed target"
24417 : "missing target",
24418 reference.targetKind)) {
24419 return false;
24420 }
24421 }
24422 return true;
24423 });
24424 if (!wroteSortEntsTable) {
24425 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
24426 "RS_FilterDXFRW: SORTENTSTABLE 0x%X writer call "
24427 "failed",
24428 record.handle);
24429 hasBlockedReplay = true;
24430 ++blockedWriterRejected;
24431 } else {
24432 ++nativeSortEntsTableObjects;
24433 }
24434 }
24435 for (auto &candidate : m_dwgFieldWritePlan.fieldListCandidates) {
24436 if (!candidate.classAdmitted ||
24437 nativeFieldListHandles.count(candidate.sourceHandle) == 0) {
24438 m_writeFailed = true;
24439 continue;
24440 }
24441 DRW_FieldList fl = candidate.payload;
24442 remapEntry(fl);
24443 struct PendingFieldListReference {
24444 std::uint32_t sourceHandle = 0;
24445 std::uint32_t outputHandle = 0;
24446 std::uint32_t ordinal = 0;
24447 };
24448 std::vector<std::uint32_t> fieldHandles;
24449 std::vector<PendingFieldListReference> pendingReferences;
24450 fieldHandles.reserve(fl.m_fieldHandles.size());
24451 pendingReferences.reserve(fl.m_fieldHandles.size());
24452 const DRW_Field::DwgReferencePolicy fieldPolicy =
24453 DRW_Field::dwgReferencePolicy(
24454 m_dwgW->getVersion(),
24455 DRW_Field::DwgReferenceKind::FieldListMember);
24456 bool fieldsValid = true;
24457 if (!fieldPolicy.hasHandleStreamSlot)
24458 fieldsValid = false;
24459 std::size_t fieldReferenceIndex = 0;
24460 for (std::size_t ordinal = 0;
24461 fieldsValid && ordinal < fl.m_fieldHandles.size(); ++ordinal) {
24462 const std::uint32_t sourceHandle = fl.m_fieldHandles[ordinal];
24463 if (sourceHandle == DRW::NoHandle) {
24464 if (!fieldPolicy.allowsNull) {
24465 fieldsValid = false;
24466 break;
24467 }
24468 fieldHandles.push_back(DRW::NoHandle);
24469 continue;
24470 }
24471 if (fieldReferenceIndex >= candidate.fieldCandidateIndexes.size()) {
24472 fieldsValid = false;
24473 break;
24474 }
24475 const std::size_t fieldIndex =
24476 candidate.fieldCandidateIndexes[fieldReferenceIndex++];
24477 if (fieldIndex >= m_dwgFieldWritePlan.fieldCandidates.size() ||
24478 !m_dwgFieldWritePlan.fieldCandidates[fieldIndex].classAdmitted ||
24479 m_dwgFieldWritePlan.fieldCandidates[fieldIndex].sourceHandle !=
24480 sourceHandle ||
24481 nativeFieldHandles.count(sourceHandle) == 0) {
24482 fieldsValid = false;
24483 break;
24484 }
24485 std::uint32_t outputHandle = 0;
24486 const DwgWriteReferenceStatus status =
24487 resolveDwgWriteObjectHandle(sourceHandle, &outputHandle);
24488 if (!isLiveDwgWriteReference(status) || outputHandle == 0) {
24489 fieldsValid = false;
24490 break;
24491 }
24492 fieldHandles.push_back(outputHandle);
24493 pendingReferences.push_back({sourceHandle, outputHandle,
24494 static_cast<std::uint32_t>(ordinal)});
24495 }
24496 if (!fieldsValid ||
24497 fieldReferenceIndex != candidate.fieldCandidateIndexes.size()) {
24498 m_writeFailed = true;
24499 hasBlockedReplay = true;
24500 ++blockedWriterRejected;
24501 continue;
24502 }
24503 fl.m_fieldHandles = std::move(fieldHandles);
24504 if (!sanitizeCommonObjectReferences(fl, "FIELDLIST common reference")) {
24505 hasBlockedReplay = true;
24506 ++blockedWriterRejected;
24507 continue;
24508 }
24509 fl.setDwgCommonObjectState(
24510 static_cast<std::int32_t>(fl.reactorHandles.size()),
24511 fl.extensionDictionaryFlag(),
24512 encodedDwgDataStoragePresenceBit(candidate.sourceHandle,
24513 fl.hasDataStorageBinaryData()));
24514 if (writeDwgObjectWithCompletion(
24515 RS_FilterDXFRW_DwgWriteIdentityRegistry::DwgWriteSourceKind::
24516 Object,
24517 candidate.sourceHandle,
24518 [&fl, this] { return m_dwgW->writeFieldList(&fl); },
24519 [&pendingReferences, &recordRetainedReference, fieldPolicy]() {
24520 for (const PendingFieldListReference &reference :
24521 pendingReferences) {
24522 if (!recordRetainedReference(
24523 reference.sourceHandle, reference.outputHandle,
24524 DwgWriteSourceKind::Object, "FIELDLIST field",
24525 reference.ordinal, fieldPolicy.wireHandleCode,
24526 true)) {
24527 return false;
24528 }
24529 }
24530 return true;
24531 }) &&
24532 captureDwgFieldFrame(candidate)) {
24533 ++nativeFieldListObjects;
24534 } else {
24535 hasBlockedReplay = true;
24536 ++blockedWriterRejected;
24537 }
24538 }
24539 for (auto &candidate : m_dwgFieldWritePlan.fieldCandidates) {
24540 if (!candidate.classAdmitted ||
24541 nativeFieldHandles.count(candidate.sourceHandle) == 0) {
24542 m_writeFailed = true;
24543 continue;
24544 }
24545 DRW_Field f = candidate.payload;
24546 remapEntry(f);
24547 if (!remapRequiredFieldCadValue(f.m_value, "FIELD value", 0)) {
24548 hasBlockedReplay = true;
24549 ++blockedWriterRejected;
24550 continue;
24551 }
24552 bool childValuesValid = true;
24553 for (std::size_t ordinal = 0; ordinal < f.m_childValues.size();
24554 ++ordinal) {
24555 auto &child = f.m_childValues[ordinal];
24556 if (!remapRequiredFieldCadValue(
24557 child.m_value, "FIELD child value",
24558 static_cast<std::uint32_t>(ordinal))) {
24559 childValuesValid = false;
24560 break;
24561 }
24562 }
24563 if (!childValuesValid) {
24564 hasBlockedReplay = true;
24565 ++blockedWriterRejected;
24566 continue;
24567 }
24568 if (!remapRequiredFieldChildren(f.m_childHandles) ||
24569 !remapRequiredFieldObjects(f.m_objectHandles)) {
24570 hasBlockedReplay = true;
24571 ++blockedWriterRejected;
24572 continue;
24573 }
24574 if (!sanitizeCommonObjectReferences(f, "FIELD common reference")) {
24575 hasBlockedReplay = true;
24576 ++blockedWriterRejected;
24577 continue;
24578 }
24579 f.setDwgCommonObjectState(
24580 static_cast<std::int32_t>(f.reactorHandles.size()),
24581 f.extensionDictionaryFlag(),
24582 encodedDwgDataStoragePresenceBit(candidate.sourceHandle,
24583 f.hasDataStorageBinaryData()));
24584 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
24585 DwgWriteSourceKind::Object,
24586 candidate.sourceHandle,
24587 [&f, this] { return m_dwgW->writeField(&f); }) &&
24588 captureDwgFieldFrame(candidate)) {
24589 ++nativeFieldObjects;
24590 } else {
24591 hasBlockedReplay = true;
24592 ++blockedWriterRejected;
24593 }
24594 }
24595 for (const auto &candidate : m_dwgFieldWritePlan.fieldListCandidates) {
24596 if (!candidate.objectEmitted)
24597 continue;
24598 const bool targetsCommitted = std::all_of(
24599 candidate.fieldCandidateIndexes.cbegin(),
24600 candidate.fieldCandidateIndexes.cend(),
24601 [this](std::size_t fieldIndex) {
24602 return fieldIndex < m_dwgFieldWritePlan.fieldCandidates.size() &&
24603 m_dwgFieldWritePlan.fieldCandidates[fieldIndex]
24604 .objectEmitted;
24605 });
24606 if (!targetsCommitted) {
24607 m_writeFailed = true;
24608 hasBlockedReplay = true;
24609 ++blockedWriterRejected;
24610 }
24611 }
24612 for (const auto &record : metadata.underlayDefinitions()) {
24613 if (nativeUnderlayDefinitionHandles.count(record.handle) == 0)
24614 continue;
24615 DRW_UnderlayDefinition definition =
24616 underlayDefinitionFromMetadata(record);
24617 if (hasDxfTableAppDataUnsupportedByDwg(definition)) {
24618 hasBlockedReplay = true;
24619 ++blockedWriterRejected;
24620 continue;
24621 }
24622 remapEntry(definition);
24623 if (!sanitizeCommonObjectReferences(
24624 definition, "UNDERLAYDEFINITION common reference")) {
24625 hasBlockedReplay = true;
24626 ++blockedWriterRejected;
24627 continue;
24628 }
24629 definition.setDwgCommonObjectState(
24630 static_cast<std::int32_t>(definition.reactorHandles.size()),
24631 definition.extensionDictionaryFlag(),
24632 encodedDwgDataStoragePresenceBit(
24633 record.handle, definition.hasDataStorageBinaryData()));
24634 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
24635 DwgWriteSourceKind::Object,
24636 record.handle, [&definition, this] {
24637 return m_dwgW->writeUnderlayDefinition(
24638 &definition);
24639 })) {
24640 ++nativeUnderlayDefinitionObjects;
24641 } else {
24642 hasBlockedReplay = true;
24643 ++blockedWriterRejected;
24644 }
24645 }
24646 for (const auto &record : metadata.pointCloudDefinitions()) {
24647 if (nativePointCloudDefinitionHandles.count(record.handle) == 0)
24648 continue;
24649 DRW_PointCloudDef definition = pointCloudDefinitionFromMetadata(record);
24650 if (hasDxfTableAppDataUnsupportedByDwg(definition)) {
24651 hasBlockedReplay = true;
24652 ++blockedWriterRejected;
24653 continue;
24654 }
24655 remapEntry(definition);
24656 if (!sanitizeCommonObjectReferences(
24657 definition, "POINTCLOUDDEFINITION common reference")) {
24658 hasBlockedReplay = true;
24659 ++blockedWriterRejected;
24660 continue;
24661 }
24662 definition.setDwgCommonObjectState(
24663 static_cast<std::int32_t>(definition.reactorHandles.size()),
24664 definition.extensionDictionaryFlag(),
24665 encodedDwgDataStoragePresenceBit(
24666 record.handle, definition.hasDataStorageBinaryData()));
24667 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
24668 DwgWriteSourceKind::Object,
24669 record.handle, [&definition, this] {
24670 return m_dwgW->writePointCloudDef(&definition);
24671 })) {
24672 ++nativePointCloudDefinitionObjects;
24673 } else {
24674 hasBlockedReplay = true;
24675 ++blockedWriterRejected;
24676 }
24677 }
24678 for (const auto &record : metadata.navisworksModelDefinitions()) {
24679 if (nativeNavisworksModelDefinitionHandles.count(record.handle) == 0)
24680 continue;
24681 DRW_NavisworksModelDef definition =
24682 navisworksModelDefinitionFromMetadata(record);
24683 if (hasDxfTableAppDataUnsupportedByDwg(definition)) {
24684 hasBlockedReplay = true;
24685 ++blockedWriterRejected;
24686 continue;
24687 }
24688 remapEntry(definition);
24689 if (!sanitizeCommonObjectReferences(
24690 definition, "NAVISWORKSMODELDEF common reference")) {
24691 hasBlockedReplay = true;
24692 ++blockedWriterRejected;
24693 continue;
24694 }
24695 definition.setDwgCommonObjectState(
24696 static_cast<std::int32_t>(definition.reactorHandles.size()),
24697 definition.extensionDictionaryFlag(),
24698 encodedDwgDataStoragePresenceBit(
24699 record.handle, definition.hasDataStorageBinaryData()));
24700 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
24701 DwgWriteSourceKind::Object,
24702 record.handle, [&definition, this] {
24703 return m_dwgW->writeNavisworksModelDef(
24704 &definition);
24705 })) {
24706 ++nativeNavisworksModelDefinitionObjects;
24707 } else {
24708 hasBlockedReplay = true;
24709 ++blockedWriterRejected;
24710 }
24711 }
24712 for (const auto &record : metadata.pointCloudColorMaps()) {
24713 if (nativePointCloudColorMapHandles.count(record.handle) == 0)
24714 continue;
24715 DRW_PointCloudColorMap colorMap =
24716 pointCloudColorMapFromMetadata(record);
24717 if (hasDxfTableAppDataUnsupportedByDwg(colorMap)) {
24718 hasBlockedReplay = true;
24719 ++blockedWriterRejected;
24720 continue;
24721 }
24722 remapEntry(colorMap);
24723 if (!sanitizeCommonObjectReferences(
24724 colorMap, "POINTCLOUDCOLORMAP common reference")) {
24725 hasBlockedReplay = true;
24726 ++blockedWriterRejected;
24727 continue;
24728 }
24729 colorMap.setDwgCommonObjectState(
24730 static_cast<std::int32_t>(colorMap.reactorHandles.size()),
24731 colorMap.extensionDictionaryFlag(),
24732 encodedDwgDataStoragePresenceBit(
24733 record.handle, colorMap.hasDataStorageBinaryData()));
24734 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
24735 DwgWriteSourceKind::Object,
24736 record.handle, [&colorMap, this] {
24737 return m_dwgW->writePointCloudColorMap(&colorMap);
24738 })) {
24739 ++nativePointCloudColorMapObjects;
24740 } else {
24741 hasBlockedReplay = true;
24742 ++blockedWriterRejected;
24743 }
24744 }
24745 }
24746 if (replayedObjects > 0) {
24747 RS_DEBUGRS_Debug::instance()->print(
24748 "RS_FilterDXFRW::writeObjects: replayed %d raw DWG objects",
24749 replayedObjects);
24750 }
24751 if (replayedSections > 0) {
24752 RS_DEBUGRS_Debug::instance()->print(
24753 "RS_FilterDXFRW::writeObjects: replayed %d raw DWG data sections",
24754 replayedSections);
24755 }
24756 if (rawFamilyCounts.total() > 0) {
24757 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::writeObjects: raw DWG object families "
24758 "assoc=%d eval-graph=%d dynamic-block=%d "
24759 "object-context=%d unknown=%d",
24760 static_cast<int>(rawFamilyCounts.associative),
24761 static_cast<int>(rawFamilyCounts.evaluationGraph),
24762 static_cast<int>(rawFamilyCounts.dynamicBlock),
24763 static_cast<int>(rawFamilyCounts.objectContext),
24764 static_cast<int>(rawFamilyCounts.unknown));
24765 }
24766 if (nativeSunObjects > 0) {
24767 RS_DEBUGRS_Debug::instance()->print(
24768 "RS_FilterDXFRW::writeObjects: wrote %d native SUN objects",
24769 nativeSunObjects);
24770 }
24771 if (nativeSunStudyObjects > 0) {
24772 RS_DEBUGRS_Debug::instance()->print(
24773 "RS_FilterDXFRW::writeObjects: wrote %d native SUNSTUDY objects",
24774 nativeSunStudyObjects);
24775 }
24776 if (nativeMotionPathObjects > 0) {
24777 RS_DEBUGRS_Debug::instance()->print(
24778 "RS_FilterDXFRW::writeObjects: wrote %d native MOTIONPATH objects",
24779 nativeMotionPathObjects);
24780 }
24781 if (nativeCurvePathObjects > 0) {
24782 RS_DEBUGRS_Debug::instance()->print(
24783 "RS_FilterDXFRW::writeObjects: wrote %d native CURVEPATH objects",
24784 nativeCurvePathObjects);
24785 }
24786 if (nativePointPathObjects > 0) {
24787 RS_DEBUGRS_Debug::instance()->print(
24788 "RS_FilterDXFRW::writeObjects: wrote %d native POINTPATH objects",
24789 nativePointPathObjects);
24790 }
24791 if (nativeObjectPtrObjects > 0) {
24792 RS_DEBUGRS_Debug::instance()->print(
24793 "RS_FilterDXFRW::writeObjects: wrote %d native OBJECT_PTR objects",
24794 nativeObjectPtrObjects);
24795 }
24796 if (nativePartialViewingIndexObjects > 0) {
24797 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::writeObjects: wrote %d native "
24798 "PARTIAL_VIEWING_INDEX objects",
24799 nativePartialViewingIndexObjects);
24800 }
24801 if (nativeRenderSettingsObjects > 0) {
24802 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::writeObjects: wrote %d native render "
24803 "settings objects",
24804 nativeRenderSettingsObjects);
24805 }
24806 if (nativeVisualStyleObjects > 0) {
24807 RS_DEBUGRS_Debug::instance()->print(
24808 "RS_FilterDXFRW::writeObjects: wrote %d native VISUALSTYLE objects",
24809 nativeVisualStyleObjects);
24810 }
24811 if (nativeTvDevicePropertiesObjects > 0) {
24812 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::writeObjects: wrote %d native "
24813 "TVDEVICEPROPERTIES objects",
24814 nativeTvDevicePropertiesObjects);
24815 }
24816 if (nativeVxControlObjects > 0) {
24817 RS_DEBUGRS_Debug::instance()->print(
24818 "RS_FilterDXFRW::writeObjects: wrote %d native VXCONTROL objects",
24819 nativeVxControlObjects);
24820 }
24821 if (nativeVxTableRecordObjects > 0) {
24822 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::writeObjects: wrote %d native "
24823 "VXTABLERECORD objects",
24824 nativeVxTableRecordObjects);
24825 }
24826 if (nativePlaceholderObjects > 0) {
24827 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::writeObjects: wrote %d native "
24828 "ACDBPLACEHOLDER objects",
24829 nativePlaceholderObjects);
24830 }
24831 if (nativeVbaProjectObjects > 0) {
24832 RS_DEBUGRS_Debug::instance()->print(
24833 "RS_FilterDXFRW::writeObjects: wrote %d native VBA_PROJECT objects",
24834 nativeVbaProjectObjects);
24835 }
24836 if (nativeMLeaderStyleObjects > 0) {
24837 RS_DEBUGRS_Debug::instance()->print(
24838 "RS_FilterDXFRW::writeObjects: wrote %d native MLEADERSTYLE objects",
24839 nativeMLeaderStyleObjects);
24840 }
24841 if (nativeMaterialObjects > 0) {
24842 RS_DEBUGRS_Debug::instance()->print(
24843 "RS_FilterDXFRW::writeObjects: wrote %d native MATERIAL objects",
24844 nativeMaterialObjects);
24845 }
24846 if (nativeLightListObjects > 0) {
24847 RS_DEBUGRS_Debug::instance()->print(
24848 "RS_FilterDXFRW::writeObjects: wrote %d native LIGHTLIST objects",
24849 nativeLightListObjects);
24850 }
24851 if (nativeBackgroundObjects > 0) {
24852 RS_DEBUGRS_Debug::instance()->print(
24853 "RS_FilterDXFRW::writeObjects: wrote %d native BACKGROUND objects",
24854 nativeBackgroundObjects);
24855 }
24856 if (nativeDictionaryObjects > 0) {
24857 RS_DEBUGRS_Debug::instance()->print(
24858 "RS_FilterDXFRW::writeObjects: wrote %d native DICTIONARY objects",
24859 nativeDictionaryObjects);
24860 }
24861 if (nativeXRecordObjects > 0) {
24862 RS_DEBUGRS_Debug::instance()->print(
24863 "RS_FilterDXFRW::writeObjects: wrote %d native XRECORD objects",
24864 nativeXRecordObjects);
24865 }
24866 if (nativeLayoutObjects > 0) {
24867 RS_DEBUGRS_Debug::instance()->print(
24868 "RS_FilterDXFRW::writeObjects: wrote %d native LAYOUT objects",
24869 nativeLayoutObjects);
24870 }
24871 if (nativeGroupObjects > 0) {
24872 RS_DEBUGRS_Debug::instance()->print(
24873 "RS_FilterDXFRW::writeObjects: wrote %d native GROUP objects",
24874 nativeGroupObjects);
24875 }
24876 if (nativeMLineStyleObjects > 0) {
24877 RS_DEBUGRS_Debug::instance()->print(
24878 "RS_FilterDXFRW::writeObjects: wrote %d native MLINESTYLE objects",
24879 nativeMLineStyleObjects);
24880 }
24881 if (nativeTableStyleObjects > 0) {
24882 RS_DEBUGRS_Debug::instance()->print(
24883 "RS_FilterDXFRW::writeObjects: wrote %d native TABLESTYLE objects",
24884 nativeTableStyleObjects);
24885 }
24886 if (nativeDbColorObjects > 0) {
24887 RS_DEBUGRS_Debug::instance()->print(
24888 "RS_FilterDXFRW::writeObjects: wrote %d native DBCOLOR objects",
24889 nativeDbColorObjects);
24890 }
24891 if (nativeDimensionAssociationObjects > 0) {
24892 RS_DEBUGRS_Debug::instance()->print(
24893 "RS_FilterDXFRW::writeObjects: wrote %d native DIMASSOC objects",
24894 nativeDimensionAssociationObjects);
24895 }
24896 if (nativeEvaluationGraphObjects > 0) {
24897 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::writeObjects: wrote %d native "
24898 "EVALUATION_GRAPH objects",
24899 nativeEvaluationGraphObjects);
24900 }
24901 if (nativeRasterVariablesObjects > 0) {
24902 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::writeObjects: wrote %d native "
24903 "RASTERVARIABLES objects",
24904 nativeRasterVariablesObjects);
24905 }
24906 if (nativeWipeoutVariablesObjects > 0) {
24907 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::writeObjects: wrote %d native "
24908 "WIPEOUTVARIABLES objects",
24909 nativeWipeoutVariablesObjects);
24910 }
24911 if (nativeGeoDataObjects > 0) {
24912 RS_DEBUGRS_Debug::instance()->print(
24913 "RS_FilterDXFRW::writeObjects: wrote %d native GEODATA objects",
24914 nativeGeoDataObjects);
24915 }
24916 if (nativeSpatialFilterObjects > 0) {
24917 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::writeObjects: wrote %d native "
24918 "SPATIAL_FILTER objects",
24919 nativeSpatialFilterObjects);
24920 }
24921 // PR 8d.2a — five small no-storage OBJECTS families.
24922 if (nativeScaleObjects > 0) {
24923 RS_DEBUGRS_Debug::instance()->print(
24924 "RS_FilterDXFRW::writeObjects: wrote %d native SCALE objects",
24925 nativeScaleObjects);
24926 }
24927 if (nativeIDBufferObjects > 0) {
24928 RS_DEBUGRS_Debug::instance()->print(
24929 "RS_FilterDXFRW::writeObjects: wrote %d native IDBUFFER objects",
24930 nativeIDBufferObjects);
24931 }
24932 if (nativeLayerIndexObjects > 0) {
24933 RS_DEBUGRS_Debug::instance()->print(
24934 "RS_FilterDXFRW::writeObjects: wrote %d native LAYER_INDEX objects",
24935 nativeLayerIndexObjects);
24936 }
24937 if (nativeSpatialIndexObjects > 0) {
24938 RS_DEBUGRS_Debug::instance()->print(
24939 "RS_FilterDXFRW::writeObjects: wrote %d native SPATIAL_INDEX objects",
24940 nativeSpatialIndexObjects);
24941 }
24942 if (nativeDictionaryVarObjects > 0) {
24943 RS_DEBUGRS_Debug::instance()->print(
24944 "RS_FilterDXFRW::writeObjects: wrote %d native DICTIONARYVAR objects",
24945 nativeDictionaryVarObjects);
24946 }
24947 // PR 8d.2b — four larger no-storage OBJECTS families.
24948 if (nativeDictionaryWithDefaultObjects > 0) {
24949 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::writeObjects: wrote %d native "
24950 "DICTIONARYWDFLT objects",
24951 nativeDictionaryWithDefaultObjects);
24952 }
24953 if (nativeSortEntsTableObjects > 0) {
24954 RS_DEBUGRS_Debug::instance()->print(
24955 "RS_FilterDXFRW::writeObjects: wrote %d native SORTENTSTABLE objects",
24956 nativeSortEntsTableObjects);
24957 }
24958 if (nativeFieldListObjects > 0) {
24959 RS_DEBUGRS_Debug::instance()->print(
24960 "RS_FilterDXFRW::writeObjects: wrote %d native FIELDLIST objects",
24961 nativeFieldListObjects);
24962 }
24963 if (nativeFieldObjects > 0) {
24964 RS_DEBUGRS_Debug::instance()->print(
24965 "RS_FilterDXFRW::writeObjects: wrote %d native FIELD objects",
24966 nativeFieldObjects);
24967 }
24968 if (nativeUnderlayDefinitionObjects > 0) {
24969 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::writeObjects: wrote %d native "
24970 "UNDERLAYDEFINITION objects",
24971 nativeUnderlayDefinitionObjects);
24972 }
24973 if (nativePointCloudDefinitionObjects > 0) {
24974 RS_DEBUGRS_Debug::instance()->print(
24975 "RS_FilterDXFRW::writeObjects: wrote %d native POINTCLOUDDEF objects",
24976 nativePointCloudDefinitionObjects);
24977 }
24978 if (nativeNavisworksModelDefinitionObjects > 0) {
24979 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::writeObjects: wrote %d native "
24980 "NAVISWORKSMODELDEF objects",
24981 nativeNavisworksModelDefinitionObjects);
24982 }
24983 if (nativePointCloudColorMapObjects > 0) {
24984 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::writeObjects: wrote %d native "
24985 "POINTCLOUDCOLORMAP objects",
24986 nativePointCloudColorMapObjects);
24987 }
24988 if (nativeSectionObjects > 0) {
24989 RS_DEBUGRS_Debug::instance()->print(
24990 "RS_FilterDXFRW::writeObjects: wrote %d native SECTION objects",
24991 nativeSectionObjects);
24992 }
24993 if (modelerPayloads.recordCount > 0) {
24994 const RS_Debug::RS_DebugLevel level =
24995 modelerPayloads.inconsistentSplit > 0 ? RS_Debug::D_WARNING
24996 : RS_Debug::D_DEBUGGING;
24997 RS_DEBUGRS_Debug::instance()->print(
24998 level,
24999 "DWG modeler geometry payloads: records=%d SAT=%d SAB=%d "
25000 "unknown=%d split-inconsistent=%d marker-body=%d marker-handle=%d",
25001 static_cast<int>(modelerPayloads.recordCount),
25002 static_cast<int>(modelerPayloads.sat),
25003 static_cast<int>(modelerPayloads.sab),
25004 static_cast<int>(modelerPayloads.unknown),
25005 static_cast<int>(modelerPayloads.inconsistentSplit),
25006 static_cast<int>(modelerPayloads.markerInBody),
25007 static_cast<int>(modelerPayloads.markerInHandleStream));
25008 }
25009 if (associativeShells.recordCount > 0) {
25010 RS_DEBUGRS_Debug::instance()->print(
25011 "DWG associative/action shells: records=%d network=%d action=%d "
25012 "dependency=%d geom-dependency=%d action-param=%d value-param=%d/%d "
25013 "prefix=%d/%d single-dep=%d compound=%d prefix-status=%d "
25014 "complete=%d partial=%d overflow=%d handles=%d values=%d unknown=%d",
25015 static_cast<int>(associativeShells.recordCount),
25016 static_cast<int>(associativeShells.network),
25017 static_cast<int>(associativeShells.action),
25018 static_cast<int>(associativeShells.dependency),
25019 static_cast<int>(associativeShells.geometryDependency),
25020 static_cast<int>(associativeShells.actionParamRecords),
25021 static_cast<int>(associativeShells.parsedValueParamRecords),
25022 static_cast<int>(associativeShells.valueParamRecords),
25023 static_cast<int>(associativeShells.parsedActionParamPrefixes),
25024 static_cast<int>(associativeShells.actionParamRecords),
25025 static_cast<int>(
25026 associativeShells.singleDependencyActionParamPrefixes),
25027 static_cast<int>(associativeShells.compoundActionParamPrefixes),
25028 static_cast<int>(associativePrefixes.prefixCount),
25029 static_cast<int>(associativePrefixes.complete),
25030 static_cast<int>(associativePrefixes.partial),
25031 static_cast<int>(associativePrefixes.boundedCountOverflow),
25032 static_cast<int>(associativePrefixes.decodedHandleCount),
25033 static_cast<int>(associativePrefixes.decodedValueCount),
25034 static_cast<int>(associativeShells.unknown));
25035 }
25036 const size_t graphReplayKnownFamilies =
25037 (graphReplayPolicy.preserved.total() -
25038 graphReplayPolicy.preserved.unknown) +
25039 (graphReplayPolicy.suppressed.total() -
25040 graphReplayPolicy.suppressed.unknown);
25041 if (graphReplayKnownFamilies > 0 ||
25042 graphReplayPolicy.totalSemanticOnly() > 0 ||
25043 graphReplayPolicy.totalReasons() > 0) {
25044 RS_DEBUGRS_Debug::instance()->print(
25045 graphReplayPolicy.suppressed.total() > 0 ||
25046 graphReplayPolicy.totalReasons() > 0
25047 ? RS_Debug::D_WARNING
25048 : RS_Debug::D_DEBUGGING,
25049 "DWG graph replay policy: preserved dimassoc=%d eval=%d "
25050 "assoc=%d dynamic-block=%d object-context=%d acsh=%d unknown=%d "
25051 "suppressed dimassoc=%d eval=%d assoc=%d dynamic-block=%d "
25052 "object-context=%d acsh=%d unknown=%d semantic-only assoc=%d "
25053 "acsh=%d reasons edited=%d missing-target=%d evaluator=%d "
25054 "parser-partial=%d fallback-edited=%d native-replaced=%d "
25055 "cycle-path=%d owner-deleted=%d raw-invalidated=%d "
25056 "raw-replaced=%d entity=%d missing-bytes=%d missing-class=%d",
25057 static_cast<int>(graphReplayPolicy.preserved.dimensionAssociation),
25058 static_cast<int>(graphReplayPolicy.preserved.evaluationGraph),
25059 static_cast<int>(graphReplayPolicy.preserved.acDbAssoc),
25060 static_cast<int>(graphReplayPolicy.preserved.dynamicBlock),
25061 static_cast<int>(graphReplayPolicy.preserved.objectContext),
25062 static_cast<int>(graphReplayPolicy.preserved.acShHistory),
25063 static_cast<int>(graphReplayPolicy.preserved.unknown),
25064 static_cast<int>(graphReplayPolicy.suppressed.dimensionAssociation),
25065 static_cast<int>(graphReplayPolicy.suppressed.evaluationGraph),
25066 static_cast<int>(graphReplayPolicy.suppressed.acDbAssoc),
25067 static_cast<int>(graphReplayPolicy.suppressed.dynamicBlock),
25068 static_cast<int>(graphReplayPolicy.suppressed.objectContext),
25069 static_cast<int>(graphReplayPolicy.suppressed.acShHistory),
25070 static_cast<int>(graphReplayPolicy.suppressed.unknown),
25071 static_cast<int>(graphReplayPolicy.semanticOnlyAssociative),
25072 static_cast<int>(graphReplayPolicy.semanticOnlyAcSh),
25073 static_cast<int>(graphReplayPolicy.editedEntity),
25074 static_cast<int>(graphReplayPolicy.missingTarget),
25075 static_cast<int>(graphReplayPolicy.unsupportedEvaluator),
25076 static_cast<int>(graphReplayPolicy.parserPartial),
25077 static_cast<int>(graphReplayPolicy.fallbackGeometryEdited),
25078 static_cast<int>(graphReplayPolicy.nativeReplacement),
25079 static_cast<int>(graphReplayPolicy.cyclePathInvalidated),
25080 static_cast<int>(graphReplayPolicy.ownerDeleted),
25081 static_cast<int>(graphReplayPolicy.invalidated),
25082 static_cast<int>(graphReplayPolicy.replaced),
25083 static_cast<int>(graphReplayPolicy.entityReplayUnsupported),
25084 static_cast<int>(graphReplayPolicy.missingRawBytes),
25085 static_cast<int>(graphReplayPolicy.missingClassMetadata));
25086 }
25087 if (tableBlockers.tableCount > 0 && tableBlockers.totalBlockers() > 0) {
25088 RS_DEBUGRS_Debug::instance()->print(
25089 RS_Debug::D_WARNING,
25090 "Native DWG table writing blocked: tables=%d eligible-text=%d "
25091 "layout-direct=%d layout-separate=%d layout-embedded=%d "
25092 "layout-unsupported=%d semantic=%d version=%d ambiguous-layout=%d "
25093 "owner=%d style=%d cell-style=%d unknown-ranges=%d "
25094 "incomplete-ranges=%d override=%d break=%d geometry=%d "
25095 "merged=%d field=%d block=%d attributes=%d unknown-content=%d "
25096 "value-payload=%d edited-fallback=%d missing-fallback-links=%d "
25097 "anon-block=%d text-style=%d linetype=%d raw-invalidated=%d "
25098 "raw-replaced=%d dimensions=%d",
25099 static_cast<int>(tableBlockers.tableCount),
25100 static_cast<int>(tableBlockers.eligibleTextOnly),
25101 static_cast<int>(tableBlockers.legacyDirectLayout),
25102 static_cast<int>(tableBlockers.separateTableContentLayout),
25103 static_cast<int>(tableBlockers.embeddedTableContentLayout),
25104 static_cast<int>(tableBlockers.unsupportedLayout),
25105 static_cast<int>(tableBlockers.noSemanticTableContent),
25106 static_cast<int>(tableBlockers.unsupportedTableVersion),
25107 static_cast<int>(tableBlockers.ambiguousTableContentStorage),
25108 static_cast<int>(tableBlockers.missingOwnerHandle),
25109 static_cast<int>(tableBlockers.unresolvedTableStyle),
25110 static_cast<int>(tableBlockers.unresolvedCellStyleMap),
25111 static_cast<int>(tableBlockers.unknownSubrecordRange),
25112 static_cast<int>(tableBlockers.incompleteSubrecordRange),
25113 static_cast<int>(tableBlockers.overrideMask),
25114 static_cast<int>(tableBlockers.breakData),
25115 static_cast<int>(tableBlockers.geometryTail),
25116 static_cast<int>(tableBlockers.mergedCell),
25117 static_cast<int>(tableBlockers.fieldContent),
25118 static_cast<int>(tableBlockers.blockContent),
25119 static_cast<int>(tableBlockers.attributeContent),
25120 static_cast<int>(tableBlockers.unknownCellContent),
25121 static_cast<int>(tableBlockers.incompleteValuePayload),
25122 static_cast<int>(tableBlockers.editedFallback),
25123 static_cast<int>(tableBlockers.missingFallbackAttachment),
25124 static_cast<int>(tableBlockers.anonymousBlockPolicyUnresolved),
25125 static_cast<int>(tableBlockers.unresolvedTextStyle),
25126 static_cast<int>(tableBlockers.unresolvedLineType),
25127 static_cast<int>(tableBlockers.rawReplayInvalidated),
25128 static_cast<int>(tableBlockers.rawReplayReplaced),
25129 static_cast<int>(tableBlockers.nonPositiveDimension));
25130 }
25131 if (mleaderBlockers.mleaderCount > 0 &&
25132 mleaderBlockers.totalBlockers() > 0) {
25133 RS_DEBUGRS_Debug::instance()->print(
25134 RS_Debug::D_WARNING,
25135 "Native DWG MLEADER writing limited: mleaders=%d unresolved-style=%d "
25136 "missing-text=%d block=%d tolerance=%d overrides=%d geometry=%d "
25137 "invalidated=%d replaced=%d",
25138 static_cast<int>(mleaderBlockers.mleaderCount),
25139 static_cast<int>(mleaderBlockers.unresolvedStyle),
25140 static_cast<int>(mleaderBlockers.missingTextContent),
25141 static_cast<int>(mleaderBlockers.blockContent),
25142 static_cast<int>(mleaderBlockers.toleranceContent),
25143 static_cast<int>(mleaderBlockers.overrideFlags),
25144 static_cast<int>(mleaderBlockers.missingLeaderGeometry),
25145 static_cast<int>(mleaderBlockers.invalidated),
25146 static_cast<int>(mleaderBlockers.replaced));
25147 }
25148 if (meshBlockers.meshCount > 0 && meshBlockers.totalBlockers() > 0) {
25149 RS_DEBUGRS_Debug::instance()->print(
25150 RS_Debug::D_WARNING,
25151 "Native DWG mesh writing blocked: meshes=%d sidecars=%d "
25152 "range-complete=%d range-missing=%d range-incomplete=%d "
25153 "crease=%d subdivision=%d fallback-only=%d edited-fallback=%d "
25154 "owner-class=%d malformed-counts=%d invalidated=%d replaced=%d",
25155 static_cast<int>(meshBlockers.meshCount),
25156 static_cast<int>(meshBlockers.sidecarCount),
25157 static_cast<int>(meshBlockers.completeRawRange),
25158 static_cast<int>(meshBlockers.missingRawRange),
25159 static_cast<int>(meshBlockers.incompleteRawRange),
25160 static_cast<int>(meshBlockers.missingCreaseData),
25161 static_cast<int>(meshBlockers.unsupportedSubdivisionData),
25162 static_cast<int>(meshBlockers.fallbackOnlyPreview),
25163 static_cast<int>(meshBlockers.editedFallback),
25164 static_cast<int>(meshBlockers.missingOwnerOrClassHandle),
25165 static_cast<int>(meshBlockers.malformedCountRelationships),
25166 static_cast<int>(meshBlockers.invalidated),
25167 static_cast<int>(meshBlockers.replaced));
25168 }
25169 if (externalRefs.imageEntities > 0 || externalRefs.wipeouts > 0 ||
25170 externalRefs.underlays > 0 || externalRefs.imageDefinitions > 0 ||
25171 externalRefs.underlayDefinitions > 0) {
25172 const bool hasExternalIssues =
25173 externalRefs.totalPathIssues() > 0 ||
25174 externalRefs.missingDefinitionHandles > 0 ||
25175 externalRefs.malformedClips > 0;
25176 RS_DEBUGRS_Debug::instance()->print(
25177 hasExternalIssues ? RS_Debug::D_WARNING : RS_Debug::D_DEBUGGING,
25178 "DWG external references: images=%d wipeouts=%d image-defs=%d "
25179 "underlays=%d underlay-defs=%d raster-vars=%d path-empty=%d "
25180 "path-relative=%d path-missing=%d external=%d scheme=%d "
25181 "case-candidate=%d missing-def=%d orphan-def=%d clip-none=%d "
25182 "clip-rect=%d clip-poly=%d clip-bad=%d inverted=%d hidden-frame=%d",
25183 static_cast<int>(externalRefs.imageEntities),
25184 static_cast<int>(externalRefs.wipeouts),
25185 static_cast<int>(externalRefs.imageDefinitions),
25186 static_cast<int>(externalRefs.underlays),
25187 static_cast<int>(externalRefs.underlayDefinitions),
25188 static_cast<int>(externalRefs.rasterVariables),
25189 static_cast<int>(externalRefs.emptyPaths),
25190 static_cast<int>(externalRefs.relativePaths),
25191 static_cast<int>(externalRefs.absoluteMissingPaths),
25192 static_cast<int>(externalRefs.externalPaths),
25193 static_cast<int>(externalRefs.unsupportedSchemes),
25194 static_cast<int>(externalRefs.caseMismatchCandidates),
25195 static_cast<int>(externalRefs.missingDefinitionHandles),
25196 static_cast<int>(externalRefs.definitionsWithoutEntities),
25197 static_cast<int>(externalRefs.noBoundaryClips),
25198 static_cast<int>(externalRefs.rectangularClips),
25199 static_cast<int>(externalRefs.polygonalClips),
25200 static_cast<int>(externalRefs.malformedClips),
25201 static_cast<int>(externalRefs.invertedClips),
25202 static_cast<int>(externalRefs.hiddenFrames));
25203 }
25204 if (shapeOleBlockers.shapeCount > 0 ||
25205 shapeOleBlockers.ole2FrameCount > 0) {
25206 RS_DEBUGRS_Debug::instance()->print(
25207 shapeOleBlockers.totalBlockers() > 0 ? RS_Debug::D_WARNING
25208 : RS_Debug::D_DEBUGGING,
25209 "DWG shape/OLE writer blockers: shapes=%d ole2=%d "
25210 "missing-style=%d unresolved-style=%d missing-ole=%d "
25211 "truncated-ole=%d oversized-ole=%d edited-preview=%d "
25212 "unsupported-ole=%d raw-missing=%d raw-incomplete=%d "
25213 "invalidated=%d replaced=%d",
25214 static_cast<int>(shapeOleBlockers.shapeCount),
25215 static_cast<int>(shapeOleBlockers.ole2FrameCount),
25216 static_cast<int>(shapeOleBlockers.missingStyleHandle),
25217 static_cast<int>(shapeOleBlockers.unresolvedShapeStyle),
25218 static_cast<int>(shapeOleBlockers.missingOlePayload),
25219 static_cast<int>(shapeOleBlockers.truncatedOlePayload),
25220 static_cast<int>(shapeOleBlockers.oversizedOlePayload),
25221 static_cast<int>(shapeOleBlockers.editedPreviewFrame),
25222 static_cast<int>(shapeOleBlockers.unsupportedOlePayloadRegeneration),
25223 static_cast<int>(shapeOleBlockers.missingRawRange),
25224 static_cast<int>(shapeOleBlockers.incompleteRawRange),
25225 static_cast<int>(shapeOleBlockers.invalidated),
25226 static_cast<int>(shapeOleBlockers.replaced));
25227 }
25228 if (visualBlockers.recordCount > 0 || visualBlockers.rawPayloads > 0) {
25229 RS_DEBUGRS_Debug::instance()->print(
25230 visualBlockers.totalBlockers() > 0 ? RS_Debug::D_WARNING
25231 : RS_Debug::D_DEBUGGING,
25232 "DWG visual metadata export policy: records=%d raw=%d "
25233 "raw-replayable=%d raw-suppressed=%d ucs=%d base-ucs=%d "
25234 "visual-style=%d sun=%d background=%d live-section=%d "
25235 "owner-layout=%d raw-invalidated=%d raw-replaced=%d "
25236 "unsupported-visual-style=%d",
25237 static_cast<int>(visualBlockers.recordCount),
25238 static_cast<int>(visualBlockers.rawPayloads),
25239 static_cast<int>(visualBlockers.replayableRawPayloads),
25240 static_cast<int>(visualBlockers.suppressedRawPayloads),
25241 static_cast<int>(visualBlockers.unresolvedUcs),
25242 static_cast<int>(visualBlockers.unresolvedBaseUcs),
25243 static_cast<int>(visualBlockers.unresolvedVisualStyle),
25244 static_cast<int>(visualBlockers.unresolvedSun),
25245 static_cast<int>(visualBlockers.unresolvedBackground),
25246 static_cast<int>(visualBlockers.unresolvedLiveSection),
25247 static_cast<int>(visualBlockers.missingOwnerOrLayout),
25248 static_cast<int>(visualBlockers.invalidatedRawPayload),
25249 static_cast<int>(visualBlockers.replacedNativeUnavailablePayload),
25250 static_cast<int>(visualBlockers.unsupportedVisualStyleWriter));
25251 }
25252 if (advancedEntityBlockers.recordCount > 0 &&
25253 advancedEntityBlockers.totalBlockers() > 0) {
25254 RS_DEBUGRS_Debug::instance()->print(
25255 RS_Debug::D_WARNING,
25256 "DWG advanced entity writer readiness: records=%d mesh=%d "
25257 "shape=%d ole2=%d image=%d wipeout=%d underlay=%d "
25258 "mleader=%d arc-dim=%d unknown=%d native=%d raw=%d "
25259 "fallback=%d edited-fallback=%d metadata=%d payload=%d "
25260 "advanced-content=%d oda-complete=%d oda-partial=%d "
25261 "oda-absent=%d",
25262 static_cast<int>(advancedEntityBlockers.recordCount),
25263 static_cast<int>(advancedEntityBlockers.mesh),
25264 static_cast<int>(advancedEntityBlockers.shape),
25265 static_cast<int>(advancedEntityBlockers.ole2Frame),
25266 static_cast<int>(advancedEntityBlockers.rasterImage),
25267 static_cast<int>(advancedEntityBlockers.wipeout),
25268 static_cast<int>(advancedEntityBlockers.underlay),
25269 static_cast<int>(advancedEntityBlockers.mleader),
25270 static_cast<int>(advancedEntityBlockers.arcDimension),
25271 static_cast<int>(advancedEntityBlockers.unknown),
25272 static_cast<int>(advancedEntityBlockers.nativeWriterAvailable),
25273 static_cast<int>(advancedEntityBlockers.rawReplayAvailable),
25274 static_cast<int>(advancedEntityBlockers.fallbackAvailable),
25275 static_cast<int>(advancedEntityBlockers.editedFallbackInvalidated),
25276 static_cast<int>(advancedEntityBlockers.missingRequiredMetadata),
25277 static_cast<int>(advancedEntityBlockers.missingPayloadBytes),
25278 static_cast<int>(advancedEntityBlockers.unsupportedAdvancedContent),
25279 static_cast<int>(advancedEntityBlockers.odaComplete),
25280 static_cast<int>(advancedEntityBlockers.odaPartial),
25281 static_cast<int>(advancedEntityBlockers.odaAbsent));
25282 }
25283 const size_t nativeSemanticRecords = static_cast<size_t>(
25284 nativeSunObjects + nativeVisualStyleObjects +
25285 nativeTvDevicePropertiesObjects + nativePlaceholderObjects +
25286 nativeMLeaderStyleObjects + nativeDictionaryObjects +
25287 nativeXRecordObjects + nativeLayoutObjects + nativeGroupObjects +
25288 nativeTableStyleObjects + nativeRasterVariablesObjects +
25289 nativeWipeoutVariablesObjects + nativeGeoDataObjects +
25290 nativeSpatialFilterObjects + nativePointCloudDefinitionObjects +
25291 nativeNavisworksModelDefinitionObjects +
25292 nativePointCloudColorMapObjects + nativeSectionObjects);
25293 const size_t semanticOnlyRecords =
25294 metadata.semanticOnlyRecordCount() > nativeSemanticRecords
25295 ? metadata.semanticOnlyRecordCount() - nativeSemanticRecords
25296 : 0;
25297 const bool hasSemanticOnlyReplayable = semanticOnlyRecords > 0;
25298 if (hasBlockedReplay || hasSemanticOnlyReplayable) {
25299 RS_DEBUGRS_Debug::instance()->print(
25300 RS_Debug::D_WARNING,
25301 "Some DWG advanced metadata still cannot be emitted natively; "
25302 "unchanged raw OBJECTS/data sections are replayed where safe, "
25303 "while entities and semantic-only records remain diagnostic-only");
25304 if (hasBlockedReplay) {
25305 RS_DEBUGRS_Debug::instance()->print(
25306 RS_Debug::D_WARNING,
25307 "Blocked raw DWG replay: invalidated=%d replaced=%d entity=%d "
25308 "missing-bytes=%d missing-class=%d writer-rejected=%d "
25309 "version-mismatch=%d fixed-handle=%d (objects=%d sections=%d)",
25310 blockedInvalidated, blockedReplaced, blockedEntityReplay,
25311 blockedMissingRawBytes, blockedMissingClassMetadata,
25312 blockedWriterRejected, blockedVersionMismatch, blockedFixedHandle,
25313 replayedObjects, replayedSections);
25314 }
25315 if (semanticOnlyRecords > 0) {
25316 RS_DEBUGRS_Debug::instance()->print(
25317 RS_Debug::D_WARNING,
25318 "Semantic-only DWG metadata records not emitted natively: %d",
25319 static_cast<int>(semanticOnlyRecords));
25320 }
25321 }
25322 if (blockedDataStorage > 0) {
25323 m_writeFailed = true;
25324 RS_DEBUGRS_Debug::instance()->print(
25325 RS_Debug::D_WARNING,
25326 "DWG export rejected: %d parsed DataStorage section/carrier "
25327 "item(s) could not be preserved for the target version",
25328 blockedDataStorage);
25329 }
25330 return; // DWG writer handles object section internally
25331 }
25332 /* PLOTSETTINGS */
25333 const auto &metadata = m_graphic->dwgAdvancedMetadata();
25334 LC_PlotSettings *gps = m_graphic->getPlotSettings();
25335 if (gps == nullptr) {
25336 m_writeFailed = true;
25337 return;
25338 }
25339 const auto writePlotSettings = [this, gps](DRW_PlotSettings ps,
25340 bool overlayActiveSettings) {
25341 if (overlayActiveSettings)
25342 overlayActivePlotSettings(ps, *gps);
25343 noteDxfWrite(m_dxfW->writePlotSettings(&ps));
25344 };
25345 if (metadata.plotSettings().empty()) {
25346 writePlotSettings(DRW_PlotSettings{}, true);
25347 } else {
25348 for (std::size_t index = 0; index < metadata.plotSettings().size(); ++index)
25349 writePlotSettings(metadata.plotSettings()[index], index == 0);
25350 }
25351
25352 // Slice A2: re-emit OBJECTS captured verbatim on read (A1) so a LibreCAD DXF
25353 // round-trip preserves unmodeled objects rather than dropping them. Records
25354 // live on the graphic, so this (separate) write-filter instance still sees
25355 // them. Skip handles the codec regenerates itself (source ACAD_GROUP dict,
25356 // C/D collisions — see m_dxfSuppressedObjectHandles set up in fileExport) so
25357 // routed named dictionaries don't duplicate the root/group dictionaries.
25358 if (m_graphic != nullptr) {
25359 for (const DRW_RawDxfObject &rawObject :
25360 m_graphic->dwgAdvancedMetadata().rawDxfObjects()) {
25361 if (rawObject.handle != 0 &&
25362 m_dxfSuppressedObjectHandles.count(rawObject.handle) != 0)
25363 continue;
25364 DRW_RawDxfObject object = rawObject;
25365 noteDxfWrite(m_dxfW->writeRawDxfObject(&object));
25366 }
25367
25368 // F4: typed DXF emit for the routed data-only OBJECTS the DWG reader stores
25369 // ONLY in typed metadata (NOT the raw net) — SUN/SCALE/DICTIONARYVAR/
25370 // RASTERVARIABLES. On DWG->DXF these are absent from the raw net, so the
25371 // loop above never emits them; emit them natively here so the type is
25372 // preserved. DEDUP vs the raw net: a DXF-READ object lives in BOTH the
25373 // typed metadata AND the raw net (processSun calls addSun AND
25374 // addRawDxfObject), so it was already re-emitted above — skip the typed
25375 // emit when its code-5 handle is already in the raw net to avoid a
25376 // double-emit. (Build the raw-handle set once.) Only ReplayAllowed,
25377 // nonzero-handle records are emitted; handles are reserved and a CLASS
25378 // record is registered in fileExport's pre-write pass.
25379 const auto &metadata = m_graphic->dwgAdvancedMetadata();
25380 std::set<std::uint32_t> rawHandles;
25381 for (const DRW_RawDxfObject &o : metadata.rawDxfObjects())
25382 if (o.handle != 0)
25383 rawHandles.insert(o.handle);
25384 auto emitTyped = [&](std::uint32_t handle,
25385 LC_DwgAdvancedMetadata::ReplayState state) {
25386 return handle != 0 &&
25387 state == LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
25388 rawHandles.count(handle) == 0;
25389 };
25390 // F4f-3: owner fallback. A data-only OBJECT's 330 parent must resolve to
25391 // an object that actually exists in the output, else ezdxf deletes the
25392 // object (INVALID_OWNER_HANDLE). A parent is reachable iff it is 0 (->
25393 // root C), a named parent dict we emit (F4f-2), or a raw-net object
25394 // re-emitted verbatim. Otherwise (e.g. a SUN owned by a per-viewport ACAD
25395 // dict LibreCAD never materializes) zero it so writeObjectOwner emits C.
25396 auto resolveOwner = [&](std::uint32_t parent) -> int {
25397 if (parent == 0 || m_dxfEmittedNamedDictHandles.count(parent) != 0 ||
25398 rawHandles.count(parent) != 0)
25399 return static_cast<int>(parent);
25400 return 0; // dangling -> owner C
25401 };
25402 for (const auto &record : metadata.suns()) {
25403 if (!emitTyped(record.handle, record.replayState))
25404 continue;
25405 DRW_Sun sun = sunFromMetadata(record);
25406 sun.parentHandle = resolveOwner(record.parentHandle);
25407 noteDxfWrite(m_dxfW->writeSun(&sun));
25408 }
25409 for (const auto &record : metadata.scales()) {
25410 if (!emitTyped(record.handle, record.replayState))
25411 continue;
25412 DRW_Scale scale = scaleFromMetadata(record);
25413 scale.parentHandle = resolveOwner(record.parentHandle);
25414 noteDxfWrite(m_dxfW->writeScale(&scale));
25415 }
25416 for (const auto &record : metadata.dictionaryVars()) {
25417 if (!emitTyped(record.handle, record.replayState))
25418 continue;
25419 DRW_DictionaryVar dv = dictionaryVarFromMetadata(record);
25420 dv.parentHandle = resolveOwner(record.parentHandle);
25421 noteDxfWrite(m_dxfW->writeDictionaryVar(&dv));
25422 }
25423 for (const auto &record : metadata.rasterVariables()) {
25424 if (!emitTyped(record.handle, record.replayState))
25425 continue;
25426 DRW_RasterVariables rv = rasterVariablesFromMetadata(record);
25427 rv.parentHandle = resolveOwner(record.parentHandle);
25428 noteDxfWrite(m_dxfW->writeRasterVariables(&rv));
25429 }
25430 // MLEADERSTYLE is a custom object with a CLASS record. This is
25431 // load-bearing for later MLEADER DXF completion, since MLEADER entities
25432 // reference these styles by handle.
25433 for (const auto &record : metadata.mleaderStyles()) {
25434 if (!emitTyped(record.handle, record.replayState))
25435 continue;
25436 DRW_MLeaderStyle style = mleaderStyleFromMetadata(record);
25437 style.parentHandle = resolveOwner(record.parentHandle);
25438 noteDxfWrite(m_dxfW->writeMLeaderStyle(&style));
25439 }
25440 // SLICE 1: MLINESTYLE (FIXED built-in, no CLASS). DWG read populates only
25441 // typed metadata; DXF read keeps it in the raw net (so emitTyped dedups by
25442 // handle). The STANDARD mline style is present in most drawings.
25443 for (const auto &record : metadata.mlineStyles()) {
25444 if (!emitTyped(record.handle, record.replayState))
25445 continue;
25446 DRW_MLineStyle style = mlineStyleFromMetadata(record);
25447 style.parentHandle = resolveOwner(record.parentHandle);
25448 noteDxfWrite(m_dxfW->writeMLineStyle(&style));
25449 }
25450 // LAYOUT is a fixed built-in object. It is typed metadata only on DXF
25451 // read too, so emit it here and let the prepass materialize ACAD_LAYOUT
25452 // when the source had a reachable layout dictionary.
25453 for (const auto &record : metadata.layouts()) {
25454 if (!emitTyped(record.handle, record.replayState))
25455 continue;
25456 DRW_Layout layout = layoutFromMetadata(record);
25457 layout.parentHandle = resolveOwner(record.parentHandle);
25458 noteDxfWrite(m_dxfW->writeLayout(&layout));
25459 }
25460 // GEODATA is a custom object with a CLASS record. DWG read captures all
25461 // typed fields, including mesh points/faces, so emit it on DWG->DXF.
25462 for (const auto &record : metadata.geoData()) {
25463 if (!emitTyped(record.handle, record.replayState))
25464 continue;
25465 DRW_GeoData gd = geoDataFromMetadata(record);
25466 gd.parentHandle = resolveOwner(record.parentHandle);
25467 noteDxfWrite(m_dxfW->writeGeoData(&gd));
25468 }
25469 // SPATIAL_FILTER is a custom object with a CLASS record. The parent is
25470 // usually an extension dictionary; resolve it through the materialized
25471 // dictionary set so exported DXF owners are reachable.
25472 for (const auto &record : metadata.spatialFilters()) {
25473 if (!emitTyped(record.handle, record.replayState))
25474 continue;
25475 DRW_SpatialFilter sf = spatialFilterFromMetadata(record);
25476 sf.parentHandle = resolveOwner(record.parentHandle);
25477 noteDxfWrite(m_dxfW->writeSpatialFilter(&sf));
25478 }
25479 // SORTENTSTABLE is a custom object with entity draw-order references.
25480 // dxfRW remaps source entity handles to the minted DXF entity handles.
25481 for (const auto &record : metadata.sortEntsTables()) {
25482 if (!emitTyped(record.handle, record.replayState))
25483 continue;
25484 DRW_SortEntsTable se = sortEntsTableFromMetadata(record);
25485 se.parentHandle = resolveOwner(record.parentHandle);
25486 noteDxfWrite(m_dxfW->writeSortEntsTable(&se));
25487 }
25488 // FIELD/FIELDLIST are custom objects. Emit FIELD first so FIELDLIST 330
25489 // references point at an object that already exists in OBJECTS.
25490 for (const auto &record : metadata.fields()) {
25491 if (!emitTyped(record.handle, record.replayState))
25492 continue;
25493 DRW_Field field = fieldFromMetadata(record);
25494 field.parentHandle = resolveOwner(record.parentHandle);
25495 noteDxfWrite(m_dxfW->writeField(&field));
25496 }
25497 for (const auto &record : metadata.fieldLists()) {
25498 if (!emitTyped(record.handle, record.replayState))
25499 continue;
25500 DRW_FieldList fieldList = fieldListFromMetadata(record);
25501 fieldList.parentHandle = resolveOwner(record.parentHandle);
25502 noteDxfWrite(m_dxfW->writeFieldList(&fieldList));
25503 }
25504 // SLICE 2: WIPEOUTVARIABLES (custom, CLASS registered). Same dedup-vs-raw
25505 //-net + owner re-attach as the other data-only OBJECTS.
25506 for (const auto &record : metadata.wipeoutVariables()) {
25507 if (!emitTyped(record.handle, record.replayState))
25508 continue;
25509 DRW_WipeoutVariables wv = wipeoutVariablesFromMetadata(record);
25510 wv.parentHandle = resolveOwner(record.parentHandle);
25511 noteDxfWrite(m_dxfW->writeWipeoutVariables(&wv));
25512 }
25513 for (const auto &record : metadata.pointCloudDefinitions()) {
25514 if (!emitTyped(record.handle, record.replayState))
25515 continue;
25516 DRW_PointCloudDef definition = pointCloudDefinitionFromMetadata(record);
25517 definition.parentHandle = resolveOwner(record.parentHandle);
25518 noteDxfWrite(m_dxfW->writePointCloudDef(&definition));
25519 }
25520 for (const auto &record : metadata.navisworksModelDefinitions()) {
25521 if (!emitTyped(record.handle, record.replayState))
25522 continue;
25523 DRW_NavisworksModelDef definition =
25524 navisworksModelDefinitionFromMetadata(record);
25525 definition.parentHandle = resolveOwner(record.parentHandle);
25526 noteDxfWrite(m_dxfW->writeNavisworksModelDef(&definition));
25527 }
25528 for (const auto &record : metadata.pointCloudColorMaps()) {
25529 if (!emitTyped(record.handle, record.replayState))
25530 continue;
25531 DRW_PointCloudColorMap colorMap = pointCloudColorMapFromMetadata(record);
25532 colorMap.parentHandle = resolveOwner(record.parentHandle);
25533 noteDxfWrite(m_dxfW->writePointCloudColorMap(&colorMap));
25534 }
25535 for (const auto &record : metadata.backgrounds()) {
25536 if (!emitTyped(record.handle, record.replayState))
25537 continue;
25538 DRW_Background background = backgroundFromMetadata(record);
25539 background.parentHandle = resolveOwner(record.parentHandle);
25540 noteDxfWrite(m_dxfW->writeBackground(&background,
25541 backgroundRecordName(record.kind)));
25542 }
25543 for (const auto &record : metadata.materials()) {
25544 if (!emitTyped(record.handle, record.replayState))
25545 continue;
25546 DRW_Material material = materialFromMetadata(record);
25547 material.parentHandle = resolveOwner(record.parentHandle);
25548 noteDxfWrite(m_dxfW->writeMaterial(&material));
25549 }
25550 for (const auto &record : metadata.renderSettings()) {
25551 if (!emitTyped(record.handle, record.replayState))
25552 continue;
25553 DRW_RenderSettings settings = renderSettingsFromMetadata(record);
25554 settings.parentHandle = resolveOwner(record.parentHandle);
25555 noteDxfWrite(m_dxfW->writeRenderSettings(
25556 &settings, renderSettingsRecordName(record.kind)));
25557 }
25558 for (const auto &record : metadata.sunStudies()) {
25559 if (!emitTyped(record.handle, record.replayState))
25560 continue;
25561 DRW_SunStudy study = sunStudyFromMetadata(record);
25562 study.parentHandle = resolveOwner(record.parentHandle);
25563 noteDxfWrite(m_dxfW->writeSunStudy(&study));
25564 }
25565 for (const auto &record : metadata.motionPaths()) {
25566 if (!emitTyped(record.handle, record.replayState))
25567 continue;
25568 DRW_MotionPath path = motionPathFromMetadata(record);
25569 path.parentHandle = resolveOwner(record.parentHandle);
25570 noteDxfWrite(m_dxfW->writeMotionPath(&path));
25571 }
25572 for (const auto &record : metadata.curvePaths()) {
25573 if (!emitTyped(record.handle, record.replayState))
25574 continue;
25575 DRW_CurvePath path = curvePathFromMetadata(record);
25576 path.parentHandle = resolveOwner(record.parentHandle);
25577 noteDxfWrite(m_dxfW->writeCurvePath(&path));
25578 }
25579 for (const auto &record : metadata.pointPaths()) {
25580 if (!emitTyped(record.handle, record.replayState))
25581 continue;
25582 DRW_PointPath path = pointPathFromMetadata(record);
25583 path.parentHandle = resolveOwner(record.parentHandle);
25584 noteDxfWrite(m_dxfW->writePointPath(&path));
25585 }
25586 for (const auto &record : metadata.objectPtrs()) {
25587 if (!emitTyped(record.handle, record.replayState))
25588 continue;
25589 DRW_ObjectPtr object = objectPtrFromMetadata(record);
25590 object.parentHandle = resolveOwner(record.parentHandle);
25591 noteDxfWrite(m_dxfW->writeObjectPtr(&object));
25592 }
25593 for (const auto &record : metadata.partialViewingIndexes()) {
25594 if (!emitTyped(record.handle, record.replayState))
25595 continue;
25596 DRW_PartialViewingIndex index = partialViewingIndexFromMetadata(record);
25597 index.parentHandle = resolveOwner(record.parentHandle);
25598 noteDxfWrite(m_dxfW->writePartialViewingIndex(&index));
25599 }
25600 for (const auto &record : metadata.dbColors()) {
25601 if (!emitTyped(record.handle, record.replayState))
25602 continue;
25603 DRW_DbColor color = dbColorFromMetadata(record);
25604 color.parentHandle = resolveOwner(record.parentHandle);
25605 noteDxfWrite(m_dxfW->writeDbColor(&color));
25606 }
25607 for (const auto &record : metadata.dimensionAssociations()) {
25608 DRW_DimensionAssociation association =
25609 dimensionAssociationFromMetadata(record);
25610 if (!canWriteNativeDimensionAssociation(association) ||
25611 !emitTyped(record.handle, record.replayState))
25612 continue;
25613 association.parentHandle = resolveOwner(record.parentHandle);
25614 noteDxfWrite(m_dxfW->writeDimensionAssociation(&association));
25615 }
25616 for (const auto &record : metadata.evaluationGraphs()) {
25617 DRW_EvaluationGraph graph = evaluationGraphFromMetadata(record);
25618 if (!canWriteNativeEvaluationGraph(graph) ||
25619 !emitTyped(record.handle, record.replayState))
25620 continue;
25621 graph.parentHandle = resolveOwner(record.parentHandle);
25622 noteDxfWrite(m_dxfW->writeEvaluationGraph(&graph));
25623 }
25624 for (const auto &record : metadata.sections()) {
25625 if (!emitTyped(record.handle, record.replayState))
25626 continue;
25627 DRW_Section section = sectionFromMetadata(record);
25628 section.parentHandle = resolveOwner(record.parentHandle);
25629 if (section.m_kind == DRW_Section::Manager) {
25630 for (auto &handle : section.m_sectionHandles) {
25631 handle = m_dxfW->remapHandle(m_dxfW->remapEntityHandle(handle));
25632 }
25633 section.m_sectionCount =
25634 static_cast<std::int32_t>(section.m_sectionHandles.size());
25635 } else {
25636 for (auto &type : section.m_types) {
25637 for (auto &handle : type.m_sourceHandles) {
25638 handle = m_dxfW->remapHandle(m_dxfW->remapEntityHandle(handle));
25639 }
25640 }
25641 }
25642 noteDxfWrite(
25643 m_dxfW->writeSection(&section, sectionRecordName(record.kind)));
25644 }
25645 }
25646}
25647
25648void RS_FilterDXFRW::collectDwgAppIds() {
25649#ifdef DWGSUPPORT1
25650 if (m_dwgW == nullptr || m_graphic == nullptr)
25651 return;
25652
25653 std::set<std::string> emittedNames;
25654 const auto collectExtData = [this, &emittedNames](const auto &extData) {
25655 for (const auto &value : extData) {
25656 if (value == nullptr || value->code() != 1001 ||
25657 value->type() != DRW_Variant::STRING || value->content.s == nullptr ||
25658 value->content.s->empty()) {
25659 continue;
25660 }
25661 const std::string name = *value->content.s;
25662 if (!emittedNames.insert(normalizeDwgTableName(name)).second)
25663 continue;
25664 DRW_AppId appId;
25665 appId.name = name;
25666 if (!m_dwgW->addAppId(&appId))
25667 m_writeFailed = true;
25668 }
25669 };
25670 const auto collectFromEntity = [&collectExtData](const RS_Entity *entity) {
25671 if (entity == nullptr || entity->getFlag(RS2::FlagDeleted) ||
25672 !entity->hasDrwExtData()) {
25673 return;
25674 }
25675 collectExtData(entity->getDrwExtData());
25676 };
25677 const auto collectFromContainer = [&collectFromEntity](
25678 const RS_EntityContainer *container) {
25679 if (container == nullptr)
25680 return;
25681 for (const RS_Entity *entity :
25682 lc::LC_ContainerTraverser{*container, RS2::ResolveNone}.entities()) {
25683 collectFromEntity(entity);
25684 }
25685 };
25686
25687 collectFromContainer(m_graphic);
25688 for (unsigned index = 0; index < m_graphic->countBlocks(); ++index)
25689 collectFromContainer(m_graphic->blockAt(index));
25690
25691 const auto &metadata = m_graphic->dwgAdvancedMetadata();
25692 for (const auto &record : metadata.visualStyles()) {
25693 if (record.replayState ==
25694 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25695 collectExtData(record.extData);
25696 }
25697 }
25698 for (const auto &record : metadata.suns()) {
25699 if (record.replayState ==
25700 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25701 collectExtData(record.extData);
25702 }
25703 }
25704 for (const auto &record : metadata.backgrounds()) {
25705 if (record.replayState ==
25706 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25707 collectExtData(record.extData);
25708 }
25709 }
25710 for (const auto &record : metadata.renderSettings()) {
25711 if (record.replayState ==
25712 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25713 collectExtData(record.extData);
25714 }
25715 }
25716 for (const auto &record : metadata.sunStudies()) {
25717 if (record.replayState ==
25718 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25719 collectExtData(record.extData);
25720 }
25721 }
25722 for (const auto &record : metadata.motionPaths()) {
25723 if (record.replayState ==
25724 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25725 collectExtData(record.extData);
25726 }
25727 }
25728 for (const auto &record : metadata.curvePaths()) {
25729 if (record.replayState ==
25730 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25731 collectExtData(record.extData);
25732 }
25733 }
25734 for (const auto &record : metadata.pointPaths()) {
25735 if (record.replayState ==
25736 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25737 collectExtData(record.extData);
25738 }
25739 }
25740 for (const auto &record : metadata.objectPtrs()) {
25741 if (record.replayState ==
25742 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25743 collectExtData(record.extData);
25744 }
25745 }
25746 for (const auto &record : metadata.partialViewingIndexes()) {
25747 if (record.replayState ==
25748 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25749 collectExtData(record.extData);
25750 }
25751 }
25752 for (const auto &record : metadata.cameras()) {
25753 if (record.replayState ==
25754 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25755 collectExtData(record.extData);
25756 }
25757 }
25758 for (const auto &record : metadata.geoPositionMarkers()) {
25759 if (record.replayState ==
25760 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25761 collectExtData(record.extData);
25762 }
25763 }
25764 for (const auto &record : metadata.sectionObjects()) {
25765 if (record.replayState ==
25766 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25767 collectExtData(record.extData);
25768 }
25769 }
25770 for (const auto &record : metadata.sections()) {
25771 if (record.replayState ==
25772 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25773 collectExtData(record.extData);
25774 }
25775 }
25776 for (const auto &record : metadata.modelerGeometry()) {
25777 if (record.replayState ==
25778 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25779 collectExtData(record.extData);
25780 }
25781 }
25782 for (const auto &record : metadata.fields()) {
25783 if (record.replayState ==
25784 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25785 collectExtData(record.extData);
25786 }
25787 }
25788 for (const auto &record : metadata.fieldLists()) {
25789 if (record.replayState ==
25790 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25791 collectExtData(record.extData);
25792 }
25793 }
25794 for (const auto &record : metadata.dictionaries()) {
25795 if (record.replayState ==
25796 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25797 collectExtData(record.extData);
25798 }
25799 }
25800 for (const auto &record : metadata.xrecords()) {
25801 if (record.replayState ==
25802 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25803 collectExtData(record.extData);
25804 }
25805 }
25806 for (const auto &record : metadata.groups()) {
25807 if (record.replayState ==
25808 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25809 collectExtData(record.extData);
25810 }
25811 }
25812 for (const auto &record : metadata.mlineStyles()) {
25813 if (record.replayState ==
25814 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25815 collectExtData(record.extData);
25816 }
25817 }
25818 for (const auto &record : metadata.layouts()) {
25819 if (record.replayState ==
25820 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25821 collectExtData(record.extData);
25822 }
25823 }
25824 for (const auto &record : metadata.mleaderStyles()) {
25825 if (record.replayState ==
25826 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25827 collectExtData(record.extData);
25828 }
25829 }
25830 for (const auto &record : metadata.materials()) {
25831 if (record.replayState ==
25832 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25833 collectExtData(record.extData);
25834 }
25835 }
25836 for (const auto &record : metadata.lightLists()) {
25837 if (record.replayState ==
25838 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25839 collectExtData(record.extData);
25840 }
25841 }
25842 for (const auto &record : metadata.tvDeviceProperties()) {
25843 if (record.replayState ==
25844 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25845 collectExtData(record.extData);
25846 }
25847 }
25848 for (const auto &record : metadata.blockRepresentationData()) {
25849 if (record.replayState ==
25850 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25851 collectExtData(record.extData);
25852 }
25853 }
25854 for (const auto &record : metadata.placeholders()) {
25855 if (record.replayState ==
25856 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25857 collectExtData(record.extData);
25858 }
25859 }
25860 for (const auto &project : metadata.vbaProjects())
25861 collectExtData(project.extData);
25862 for (const auto &settings : metadata.plotSettings())
25863 collectExtData(settings.extData);
25864 for (const auto &record : metadata.vxControls()) {
25865 if (record.replayState ==
25866 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25867 collectExtData(record.extData);
25868 }
25869 }
25870 for (const auto &record : metadata.vxTableRecords()) {
25871 if (record.replayState ==
25872 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25873 collectExtData(record.extData);
25874 }
25875 }
25876 for (const auto &record : metadata.rasterVariables()) {
25877 if (record.replayState ==
25878 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25879 collectExtData(record.extData);
25880 }
25881 }
25882 for (const auto &record : metadata.wipeoutVariables()) {
25883 if (record.replayState ==
25884 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25885 collectExtData(record.extData);
25886 }
25887 }
25888 for (const auto &record : metadata.dbColors()) {
25889 if (record.replayState ==
25890 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25891 collectExtData(record.extData);
25892 }
25893 }
25894 for (const auto &record : metadata.dimensionAssociations()) {
25895 if (record.replayState ==
25896 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25897 collectExtData(record.extData);
25898 }
25899 }
25900 for (const auto &record : metadata.evaluationGraphs()) {
25901 if (record.replayState ==
25902 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25903 collectExtData(record.extData);
25904 }
25905 }
25906 for (const auto &record : metadata.scales()) {
25907 if (record.replayState ==
25908 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25909 collectExtData(record.extData);
25910 }
25911 }
25912 for (const auto &record : metadata.idBuffers()) {
25913 if (record.replayState ==
25914 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25915 collectExtData(record.extData);
25916 }
25917 }
25918 for (const auto &record : metadata.spatialIndexes()) {
25919 if (record.replayState ==
25920 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25921 collectExtData(record.extData);
25922 }
25923 }
25924 for (const auto &record : metadata.layerIndexes()) {
25925 if (record.replayState ==
25926 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25927 collectExtData(record.extData);
25928 }
25929 }
25930 for (const auto &record : metadata.dictionaryVars()) {
25931 if (record.replayState ==
25932 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25933 collectExtData(record.extData);
25934 }
25935 }
25936 for (const auto &record : metadata.dictionariesWithDefault()) {
25937 if (record.replayState ==
25938 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25939 collectExtData(record.extData);
25940 }
25941 }
25942 for (const auto &record : metadata.sortEntsTables()) {
25943 if (record.replayState ==
25944 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25945 collectExtData(record.extData);
25946 }
25947 }
25948 for (const auto &record : metadata.underlayDefinitions()) {
25949 if (record.replayState ==
25950 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25951 collectExtData(record.extData);
25952 }
25953 }
25954 for (const auto &record : metadata.imageDefinitions()) {
25955 if (record.replayState ==
25956 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25957 collectExtData(record.extData);
25958 }
25959 }
25960 for (const auto &record : metadata.imageDefinitionReactors()) {
25961 if (record.replayState ==
25962 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25963 collectExtData(record.extData);
25964 }
25965 }
25966 for (const auto &record : metadata.pointCloudDefinitions()) {
25967 if (record.replayState ==
25968 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25969 collectExtData(record.extData);
25970 }
25971 }
25972 for (const auto &record : metadata.navisworksModelDefinitions()) {
25973 if (record.replayState ==
25974 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25975 collectExtData(record.extData);
25976 }
25977 }
25978 for (const auto &record : metadata.pointCloudColorMaps()) {
25979 if (record.replayState ==
25980 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25981 collectExtData(record.extData);
25982 }
25983 }
25984 for (const auto &record : metadata.geoData()) {
25985 if (record.replayState ==
25986 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25987 collectExtData(record.extData);
25988 }
25989 }
25990 for (const auto &record : metadata.spatialFilters()) {
25991 if (record.replayState ==
25992 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
25993 collectExtData(record.extData);
25994 }
25995 }
25996 if (m_dwgW->getVersion() <= DRW::AC1021) {
25997 for (const auto &record : metadata.tables()) {
25998 if (record.replayState ==
25999 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
26000 record.nativeTableStyle) {
26001 collectExtData(record.nativeTableStyle->extData);
26002 }
26003 }
26004 }
26005#endif
26006}
26007
26008void RS_FilterDXFRW::writeAppId() {
26009 const auto &metadata = m_graphic->dwgAdvancedMetadata();
26010 std::set<std::string> emittedNames;
26011 const auto emitAppId = [this, &emittedNames](DRW_AppId appId) {
26012 const std::string normalizedName = normalizeDwgTableName(appId.name);
26013 if (!emittedNames.insert(normalizedName).second)
26014 return;
26015 if (m_dwgW != nullptr && hasDxfTableAppDataUnsupportedByDwg(appId)) {
26016 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
26017 "RS_FilterDXFRW: cannot convert DXF APPID "
26018 "application data to DWG");
26019 m_writeFailed = true;
26020 return;
26021 }
26022 if (m_dwgW != nullptr) {
26023 const std::uint32_t sourceHandle = appId.handle;
26024 if (!m_dwgW->addAppId(&appId) ||
26025 !stageDwgTableRecordIdentity(DwgWriteSourceKind::TableRecord,
26026 sourceHandle, appId.handle))
26027 m_writeFailed = true;
26028 } else {
26029 noteDxfWrite(m_dxfW->writeAppId(&appId));
26030 }
26031 };
26032 const auto emitRequired = [&metadata, &emitAppId](const char *name) {
26033 if (const auto *source = metadata.findAppIdTableEntryByName(name)) {
26034 emitAppId(*source);
26035 return;
26036 }
26037 DRW_AppId appId;
26038 appId.name = name;
26039 emitAppId(std::move(appId));
26040 };
26041
26042 if (const auto *acad = metadata.findAppIdTableEntryByName("ACAD")) {
26043 if (!acad->appData.empty() || !acad->reactorHandles.empty() ||
26044 acad->xDictHandle != 0 || !acad->extData.empty()) {
26045 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
26046 "RS_FilterDXFRW: cannot preserve application "
26047 "data on the codec-owned ACAD APPID");
26048 m_writeFailed = true;
26049 }
26050 }
26051
26052 emitRequired("LibreCad");
26053 emitRequired("ACAD_DSTYLE_DIMTALN");
26054 emitRequired("ACAD_DSTYLE_DIMJAG_POSITION");
26055 emitRequired("ACAD_DSTYLE_DIMJAG");
26056 for (const auto &entry : metadata.appIdTableEntries()) {
26057 if (normalizeDwgTableName(entry.first) != "ACAD")
26058 emitAppId(entry.second);
26059 }
26060}
26061
26062bool RS_FilterDXFRW::finalizeDwgWrite() {
26063#ifdef DWGSUPPORT1
26064 if (m_dwgW != nullptr) {
26065 // A completed deferred table phase cannot rescue a failed content
26066 // phase. Keep the filter ledger unpublished for the whole failed
26067 // output transaction.
26068 if (m_writeFailed)
26069 return false;
26070 if (!validateDwgDataStorageReplayReceipts(m_dwgW->getVersion())) {
26071 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
26072 "RS_FilterDXFRW: DWG DataStorage section receipts are "
26073 "missing, stale, or bound to the wrong source record");
26074 m_writeFailed = true;
26075 return false;
26076 }
26077 const auto checkpoint = checkpointDwgWriteIdentity();
26078 const auto fail = [this, &checkpoint]() {
26079 restoreDwgWriteIdentity(checkpoint);
26080 m_writeFailed = true;
26081 return false;
26082 };
26083 const bool tableRecordsOk = commitDwgTableRecordIdentities();
26084 const bool tableControlsOk = commitDwgTableControlIdentities();
26085 const bool opaqueTableReferencesOk =
26086 validateDwgOpaqueTableReferenceReceipts(
26087 DwgOpaqueTableReceiptStage::Finalized,
26088 DwgOpaqueTableFailurePhase::Finalized);
26089 const bool ledgerOk = validateDwgWriteIdentityLedger(
26090 /*allowSelectedBlocks=*/true);
26091 const bool classFrameAdmissionsOk = validateDwgClassFrameAdmissions();
26092 const bool fixedEntityFrameReceiptsOk =
26093 validateDwgFixedEntityFrameReceipts();
26094 const bool entityWritePlansOk = validateDwgEntityWritePlans();
26095 const bool classInstanceCountsOk = m_dwgW->validateDwgClassInstanceCounts();
26096 if (!tableRecordsOk || !tableControlsOk || !opaqueTableReferencesOk ||
26097 !ledgerOk || !classFrameAdmissionsOk || !fixedEntityFrameReceiptsOk ||
26098 !entityWritePlansOk || !classInstanceCountsOk) {
26099 RS_DEBUGRS_Debug::instance()->print(
26100 RS_Debug::D_WARNING,
26101 "RS_FilterDXFRW: DWG final identity checks failed "
26102 "(tableRecords=%d tableControls=%d opaqueTables=%d ledger=%d "
26103 "classFrames=%d fixedFrames=%d entityPlans=%d classes=%d "
26104 "prior=%d)",
26105 tableRecordsOk ? 1 : 0, tableControlsOk ? 1 : 0,
26106 opaqueTableReferencesOk ? 1 : 0, ledgerOk ? 1 : 0,
26107 classFrameAdmissionsOk ? 1 : 0, fixedEntityFrameReceiptsOk ? 1 : 0,
26108 entityWritePlansOk ? 1 : 0, classInstanceCountsOk ? 1 : 0,
26109 m_writeFailed ? 1 : 0);
26110 return fail();
26111 }
26112 }
26113#endif
26114 return !m_writeFailed;
26115}
26116
26117bool RS_FilterDXFRW::finalizeDwgWriteStructure() {
26118#ifdef DWGSUPPORT1
26119 if (m_dwgW == nullptr)
26120 return true;
26121 if (m_writeFailed)
26122 return false;
26123
26124 DRW::DwgBlockWriteResult result;
26125 if (!m_dwgW->getEmittedDwgBlockWriteResult(result) ||
26126 result.blockControlHandle == 0) {
26127 m_writeFailed = true;
26128 return false;
26129 }
26130
26131 const auto checkpoint = checkpointDwgWriteIdentity();
26132 const auto fail = [this, &checkpoint]() {
26133 restoreDwgWriteIdentity(checkpoint);
26134 m_writeFailed = true;
26135 return false;
26136 };
26137
26138 const auto commitOrRegister = [this](DwgWriteSourceKind kind,
26139 std::uint32_t outputHandle) {
26140 if (outputHandle == 0)
26141 return false;
26142 const auto ownerIt = m_dwgWriteIdentityOwnerByOutput.find(outputHandle);
26143 if (ownerIt != m_dwgWriteIdentityOwnerByOutput.end()) {
26144 const auto identityIt = m_dwgWriteIdentityByOutput.find(ownerIt->second);
26145 if (ownerIt->second.kind != kind ||
26146 identityIt == m_dwgWriteIdentityByOutput.end() ||
26147 !selectDwgWriteIdentity(kind, identityIt->second.sourceHandle,
26148 outputHandle) ||
26149 !commitDwgWriteIdentity(kind, outputHandle))
26150 return false;
26151 return true;
26152 }
26153 return reserveDwgWriteIdentity(kind, 0, outputHandle) &&
26154 selectDwgWriteIdentity(kind, 0, outputHandle) &&
26155 commitDwgWriteIdentity(kind, outputHandle);
26156 };
26157
26158 if (!commitOrRegister(DwgWriteSourceKind::TableControl,
26159 result.blockControlHandle)) {
26160 return fail();
26161 }
26162
26163 std::set<std::uint32_t> emittedBlockRecordHandles;
26164 for (const DRW::DwgBlockWriteRecord &record : result.blockRecords) {
26165 if (record.blockRecordHandle == 0 ||
26166 !emittedBlockRecordHandles.insert(record.blockRecordHandle).second) {
26167 return fail();
26168 }
26169 const DwgWriteSourceKind kind =
26170 record.blockRecordHandle == DRW::DwgModelSpaceBlockRecordHandle ||
26171 record.blockRecordHandle == DRW::DwgPaperSpaceBlockRecordHandle
26172 ? DwgWriteSourceKind::FixedStructural
26173 : DwgWriteSourceKind::Block;
26174 if (!commitOrRegister(kind, record.blockRecordHandle)) {
26175 return fail();
26176 }
26177 }
26178
26179 std::set<std::uint32_t> emittedBlockEntityHandles;
26180 for (const std::uint32_t handle : result.blockEntityHandles) {
26181 if (handle == 0 || !emittedBlockEntityHandles.insert(handle).second) {
26182 return fail();
26183 }
26184 const bool fixed = handle == DRW::DwgModelSpaceBlockEntityHandle ||
26185 handle == DRW::DwgModelSpaceEndBlockEntityHandle ||
26186 handle == DRW::DwgPaperSpaceBlockEntityHandle ||
26187 handle == DRW::DwgPaperSpaceEndBlockEntityHandle;
26188 if (!commitOrRegister(fixed ? DwgWriteSourceKind::FixedStructural
26189 : DwgWriteSourceKind::GeneratedChild,
26190 handle)) {
26191 return fail();
26192 }
26193 }
26194
26195 // BLOCK_RECORD lists contain every entity physically owned by the block,
26196 // including compound children such as ATTRIB and SEQEND. Those children
26197 // have no source-side RS_Entity identity, so publish them as generated
26198 // identities only after their parent frame has completed.
26199 const auto commitOwnedEntityOrRegister = [this](std::uint32_t outputHandle) {
26200 if (outputHandle == 0)
26201 return false;
26202 const auto ownerIt = m_dwgWriteIdentityOwnerByOutput.find(outputHandle);
26203 if (ownerIt != m_dwgWriteIdentityOwnerByOutput.end()) {
26204 if ((ownerIt->second.kind != DwgWriteSourceKind::Entity &&
26205 ownerIt->second.kind != DwgWriteSourceKind::GeneratedChild &&
26206 ownerIt->second.kind != DwgWriteSourceKind::RawCarrier) ||
26207 !isDwgWriteIdentityCommitted(ownerIt->second.kind, outputHandle)) {
26208 return false;
26209 }
26210 return true;
26211 }
26212 return reserveDwgWriteIdentity(DwgWriteSourceKind::GeneratedChild, 0,
26213 outputHandle) &&
26214 selectDwgWriteIdentity(DwgWriteSourceKind::GeneratedChild, 0,
26215 outputHandle) &&
26216 commitDwgWriteIdentity(DwgWriteSourceKind::GeneratedChild,
26217 outputHandle);
26218 };
26219 for (const DRW::DwgBlockWriteRecord &record : result.blockRecords) {
26220 for (const std::uint32_t handle : record.entityHandles) {
26221 if (!commitOwnedEntityOrRegister(handle)) {
26222 return fail();
26223 }
26224 }
26225 for (const std::uint32_t handle : record.insertHandles) {
26226 if (!commitOwnedEntityOrRegister(handle)) {
26227 return fail();
26228 }
26229 }
26230 }
26231
26232 const std::array<std::uint32_t, 4> fixedBlockEntities = {
26233 DRW::DwgModelSpaceBlockEntityHandle,
26234 DRW::DwgModelSpaceEndBlockEntityHandle,
26235 DRW::DwgPaperSpaceBlockEntityHandle,
26236 DRW::DwgPaperSpaceEndBlockEntityHandle};
26237 for (const std::uint32_t handle : fixedBlockEntities) {
26238 if (emittedBlockEntityHandles.count(handle) == 0) {
26239 return fail();
26240 }
26241 }
26242
26243 std::set<std::uint32_t> ownedEntityHandles;
26244 std::set<std::uint32_t> referencedBlockEntities;
26245 const auto validateEdgeList =
26246 [this](const std::vector<std::uint32_t> &handles) {
26247 std::set<std::uint32_t> unique;
26248 for (const std::uint32_t handle : handles) {
26249 if (handle == 0 || !unique.insert(handle).second ||
26250 !isDwgWriteOutputCommitted(handle))
26251 return false;
26252 }
26253 return true;
26254 };
26255 for (const DRW::DwgBlockWriteRecord &record : result.blockRecords) {
26256 for (const std::uint32_t handle : record.entityHandles) {
26257 if (!ownedEntityHandles.insert(handle).second) {
26258 return fail();
26259 }
26260 }
26261 if (!isDwgWriteOutputCommitted(record.blockRecordHandle) ||
26262 record.blockHandle == 0 || record.endBlockHandle == 0 ||
26263 emittedBlockEntityHandles.count(record.blockHandle) == 0 ||
26264 emittedBlockEntityHandles.count(record.endBlockHandle) == 0 ||
26265 !isDwgWriteOutputCommitted(record.blockHandle) ||
26266 !isDwgWriteOutputCommitted(record.endBlockHandle) ||
26267 !validateEdgeList(record.entityHandles) ||
26268 !validateEdgeList(record.insertHandles)) {
26269 return fail();
26270 }
26271 referencedBlockEntities.insert(record.blockHandle);
26272 referencedBlockEntities.insert(record.endBlockHandle);
26273 }
26274 for (const std::uint32_t sourceHandle : m_dwgWriteEntityHandlesEmitted) {
26275 std::uint32_t outputHandle = 0;
26276 if (lookupDwgWriteIdentity(DwgWriteSourceKind::Entity, sourceHandle,
26277 &outputHandle) !=
26278 DwgWriteIdentityLookup::Committed ||
26279 ownedEntityHandles.count(outputHandle) == 0) {
26280 return fail();
26281 }
26282 }
26283 if (referencedBlockEntities != emittedBlockEntityHandles) {
26284 return fail();
26285 }
26286
26287 if (m_dwgPostObjectsTestHook)
26288 m_dwgPostObjectsTestHook();
26289 const bool fieldReceiptsValid = validateDwgFieldWriteReceipts();
26290 const bool ownerReceiptsValid = validateDwgWriteOwnerReceipts(&result);
26291 const bool referenceReceiptsValid = validateDwgWriteReferenceReceipts();
26292 const bool optionalDropReceiptsValid = validateDwgWriteOptionalDropReceipts();
26293 const bool identityLedgerValid = validateDwgWriteIdentityLedger();
26294 if (!fieldReceiptsValid || !ownerReceiptsValid || !referenceReceiptsValid ||
26295 !optionalDropReceiptsValid || !identityLedgerValid) {
26296 if (!referenceReceiptsValid) {
26297 for (const DwgWriteReferenceReceipt &receipt :
26298 m_dwgWriteReferenceReceipts) {
26299 std::uint32_t resolvedTarget = 0;
26300 const bool producerCommitted =
26301 isDwgWriteOutputCommitted(receipt.producerOutputHandle);
26302 const bool targetCommitted =
26303 receipt.targetSourceHandle == 0
26304 ? isDwgWriteIdentityCommitted(receipt.targetKind,
26305 receipt.targetOutputHandle)
26306 : lookupDwgWriteIdentity(
26307 receipt.targetKind, receipt.targetSourceHandle,
26308 &resolvedTarget) == DwgWriteIdentityLookup::Committed &&
26309 resolvedTarget == receipt.targetOutputHandle;
26310 if (!producerCommitted ||
26311 (!receipt.required && receipt.targetOutputHandle == 0) ||
26312 receipt.targetOutputHandle == 0 || !targetCommitted) {
26313 RS_DEBUGRS_Debug::instance()->print(
26314 RS_Debug::D_WARNING,
26315 "RS_FilterDXFRW: invalid DWG reference receipt "
26316 "producer=0x%X target=0x%X target-kind=%d "
26317 "resolved=0x%X producer-committed=%d "
26318 "target-committed=%d name=%s ordinal=%u",
26319 receipt.producerSourceHandle, receipt.targetSourceHandle,
26320 static_cast<int>(receipt.targetKind), resolvedTarget,
26321 producerCommitted ? 1 : 0, targetCommitted ? 1 : 0,
26322 receipt.referenceName.c_str(), receipt.referenceOrdinal);
26323 }
26324 }
26325 }
26326 if (!optionalDropReceiptsValid) {
26327 for (const DwgWriteOptionalDropReceipt &receipt :
26328 m_dwgWriteOptionalDropReceipts) {
26329 const bool valid =
26330 isDwgWriteOutputCommitted(receipt.producerOutputHandle) &&
26331 receipt.targetSourceHandle != 0 &&
26332 receipt.version != DRW::UNKNOWNV && !receipt.family.empty() &&
26333 !receipt.reason.empty() &&
26334 (receipt.targetStatus == DwgWriteReferenceStatus::Missing ||
26335 receipt.targetStatus == DwgWriteReferenceStatus::Ambiguous ||
26336 receipt.targetStatus == DwgWriteReferenceStatus::Suppressed ||
26337 receipt.targetStatus == DwgWriteReferenceStatus::Unpaired);
26338 if (!valid)
26339 RS_DEBUGRS_Debug::instance()->print(
26340 RS_Debug::D_WARNING,
26341 "RS_FilterDXFRW: invalid DWG optional-drop receipt "
26342 "producer=0x%X target=0x%X status=%d committed=%d "
26343 "name=%s ordinal=%u",
26344 receipt.producerSourceHandle, receipt.targetSourceHandle,
26345 static_cast<int>(receipt.targetStatus),
26346 isDwgWriteOutputCommitted(receipt.producerOutputHandle) ? 1 : 0,
26347 receipt.referenceName.c_str(), receipt.referenceOrdinal);
26348 }
26349 }
26350 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
26351 "RS_FilterDXFRW: DWG structure receipts invalid "
26352 "fields=%d owners=%d references=%d optional-drops=%d "
26353 "identities=%d",
26354 fieldReceiptsValid ? 1 : 0,
26355 ownerReceiptsValid ? 1 : 0, referenceReceiptsValid ? 1 : 0,
26356 optionalDropReceiptsValid ? 1 : 0,
26357 identityLedgerValid ? 1 : 0);
26358 return fail();
26359 }
26360#endif
26361 return !m_writeFailed;
26362}
26363
26364void RS_FilterDXFRW::writeEntities() {
26365#ifdef DWGSUPPORT1
26366 if (m_dwgW != nullptr && !validateDwgExportAdmissionPlan(
1
Assuming the condition is false
26367 DwgOpaqueTableFailurePhase::PostClasses)) {
26368 m_writeFailed = true;
26369 m_dwgW->markDwgWriteFailure();
26370 return;
26371 }
26372#endif
26373 // Pre-pass: reconstruct MLINE entities from decomposed polylines that
26374 // carry LibreCAD_MLINE XDATA. Consumed polylines are emitted as
26375 // MLINE; the rest fall through to the normal write path.
26376 std::set<RS_Entity *> consumed;
26377 reconstructPolylineSidecars(m_graphic, consumed);
26378 if (m_writeFailed)
2
Assuming field 'm_writeFailed' is false
3
Taking false branch
26379 return;
26380 reconstructMLines(m_graphic, consumed);
26381 if (m_writeFailed
3.1
Field 'm_writeFailed' is false
|| !reconstructSingleFrameSidecars(m_graphic, consumed))
4
Calling 'RS_FilterDXFRW::reconstructSingleFrameSidecars'
8
Returning from 'RS_FilterDXFRW::reconstructSingleFrameSidecars'
9
Taking false branch
26382 return;
26383#ifdef DWGSUPPORT1
26384 auto resolveDwgEntityOwner = [this](std::uint32_t sourceHandle,
26385 const char *recordName,
26386 std::uint32_t *outputHandle) {
26387 const DwgWriteReferenceStatus status =
26388 resolveDwgWriteBlockOwner(sourceHandle, outputHandle);
26389 if (status == DwgWriteReferenceStatus::LegacySpaceAlias ||
26390 status == DwgWriteReferenceStatus::Selected ||
26391 status == DwgWriteReferenceStatus::Committed) {
26392 return true;
26393 }
26394 RS_DEBUGRS_Debug::instance()->print(
26395 RS_Debug::D_WARNING,
26396 "RS_FilterDXFRW: rejecting DWG %s with unresolved owner 0x%X "
26397 "(status=%d)",
26398 recordName, sourceHandle, static_cast<int>(status));
26399 m_writeFailed = true;
26400 return false;
26401 };
26402 const auto writeDwgMetadataEntityWithCompletion =
26403 [this](std::uint32_t sourceHandle, std::uint32_t sourceOwnerHandle,
26404 const char *recordName, DRW_Entity &entity, const auto &write,
26405 const auto &complete) {
26406 using SourceKind =
26407 RS_FilterDXFRW_DwgWriteIdentityRegistry::DwgWriteSourceKind;
26408 if (m_dwgW == nullptr || sourceHandle == 0 ||
26409 isDwgWriteSourceAmbiguous(sourceHandle, SourceKind::Entity)) {
26410 m_writeFailed = true;
26411 return false;
26412 }
26413
26414 std::uint32_t outputOwnerHandle = 0;
26415 const DwgWriteReferenceStatus ownerStatus =
26416 resolveDwgWriteBlockOwner(sourceOwnerHandle, &outputOwnerHandle);
26417 if (ownerStatus != DwgWriteReferenceStatus::LegacySpaceAlias &&
26418 ownerStatus != DwgWriteReferenceStatus::Selected &&
26419 ownerStatus != DwgWriteReferenceStatus::Committed) {
26420 m_writeFailed = true;
26421 RS_DEBUGRS_Debug::instance()->print(
26422 RS_Debug::D_WARNING,
26423 "RS_FilterDXFRW: rejecting DWG %s with unresolved owner 0x%X",
26424 recordName, sourceOwnerHandle);
26425 return false;
26426 }
26427
26428 std::uint32_t existingOutputHandle = 0;
26429 if (lookupDwgWriteIdentity(SourceKind::Entity, sourceHandle,
26430 &existingOutputHandle) !=
26431 DwgWriteIdentityLookup::Missing) {
26432 m_writeFailed = true;
26433 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
26434 "RS_FilterDXFRW: duplicate DWG %s source handle 0x%X",
26435 recordName, sourceHandle);
26436 return false;
26437 }
26438
26439 const std::uint32_t outputHandle = resolveDwgWriteHandle(sourceHandle);
26440 entity.handle = outputHandle;
26441 entity.parentHandle = outputOwnerHandle;
26442 const auto identityCheckpoint = checkpointDwgWriteIdentity();
26443 if (!selectDwgWriteIdentity(SourceKind::Entity, sourceHandle,
26444 outputHandle)) {
26445 m_writeFailed = true;
26446 return false;
26447 }
26448 const std::uint64_t transaction =
26449 m_dwgW->beginDwgWriteTransaction(false, false, false);
26450 if (transaction == 0) {
26451 restoreDwgWriteIdentity(identityCheckpoint);
26452 m_writeFailed = true;
26453 return false;
26454 }
26455 const auto rollback = [&]() {
26456 m_dwgW->clearDwgObjectFrameProvenance();
26457 (void)m_dwgW->rollbackDwgWriteTransaction(transaction);
26458 restoreDwgWriteIdentity(identityCheckpoint);
26459 m_writeFailed = true;
26460 };
26461 if (!bindDwgDataStorageFrame(sourceHandle,
26462 DwgDataStorageCarrier::Kind::Typed)) {
26463 rollback();
26464 return false;
26465 }
26466 const bool failedBefore = m_writeFailed;
26467 if (!write() || failedBefore || m_writeFailed) {
26468 rollback();
26469 return false;
26470 }
26471
26472 DRW::DwgObjectFrameReceipt frame;
26473 if (!m_dwgW->getLastDwgObjectFrame(frame) ||
26474 !recordDwgWriteObjectOccurrences(outputHandle, frame)) {
26475 rollback();
26476 return false;
26477 }
26478
26479 const DwgWriteOwnerReceipt receipt{
26480 SourceKind::Entity, sourceHandle, outputHandle,
26481 sourceOwnerHandle, outputOwnerHandle, true};
26482 if (!recordDwgWriteOwnerReceipt(receipt)) {
26483 rollback();
26484 return false;
26485 }
26486 if (!complete() || m_writeFailed) {
26487 rollback();
26488 return false;
26489 }
26490 if (m_dwgW->getVersion() > DRW::AC1024) {
26491 const auto *dataStorageDecision =
26492 dataStorageDecisionForOwner(sourceHandle);
26493 if (dataStorageDecision != nullptr) {
26494 if (dataStorageDecision->decision !=
26495 DwgDataStorageDecision::EmitTyped ||
26496 dataStorageDecision->selectedPhase !=
26497 DwgDataStorageCarrier::EmissionPhase::Objects ||
26498 !recordDwgDataStorageObjectCommit(
26499 sourceHandle, DwgDataStorageCarrier::Kind::Typed,
26500 DwgWriteSourceKind::Entity, outputHandle, frame)) {
26501 rollback();
26502 return false;
26503 }
26504 }
26505 }
26506 if (sourceHandle != 0 &&
26507 m_dwgW->hasDwgClassDefinition(frame.classNumber) &&
26508 (!admitDwgClassFrameDescriptor(
26509 DwgWriteSourceKind::Entity, sourceHandle, outputHandle,
26510 frame.classNumber, 0, m_dwgW->getVersion()) ||
26511 !commitDwgClassFrame(DwgWriteSourceKind::Entity, sourceHandle,
26512 outputHandle, frame.classNumber))) {
26513 rollback();
26514 return false;
26515 }
26516 if (!commitDwgWriteIdentity(SourceKind::Entity, outputHandle)) {
26517 rollback();
26518 return false;
26519 }
26520 if (!m_dwgW->commitDwgWriteTransaction(transaction)) {
26521 rollback();
26522 return false;
26523 }
26524 return true;
26525 };
26526 const auto writeDwgMetadataEntity =
26527 [&writeDwgMetadataEntityWithCompletion](
26528 std::uint32_t sourceHandle, std::uint32_t sourceOwnerHandle,
26529 const char *recordName, DRW_Entity &entity, const auto &write) {
26530 return writeDwgMetadataEntityWithCompletion(
26531 sourceHandle, sourceOwnerHandle, recordName, entity, write,
26532 [] { return true; });
26533 };
26534 struct PendingOptionalDrop {
26535 std::uint32_t targetSourceHandle = 0;
26536 DwgWriteReferenceStatus targetStatus = DwgWriteReferenceStatus::Missing;
26537 DwgWriteSourceKind targetKind = DwgWriteSourceKind::Object;
26538 const char *referenceName = nullptr;
26539 std::uint32_t referenceOrdinal = 0;
26540 const char *reason = nullptr;
26541 };
26542 auto remapDwgObjectReference = [this](
26543 std::uint32_t sourceHandle,
26544 const char *referenceKind,
26545 std::vector<PendingOptionalDrop> &drops) {
26546 std::uint32_t outputHandle = 0;
26547 const DwgWriteReferenceStatus status =
26548 resolveDwgWriteObjectHandle(sourceHandle, &outputHandle);
26549 if (status == DwgWriteReferenceStatus::Ambiguous ||
26550 status == DwgWriteReferenceStatus::Missing ||
26551 status == DwgWriteReferenceStatus::Suppressed) {
26552 RS_DEBUGRS_Debug::instance()->print(
26553 RS_Debug::D_WARNING,
26554 status == DwgWriteReferenceStatus::Ambiguous
26555 ? "RS_FilterDXFRW: dropping ambiguous DWG %s target 0x%X"
26556 : "RS_FilterDXFRW: dropping dangling DWG %s target 0x%X",
26557 referenceKind, sourceHandle);
26558 drops.push_back(
26559 {sourceHandle, status, DwgWriteSourceKind::Object, referenceKind, 0,
26560 status == DwgWriteReferenceStatus::Ambiguous ? "ambiguous target"
26561 : "missing target"});
26562 return std::uint32_t{0};
26563 }
26564 return outputHandle;
26565 };
26566 const auto recordOptionalDrops =
26567 [this](DwgWriteSourceKind producerKind,
26568 std::uint32_t producerSourceHandle,
26569 std::uint32_t producerOutputHandle,
26570 const std::vector<PendingOptionalDrop> &drops,
26571 DRW::Version version, const char *family) {
26572 for (const PendingOptionalDrop &drop : drops) {
26573 if (!recordDwgWriteOptionalDrop(
26574 producerKind, producerSourceHandle, producerOutputHandle,
26575 drop.referenceName, drop.referenceOrdinal, drop.targetKind,
26576 drop.targetSourceHandle, drop.targetStatus, version, family,
26577 drop.reason)) {
26578 return false;
26579 }
26580 }
26581 return true;
26582 };
26583#endif
26584 std::set<std::uint32_t> rawGeoPositionMarkerHandles;
26585 std::set<std::uint32_t> rawSectionObjectHandles;
26586 if (m_dwgW == nullptr && m_graphic != nullptr) {
10
Assuming the condition is true
11
Assuming pointer value is null
12
Taking false branch
26587 for (const auto &raw : m_graphic->dwgAdvancedMetadata().rawDxfEntities()) {
26588 if (raw.name == "GEOPOSITIONMARKER" || raw.name == "POSITIONMARKER")
26589 rawGeoPositionMarkerHandles.insert(raw.handle);
26590 if (raw.name == "SECTIONOBJECT" || raw.name == "SECTION_OBJECT")
26591 rawSectionObjectHandles.insert(raw.handle);
26592 }
26593 }
26594 for (RS_Entity *e :
26595 lc::LC_ContainerTraverser{*m_graphic, RS2::ResolveNone}.entities()) {
13
Forming reference to null pointer
26596 if (e->getFlag(RS2::FlagDeleted))
26597 continue;
26598 if (consumed.find(e) != consumed.end())
26599 continue;
26600 writeEntity(e);
26601 }
26602
26603 // LIGHT entities have no RS_Entity representation. Re-emit their typed
26604 // metadata before LIGHTLIST OBJECTS so imported list references remain
26605 // resolvable in DWG output.
26606 if (m_dwgW != nullptr && m_dwgW->getVersion() >= DRW::AC1021 &&
26607 m_graphic != nullptr) {
26608 for (const auto &record : m_graphic->dwgAdvancedMetadata().lights()) {
26609 if (record.replayState !=
26610 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
26611 record.handle == 0) {
26612 continue;
26613 }
26614 DRW_Light light = lightFromMetadata(record);
26615 (void)writeDwgMetadataEntity(
26616 record.handle, record.parentHandle, "LIGHT", light,
26617 [&light, this] { return m_dwgW->writeLight(&light); });
26618 }
26619 }
26620
26621 // NAVISWORKSMODEL has no LibreCAD geometry counterpart. Keep it on the
26622 // metadata shelf and re-emit the typed entity with its source references.
26623 if (m_graphic != nullptr) {
26624 for (const auto &record :
26625 m_graphic->dwgAdvancedMetadata().navisworksModels()) {
26626 if (record.replayState !=
26627 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
26628 record.handle == 0)
26629 continue;
26630 DRW_NavisworksModel model = navisworksModelFromMetadata(record);
26631 if (m_dwgW != nullptr) {
26632 std::vector<PendingOptionalDrop> optionalDrops;
26633 model.definitionHandle = remapDwgObjectReference(
26634 record.definitionHandle, "NAVISWORKSMODEL definition",
26635 optionalDrops);
26636 (void)writeDwgMetadataEntityWithCompletion(
26637 record.handle, record.parentHandle, "NAVISWORKSMODEL", model,
26638 [&model, this] { return m_dwgW->writeNavisworksModel(&model); },
26639 [&optionalDrops, &record, &model, &recordOptionalDrops, this]() {
26640 return recordOptionalDrops(
26641 DwgWriteSourceKind::Entity, record.handle, model.handle,
26642 optionalDrops, m_dwgW->getVersion(), "DWG ENTITIES");
26643 });
26644 } else if (m_dxfW != nullptr && m_dxfW->getVersion() >= DRW::AC1015) {
26645 if (model.parentHandle == DRW::DwgModelSpaceBlockRecordHandle)
26646 model.parentHandle = DRW::NoHandle;
26647 model.definitionHandle = m_dxfW->remapHandle(model.definitionHandle);
26648 noteDxfWrite(m_dxfW->writeNavisworksModel(&model));
26649 }
26650 }
26651 }
26652
26653 // Slice A2 (entities): re-emit ENTITIES captured verbatim on read (A4) so a
26654 // LibreCAD DXF round-trip preserves unmodeled entities rather than dropping
26655 // them. DXF-only: the DWG writer has its own object/entity
26656 // path. Records live on the graphic, shared across read/write filter
26657 // instances.
26658 if (!m_dwgW && m_graphic != nullptr) {
26659 for (const DRW_RawDxfObject &rawEntity :
26660 m_graphic->dwgAdvancedMetadata().rawDxfEntities()) {
26661 DRW_RawDxfObject entity = rawEntity;
26662 noteDxfWrite(m_dxfW->writeRawDxfObject(&entity));
26663 }
26664 // 3DSOLID/REGION/BODY modeler shells read from DWG or typed DXF live only
26665 // on the metadata shelf. Re-emit their opaque ACIS/SAB payload without
26666 // attempting B-rep interpretation.
26667 for (const auto &rec : m_graphic->dwgAdvancedMetadata().modelerGeometry()) {
26668 if (rec.replayState != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed)
26669 continue;
26670 DRW_ModelerGeometry geom(rec.type);
26671 geom.handle = rec.handle;
26672 geom.parentHandle = rec.parentHandle;
26673 geom.m_modelerVersion = rec.modelerVersion;
26674 geom.m_bodyBitSize =
26675 static_cast<std::uint32_t>(rec.rawBodyByteCount * 8u);
26676 geom.m_objectSize = rec.objectSize;
26677 geom.m_isEmpty = rec.isEmpty;
26678 geom.m_hasModelerData = rec.hasModelerData;
26679 geom.m_modelerDataUnknownBit = rec.modelerDataUnknownBit;
26680 geom.m_hasWireframe = rec.hasWireframe;
26681 geom.hasDataStorageRecord = rec.hasDataStorageRecord;
26682 geom.dataStorageHandle = rec.dataStorageHandle;
26683 geom.dataStorageHandleKey = rec.dataStorageHandleKey;
26684 geom.dataStorageData = rec.dataStorageData;
26685 geom.dataStorageSegmentIndex = rec.dataStorageSegmentIndex;
26686 geom.dataStorageSchemaIndex = rec.dataStorageSchemaIndex;
26687 geom.m_historyHandle = rec.historyHandle;
26688 geom.m_rawBytes = rec.rawBytes;
26689 geom.extData = rec.extData;
26690 noteDxfWrite(m_dxfW->writeModelerGeometry(&geom));
26691 }
26692 // LIGHT entities read from a DWG live only on the metadata shelf (no
26693 // RS_Light model), so without this loop a DWG->DXF export silently drops
26694 // them. Re-emit them as typed AcDbLight (R2007+; writeLight no-ops on older
26695 // DXF targets).
26696 if (m_dxfW->getVersion() >= DRW::AC1021) {
26697 for (const auto &rec : m_graphic->dwgAdvancedMetadata().lights()) {
26698 DRW_Light light = lightFromMetadata(rec);
26699 noteDxfWrite(m_dxfW->writeLight(&light));
26700 }
26701 }
26702 // SHAPE entities read from a DWG live only on the metadata shelf (no
26703 // RS_Shape model) -> re-emit as typed AcDbShape so DWG->DXF preserves them.
26704 // Group 2 is the resolved SHAPEFILE/STYLE record name (the glyph index is
26705 // not round- trippable without the .shx; this matches libredwg/ACadSharp
26706 // output).
26707 for (const auto &rec : m_graphic->dwgAdvancedMetadata().shapes()) {
26708 DRW_Shape shape;
26709 shape.handle = rec.handle;
26710 shape.parentHandle = rec.parentHandle;
26711 shape.m_shapeFileHandle = rec.shapeFileHandle;
26712 shape.m_shapeIndex = rec.shapeIndex;
26713 shape.m_styleName = rec.styleName;
26714 shape.m_insertionPoint = rec.insertionPoint;
26715 shape.m_extrusion = rec.extrusion;
26716 shape.m_scale = rec.scale;
26717 shape.m_rotation = rec.rotation;
26718 shape.m_oblique = rec.oblique;
26719 shape.m_widthFactor = rec.widthFactor;
26720 shape.m_thickness = rec.thickness;
26721 noteDxfWrite(m_dxfW->writeShape(&shape));
26722 }
26723 // OLE2FRAME entities read from a DWG live only on the metadata shelf ->
26724 // re-emit as typed AcDbOle2Frame. pt1/pt2 (frame rectangle) were decoded
26725 // from the OLE payload header on read; the opaque payload is replayed
26726 // verbatim (group 310).
26727 for (const auto &rec : m_graphic->dwgAdvancedMetadata().ole2Frames()) {
26728 if (rec.payloadBytes.empty())
26729 continue; // nothing to preserve (truncated/absent payload)
26730 DRW_Ole2Frame ole;
26731 ole.handle = rec.handle;
26732 ole.parentHandle = rec.parentHandle;
26733 ole.m_flags = rec.flags;
26734 ole.m_mode = rec.mode;
26735 ole.m_lockAspect = rec.lockAspect;
26736 ole.m_oleVersion = rec.oleVersion;
26737 ole.m_pt1 = rec.pt1;
26738 ole.m_pt2 = rec.pt2;
26739 ole.m_payloadBytes = rec.payloadBytes;
26740 noteDxfWrite(m_dxfW->writeOle2Frame(&ole));
26741 }
26742 // Legacy OLEFRAME entities carry no frame rectangle; preserve their
26743 // bounded status and opaque payload as the original DXF entity.
26744 for (const auto &rec : m_graphic->dwgAdvancedMetadata().oleFrames()) {
26745 if (rec.payloadTruncated || rec.payloadTooLarge)
26746 continue;
26747 DRW_OleFrame ole;
26748 ole.handle = rec.handle;
26749 ole.parentHandle = rec.parentHandle;
26750 ole.m_flags = rec.flags;
26751 ole.m_mode = rec.mode;
26752 ole.m_payloadBytes = rec.payloadBytes;
26753 noteDxfWrite(m_dxfW->writeOleFrame(&ole));
26754 }
26755 }
26756
26757 // CAMERA has no LibreCAD geometry counterpart. Keep the typed view-table
26758 // reference on the metadata shelf and emit it through the native writer.
26759 if (m_graphic != nullptr) {
26760 for (const auto &record : m_graphic->dwgAdvancedMetadata().cameras()) {
26761 if (record.replayState !=
26762 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed)
26763 continue;
26764 DRW_Camera camera;
26765 if (m_dwgW != nullptr) {
26766 const DwgWriteReferenceStatus viewStatus =
26767 resolveDwgWriteTableRecordHandle(record.viewHandle,
26768 m_dwgWriteViewHandleRemap,
26769 &camera.m_viewHandle);
26770 if (viewStatus != DwgWriteReferenceStatus::Resolved &&
26771 viewStatus != DwgWriteReferenceStatus::Selected &&
26772 viewStatus != DwgWriteReferenceStatus::Committed) {
26773 RS_DEBUGRS_Debug::instance()->print(
26774 RS_Debug::D_WARNING,
26775 "RS_FilterDXFRW: dropping dangling DWG CAMERA view target "
26776 "0x%X",
26777 record.viewHandle);
26778 m_writeFailed = true;
26779 continue;
26780 }
26781 camera.extData = record.extData;
26782 (void)writeDwgMetadataEntityWithCompletion(
26783 record.handle, record.parentHandle, "CAMERA", camera,
26784 [&camera, this] { return m_dwgW->writeCamera(&camera); },
26785 [&camera, &record, this]() {
26786 if (record.viewHandle == DRW::NoHandle)
26787 return true;
26788 const DwgWriteReferenceReceipt receipt{
26789 DwgWriteSourceKind::Entity,
26790 record.handle,
26791 camera.handle,
26792 "CAMERA VIEW",
26793 0,
26794 DwgWriteSourceKind::TableRecord,
26795 record.viewHandle,
26796 camera.m_viewHandle,
26797 true,
26798 5};
26799 return recordDwgWriteReferenceReceipt(receipt);
26800 });
26801 } else if (m_dxfW != nullptr && m_dxfW->getVersion() >= DRW::AC1015) {
26802 camera.handle = record.handle;
26803 camera.parentHandle =
26804 record.parentHandle == DRW::DwgModelSpaceBlockRecordHandle
26805 ? DRW::NoHandle
26806 : record.parentHandle;
26807 camera.m_viewHandle = m_dxfW->remapHandle(record.viewHandle);
26808 camera.extData = record.extData;
26809 noteDxfWrite(m_dxfW->writeCamera(&camera));
26810 } else if (m_dxfW != nullptr) {
26811 m_writeFailed = true;
26812 }
26813 }
26814 }
26815
26816 // GEOPOSITIONMARKER has no LibreCAD geometry counterpart. DWG stores it as
26817 // fixed type 1164; DXF input already has a lossless raw carrier, so use that
26818 // carrier when present and type-emit only metadata originating from DWG.
26819 if (m_graphic != nullptr) {
26820 for (const auto &record :
26821 m_graphic->dwgAdvancedMetadata().geoPositionMarkers()) {
26822 if (record.replayState !=
26823 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
26824 record.handle == 0 ||
26825 (m_dxfW != nullptr &&
26826 rawGeoPositionMarkerHandles.count(record.handle) != 0))
26827 continue;
26828
26829 DRW_GeoPositionMarker marker;
26830 marker.m_classVersion = record.classVersion;
26831 marker.m_position = record.position;
26832 marker.m_radius = record.radius;
26833 marker.m_notes = record.notes;
26834 marker.m_landingGap = record.landingGap;
26835 marker.m_mtextVisible = record.mtextVisible;
26836 marker.m_textAlignment = record.textAlignment;
26837 marker.m_enableFrameText = record.enableFrameText;
26838 if (record.embeddedMText != nullptr)
26839 marker.mtext = std::make_unique<DRW_MText>(*record.embeddedMText);
26840 marker.extData = record.extData;
26841
26842 if (m_dwgW != nullptr) {
26843 if (m_dwgW->getVersion() < DRW::AC1027) {
26844 m_writeFailed = true;
26845 continue;
26846 }
26847 auto remap = [this](std::uint32_t handle) {
26848 const auto it = m_dwgWriteHandleRemap.find(handle);
26849 return it == m_dwgWriteHandleRemap.end() ? handle : it->second;
26850 };
26851 marker.handle = remap(record.handle);
26852 if (!resolveDwgEntityOwner(record.parentHandle, "GEOPOSITIONMARKER",
26853 &marker.parentHandle)) {
26854 continue;
26855 }
26856 (void)writeDwgMetadataEntity(
26857 record.handle, record.parentHandle, "GEOPOSITIONMARKER", marker,
26858 [&marker, this] {
26859 return m_dwgW->writeGeoPositionMarker(&marker);
26860 });
26861 } else if (m_dxfW != nullptr) {
26862 if (m_dxfW->getVersion() < DRW::AC1027) {
26863 m_writeFailed = true;
26864 continue;
26865 }
26866 marker.handle = record.handle;
26867 const bool paperSpace =
26868 record.parentHandle == DRW::DwgPaperSpaceBlockRecordHandle ||
26869 record.parentHandle == DRW::DxfPaperSpaceBlockRecordHandle;
26870 const bool modelSpace =
26871 record.parentHandle == DRW::DwgModelSpaceBlockRecordHandle ||
26872 record.parentHandle == DRW::DxfModelSpaceBlockRecordHandle ||
26873 record.parentHandle == DRW::NoHandle;
26874 if (paperSpace || modelSpace) {
26875 marker.parentHandle = DRW::NoHandle;
26876 marker.space = paperSpace ? DRW::PaperSpace : DRW::ModelSpace;
26877 } else {
26878 marker.parentHandle = record.parentHandle;
26879 }
26880 noteDxfWrite(m_dxfW->writeGeoPositionMarker(&marker));
26881 }
26882 }
26883 }
26884
26885 // SECTIONOBJECT has no LibreCAD geometry counterpart. DXF-originated
26886 // records remain raw-authoritative; DWG-originated metadata is typed-emitted.
26887 if (m_graphic != nullptr) {
26888 for (const auto &record :
26889 m_graphic->dwgAdvancedMetadata().sectionObjects()) {
26890 if (record.replayState !=
26891 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
26892 record.handle == 0 ||
26893 (m_dxfW != nullptr &&
26894 rawSectionObjectHandles.count(record.handle) != 0)) {
26895 continue;
26896 }
26897
26898 DRW_SectionObject section = sectionObjectFromMetadata(record);
26899 if (m_dwgW != nullptr) {
26900 if (m_dwgW->getVersion() < DRW::AC1021) {
26901 m_writeFailed = true;
26902 continue;
26903 }
26904 std::vector<PendingOptionalDrop> optionalDrops;
26905 section.m_sectionSettingsHandle =
26906 remapDwgObjectReference(record.sectionSettingsHandle,
26907 "SECTIONOBJECT settings", optionalDrops);
26908 (void)writeDwgMetadataEntityWithCompletion(
26909 record.handle, record.parentHandle, "SECTIONOBJECT", section,
26910 [&section, this] { return m_dwgW->writeSectionObject(&section); },
26911 [&optionalDrops, &record, &section, &recordOptionalDrops, this]() {
26912 return recordOptionalDrops(
26913 DwgWriteSourceKind::Entity, record.handle, section.handle,
26914 optionalDrops, m_dwgW->getVersion(), "DWG ENTITIES");
26915 });
26916 } else if (m_dxfW != nullptr) {
26917 if (m_dxfW->getVersion() < DRW::AC1021) {
26918 m_writeFailed = true;
26919 continue;
26920 }
26921 section.handle = record.handle;
26922 section.m_sectionSettingsHandle =
26923 m_dxfW->remapHandle(record.sectionSettingsHandle);
26924 const bool paperSpace =
26925 record.parentHandle == DRW::DwgPaperSpaceBlockRecordHandle ||
26926 record.parentHandle == DRW::DxfPaperSpaceBlockRecordHandle;
26927 const bool modelSpace =
26928 record.parentHandle == DRW::DwgModelSpaceBlockRecordHandle ||
26929 record.parentHandle == DRW::DxfModelSpaceBlockRecordHandle ||
26930 record.parentHandle == DRW::NoHandle;
26931 if (paperSpace || modelSpace) {
26932 section.parentHandle = DRW::NoHandle;
26933 section.space = paperSpace ? DRW::PaperSpace : DRW::ModelSpace;
26934 } else {
26935 section.parentHandle = m_dxfW->remapHandle(record.parentHandle);
26936 }
26937 noteDxfWrite(m_dxfW->writeSectionObject(&section));
26938 }
26939 }
26940 }
26941}
26942
26943namespace {
26944// Parsed view of one polyline's LibreCAD_MLINE XDATA.
26945struct MLineEntry {
26946 RS_Entity *entity = nullptr;
26947 QString mlineId;
26948 QString styleName;
26949 std::uint32_t styleHandle = 0;
26950 double scale = 1.0;
26951 int justification = 0;
26952 int elementCount = 0;
26953 int elementIndex = 0;
26954 double offset = 0.0;
26955 int openClosed = 1;
26956 int markerCount = 0;
26957 bool isAnchor = false; // i==0 polyline carries baseline + miter
26958 std::vector<DRW_Coord> baselineVerts;
26959 std::vector<DRW_Coord> miterDirs;
26960};
26961
26962struct LWPolylineMeta {
26963 double width = 0.0;
26964 double elevation = 0.0;
26965 double thickness = 0.0;
26966 DRW_Coord extrusion{0.0, 0.0, 1.0};
26967 int vertexCount = 0;
26968 std::vector<double> startWidths;
26969 std::vector<double> endWidths;
26970 std::vector<int> identifiers;
26971};
26972
26973std::optional<LWPolylineMeta> extractLWPolylineMeta(RS_Entity *e) {
26974 if (!e || !e->hasDrwExtData())
26975 return std::nullopt;
26976 const auto &ext = e->getDrwExtData();
26977 bool inGroup = false;
26978 LWPolylineMeta meta;
26979 int seen1040 = 0; // 0=width, 1=elevation, 2=thickness, then vertex widths
26980 bool gotMarker = false;
26981
26982 for (const auto &sp : ext) {
26983 if (!sp) {
26984 if (inGroup)
26985 return std::nullopt;
26986 continue;
26987 }
26988 const int code = sp->code();
26989 if (code == 1001) {
26990 std::string marker;
26991 if (readXDataMarker(sp.get(), marker) != XDataReadResult::Valid) {
26992 if (inGroup)
26993 return std::nullopt;
26994 inGroup = false;
26995 continue;
26996 }
26997 inGroup = marker == "LibreCAD_LWPOLYLINE";
26998 if (inGroup) {
26999 if (gotMarker)
27000 return std::nullopt;
27001 gotMarker = true;
27002 }
27003 continue;
27004 }
27005 if (!inGroup)
27006 continue;
27007
27008 switch (code) {
27009 case 1040: {
27010 if (sp->type() != DRW_Variant::DOUBLE || !std::isfinite(sp->d_val()))
27011 return std::nullopt;
27012 const double d = sp->d_val();
27013 if (seen1040 == 0)
27014 meta.width = d;
27015 else if (seen1040 == 1)
27016 meta.elevation = d;
27017 else if (seen1040 == 2)
27018 meta.thickness = d;
27019 else if ((seen1040 - 3) % 2 == 0)
27020 meta.startWidths.push_back(d);
27021 else
27022 meta.endWidths.push_back(d);
27023 ++seen1040;
27024 break;
27025 }
27026 case 1010: {
27027 DRW_Coord coordinate;
27028 if (readXDataCoord(sp.get(), 1010, coordinate) != XDataReadResult::Valid)
27029 return std::nullopt;
27030 meta.extrusion = coordinate;
27031 break;
27032 }
27033 case 1070: {
27034 std::int64_t value;
27035 if (readXDataInteger(sp.get(), 1070, value, false) !=
27036 XDataReadResult::Valid ||
27037 !fitsXDataInt32(value))
27038 return std::nullopt;
27039 if (meta.vertexCount == 0)
27040 meta.vertexCount = static_cast<int>(value);
27041 break;
27042 }
27043 case 1071: {
27044 std::int64_t value;
27045 if (readXDataInteger(sp.get(), 1071, value) != XDataReadResult::Valid ||
27046 !fitsXDataInt32(value))
27047 return std::nullopt;
27048 meta.identifiers.push_back(static_cast<int>(value));
27049 break;
27050 }
27051 default:
27052 break;
27053 }
27054 }
27055
27056 if (!gotMarker)
27057 return std::nullopt;
27058 return meta;
27059}
27060
27061struct MeshSidecarEntry {
27062 RS_Entity *entity = nullptr;
27063 QString meshId;
27064 QString role;
27065 int elementIndex = -1;
27066 int elementCount = 0;
27067 int roleIndex = -1;
27068 int flags = 0;
27069 int mCount = 0;
27070 int nCount = 0;
27071 int smoothM = 0;
27072 int smoothN = 0;
27073 int curveType = 0;
27074 int stringFieldCount = 0;
27075 int integerFieldCount = 0;
27076 int markerCount = 0;
27077 bool malformedSchema = false;
27078 std::vector<DRW_Coord> sourceVertices;
27079};
27080
27081struct PolyfaceSidecarEntry {
27082 RS_Entity *entity = nullptr;
27083 QString polyfaceId;
27084 int faceIndex = -1;
27085 int faceCount = 0;
27086 int flags = 0;
27087 int vertexCount = 0;
27088 int originalFaceCount = 0;
27089 std::array<int, 4> indices{{0, 0, 0, 0}};
27090 int stringFieldCount = 0;
27091 int integerFieldCount = 0;
27092 int markerCount = 0;
27093 bool malformedSchema = false;
27094 std::vector<DRW_Coord> sourceVertices;
27095};
27096
27097std::vector<RS_Vector> collectPolylineVertices(RS_Polyline *polyline) {
27098 std::vector<RS_Vector> vertices;
27099 if (polyline == nullptr)
27100 return vertices;
27101
27102 const RS_AtomicEntity *lastAtomic = nullptr;
27103 for (RS_Entity *sub :
27104 lc::LC_ContainerTraverser{*polyline, RS2::ResolveNone}.entities()) {
27105 if (sub == nullptr || !sub->isAtomic())
27106 continue;
27107 const auto *atomic = static_cast<const RS_AtomicEntity *>(sub);
27108 vertices.push_back(atomic->getStartpoint());
27109 lastAtomic = atomic;
27110 }
27111 if (lastAtomic != nullptr && !polyline->isClosed())
27112 vertices.push_back(lastAtomic->getEndpoint());
27113
27114 return vertices;
27115}
27116
27117bool pointsMatch2D(const RS_Vector &point, const DRW_Coord &coord) {
27118 return std::abs(point.x - coord.x) <= RS_TOLERANCE1.0e-10 &&
27119 std::abs(point.y - coord.y) <= RS_TOLERANCE1.0e-10;
27120}
27121
27122bool hasFiniteDwgCoordinate(const DRW_Coord &coordinate) {
27123 return std::isfinite(coordinate.x) && std::isfinite(coordinate.y) &&
27124 std::isfinite(coordinate.z);
27125}
27126
27127bool hasFiniteVisibleCoordinate(const RS_Vector &coordinate) {
27128 return std::isfinite(coordinate.x) && std::isfinite(coordinate.y);
27129}
27130
27131bool matchesSidecarPresentation(const RS_Entity *entry,
27132 const RS_Entity *anchor) {
27133 if (entry == nullptr || anchor == nullptr ||
27134 entry->getLayer(false) != anchor->getLayer(false) ||
27135 entry->getFlag(RS2::FlagVisible) != anchor->getFlag(RS2::FlagVisible)) {
27136 return false;
27137 }
27138
27139 const RS_Pen entryPen = entry->getPen(false);
27140 const RS_Pen anchorPen = anchor->getPen(false);
27141 return entryPen == anchorPen && entryPen.getFlags() == anchorPen.getFlags() &&
27142 std::abs(entryPen.getAlpha() - anchorPen.getAlpha()) <= RS_TOLERANCE1.0e-10 &&
27143 std::abs(entryPen.dashOffset() - anchorPen.dashOffset()) <=
27144 RS_TOLERANCE1.0e-10;
27145}
27146
27147std::optional<std::vector<RS_Vector>>
27148collectLinearPolylineVertices(const RS_Entity *entity, bool expectedClosed) {
27149 if (entity == nullptr || entity->rtti() != RS2::EntityPolyline)
27150 return std::nullopt;
27151
27152 const auto *polyline = static_cast<const RS_Polyline *>(entity);
27153 if (polyline->isClosed() != expectedClosed)
27154 return std::nullopt;
27155
27156 std::vector<RS_Vector> vertices;
27157 const RS_Line *lastLine = nullptr;
27158 for (RS_Entity *sub :
27159 lc::LC_ContainerTraverser{*polyline, RS2::ResolveNone}.entities()) {
27160 if (sub == nullptr || sub->rtti() != RS2::EntityLine)
27161 return std::nullopt;
27162 const auto *line = static_cast<const RS_Line *>(sub);
27163 const RS_Vector start = line->getStartpoint();
27164 const RS_Vector end = line->getEndpoint();
27165 if (!hasFiniteVisibleCoordinate(start) || !hasFiniteVisibleCoordinate(end))
27166 return std::nullopt;
27167 vertices.push_back(start);
27168 lastLine = line;
27169 }
27170 if (lastLine == nullptr)
27171 return std::nullopt;
27172
27173 const RS_Vector lastEndpoint = lastLine->getEndpoint();
27174 if (expectedClosed) {
27175 if (vertices.empty() ||
27176 !pointsMatch2D(lastEndpoint, DRW_Coord{vertices.front().x,
27177 vertices.front().y, 0.0})) {
27178 return std::nullopt;
27179 }
27180 } else {
27181 vertices.push_back(lastEndpoint);
27182 }
27183 return vertices;
27184}
27185
27186std::optional<MeshSidecarEntry> extractMeshSidecar(RS_Entity *e) {
27187 if (!e || !e->hasDrwExtData())
27188 return std::nullopt;
27189
27190 MeshSidecarEntry meta;
27191 meta.entity = e;
27192 bool inGroup = false;
27193 int seen1000 = 0;
27194 int seen1070 = 0;
27195
27196 for (const auto &sp : e->getDrwExtData()) {
27197 if (!sp) {
27198 if (inGroup)
27199 return std::nullopt;
27200 continue;
27201 }
27202 const int code = sp->code();
27203 if (code == 1001) {
27204 const bool wasInGroup = inGroup;
27205 inGroup = false;
27206 std::string marker;
27207 if (readXDataMarker(sp.get(), marker) != XDataReadResult::Valid) {
27208 if (wasInGroup)
27209 return std::nullopt;
27210 continue;
27211 }
27212 inGroup = marker == "LibreCAD_POLYLINE_MESH";
27213 if (inGroup) {
27214 if (meta.markerCount != 0)
27215 return std::nullopt;
27216 ++meta.markerCount;
27217 }
27218 continue;
27219 }
27220 if (!inGroup)
27221 continue;
27222
27223 switch (code) {
27224 case 1000: {
27225 std::string text;
27226 if (readXDataString(sp.get(), 1000, text) != XDataReadResult::Valid)
27227 return std::nullopt;
27228 const QString value = QString::fromStdString(text);
27229 if (seen1000 == 0)
27230 meta.meshId = value;
27231 else if (seen1000 == 1)
27232 meta.role = value;
27233 ++seen1000;
27234 ++meta.stringFieldCount;
27235 break;
27236 }
27237 case 1070: {
27238 std::int64_t integerValue;
27239 if (readXDataInteger(sp.get(), 1070, integerValue, false) !=
27240 XDataReadResult::Valid ||
27241 !fitsXDataInt32(integerValue))
27242 return std::nullopt;
27243 const int value = static_cast<int>(integerValue);
27244 if (seen1070 == 0)
27245 meta.elementIndex = value;
27246 else if (seen1070 == 1)
27247 meta.elementCount = value;
27248 else if (seen1070 == 2)
27249 meta.roleIndex = value;
27250 else if (seen1070 == 3)
27251 meta.flags = value;
27252 else if (seen1070 == 4)
27253 meta.mCount = value;
27254 else if (seen1070 == 5)
27255 meta.nCount = value;
27256 else if (seen1070 == 6)
27257 meta.smoothM = value;
27258 else if (seen1070 == 7)
27259 meta.smoothN = value;
27260 else if (seen1070 == 8)
27261 meta.curveType = value;
27262 ++seen1070;
27263 ++meta.integerFieldCount;
27264 break;
27265 }
27266 case 1010: {
27267 DRW_Coord coordinate;
27268 if (readXDataCoord(sp.get(), 1010, coordinate) != XDataReadResult::Valid)
27269 return std::nullopt;
27270 meta.sourceVertices.push_back(coordinate);
27271 break;
27272 }
27273 default:
27274 return std::nullopt;
27275 }
27276 }
27277
27278 if (meta.markerCount == 0 || meta.meshId.isEmpty())
27279 return std::nullopt;
27280 return meta;
27281}
27282
27283std::optional<PolyfaceSidecarEntry> extractPolyfaceSidecar(RS_Entity *e) {
27284 if (!e || !e->hasDrwExtData())
27285 return std::nullopt;
27286
27287 PolyfaceSidecarEntry meta;
27288 meta.entity = e;
27289 bool inGroup = false;
27290 int seen1070 = 0;
27291
27292 for (const auto &sp : e->getDrwExtData()) {
27293 if (!sp) {
27294 if (inGroup)
27295 return std::nullopt;
27296 continue;
27297 }
27298 const int code = sp->code();
27299 if (code == 1001) {
27300 const bool wasInGroup = inGroup;
27301 inGroup = false;
27302 std::string marker;
27303 if (readXDataMarker(sp.get(), marker) != XDataReadResult::Valid) {
27304 if (wasInGroup)
27305 return std::nullopt;
27306 continue;
27307 }
27308 inGroup = marker == "LibreCAD_POLYLINE_PFACE";
27309 if (inGroup) {
27310 if (meta.markerCount != 0)
27311 return std::nullopt;
27312 ++meta.markerCount;
27313 }
27314 continue;
27315 }
27316 if (!inGroup)
27317 continue;
27318
27319 switch (code) {
27320 case 1000: {
27321 std::string text;
27322 if (readXDataString(sp.get(), 1000, text) != XDataReadResult::Valid)
27323 return std::nullopt;
27324 if (meta.polyfaceId.isEmpty())
27325 meta.polyfaceId = QString::fromStdString(text);
27326 ++meta.stringFieldCount;
27327 break;
27328 }
27329 case 1070: {
27330 std::int64_t integerValue;
27331 if (readXDataInteger(sp.get(), 1070, integerValue, false) !=
27332 XDataReadResult::Valid ||
27333 !fitsXDataInt32(integerValue))
27334 return std::nullopt;
27335 const int value = static_cast<int>(integerValue);
27336 if (seen1070 == 0)
27337 meta.faceIndex = value;
27338 else if (seen1070 == 1)
27339 meta.faceCount = value;
27340 else if (seen1070 == 2)
27341 meta.flags = value;
27342 else if (seen1070 == 3)
27343 meta.vertexCount = value;
27344 else if (seen1070 == 4)
27345 meta.originalFaceCount = value;
27346 else if (seen1070 >= 5 && seen1070 < 9)
27347 meta.indices[seen1070 - 5] = value;
27348 ++seen1070;
27349 ++meta.integerFieldCount;
27350 break;
27351 }
27352 case 1010: {
27353 DRW_Coord coordinate;
27354 if (readXDataCoord(sp.get(), 1010, coordinate) != XDataReadResult::Valid)
27355 return std::nullopt;
27356 meta.sourceVertices.push_back(coordinate);
27357 break;
27358 }
27359 default:
27360 return std::nullopt;
27361 }
27362 }
27363
27364 if (meta.markerCount == 0 || meta.polyfaceId.isEmpty())
27365 return std::nullopt;
27366 return meta;
27367}
27368
27369struct MeshSidecarDescriptor {
27370 const MeshSidecarEntry *anchor = nullptr;
27371 std::vector<const MeshSidecarEntry *> members;
27372 std::size_t sourceVertexCount = 0;
27373};
27374
27375struct PolyfaceSidecarDescriptor {
27376 const PolyfaceSidecarEntry *anchor = nullptr;
27377 std::vector<const PolyfaceSidecarEntry *> members;
27378 std::size_t sourceVertexCount = 0;
27379 std::size_t compoundVertexCount = 0;
27380};
27381
27382bool checkedMeshSidecarCounts(int mCount, int nCount, std::size_t &elementCount,
27383 std::size_t &vertexCount) {
27384 if (mCount <= 0 || nCount <= 0)
27385 return false;
27386
27387 std::uint64_t elements = 0;
27388 std::uint64_t vertices = 0;
27389 if (!dwgSafety::add(static_cast<std::uint64_t>(mCount),
27390 static_cast<std::uint64_t>(nCount), elements) ||
27391 !dwgSafety::multiply(static_cast<std::uint64_t>(mCount),
27392 static_cast<std::uint64_t>(nCount), vertices) ||
27393 elements > static_cast<std::uint64_t>(std::numeric_limits<int>::max()) ||
27394 vertices > dwgSafety::MaxOwnedObjectCount ||
27395 elements > std::numeric_limits<std::size_t>::max() ||
27396 vertices > std::numeric_limits<std::size_t>::max()) {
27397 return false;
27398 }
27399 elementCount = static_cast<std::size_t>(elements);
27400 vertexCount = static_cast<std::size_t>(vertices);
27401 return true;
27402}
27403
27404bool matchesSidecarSourceCoordinates(
27405 const std::vector<DRW_Coord> &coordinates) {
27406 return std::all_of(coordinates.cbegin(), coordinates.cend(),
27407 [](const DRW_Coord &coordinate) {
27408 return hasFiniteDwgCoordinate(coordinate);
27409 });
27410}
27411
27412std::optional<MeshSidecarDescriptor>
27413makeMeshSidecarDescriptor(const std::vector<MeshSidecarEntry> &entries) {
27414 if (entries.empty())
27415 return std::nullopt;
27416
27417 const MeshSidecarEntry *anchor = nullptr;
27418 for (const MeshSidecarEntry &entry : entries) {
27419 if (!entry.sourceVertices.empty()) {
27420 if (anchor != nullptr)
27421 return std::nullopt;
27422 anchor = &entry;
27423 }
27424 }
27425 if (anchor == nullptr || anchor->entity == nullptr ||
27426 anchor->markerCount != 1 || anchor->malformedSchema ||
27427 anchor->stringFieldCount != 2 || anchor->integerFieldCount != 9 ||
27428 anchor->role != QStringLiteral("row")(QString(QtPrivate::qMakeStringPrivate(u"" "row"))) || anchor->elementIndex != 0 ||
27429 anchor->roleIndex != 0 || (anchor->flags & 0x10) == 0 ||
27430 anchor->curveType != 0) {
27431 return std::nullopt;
27432 }
27433
27434 std::size_t expectedElementCount = 0;
27435 std::size_t expectedVertexCount = 0;
27436 if (!checkedMeshSidecarCounts(anchor->mCount, anchor->nCount,
27437 expectedElementCount, expectedVertexCount) ||
27438 anchor->elementCount != static_cast<int>(expectedElementCount) ||
27439 entries.size() != expectedElementCount ||
27440 anchor->sourceVertices.size() != expectedVertexCount ||
27441 !matchesSidecarSourceCoordinates(anchor->sourceVertices)) {
27442 return std::nullopt;
27443 }
27444
27445 MeshSidecarDescriptor descriptor;
27446 descriptor.anchor = anchor;
27447 descriptor.sourceVertexCount = expectedVertexCount;
27448 descriptor.members.resize(expectedElementCount, nullptr);
27449 std::vector<bool> seenRows(static_cast<std::size_t>(anchor->mCount), false);
27450 std::vector<bool> seenColumns(static_cast<std::size_t>(anchor->nCount),
27451 false);
27452 const bool rowsClosed = (anchor->flags & 0x20) != 0;
27453 const bool columnsClosed = (anchor->flags & 0x1) != 0;
27454
27455 for (const MeshSidecarEntry &entry : entries) {
27456 if (entry.entity == nullptr || entry.markerCount != 1 ||
27457 entry.malformedSchema || entry.stringFieldCount != 2 ||
27458 entry.integerFieldCount != 9 || entry.meshId != anchor->meshId ||
27459 entry.flags != anchor->flags || entry.mCount != anchor->mCount ||
27460 entry.nCount != anchor->nCount || entry.smoothM != anchor->smoothM ||
27461 entry.smoothN != anchor->smoothN ||
27462 entry.curveType != anchor->curveType ||
27463 entry.elementCount != anchor->elementCount || entry.elementIndex < 0 ||
27464 static_cast<std::size_t>(entry.elementIndex) >= expectedElementCount ||
27465 (!entry.sourceVertices.empty() && &entry != anchor) ||
27466 !matchesSidecarPresentation(entry.entity, anchor->entity)) {
27467 return std::nullopt;
27468 }
27469
27470 const bool isRow = entry.role == QStringLiteral("row")(QString(QtPrivate::qMakeStringPrivate(u"" "row")));
27471 const bool isColumn = entry.role == QStringLiteral("column")(QString(QtPrivate::qMakeStringPrivate(u"" "column")));
27472 if (isRow) {
27473 if (entry.roleIndex < 0 || entry.roleIndex >= anchor->mCount ||
27474 entry.elementIndex != entry.roleIndex ||
27475 seenRows[static_cast<std::size_t>(entry.roleIndex)]) {
27476 return std::nullopt;
27477 }
27478 const auto visible =
27479 collectLinearPolylineVertices(entry.entity, rowsClosed);
27480 if (!visible ||
27481 visible->size() != static_cast<std::size_t>(anchor->nCount))
27482 return std::nullopt;
27483 for (int column = 0; column < anchor->nCount; ++column) {
27484 const std::size_t sourceIndex =
27485 static_cast<std::size_t>(entry.roleIndex) * anchor->nCount + column;
27486 if (!pointsMatch2D(visible->at(static_cast<std::size_t>(column)),
27487 anchor->sourceVertices[sourceIndex])) {
27488 return std::nullopt;
27489 }
27490 }
27491 seenRows[static_cast<std::size_t>(entry.roleIndex)] = true;
27492 } else if (isColumn) {
27493 if (entry.roleIndex < 0 || entry.roleIndex >= anchor->nCount ||
27494 entry.elementIndex != anchor->mCount + entry.roleIndex ||
27495 seenColumns[static_cast<std::size_t>(entry.roleIndex)]) {
27496 return std::nullopt;
27497 }
27498 const auto visible =
27499 collectLinearPolylineVertices(entry.entity, columnsClosed);
27500 if (!visible ||
27501 visible->size() != static_cast<std::size_t>(anchor->mCount))
27502 return std::nullopt;
27503 for (int row = 0; row < anchor->mCount; ++row) {
27504 const std::size_t sourceIndex =
27505 static_cast<std::size_t>(row) * anchor->nCount + entry.roleIndex;
27506 if (!pointsMatch2D(visible->at(static_cast<std::size_t>(row)),
27507 anchor->sourceVertices[sourceIndex])) {
27508 return std::nullopt;
27509 }
27510 }
27511 seenColumns[static_cast<std::size_t>(entry.roleIndex)] = true;
27512 } else {
27513 return std::nullopt;
27514 }
27515
27516 const std::size_t elementIndex =
27517 static_cast<std::size_t>(entry.elementIndex);
27518 if (descriptor.members[elementIndex] != nullptr)
27519 return std::nullopt;
27520 descriptor.members[elementIndex] = &entry;
27521 }
27522
27523 if (std::any_of(
27524 descriptor.members.cbegin(), descriptor.members.cend(),
27525 [](const MeshSidecarEntry *entry) { return entry == nullptr; }) ||
27526 std::any_of(seenRows.cbegin(), seenRows.cend(),
27527 [](bool seen) { return !seen; }) ||
27528 std::any_of(seenColumns.cbegin(), seenColumns.cend(),
27529 [](bool seen) { return !seen; })) {
27530 return std::nullopt;
27531 }
27532 return descriptor;
27533}
27534
27535std::optional<PolyfaceSidecarDescriptor> makePolyfaceSidecarDescriptor(
27536 const std::vector<PolyfaceSidecarEntry> &entries) {
27537 if (entries.empty())
27538 return std::nullopt;
27539
27540 const PolyfaceSidecarEntry *anchor = nullptr;
27541 for (const PolyfaceSidecarEntry &entry : entries) {
27542 if (!entry.sourceVertices.empty()) {
27543 if (anchor != nullptr)
27544 return std::nullopt;
27545 anchor = &entry;
27546 }
27547 }
27548 if (anchor == nullptr || anchor->entity == nullptr ||
27549 anchor->markerCount != 1 || anchor->malformedSchema ||
27550 anchor->stringFieldCount != 1 || anchor->integerFieldCount != 9 ||
27551 anchor->faceIndex != 0 || anchor->vertexCount <= 0 ||
27552 anchor->faceCount <= 0 || (anchor->flags & 0x40) == 0 ||
27553 anchor->faceCount != anchor->originalFaceCount) {
27554 return std::nullopt;
27555 }
27556
27557 const std::size_t sourceVertexCount =
27558 static_cast<std::size_t>(anchor->vertexCount);
27559 const std::size_t faceCount = static_cast<std::size_t>(anchor->faceCount);
27560 if (sourceVertexCount > dwgSafety::MaxOwnedObjectCount ||
27561 faceCount > dwgSafety::MaxOwnedObjectCount - sourceVertexCount ||
27562 entries.size() != faceCount ||
27563 anchor->sourceVertices.size() != sourceVertexCount ||
27564 !matchesSidecarSourceCoordinates(anchor->sourceVertices)) {
27565 return std::nullopt;
27566 }
27567
27568 PolyfaceSidecarDescriptor descriptor;
27569 descriptor.anchor = anchor;
27570 descriptor.sourceVertexCount = sourceVertexCount;
27571 descriptor.compoundVertexCount = sourceVertexCount + faceCount;
27572 descriptor.members.resize(faceCount, nullptr);
27573
27574 for (const PolyfaceSidecarEntry &entry : entries) {
27575 if (entry.entity == nullptr || entry.markerCount != 1 ||
27576 entry.malformedSchema || entry.stringFieldCount != 1 ||
27577 entry.integerFieldCount != 9 ||
27578 entry.polyfaceId != anchor->polyfaceId ||
27579 entry.flags != anchor->flags ||
27580 entry.vertexCount != anchor->vertexCount ||
27581 entry.faceCount != anchor->faceCount ||
27582 entry.originalFaceCount != anchor->originalFaceCount ||
27583 entry.faceIndex < 0 ||
27584 static_cast<std::size_t>(entry.faceIndex) >= faceCount ||
27585 (!entry.sourceVertices.empty() && &entry != anchor) ||
27586 !matchesSidecarPresentation(entry.entity, anchor->entity)) {
27587 return std::nullopt;
27588 }
27589
27590 const int expectedPoints = entry.indices[3] == 0 ? 3 : 4;
27591 const auto visible = collectLinearPolylineVertices(entry.entity, true);
27592 if (!visible || visible->size() != static_cast<std::size_t>(expectedPoints))
27593 return std::nullopt;
27594 for (int index = 0; index < expectedPoints; ++index) {
27595 const std::int64_t signedIndex = entry.indices[index];
27596 const std::int64_t vertexIndex =
27597 signedIndex < 0 ? -signedIndex : signedIndex;
27598 if (signedIndex == 0 || vertexIndex > anchor->vertexCount ||
27599 !pointsMatch2D(visible->at(static_cast<std::size_t>(index)),
27600 anchor->sourceVertices[static_cast<std::size_t>(
27601 vertexIndex - 1)])) {
27602 return std::nullopt;
27603 }
27604 }
27605
27606 const std::size_t faceIndex = static_cast<std::size_t>(entry.faceIndex);
27607 if (descriptor.members[faceIndex] != nullptr)
27608 return std::nullopt;
27609 descriptor.members[faceIndex] = &entry;
27610 }
27611
27612 if (std::any_of(
27613 descriptor.members.cbegin(), descriptor.members.cend(),
27614 [](const PolyfaceSidecarEntry *entry) { return entry == nullptr; })) {
27615 return std::nullopt;
27616 }
27617 return descriptor;
27618}
27619
27620// Walk the entity's drwExtData (XDATA stream) and extract LibreCAD_MLINE
27621// metadata. Returns nullopt if the marker isn't present. The XDATA layout
27622// matches RS_FilterDXFRW::addMLine's emission schema.
27623std::optional<MLineEntry> extractMLineMeta(RS_Entity *e) {
27624 if (!e || !e->hasDrwExtData())
27625 return std::nullopt;
27626 const auto &ext = e->getDrwExtData();
27627 bool inGroup = false;
27628 MLineEntry m;
27629 int seen1000 = 0; // 0 = mlineId, 1 = styleName
27630 int seen1040 = 0; // 0 = scale, 1 = offset
27631 int seen1070 = 0; // 0 = just, 1 = N, 2 = i, 3 = flags
27632 for (const auto &sp : ext) {
27633 if (!sp) {
27634 if (inGroup)
27635 return std::nullopt;
27636 continue;
27637 }
27638 const int code = sp->code();
27639 if (code == 1001) {
27640 const bool wasInGroup = inGroup;
27641 std::string marker;
27642 if (readXDataMarker(sp.get(), marker) != XDataReadResult::Valid) {
27643 if (wasInGroup)
27644 return std::nullopt;
27645 inGroup = false;
27646 continue;
27647 }
27648 inGroup = marker == "LibreCAD_MLINE";
27649 if (inGroup && ++m.markerCount > 1)
27650 return std::nullopt;
27651 continue;
27652 }
27653 if (!inGroup)
27654 continue;
27655 switch (code) {
27656 case 1000: {
27657 std::string text;
27658 if (readXDataString(sp.get(), 1000, text) != XDataReadResult::Valid)
27659 return std::nullopt;
27660 QString s = QString::fromStdString(text);
27661 if (seen1000 == 0)
27662 m.mlineId = s;
27663 else if (seen1000 == 1)
27664 m.styleName = s;
27665 ++seen1000;
27666 break;
27667 }
27668 case 1040: {
27669 if (sp->type() != DRW_Variant::DOUBLE || !std::isfinite(sp->d_val()))
27670 return std::nullopt;
27671 const double d = sp->d_val();
27672 if (seen1040 == 0)
27673 m.scale = d;
27674 else if (seen1040 == 1)
27675 m.offset = d;
27676 ++seen1040;
27677 break;
27678 }
27679 case 1070: {
27680 if (sp->type() != DRW_Variant::INTEGER)
27681 return std::nullopt;
27682 const int v = static_cast<int>(sp->i_val());
27683 if (seen1070 == 0)
27684 m.justification = v;
27685 else if (seen1070 == 1)
27686 m.elementCount = v;
27687 else if (seen1070 == 2)
27688 m.elementIndex = v;
27689 else if (seen1070 == 3)
27690 m.openClosed = v;
27691 ++seen1070;
27692 break;
27693 }
27694 case 1071: {
27695 std::int64_t value;
27696 if (readXDataInteger(sp.get(), 1071, value) != XDataReadResult::Valid ||
27697 value < 0 ||
27698 static_cast<std::uint64_t>(value) >
27699 std::numeric_limits<std::uint32_t>::max())
27700 return std::nullopt;
27701 m.styleHandle = static_cast<std::uint32_t>(value);
27702 break;
27703 }
27704 case 1011: {
27705 // Anchor-only baseline vertex
27706 DRW_Coord coordinate;
27707 if (readXDataCoord(sp.get(), 1011, coordinate) != XDataReadResult::Valid)
27708 return std::nullopt;
27709 m.baselineVerts.push_back(coordinate);
27710 m.isAnchor = true;
27711 break;
27712 }
27713 case 1013: {
27714 DRW_Coord coordinate;
27715 if (readXDataCoord(sp.get(), 1013, coordinate) != XDataReadResult::Valid)
27716 return std::nullopt;
27717 m.miterDirs.push_back(coordinate);
27718 break;
27719 }
27720 default:
27721 break;
27722 }
27723 }
27724 if (m.mlineId.isEmpty() || m.elementCount == 0)
27725 return std::nullopt;
27726 m.entity = e;
27727 return m;
27728}
27729
27730struct MLineSidecarDescriptor {
27731 const MLineEntry *anchor = nullptr;
27732 std::vector<const MLineEntry *> members;
27733};
27734
27735bool matchesMLineSidecarGeometry(const MLineEntry &entry,
27736 const MLineEntry &anchor) {
27737 if (entry.entity == nullptr || anchor.baselineVerts.empty() ||
27738 anchor.baselineVerts.size() != anchor.miterDirs.size()) {
27739 return false;
27740 }
27741
27742 auto *polyline = static_cast<RS_Polyline *>(entry.entity);
27743 const bool closed = (anchor.openClosed & 0x1) != 0;
27744 if (polyline->isClosed() != closed)
27745 return false;
27746 const std::vector<RS_Vector> visible = collectPolylineVertices(polyline);
27747 if (visible.size() != anchor.baselineVerts.size())
27748 return false;
27749
27750 for (std::size_t i = 0; i < visible.size(); ++i) {
27751 const DRW_Coord &base = anchor.baselineVerts[i];
27752 const DRW_Coord &miter = anchor.miterDirs[i];
27753 if (!hasFiniteDwgCoordinate(base) || !hasFiniteDwgCoordinate(miter))
27754 return false;
27755 const double length = std::hypot(miter.x, miter.y);
27756 if (!std::isfinite(length))
27757 return false;
27758 RS_Vector expected{base.x, base.y};
27759 if (length > RS_TOLERANCE1.0e-10) {
27760 expected.x += miter.x / length * entry.offset;
27761 expected.y += miter.y / length * entry.offset;
27762 }
27763 if (!pointsMatch2D(visible[i], DRW_Coord{expected.x, expected.y, 0.0}))
27764 return false;
27765 }
27766 return true;
27767}
27768
27769std::optional<MLineSidecarDescriptor>
27770makeMLineSidecarDescriptor(std::vector<MLineEntry> &entries) {
27771 if (entries.empty())
27772 return std::nullopt;
27773
27774 const int elementCount = entries.front().elementCount;
27775 if (elementCount <= 0 ||
27776 elementCount > std::numeric_limits<std::uint8_t>::max() ||
27777 static_cast<int>(entries.size()) != elementCount) {
27778 return std::nullopt;
27779 }
27780
27781 std::sort(entries.begin(), entries.end(),
27782 [](const MLineEntry &lhs, const MLineEntry &rhs) {
27783 return lhs.elementIndex < rhs.elementIndex;
27784 });
27785 const MLineEntry &anchor = entries.front();
27786 if (anchor.markerCount != 1 || anchor.elementIndex != 0 || !anchor.isAnchor ||
27787 anchor.baselineVerts.empty() ||
27788 anchor.baselineVerts.size() != anchor.miterDirs.size() ||
27789 anchor.baselineVerts.size() > std::numeric_limits<std::uint16_t>::max()) {
27790 return std::nullopt;
27791 }
27792
27793 MLineSidecarDescriptor descriptor;
27794 descriptor.anchor = &anchor;
27795 descriptor.members.reserve(entries.size());
27796 for (int index = 0; index < elementCount; ++index) {
27797 const MLineEntry &entry = entries[static_cast<std::size_t>(index)];
27798 if (entry.entity == nullptr || entry.markerCount != 1 ||
27799 entry.elementIndex != index || entry.elementCount != elementCount ||
27800 entry.styleName != anchor.styleName ||
27801 entry.styleHandle != anchor.styleHandle ||
27802 entry.justification != anchor.justification ||
27803 entry.openClosed != anchor.openClosed || entry.justification < 0 ||
27804 entry.justification > 2 || entry.openClosed < 0 ||
27805 entry.openClosed > std::numeric_limits<std::uint8_t>::max() ||
27806 !std::isfinite(entry.scale) || !std::isfinite(entry.offset) ||
27807 std::abs(entry.scale - anchor.scale) > RS_TOLERANCE1.0e-10 ||
27808 (index != 0 &&
27809 (!entry.baselineVerts.empty() || !entry.miterDirs.empty())) ||
27810 !matchesMLineSidecarGeometry(entry, anchor)) {
27811 return std::nullopt;
27812 }
27813 descriptor.members.push_back(&entry);
27814 }
27815 return descriptor;
27816}
27817} // namespace
27818
27819void RS_FilterDXFRW::reconstructPolylineSidecars(
27820 RS_EntityContainer *container, std::set<RS_Entity *> &consumed) {
27821 if (container == nullptr)
27822 return;
27823
27824 std::map<QString, std::vector<MeshSidecarEntry>> meshGroups;
27825 std::map<QString, std::vector<PolyfaceSidecarEntry>> polyfaceGroups;
27826
27827 for (RS_Entity *entity :
27828 lc::LC_ContainerTraverser{*container, RS2::ResolveNone}.entities()) {
27829 if (entity == nullptr || entity->getFlag(RS2::FlagDeleted) ||
27830 consumed.find(entity) != consumed.end() ||
27831 entity->rtti() != RS2::EntityPolyline) {
27832 continue;
27833 }
27834 if (auto meshMeta = extractMeshSidecar(entity))
27835 meshGroups[meshMeta->meshId].push_back(std::move(*meshMeta));
27836 else if (auto polyfaceMeta = extractPolyfaceSidecar(entity))
27837 polyfaceGroups[polyfaceMeta->polyfaceId].push_back(
27838 std::move(*polyfaceMeta));
27839 }
27840
27841 const auto writeDwgPolylineSidecar =
27842 [this](const RS_Entity *anchor, const auto &entries,
27843 DRW_Polyline &polyline, std::size_t expectedVertexListSize,
27844 int expectedVertexCount, int expectedFaceCount) {
27845 // The descriptor is the source of truth for both the physical child
27846 // sequence and the POLYLINE semantic counts. Refuse before opening a
27847 // writer transaction if the emitter ever drifts from that contract.
27848 if (polyline.vertlist.size() != expectedVertexListSize ||
27849 polyline.vertexcount != expectedVertexCount ||
27850 polyline.facecount != expectedFaceCount) {
27851 return false;
27852 }
27853 const auto identityCheckpoint = checkpointDwgWriteIdentity();
27854 const auto rollback = [&]() {
27855 (void)m_dwgW->rollbackLastDwgObjectWrite(false);
27856 restoreDwgWriteIdentity(identityCheckpoint);
27857 for (const auto *entry : entries)
27858 rollbackDwgEntityWrite(entry->entity);
27859 m_writeFailed = true;
27860 };
27861 const bool failedBefore = m_writeFailed;
27862 if (!m_dwgW->writePolyline(&polyline) || failedBefore ||
27863 m_writeFailed) {
27864 rollback();
27865 return false;
27866 }
27867 if (!commitDwgPolylineCompound(anchor)) {
27868 rollback();
27869 return false;
27870 }
27871 for (const auto *entry : entries) {
27872 if (entry->entity != anchor)
27873 rollbackDwgEntityWrite(entry->entity);
27874 }
27875 return true;
27876 };
27877
27878 for (auto &[meshId, entries] : meshGroups) {
27879 (void)meshId;
27880 const auto descriptor = makeMeshSidecarDescriptor(entries);
27881 if (!descriptor)
27882 continue;
27883 const MeshSidecarEntry *anchor = descriptor->anchor;
27884
27885 DRW_Polyline polyline;
27886 polyline.flags = anchor->flags | 0x10;
27887 polyline.vertexcount = anchor->mCount;
27888 polyline.facecount = anchor->nCount;
27889 polyline.smoothM = anchor->smoothM;
27890 polyline.smoothN = anchor->smoothN;
27891 polyline.curvetype = anchor->curveType;
27892 getEntityAttributes(&polyline, anchor->entity);
27893 for (const DRW_Coord &coord : anchor->sourceVertices) {
27894 DRW_Vertex vertex(coord.x, coord.y, coord.z, 0.0);
27895 vertex.setDwgSubtype(DRW_Vertex::DwgSubtype::Mesh);
27896 polyline.addVertex(vertex);
27897 }
27898
27899 const bool emitted =
27900 m_dwgW != nullptr
27901 ? writeDwgPolylineSidecar(anchor->entity, descriptor->members,
27902 polyline, descriptor->sourceVertexCount,
27903 anchor->mCount, anchor->nCount)
27904 : m_dxfW != nullptr &&
27905 noteDxfWrite(m_dxfW->writePolyline(&polyline));
27906 if (!emitted) {
27907 if (m_dwgW != nullptr)
27908 return;
27909 continue;
27910 }
27911 for (const auto *entry : descriptor->members)
27912 consumed.insert(entry->entity);
27913 }
27914
27915 for (auto &[polyfaceId, entries] : polyfaceGroups) {
27916 (void)polyfaceId;
27917 const auto descriptor = makePolyfaceSidecarDescriptor(entries);
27918 if (!descriptor)
27919 continue;
27920 const PolyfaceSidecarEntry *anchor = descriptor->anchor;
27921
27922 DRW_Polyline polyline;
27923 polyline.flags = anchor->flags | 0x40;
27924 polyline.vertexcount = anchor->vertexCount;
27925 polyline.facecount = anchor->faceCount;
27926 getEntityAttributes(&polyline, anchor->entity);
27927
27928 for (const DRW_Coord &coord : anchor->sourceVertices) {
27929 DRW_Vertex vertex(coord.x, coord.y, coord.z, 0.0);
27930 vertex.flags = 0x40 | 0x80;
27931 vertex.setDwgSubtype(DRW_Vertex::DwgSubtype::Polyface);
27932 polyline.addVertex(vertex);
27933 }
27934 for (const auto *entry : descriptor->members) {
27935 DRW_Vertex face;
27936 face.flags = 0x80;
27937 face.vindex1 = entry->indices[0];
27938 face.vindex2 = entry->indices[1];
27939 face.vindex3 = entry->indices[2];
27940 face.vindex4 = entry->indices[3];
27941 face.setDwgSubtype(DRW_Vertex::DwgSubtype::PolyfaceFace);
27942 polyline.addVertex(face);
27943 }
27944
27945 const bool emitted =
27946 m_dwgW != nullptr
27947 ? writeDwgPolylineSidecar(anchor->entity, descriptor->members,
27948 polyline, descriptor->compoundVertexCount,
27949 anchor->vertexCount, anchor->faceCount)
27950 : m_dxfW != nullptr &&
27951 noteDxfWrite(m_dxfW->writePolyline(&polyline));
27952 if (!emitted) {
27953 if (m_dwgW != nullptr)
27954 return;
27955 continue;
27956 }
27957 for (const auto *entry : descriptor->members)
27958 consumed.insert(entry->entity);
27959 }
27960}
27961
27962void RS_FilterDXFRW::reconstructMLines(RS_EntityContainer *container,
27963 std::set<RS_Entity *> &consumed) {
27964 if (!container)
27965 return;
27966
27967 // First pass: collect all polylines carrying the MLINE marker.
27968 std::map<QString, std::vector<MLineEntry>> groups;
27969 for (RS_Entity *e :
27970 lc::LC_ContainerTraverser{*container, RS2::ResolveNone}.entities()) {
27971 if (e->getFlag(RS2::FlagDeleted))
27972 continue;
27973 if (e->rtti() != RS2::EntityPolyline)
27974 continue;
27975 auto m = extractMLineMeta(e);
27976 if (!m)
27977 continue;
27978 groups[m->mlineId].push_back(std::move(*m));
27979 }
27980
27981 // Second pass: reconstruct + emit each complete group.
27982 for (auto &[mlineId, entries] : groups) {
27983 (void)mlineId;
27984 if (entries.empty())
27985 continue;
27986 const auto descriptor = makeMLineSidecarDescriptor(entries);
27987 if (!descriptor) {
27988 // Incomplete or edited groups retain their visible fallback geometry.
27989 continue;
27990 }
27991 const MLineEntry *anchor = descriptor->anchor;
27992 const int N = static_cast<int>(descriptor->members.size());
27993
27994 // Build DRW_MLine from anchor metadata + baseline vertices.
27995 DRW_MLine ml;
27996 getEntityAttributes(&ml, anchor->entity);
27997 ml.styleName = anchor->styleName.toStdString();
27998 std::uint32_t styleSourceHandle = anchor->styleHandle;
27999 if (anchor->styleHandle != 0) {
28000 ml.styleHandle = styleSourceHandle;
28001 } else if (m_dwgW && m_graphic != nullptr) {
28002 const auto &metadata = m_graphic->dwgAdvancedMetadata();
28003 for (const auto &record : metadata.mlineStyles()) {
28004 if (record.handle == 0 ||
28005 record.replayState !=
28006 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed)
28007 continue;
28008 if (QString::compare(QString::fromUtf8(record.name.c_str()),
28009 anchor->styleName, Qt::CaseInsensitive) == 0) {
28010 styleSourceHandle = record.handle;
28011 ml.styleHandle = styleSourceHandle;
28012 break;
28013 }
28014 }
28015 }
28016 std::uint32_t styleOutputHandle = styleSourceHandle;
28017 if (m_dwgW != nullptr && styleSourceHandle != 0) {
28018 const DwgWriteReferenceStatus status =
28019 resolveDwgWriteObjectHandle(styleSourceHandle, &styleOutputHandle);
28020 const bool resolved = status == DwgWriteReferenceStatus::Resolved ||
28021 status == DwgWriteReferenceStatus::Reserved ||
28022 status == DwgWriteReferenceStatus::Selected ||
28023 status == DwgWriteReferenceStatus::Committed;
28024 if (!resolved || styleOutputHandle == 0)
28025 continue;
28026 ml.styleHandle = styleOutputHandle;
28027 }
28028 ml.scale = anchor->scale;
28029 ml.justification = static_cast<std::uint8_t>(anchor->justification);
28030 ml.openClosed = anchor->openClosed;
28031 ml.numLines = static_cast<std::uint8_t>(N);
28032 ml.numVerts = static_cast<std::uint16_t>(anchor->baselineVerts.size());
28033 if (!anchor->baselineVerts.empty()) {
28034 ml.basePoint = anchor->baselineVerts.front();
28035 }
28036 ml.layer = anchor->entity->getLayer()
28037 ? anchor->entity->getLayer()->getName().toStdString()
28038 : std::string{"0"};
28039 const size_t V = anchor->baselineVerts.size();
28040 const size_t M = anchor->miterDirs.size();
28041 ml.vertlist.reserve(V);
28042 for (size_t i = 0; i < V; ++i) {
28043 DRW_MLineVertex v;
28044 v.position = anchor->baselineVerts[i];
28045 if (i < M)
28046 v.miterDir = anchor->miterDirs[i];
28047 // Default segment params: a single zero parameter per element
28048 // (sufficient for a continuous unbroken multiline).
28049 v.segParms.assign(N, std::vector<double>{0.0});
28050 v.areaFillParms.assign(N, std::vector<double>{});
28051 ml.vertlist.push_back(std::move(v));
28052 }
28053
28054 bool emitted = false;
28055 if (m_dwgW) {
28056 const auto identityCheckpoint = checkpointDwgWriteIdentity();
28057 const std::uint64_t transaction =
28058 m_dwgW->beginDwgWriteTransaction(false, false, false);
28059 const auto rollback = [&]() {
28060 if (transaction != 0)
28061 (void)m_dwgW->rollbackDwgWriteTransaction(transaction);
28062 restoreDwgWriteIdentity(identityCheckpoint);
28063 for (const MLineEntry *member : descriptor->members)
28064 rollbackDwgEntityWrite(member->entity);
28065 m_writeFailed = true;
28066 };
28067 if (transaction == 0 || !m_dwgW->writeMLine(&ml) || m_writeFailed) {
28068 rollback();
28069 } else {
28070 const std::uint32_t outputHandle =
28071 m_dwgW->getLastSuccessfulEntityHandle();
28072 const DwgWriteReferenceReceipt styleReceipt{
28073 DwgWriteSourceKind::Entity,
28074 anchor->entity->sourceHandle(),
28075 outputHandle,
28076 "MLINE.STYLE",
28077 0,
28078 DwgWriteSourceKind::Object,
28079 styleSourceHandle,
28080 styleOutputHandle,
28081 true,
28082 DRW::DwgHardPointer};
28083 const bool committed =
28084 outputHandle != 0 && recordDwgEntityFrame(outputHandle) &&
28085 (anchor->entity->sourceHandle() == 0
28086 ? commitDwgGeneratedEntityWrite(outputHandle)
28087 : commitDwgEntityWrite(anchor->entity)) &&
28088 (styleSourceHandle == 0 ||
28089 recordDwgWriteReferenceReceipt(styleReceipt)) &&
28090 m_dwgW->commitDwgWriteTransaction(transaction);
28091 if (!committed) {
28092 rollback();
28093 } else {
28094 for (const MLineEntry *member : descriptor->members) {
28095 if (member != anchor)
28096 rollbackDwgEntityWrite(member->entity);
28097 }
28098 emitted = true;
28099 }
28100 }
28101 } else if (m_dxfW) {
28102 emitted = noteDxfWrite(m_dxfW->writeMLine(&ml));
28103 }
28104
28105 if (!emitted) {
28106 if (m_dwgW != nullptr)
28107 return;
28108 continue;
28109 }
28110 for (const MLineEntry *member : descriptor->members)
28111 consumed.insert(member->entity);
28112 }
28113}
28114
28115namespace {
28116// Find a single F2 type-fidelity sidecar on an RS entity's XDATA, if present.
28117// The marker codes match those written by appendTypeSidecar() at read time.
28118std::optional<TypedConversionSidecar>
28119extractTypedConversionSidecar(RS_Entity *e) {
28120 return parseTypedConversionSidecar(e);
28121}
28122
28123struct HatchExtrusionSidecar {
28124 double elevation = 0.0;
28125 DRW_Coord extrusion{0.0, 0.0, 1.0};
28126};
28127
28128std::optional<HatchExtrusionSidecar>
28129extractHatchExtrusionSidecar(const RS_Entity *entity) {
28130 if (!entity || !entity->hasDrwExtData())
28131 return std::nullopt;
28132
28133 HatchExtrusionSidecar sidecar;
28134 bool inGroup = false;
28135 bool gotElevation = false;
28136 bool gotExtrusion = false;
28137 for (const auto &value : entity->getDrwExtData()) {
28138 if (!value)
28139 continue;
28140 if (value->code() == 1001) {
28141 std::string marker;
28142 inGroup = readXDataString(value.get(), 1001, marker) ==
28143 XDataReadResult::Valid &&
28144 marker == kHatchExtrusionMarker;
28145 continue;
28146 }
28147 if (!inGroup)
28148 continue;
28149 if (value->code() == 1010) {
28150 DRW_Coord point;
28151 if (readXDataCoord(value.get(), 1010, point) == XDataReadResult::Valid) {
28152 sidecar.elevation = point.z;
28153 gotElevation = true;
28154 }
28155 } else if (value->code() == 1011) {
28156 DRW_Coord normal;
28157 if (readXDataCoord(value.get(), 1011, normal) == XDataReadResult::Valid) {
28158 sidecar.extrusion = normal;
28159 gotExtrusion = true;
28160 }
28161 }
28162 }
28163 if (!gotElevation || !gotExtrusion)
28164 return std::nullopt;
28165 return sidecar;
28166}
28167
28168void removeHatchExtrusionSidecar(DRW_Entity &entity) {
28169 std::vector<std::shared_ptr<DRW_Variant>> filtered;
28170 bool inGroup = false;
28171 for (const auto &value : entity.extData) {
28172 if (value && value->code() == 1001) {
28173 std::string marker;
28174 inGroup = readXDataString(value.get(), 1001, marker) ==
28175 XDataReadResult::Valid &&
28176 marker == kHatchExtrusionMarker;
28177 if (inGroup)
28178 continue;
28179 }
28180 if (!inGroup)
28181 filtered.push_back(value);
28182 }
28183 entity.extData = std::move(filtered);
28184}
28185
28186struct ImageFrameSidecar {
28187 DRW_Coord insertion;
28188 DRW_Coord uVector;
28189 DRW_Coord vVector;
28190};
28191
28192std::optional<ImageFrameSidecar>
28193extractImageFrameSidecar(const RS_Entity *entity) {
28194 if (!entity || !entity->hasDrwExtData())
28195 return std::nullopt;
28196
28197 ImageFrameSidecar sidecar;
28198 bool gotInsertion = false;
28199 bool gotUVector = false;
28200 bool gotVVector = false;
28201 bool inGroup = false;
28202 for (const auto &value : entity->getDrwExtData()) {
28203 if (!value)
28204 continue;
28205 if (value->code() == 1001) {
28206 std::string marker;
28207 inGroup = readXDataString(value.get(), 1001, marker) ==
28208 XDataReadResult::Valid &&
28209 marker == kImageFrameMarker;
28210 continue;
28211 }
28212 if (!inGroup || value->code() < 1010 || value->code() > 1012)
28213 continue;
28214 DRW_Coord point;
28215 if (readXDataCoord(value.get(), value->code(), point) !=
28216 XDataReadResult::Valid)
28217 continue;
28218 if (value->code() == 1010) {
28219 sidecar.insertion = point;
28220 gotInsertion = true;
28221 } else if (value->code() == 1011) {
28222 sidecar.uVector = point;
28223 gotUVector = true;
28224 } else {
28225 sidecar.vVector = point;
28226 gotVVector = true;
28227 }
28228 }
28229 if (!gotInsertion || !gotUVector || !gotVVector)
28230 return std::nullopt;
28231 return sidecar;
28232}
28233
28234void removeImageFrameSidecar(DRW_Entity &entity) {
28235 std::vector<std::shared_ptr<DRW_Variant>> filtered;
28236 bool inGroup = false;
28237 for (const auto &value : entity.extData) {
28238 if (value && value->code() == 1001) {
28239 std::string marker;
28240 inGroup = readXDataString(value.get(), 1001, marker) ==
28241 XDataReadResult::Valid &&
28242 marker == kImageFrameMarker;
28243 if (inGroup)
28244 continue;
28245 }
28246 if (!inGroup)
28247 filtered.push_back(value);
28248 }
28249 entity.extData = std::move(filtered);
28250}
28251
28252struct TextOcsSidecar {
28253 double baseElevation = 0.0;
28254 double alignmentElevation = 0.0;
28255 DRW_Coord extrusion{0.0, 0.0, 1.0};
28256 double thickness = 0.0;
28257};
28258
28259std::optional<TextOcsSidecar> extractTextOcsSidecar(const RS_Entity *entity) {
28260 if (!entity || !entity->hasDrwExtData())
28261 return std::nullopt;
28262 TextOcsSidecar sidecar;
28263 bool gotBase = false;
28264 bool gotAlignment = false;
28265 bool gotExtrusion = false;
28266 bool gotThickness = false;
28267 bool inGroup = false;
28268 for (const auto &value : entity->getDrwExtData()) {
28269 if (!value)
28270 continue;
28271 if (value->code() == 1001) {
28272 std::string marker;
28273 inGroup = readXDataString(value.get(), 1001, marker) ==
28274 XDataReadResult::Valid &&
28275 marker == kTextOcsMarker;
28276 continue;
28277 }
28278 if (!inGroup)
28279 continue;
28280 if (value->code() == 1010) {
28281 DRW_Coord point;
28282 if (readXDataCoord(value.get(), 1010, point) == XDataReadResult::Valid) {
28283 sidecar.baseElevation = point.z;
28284 gotBase = true;
28285 }
28286 } else if (value->code() == 1011) {
28287 DRW_Coord point;
28288 if (readXDataCoord(value.get(), 1011, point) == XDataReadResult::Valid) {
28289 sidecar.alignmentElevation = point.z;
28290 gotAlignment = true;
28291 }
28292 } else if (value->code() == 1012) {
28293 DRW_Coord normal;
28294 if (readXDataCoord(value.get(), 1012, normal) == XDataReadResult::Valid) {
28295 sidecar.extrusion = normal;
28296 gotExtrusion = true;
28297 }
28298 } else if (value->code() == 1040) {
28299 double thickness = 0.0;
28300 if (readXDataDouble(value.get(), 1040, thickness) ==
28301 XDataReadResult::Valid) {
28302 sidecar.thickness = thickness;
28303 gotThickness = true;
28304 }
28305 }
28306 }
28307 if (!gotBase || !gotAlignment || !gotExtrusion || !gotThickness)
28308 return std::nullopt;
28309 return sidecar;
28310}
28311
28312void removeTextOcsSidecar(DRW_Entity &entity) {
28313 std::vector<std::shared_ptr<DRW_Variant>> filtered;
28314 bool inGroup = false;
28315 for (const auto &value : entity.extData) {
28316 if (value && value->code() == 1001) {
28317 std::string marker;
28318 inGroup = readXDataString(value.get(), 1001, marker) ==
28319 XDataReadResult::Valid &&
28320 marker == kTextOcsMarker;
28321 if (inGroup)
28322 continue;
28323 }
28324 if (!inGroup)
28325 filtered.push_back(value);
28326 }
28327 entity.extData = std::move(filtered);
28328}
28329
28330struct MTextOcsSidecar {
28331 double elevation = 0.0;
28332 DRW_Coord xAxis;
28333 DRW_Coord extrusion{0.0, 0.0, 1.0};
28334};
28335
28336std::optional<MTextOcsSidecar> extractMTextOcsSidecar(const RS_Entity *entity) {
28337 if (!entity || !entity->hasDrwExtData())
28338 return std::nullopt;
28339 MTextOcsSidecar sidecar;
28340 bool gotElevation = false;
28341 bool gotXAxis = false;
28342 bool gotExtrusion = false;
28343 bool inGroup = false;
28344 for (const auto &value : entity->getDrwExtData()) {
28345 if (!value)
28346 continue;
28347 if (value->code() == 1001) {
28348 std::string marker;
28349 inGroup = readXDataString(value.get(), 1001, marker) ==
28350 XDataReadResult::Valid &&
28351 marker == kMTextOcsMarker;
28352 continue;
28353 }
28354 if (!inGroup)
28355 continue;
28356 if (value->code() == 1010) {
28357 DRW_Coord point;
28358 if (readXDataCoord(value.get(), 1010, point) == XDataReadResult::Valid) {
28359 sidecar.elevation = point.z;
28360 gotElevation = true;
28361 }
28362 } else if (value->code() == 1011) {
28363 DRW_Coord axis;
28364 if (readXDataCoord(value.get(), 1011, axis) == XDataReadResult::Valid) {
28365 sidecar.xAxis = axis;
28366 gotXAxis = true;
28367 }
28368 } else if (value->code() == 1012) {
28369 DRW_Coord normal;
28370 if (readXDataCoord(value.get(), 1012, normal) == XDataReadResult::Valid) {
28371 sidecar.extrusion = normal;
28372 gotExtrusion = true;
28373 }
28374 }
28375 }
28376 if (!gotElevation || !gotXAxis || !gotExtrusion)
28377 return std::nullopt;
28378 return sidecar;
28379}
28380
28381void removeMTextOcsSidecar(DRW_Entity &entity) {
28382 std::vector<std::shared_ptr<DRW_Variant>> filtered;
28383 bool inGroup = false;
28384 for (const auto &value : entity.extData) {
28385 if (value && value->code() == 1001) {
28386 std::string marker;
28387 inGroup = readXDataString(value.get(), 1001, marker) ==
28388 XDataReadResult::Valid &&
28389 marker == kMTextOcsMarker;
28390 if (inGroup)
28391 continue;
28392 }
28393 if (!inGroup)
28394 filtered.push_back(value);
28395 }
28396 entity.extData = std::move(filtered);
28397}
28398} // namespace
28399
28400void RS_FilterDXFRW::reconstructTypedConversions(
28401 RS_EntityContainer *container, std::set<RS_Entity *> &consumed) {
28402 if (!container)
28403 return;
28404
28405 for (RS_Entity *e :
28406 lc::LC_ContainerTraverser{*container, RS2::ResolveNone}.entities()) {
28407 if (e->getFlag(RS2::FlagDeleted))
28408 continue;
28409 if (consumed.find(e) != consumed.end())
28410 continue;
28411 auto meta = extractTypedConversionSidecar(e);
28412 if (!meta) {
28413 if (hasDwgTypedConversionSidecar(e))
28414 m_writeFailed = true;
28415 continue;
28416 }
28417
28418 // Build the native DRW entity with full geometry. Attributes (layer,
28419 // color, linetype, ...) come from getEntityAttributes(); afterwards the
28420 // F2 sidecar markers are stripped so the rebuilt native type does not
28421 // carry them back out as XDATA.
28422 auto emitNative = [&](DRW_Entity *drw) {
28423 getEntityAttributes(drw, e);
28424 // Drop the F2 sidecar groups from the re-emitted XDATA so the rebuilt
28425 // native type does not carry the marker back out.
28426 std::vector<std::shared_ptr<DRW_Variant>> filtered;
28427 bool inGroup = false;
28428 for (const auto &v : drw->extData) {
28429 if (v && v->code() == 1001) {
28430 std::string app;
28431 inGroup =
28432 readXDataString(v.get(), 1001, app) == XDataReadResult::Valid &&
28433 (app == kPointExtrusionMarker || app == kLineExtrusionMarker ||
28434 app == kCircleOcsMarker || app == kArcOcsMarker ||
28435 app == k3DLineMarker || app == kRayMarker ||
28436 app == kXlineMarker || app == kTraceMarker ||
28437 app == kSolidMarker || app == k3dFaceMarker);
28438 if (inGroup)
28439 continue;
28440 }
28441 if (inGroup)
28442 continue;
28443 filtered.push_back(v);
28444 }
28445 drw->extData = std::move(filtered);
28446 };
28447 const auto writeDwgTyped = [this, e](const auto &write) {
28448 if (m_dwgW == nullptr)
28449 return false;
28450
28451 const auto identityCheckpoint = checkpointDwgWriteIdentity();
28452 const std::uint64_t transaction =
28453 m_dwgW->beginDwgWriteTransaction(false, false, false);
28454 if (transaction == 0) {
28455 m_writeFailed = true;
28456 return false;
28457 }
28458 const auto rollback = [&]() {
28459 (void)m_dwgW->rollbackDwgWriteTransaction(transaction);
28460 restoreDwgWriteIdentity(identityCheckpoint);
28461 rollbackDwgEntityWrite(e);
28462 m_writeFailed = true;
28463 };
28464
28465 const bool failedBefore = m_writeFailed;
28466 if (!write() || failedBefore || m_writeFailed) {
28467 rollback();
28468 return false;
28469 }
28470
28471 const std::uint32_t outputHandle =
28472 m_dwgW->getLastSuccessfulEntityHandle();
28473 if (!recordDwgEntityFrame(outputHandle)) {
28474 rollback();
28475 return false;
28476 }
28477 const bool committed = e->sourceHandle() == 0
28478 ? commitDwgGeneratedEntityWrite(outputHandle)
28479 : commitDwgEntityWrite(e);
28480 if (!committed || !m_dwgW->commitDwgWriteTransaction(transaction)) {
28481 rollback();
28482 return false;
28483 }
28484 return true;
28485 };
28486
28487 if (meta->marker == kPointExtrusionMarker) {
28488 auto *source = static_cast<RS_Point *>(e);
28489 DRW_Point point;
28490 point.basePoint = meta->coords[0];
28491 point.basePoint.x = source->getStartpoint().x;
28492 point.basePoint.y = source->getStartpoint().y;
28493 point.extPoint = meta->coords[1];
28494 point.thickness = meta->thickness;
28495 point.xAxisAngle = meta->pointXAxisAngle;
28496 emitNative(&point);
28497 const bool emitted =
28498 m_dwgW
28499 ? writeDwgTyped(
28500 [&point, this] { return m_dwgW->writePoint(&point); })
28501 : m_dxfW != nullptr && noteDxfWrite(m_dxfW->writePoint(&point));
28502 if (emitted)
28503 consumed.insert(e);
28504 } else if (meta->marker == k3DLineMarker) {
28505 auto *source = static_cast<RS_Line *>(e);
28506 DRW_3DLine line;
28507 line.basePoint = meta->coords[0];
28508 line.secPoint = meta->coords[1];
28509 line.basePoint.x = source->getStartpoint().x;
28510 line.basePoint.y = source->getStartpoint().y;
28511 line.secPoint.x = source->getEndpoint().x;
28512 line.secPoint.y = source->getEndpoint().y;
28513 line.extPoint = meta->coords[2];
28514 line.thickness = meta->thickness;
28515 emitNative(&line);
28516 const bool emitted =
28517 m_dwgW
28518 ? writeDwgTyped(
28519 [&line, this] { return m_dwgW->write3DLine(&line); })
28520 : m_dxfW != nullptr && noteDxfWrite(m_dxfW->write3DLine(&line));
28521 if (emitted)
28522 consumed.insert(e);
28523 } else if (meta->marker == kLineExtrusionMarker) {
28524 auto *source = static_cast<RS_Line *>(e);
28525 DRW_Line line;
28526 line.basePoint = meta->coords[0];
28527 line.secPoint = meta->coords[1];
28528 line.basePoint.x = source->getStartpoint().x;
28529 line.basePoint.y = source->getStartpoint().y;
28530 line.secPoint.x = source->getEndpoint().x;
28531 line.secPoint.y = source->getEndpoint().y;
28532 line.extPoint = meta->coords[2];
28533 line.thickness = meta->thickness;
28534 emitNative(&line);
28535 const bool emitted =
28536 m_dwgW ? writeDwgTyped(
28537 [&line, this] { return m_dwgW->writeLine(&line); })
28538 : m_dxfW != nullptr && noteDxfWrite(m_dxfW->writeLine(&line));
28539 if (emitted)
28540 consumed.insert(e);
28541 } else if (meta->marker == kCircleOcsMarker) {
28542 auto *source = static_cast<RS_Circle *>(e);
28543 DRW_Circle circle;
28544 circle.basePoint = meta->coords[0];
28545 circle.basePoint.x = source->getCenter().x;
28546 circle.basePoint.y = source->getCenter().y;
28547 circle.radious = source->getRadius();
28548 circle.extPoint = meta->coords[1];
28549 if (circle.extPoint.x != 0.0 || circle.extPoint.y != 0.0 ||
28550 (circle.extPoint.z != 1.0 && circle.extPoint.z != -1.0)) {
28551 continue;
28552 }
28553 circle.thickness = meta->thickness;
28554 if (circle.extPoint.z < 0.0) {
28555 circle.basePoint.x = -circle.basePoint.x;
28556 }
28557 emitNative(&circle);
28558 const bool emitted =
28559 m_dwgW
28560 ? writeDwgTyped(
28561 [&circle, this] { return m_dwgW->writeCircle(&circle); })
28562 : m_dxfW != nullptr && noteDxfWrite(m_dxfW->writeCircle(&circle));
28563 if (emitted)
28564 consumed.insert(e);
28565 } else if (meta->marker == kArcOcsMarker) {
28566 auto *source = static_cast<RS_Arc *>(e);
28567 DRW_Arc arc;
28568 arc.basePoint = meta->coords[0];
28569 arc.basePoint.x = source->getCenter().x;
28570 arc.basePoint.y = source->getCenter().y;
28571 arc.radious = source->getRadius();
28572 arc.extPoint = meta->coords[1];
28573 if (arc.extPoint.x != 0.0 || arc.extPoint.y != 0.0 ||
28574 (arc.extPoint.z != 1.0 && arc.extPoint.z != -1.0)) {
28575 continue;
28576 }
28577 arc.thickness = meta->thickness;
28578 const double startAngle =
28579 source->isReversed() ? source->getAngle2() : source->getAngle1();
28580 const double endAngle =
28581 source->isReversed() ? source->getAngle1() : source->getAngle2();
28582 if (arc.extPoint.z < 0.0) {
28583 arc.basePoint.x = -arc.basePoint.x;
28584 arc.staangle = M_PI3.14159265358979323846 - endAngle;
28585 arc.endangle = M_PI3.14159265358979323846 - startAngle;
28586 } else {
28587 arc.staangle = startAngle;
28588 arc.endangle = endAngle;
28589 }
28590 emitNative(&arc);
28591 const bool emitted =
28592 m_dwgW
28593 ? writeDwgTyped([&arc, this] { return m_dwgW->writeArc(&arc); })
28594 : m_dxfW != nullptr && noteDxfWrite(m_dxfW->writeArc(&arc));
28595 if (emitted)
28596 consumed.insert(e);
28597 } else if (meta->marker == kRayMarker) {
28598 DRW_Ray ray;
28599 ray.basePoint = meta->coords[0];
28600 ray.secPoint = meta->coords[1];
28601 emitNative(&ray);
28602 const bool emitted =
28603 m_dwgW
28604 ? writeDwgTyped([&ray, this] { return m_dwgW->writeRay(&ray); })
28605 : m_dxfW != nullptr && noteDxfWrite(m_dxfW->writeRay(&ray));
28606 if (emitted)
28607 consumed.insert(e);
28608 } else if (meta->marker == kXlineMarker) {
28609 DRW_Xline xline;
28610 xline.basePoint = meta->coords[0];
28611 xline.secPoint = meta->coords[1];
28612 emitNative(&xline);
28613 const bool emitted =
28614 m_dwgW
28615 ? writeDwgTyped(
28616 [&xline, this] { return m_dwgW->writeXline(&xline); })
28617 : m_dxfW != nullptr && noteDxfWrite(m_dxfW->writeXline(&xline));
28618 if (emitted)
28619 consumed.insert(e);
28620 } else if (meta->marker == kTraceMarker) {
28621 DRW_Trace trace;
28622 trace.basePoint = meta->coords[0];
28623 trace.secPoint = meta->coords[1];
28624 trace.thirdPoint = meta->coords[2];
28625 trace.fourPoint = meta->coords[3];
28626 trace.thickness = meta->thickness;
28627 emitNative(&trace);
28628 const bool emitted =
28629 m_dwgW
28630 ? writeDwgTyped(
28631 [&trace, this] { return m_dwgW->writeTrace(&trace); })
28632 : m_dxfW != nullptr && noteDxfWrite(m_dxfW->writeTrace(&trace));
28633 if (emitted)
28634 consumed.insert(e);
28635 } else if (meta->marker == kSolidMarker) {
28636 DRW_Solid solid;
28637 solid.basePoint = meta->coords[0];
28638 solid.secPoint = meta->coords[1];
28639 solid.thirdPoint = meta->coords[2];
28640 solid.fourPoint = meta->coords[3];
28641 solid.thickness = meta->thickness;
28642 emitNative(&solid);
28643 const bool emitted =
28644 m_dwgW
28645 ? writeDwgTyped(
28646 [&solid, this] { return m_dwgW->writeSolid(&solid); })
28647 : m_dxfW != nullptr && noteDxfWrite(m_dxfW->writeSolid(&solid));
28648 if (emitted)
28649 consumed.insert(e);
28650 } else if (meta->marker == k3dFaceMarker) {
28651 DRW_3Dface face;
28652 face.basePoint = meta->coords[0];
28653 face.secPoint = meta->coords[1];
28654 face.thirdPoint = meta->coords[2];
28655 face.fourPoint = meta->coords[3];
28656 face.invisibleflag = meta->flag;
28657 emitNative(&face);
28658 const bool emitted =
28659 m_dwgW
28660 ? writeDwgTyped(
28661 [&face, this] { return m_dwgW->write3dface(&face); })
28662 : m_dxfW != nullptr && noteDxfWrite(m_dxfW->write3dface(&face));
28663 if (emitted)
28664 consumed.insert(e);
28665 }
28666 }
28667}
28668
28669bool RS_FilterDXFRW::reconstructSingleFrameSidecars(
28670 RS_EntityContainer *container, std::set<RS_Entity *> &consumed) {
28671 reconstructTypedConversions(container, consumed);
5
Value assigned to field 'm_graphic'
28672 if (m_writeFailed)
6
Assuming field 'm_writeFailed' is false
7
Taking false branch
28673 return false;
28674 reconstructUnderlays(container, consumed);
28675 return !m_writeFailed;
28676}
28677
28678void RS_FilterDXFRW::writeEntity(RS_Entity *e) {
28679 if (e == nullptr || isGeneratedInsertAttrib(e))
28680 return;
28681 DwgEntityProductionResult production = DwgEntityProductionResult::SingleFrame;
28682#ifdef DWGSUPPORT1
28683 const bool isDwgWrite = m_dwgW != nullptr;
28684 const bool failedBefore = isDwgWrite && m_writeFailed;
28685 const std::size_t successfulWritesBefore =
28686 isDwgWrite ? m_dwgW->getSuccessfulEntityWriteCount() : 0;
28687 const auto identityCheckpoint =
28688 isDwgWrite ? checkpointDwgWriteIdentity()
28689 : RS_FilterDXFRW_DwgWriteIdentityRegistry::
28690 DwgWriteIdentityCheckpoint{};
28691#endif
28692 switch (e->rtti()) {
28693 case RS2::EntityPoint:
28694 writePoint(static_cast<RS_Point *>(e));
28695 break;
28696 case RS2::EntityLine:
28697 writeLine(static_cast<RS_Line *>(e));
28698 break;
28699 case RS2::EntityCircle:
28700 writeCircle(static_cast<RS_Circle *>(e));
28701 break;
28702 case RS2::EntityArc:
28703 writeArc(static_cast<RS_Arc *>(e));
28704 break;
28705 case RS2::EntitySolid:
28706 writeSolid(static_cast<RS_Solid *>(e));
28707 break;
28708 case RS2::EntityEllipse:
28709 writeEllipse(static_cast<RS_Ellipse *>(e));
28710 break;
28711 case RS2::EntityHyperbola:
28712 writeHyperbola(static_cast<LC_Hyperbola *>(e));
28713 break;
28714 case RS2::EntityPolyline:
28715 writeLWPolyline(static_cast<RS_Polyline *>(e));
28716 break;
28717 case RS2::EntitySpline:
28718 writeSpline(static_cast<RS_Spline *>(e));
28719 break;
28720 case RS2::EntitySplinePoints:
28721 writeSplinePoints(static_cast<LC_SplinePoints *>(e));
28722 break;
28723 case RS2::EntityParabola:
28724 writeParabola(static_cast<LC_Parabola *>(e));
28725 break;
28726 // case RS2::EntityVertex:
28727 // break;
28728 case RS2::EntityInsert:
28729 writeInsert(static_cast<RS_Insert *>(e));
28730 break;
28731 case RS2::EntityMText:
28732 writeMText(static_cast<RS_MText *>(e));
28733 break;
28734 case RS2::EntityText:
28735 writeText(static_cast<RS_Text *>(e));
28736 break;
28737 case RS2::EntityDimLinear:
28738 case RS2::EntityDimOrdinate:
28739 case RS2::EntityDimAligned:
28740 case RS2::EntityDimAngular:
28741 case RS2::EntityDimRadial:
28742 case RS2::EntityDimDiametric:
28743 case RS2::EntityDimArc:
28744 writeDimension(static_cast<RS_Dimension *>(e));
28745 break;
28746 case RS2::EntityDimLeader:
28747 writeLeader(static_cast<RS_Leader *>(e));
28748 break;
28749 case RS2::EntityTolerance:
28750 writeTolerance(static_cast<LC_Tolerance *>(e));
28751 break;
28752 case RS2::EntityHatch:
28753 writeHatch(static_cast<RS_Hatch *>(e));
28754 break;
28755 case RS2::EntityImage:
28756 writeImage(static_cast<RS_Image *>(e));
28757 break;
28758 case RS2::EntityWipeout:
28759 writeWipeout(static_cast<LC_Wipeout *>(e));
28760 break;
28761 case RS2::EntityMLeader:
28762 production = writeMLeader(static_cast<LC_MLeader *>(e));
28763 break;
28764 default:
28765 break;
28766 }
28767#ifdef DWGSUPPORT1
28768 if (isDwgWrite) {
28769 if (production == DwgEntityProductionResult::SelfCommittedCompound) {
28770 if (failedBefore || m_writeFailed ||
28771 m_dwgW->getSuccessfulEntityWriteCount() <= successfulWritesBefore) {
28772 restoreDwgWriteIdentity(identityCheckpoint);
28773 rollbackDwgEntityWrite(e);
28774 m_writeFailed = true;
28775 }
28776 return;
28777 }
28778 if (production == DwgEntityProductionResult::Failed) {
28779 restoreDwgWriteIdentity(identityCheckpoint);
28780 rollbackDwgEntityWrite(e);
28781 m_writeFailed = true;
28782 return;
28783 }
28784 const bool wroteEntity =
28785 m_dwgW->getSuccessfulEntityWriteCount() > successfulWritesBefore;
28786 if (wroteEntity && !failedBefore && !m_writeFailed) {
28787 const std::uint32_t outputHandle =
28788 m_dwgW->getLastSuccessfulEntityHandle();
28789 if (!recordDwgEntityFrame(outputHandle)) {
28790 (void)m_dwgW->rollbackLastDwgObjectWrite(false);
28791 restoreDwgWriteIdentity(identityCheckpoint);
28792 rollbackDwgEntityWrite(e);
28793 m_writeFailed = true;
28794 return;
28795 }
28796 if (!commitDwgEntityWrite(e)) {
28797 (void)m_dwgW->rollbackLastDwgObjectWrite(false);
28798 restoreDwgWriteIdentity(identityCheckpoint);
28799 rollbackDwgEntityWrite(e);
28800 m_writeFailed = true;
28801 return;
28802 }
28803 if (e->sourceHandle() == 0 &&
28804 !commitDwgGeneratedEntityWrite(outputHandle)) {
28805 (void)m_dwgW->rollbackLastDwgObjectWrite(false);
28806 restoreDwgWriteIdentity(identityCheckpoint);
28807 rollbackDwgEntityWrite(e);
28808 m_writeFailed = true;
28809 return;
28810 }
28811 } else if (wroteEntity) {
28812 // A direct writer call can finish its frame before a later
28813 // reference check latches m_writeFailed. Remove that frame before
28814 // the next callback can observe it.
28815 (void)m_dwgW->rollbackLastDwgObjectWrite(false);
28816 restoreDwgWriteIdentity(identityCheckpoint);
28817 rollbackDwgEntityWrite(e);
28818 m_writeFailed = true;
28819 } else {
28820 rollbackDwgEntityWrite(e);
28821 }
28822 }
28823#endif
28824}
28825
28826void RS_FilterDXFRW::reconstructUnderlays(RS_EntityContainer *container,
28827 std::set<RS_Entity *> &consumed) {
28828 if (!container)
28829 return;
28830 // Single-pass: each polyline carrying LibreCAD_UNDERLAY XDATA
28831 // reconstructs ONE DRW_Underlay (no group/sibling matching like MLINE).
28832 for (RS_Entity *e :
28833 lc::LC_ContainerTraverser{*container, RS2::ResolveNone}.entities()) {
28834 if (e->getFlag(RS2::FlagDeleted))
28835 continue;
28836 if (e->rtti() != RS2::EntityPolyline)
28837 continue;
28838 if (!hasDwgUnderlaySidecar(e))
28839 continue;
28840
28841 DwgUnderlaySidecar sidecar;
28842 if (!parseDwgUnderlaySidecar(e, sidecar)) {
28843 m_writeFailed = true;
28844 continue;
28845 }
28846 DRW_Underlay u = std::move(sidecar.value);
28847 if (u.definitionHandle == 0) {
28848 m_writeFailed = true;
28849 continue;
28850 }
28851 if (m_dwgW != nullptr && m_dwgW->getVersion() <= DRW::AC1021 &&
28852 (!u.inverseClipBoundary.empty() || (u.flags & 0x10) != 0)) {
28853 m_writeFailed = true;
28854 continue;
28855 }
28856 getEntityAttributes(&u, e);
28857
28858 if (!sidecar.hasOriginalClip && !sidecar.noClip) {
28859 // Legacy sidecars did not carry the source clip array. Reconstruct it
28860 // from the preview polyline, but reject a singular transform instead of
28861 // silently replacing a zero scale with one.
28862 if (std::abs(u.scale.x) <= RS_TOLERANCE1.0e-10 ||
28863 std::abs(u.scale.y) <= RS_TOLERANCE1.0e-10) {
28864 m_writeFailed = true;
28865 continue;
28866 }
28867 const auto *pl = static_cast<const RS_Polyline *>(e);
28868 const double cs = std::cos(u.rotation);
28869 const double sn = std::sin(u.rotation);
28870 u.clipBoundary.clear();
28871 for (RS_Entity *sub : *pl) {
28872 if (sub == nullptr || sub->rtti() != RS2::EntityLine) {
28873 m_writeFailed = true;
28874 u.clipBoundary.clear();
28875 break;
28876 }
28877 // Polyline sub-entities are line segments; first endpoint of each
28878 // gives the polygon vertex.
28879 const RS_Vector p = sub->getStartpoint();
28880 const double dx = p.x - u.position.x;
28881 const double dy = p.y - u.position.y;
28882 const double rx = (dx * cs + dy * sn) / u.scale.x;
28883 const double ry = (-dx * sn + dy * cs) / u.scale.y;
28884 if (!std::isfinite(rx) || !std::isfinite(ry)) {
28885 m_writeFailed = true;
28886 u.clipBoundary.clear();
28887 break;
28888 }
28889 u.clipBoundary.emplace_back(rx, ry, 0.0);
28890 }
28891 if (m_writeFailed)
28892 continue;
28893 } else if (sidecar.noClip) {
28894 u.clipBoundary.clear();
28895 }
28896
28897 if (m_dwgW) {
28898 if (e->sourceHandle() == 0) {
28899 // A generated sidecar has no preallocated entity handle and therefore
28900 // cannot enter the class-501-style admission ledger yet.
28901 m_writeFailed = true;
28902 continue;
28903 }
28904 std::uint32_t definitionOutputHandle = 0;
28905 const DwgWriteReferenceStatus definitionStatus =
28906 resolveDwgWriteObjectHandle(u.definitionHandle,
28907 &definitionOutputHandle);
28908 if ((definitionStatus != DwgWriteReferenceStatus::Resolved &&
28909 definitionStatus != DwgWriteReferenceStatus::Reserved &&
28910 definitionStatus != DwgWriteReferenceStatus::Selected &&
28911 definitionStatus != DwgWriteReferenceStatus::Committed) ||
28912 definitionOutputHandle == 0) {
28913 m_writeFailed = true;
28914 continue;
28915 }
28916 const std::uint32_t definitionSourceHandle = u.definitionHandle;
28917 u.definitionHandle = definitionOutputHandle;
28918 const auto identityCheckpoint = checkpointDwgWriteIdentity();
28919 const std::uint64_t transaction =
28920 m_dwgW->beginDwgWriteTransaction(false, false, false);
28921 if (transaction == 0) {
28922 m_writeFailed = true;
28923 continue;
28924 }
28925 const auto rollback = [&]() {
28926 (void)m_dwgW->rollbackDwgWriteTransaction(transaction);
28927 restoreDwgWriteIdentity(identityCheckpoint);
28928 rollbackDwgEntityWrite(e);
28929 m_writeFailed = true;
28930 };
28931 const bool failedBefore = m_writeFailed;
28932 const bool wrote = m_dwgW->writeUnderlay(&u);
28933 if (!wrote || failedBefore || m_writeFailed) {
28934 rollback();
28935 } else {
28936 const std::uint32_t outputHandle =
28937 m_dwgW->getLastSuccessfulEntityHandle();
28938 if (!recordDwgEntityFrame(outputHandle)) {
28939 rollback();
28940 } else {
28941 const bool committed =
28942 e->sourceHandle() == 0
28943 ? commitDwgGeneratedEntityWrite(outputHandle)
28944 : commitDwgEntityWrite(e);
28945 const DwgWriteReferenceReceipt referenceReceipt{
28946 DwgWriteSourceKind::Entity,
28947 e->sourceHandle(),
28948 outputHandle,
28949 "UNDERLAY.DEFINITION",
28950 0,
28951 DwgWriteSourceKind::Object,
28952 definitionSourceHandle,
28953 definitionOutputHandle,
28954 true,
28955 2};
28956 if (!committed || !recordDwgWriteReferenceReceipt(referenceReceipt) ||
28957 !m_dwgW->commitDwgWriteTransaction(transaction)) {
28958 (void)rollbackDwgWriteReferenceReceipt(referenceReceipt);
28959 rollback();
28960 } else {
28961 consumed.insert(e);
28962 }
28963 }
28964 }
28965 } else if (m_dxfW) {
28966 noteDxfWrite(m_dxfW->writeUnderlay(&u));
28967 consumed.insert(e);
28968 }
28969 }
28970}
28971
28972/**
28973 * Writes the given Point entity to the file.
28974 */
28975void RS_FilterDXFRW::writePoint(const RS_Point *p) {
28976 DRW_Point point;
28977 getEntityAttributes(&point, p);
28978 point.basePoint.x = p->getStartpoint().x;
28979 point.basePoint.y = p->getStartpoint().y;
28980 if (m_dwgW) {
28981 if (!m_dwgW->writePoint(&point))
28982 m_writeFailed = true;
28983 return;
28984 }
28985 noteDxfWrite(m_dxfW->writePoint(&point));
28986}
28987
28988/**
28989 * Writes the given Line( entity to the file.
28990 */
28991void RS_FilterDXFRW::writeLine(const RS_Line *l) {
28992 DRW_Line line;
28993 getEntityAttributes(&line, l);
28994 line.basePoint.x = l->getStartpoint().x;
28995 line.basePoint.y = l->getStartpoint().y;
28996 line.secPoint.x = l->getEndpoint().x;
28997 line.secPoint.y = l->getEndpoint().y;
28998 if (m_dwgW) {
28999 if (!m_dwgW->writeLine(&line))
29000 m_writeFailed = true;
29001 return;
29002 }
29003 noteDxfWrite(m_dxfW->writeLine(&line));
29004}
29005
29006/**
29007 * Writes the given circle entity to the file.
29008 */
29009void RS_FilterDXFRW::writeCircle(const RS_Circle *c) {
29010 DRW_Circle circle;
29011 getEntityAttributes(&circle, c);
29012 circle.basePoint.x = c->getCenter().x;
29013 circle.basePoint.y = c->getCenter().y;
29014 circle.radious = c->getRadius();
29015 if (m_dwgW) {
29016 if (!m_dwgW->writeCircle(&circle))
29017 m_writeFailed = true;
29018 return;
29019 }
29020 noteDxfWrite(m_dxfW->writeCircle(&circle));
29021}
29022
29023/**
29024 * Writes the given arc entity to the file.
29025 */
29026void RS_FilterDXFRW::writeArc(const RS_Arc *a) {
29027 DRW_Arc arc;
29028 getEntityAttributes(&arc, a);
29029 arc.basePoint.x = a->getCenter().x;
29030 arc.basePoint.y = a->getCenter().y;
29031 arc.radious = a->getRadius();
29032 if (a->isReversed()) {
29033 arc.staangle = a->getAngle2();
29034 arc.endangle = a->getAngle1();
29035 } else {
29036 arc.staangle = a->getAngle1();
29037 arc.endangle = a->getAngle2();
29038 }
29039 if (m_dwgW) {
29040 if (!m_dwgW->writeArc(&arc))
29041 m_writeFailed = true;
29042 return;
29043 }
29044 noteDxfWrite(m_dxfW->writeArc(&arc));
29045}
29046
29047/**
29048 * Writes the given polyline entity to the file as lwpolyline.
29049 */
29050void RS_FilterDXFRW::writeLWPolyline(RS_Polyline *l) {
29051 // skip if are empty polyline
29052 if (l->isEmpty()) {
29053 return;
29054 }
29055 // version 12 are old style polyline
29056 if (m_version == 1009) {
29057 writePolyline(l);
29058 return;
29059 }
29060 bool has_ellipse = false;
29061 for (RS_Entity *e = l->firstEntity(RS2::ResolveNone); e;
29062 e = l->nextEntity(RS2::ResolveNone)) {
29063 if (e->rtti() == RS2::EntityEllipse) {
29064 has_ellipse = true;
29065 break;
29066 }
29067 }
29068 if (has_ellipse) {
29069 writePolyline(l);
29070 return;
29071 }
29072 DRW_LWPolyline pol;
29073 RS_Entity *currEntity = nullptr;
29074
29075 RS_AtomicEntity *ae = nullptr;
29076 double bulge = 0.0;
29077
29078 lc::LC_ContainerTraverser traverser{*l, RS2::ResolveNone};
29079 for (RS_Entity *e = traverser.first(); e != nullptr; e = traverser.next()) {
29080 currEntity = e;
29081 // nextEntity = traverser.next();
29082
29083 if (!e->isAtomic()) {
29084 continue;
29085 }
29086 ae = static_cast<RS_AtomicEntity *>(e);
29087
29088 // Write vertex:
29089 if (e->rtti() == RS2::EntityArc) {
29090 bulge = static_cast<RS_Arc *>(e)->getBulge();
29091 } else {
29092 bulge = 0.0;
29093 }
29094 pol.addVertex(
29095 DRW_Vertex2D(ae->getStartpoint().x, ae->getStartpoint().y, bulge));
29096 }
29097 if (l->isClosed()) {
29098 pol.flags = 1;
29099 } else {
29100 ae = static_cast<RS_AtomicEntity *>(currEntity);
29101 if (ae->rtti() == RS2::EntityArc) {
29102 bulge = static_cast<RS_Arc *>(ae)->getBulge();
29103 }
29104 pol.addVertex(
29105 DRW_Vertex2D(ae->getEndpoint().x, ae->getEndpoint().y, bulge));
29106 }
29107 pol.vertexnum = pol.vertlist.size();
29108 getEntityAttributes(&pol, l);
29109 auto lwMeta = extractLWPolylineMeta(l);
29110 if (lwMeta) {
29111 pol.width = lwMeta->width;
29112 pol.elevation = lwMeta->elevation;
29113 pol.thickness = lwMeta->thickness;
29114 pol.extPoint = lwMeta->extrusion;
29115 if (lwMeta->vertexCount == static_cast<int>(pol.vertlist.size())) {
29116 for (size_t i = 0; i < pol.vertlist.size(); ++i) {
29117 if (i < lwMeta->startWidths.size())
29118 pol.vertlist[i]->stawidth = lwMeta->startWidths[i];
29119 if (i < lwMeta->endWidths.size())
29120 pol.vertlist[i]->endwidth = lwMeta->endWidths[i];
29121 if (i < lwMeta->identifiers.size())
29122 pol.vertlist[i]->identifier = lwMeta->identifiers[i];
29123 }
29124 }
29125 }
29126 if (m_dwgW) {
29127 if (!m_dwgW->writeLWPolyline(&pol))
29128 m_writeFailed = true;
29129 return;
29130 }
29131 noteDxfWrite(m_dxfW->writeLWPolyline(&pol));
29132}
29133
29134/**
29135 * Writes the given polyline entity to the file (old style).
29136 */
29137void RS_FilterDXFRW::writePolyline(const RS_Polyline *p) {
29138 if (p == nullptr)
29139 return;
29140
29141 DRW_Polyline pol;
29142 if (p->isClosed()) {
29143 pol.flags = 1;
29144 }
29145
29146 RS_Entity *nextEntity = nullptr;
29147 for (RS_Entity *e = p->firstEntity(RS2::ResolveNone); e != nullptr;
29148 e = nextEntity) {
29149 nextEntity = p->nextEntity(RS2::ResolveNone);
29150
29151 if (!e->isAtomic()) {
29152 continue;
29153 }
29154 RS_AtomicEntity *ae = static_cast<RS_AtomicEntity *>(e);
29155
29156 // Write vertex:
29157 double bulge = 0.0;
29158 bool isElliptic = false;
29159 double yRadius = 0.0;
29160 switch (e->rtti()) {
29161 case RS2::EntityLine:
29162 break;
29163 case RS2::EntityArc:
29164 bulge = ((RS_Arc *)e)->getBulge();
29165 break;
29166 case RS2::EntityEllipse: {
29167 // Issue #1946: prepare to write elliptic arcs as RS_Arc
29168 RS_Ellipse *ellipse = static_cast<RS_Ellipse *>(e);
29169 auto pair = RS_Polyline::convertToArcPair(ellipse);
29170 std::unique_ptr<RS_Arc> arc{pair.first};
29171 bulge = arc->getBulge();
29172 yRadius = arc->getRadius() * pair.second;
29173 isElliptic = true;
29174 } break;
29175 default:
29176 // should not happen: unknown entity type
29177 continue;
29178 }
29179 pol.addVertex(
29180 DRW_Vertex(ae->getStartpoint().x, ae->getStartpoint().y, 0.0, bulge));
29181 if (isElliptic) {
29182 // Add flag to indicate the vertex should be elliptic
29183 pol.vertlist.back()->extData.push_back(
29184 std::make_shared<DRW_Variant>(1001, "LibreCad"));
29185 // Keep to ellipse minor radius to allow elliptic creation at loading
29186 pol.vertlist.back()->extData.push_back(
29187 std::make_shared<DRW_Variant>(1040, yRadius));
29188 }
29189 }
29190 getEntityAttributes(&pol, p);
29191 if (auto lwMeta = extractLWPolylineMeta(const_cast<RS_Polyline *>(p))) {
29192 pol.basePoint.z = lwMeta->elevation;
29193 pol.thickness = lwMeta->thickness;
29194 pol.extPoint = lwMeta->extrusion;
29195 if (lwMeta->vertexCount == static_cast<int>(pol.vertlist.size())) {
29196 for (size_t i = 0; i < pol.vertlist.size(); ++i) {
29197 if (i < lwMeta->startWidths.size())
29198 pol.vertlist[i]->stawidth = lwMeta->startWidths[i];
29199 if (i < lwMeta->endWidths.size())
29200 pol.vertlist[i]->endwidth = lwMeta->endWidths[i];
29201 if (i < lwMeta->identifiers.size())
29202 pol.vertlist[i]->identifier = lwMeta->identifiers[i];
29203 }
29204 }
29205 }
29206 if (m_dwgW) {
29207 if (!m_dwgW->writePolyline(&pol))
29208 m_writeFailed = true;
29209 return;
29210 }
29211 noteDxfWrite(m_dxfW->writePolyline(&pol));
29212}
29213
29214/**
29215 * Writes the given spline entity to the file.
29216 */
29217void RS_FilterDXFRW::writeSpline(RS_Spline *s) {
29218 if (s == nullptr) {
29219 return;
29220 }
29221
29222 if (s->getNumberOfControlPoints() < size_t(s->getDegree() + 1)) {
29223 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_ERROR,
29224 "RS_FilterDXF::writeSpline: "
29225 "Discarding spline: not enough control points given.");
29226 return;
29227 }
29228
29229 // version 12 do not support Spline write as polyline
29230 if (m_version == 1009) {
29231 DRW_Polyline pol;
29232 for (RS_Entity *e :
29233 lc::LC_ContainerTraverser{*s, RS2::ResolveNone}.entities()) {
29234 pol.addVertex(
29235 DRW_Vertex(e->getStartpoint().x, e->getStartpoint().y, 0.0, 0.0));
29236 }
29237 if (s->isClosed()) {
29238 pol.flags = 1;
29239 } else {
29240 pol.addVertex(
29241 DRW_Vertex(s->getEndpoint().x, s->getEndpoint().y, 0.0, 0.0));
29242 }
29243 getEntityAttributes(&pol, s);
29244 noteDxfWrite(m_dxfW->writePolyline(&pol));
29245 return;
29246 }
29247
29248 DRW_Spline sp{};
29249
29250 // dxf spline group code=70
29251 // bit coded: 1: closed; 2: periodic; 4: rational; 8: planar; 16:linear
29252 sp.flags = (s->isClosed()) ? 0b1011 : 0b1000;
29253
29254 // write spline control points:
29255 for (const RS_Vector &v : s->getUnwrappedControlPoints()) {
29256 sp.controllist.push_back(std::make_shared<DRW_Coord>(v.x, v.y));
29257 }
29258 sp.weightlist = s->getUnwrappedWeights();
29259 if (std::any_of(sp.weightlist.begin(), sp.weightlist.end(),
29260 differsFromUnitWeight)) {
29261 sp.flags |= 0x04;
29262 }
29263
29264 sp.ncontrol = sp.controllist.size();
29265 sp.degree = s->getDegree();
29266
29267 // knot vector from RS_Spline
29268 sp.knotslist = s->getUnwrappedKnotVector();
29269 sp.nknots = sp.knotslist.size();
29270
29271 getEntityAttributes(&sp, s);
29272 if (m_dwgW) {
29273 if (!m_dwgW->writeSpline(&sp))
29274 m_writeFailed = true;
29275 return;
29276 }
29277 noteDxfWrite(m_dxfW->writeSpline(&sp));
29278}
29279
29280/**
29281 * Writes the given spline entity to the file.
29282 */
29283void RS_FilterDXFRW::writeSplinePoints(LC_SplinePoints *s) {
29284 int nCtrls = s->getNumberOfControlPoints();
29285 auto const &cp = s->getControlPoints();
29286
29287 if (nCtrls < 3) {
29288 if (nCtrls > 1) {
29289 DRW_Line line;
29290 line.basePoint.x = cp.at(0).x;
29291 line.basePoint.y = cp.at(0).y;
29292 line.secPoint.x = cp.at(1).x;
29293 line.secPoint.y = cp.at(1).y;
29294 getEntityAttributes(&line, s);
29295 if (m_dwgW) {
29296 if (!m_dwgW->writeLine(&line))
29297 m_writeFailed = true;
29298 return;
29299 }
29300 noteDxfWrite(m_dxfW->writeLine(&line));
29301 }
29302 return;
29303 }
29304
29305 // version 12 do not support Spline write as polyline
29306 if (m_version == 1009) {
29307 DRW_Polyline pol;
29308 auto const &sp = s->getStrokePoints();
29309
29310 for (size_t i = 0; i < sp.size(); i++) {
29311 pol.addVertex(DRW_Vertex(sp.at(i).x, sp.at(i).y, 0.0, 0.0));
29312 }
29313
29314 if (s->isClosed()) {
29315 pol.flags = 1;
29316 }
29317
29318 getEntityAttributes(&pol, s);
29319 noteDxfWrite(m_dxfW->writePolyline(&pol));
29320 return;
29321 }
29322
29323 DRW_Spline sp;
29324 if (s->isClosed()) {
29325 sp.flags = 11;
29326 } else {
29327 sp.flags = 8;
29328 }
29329
29330 LC_SplinePointsData &data = s->getData();
29331 auto const &fitPoints = data.splinePoints;
29332 const bool writeFitScenario = !data.useControlPoints && fitPoints.size() >= 2;
29333 sp.degree = 2;
29334 sp.nfit = static_cast<std::int32_t>(fitPoints.size());
29335
29336 if (writeFitScenario) {
29337 sp.m_scenario = 2;
29338 sp.m_knotParam = 0;
29339 sp.ncontrol = 0;
29340 sp.nknots = 0;
29341 sp.tolfit = 0.0000001;
29342 const RS_Vector startTangent = fitPoints[1] - fitPoints[0];
29343 const RS_Vector endTangent =
29344 fitPoints.back() - fitPoints[fitPoints.size() - 2];
29345 sp.tgStart = DRW_Coord{startTangent.x, startTangent.y, 0.0};
29346 sp.tgEnd = DRW_Coord{endTangent.x, endTangent.y, 0.0};
29347 } else {
29348 sp.ncontrol = nCtrls;
29349 sp.nknots = nCtrls + 3;
29350 }
29351
29352 // write spline knots:
29353 if (!writeFitScenario) {
29354 for (int i = 1; i <= sp.nknots; i++) {
29355 if (i <= 3) {
29356 sp.knotslist.push_back(0.0);
29357 } else if (i <= nCtrls) {
29358 sp.knotslist.push_back((i - 3.0) / (nCtrls - 2.0));
29359 } else {
29360 sp.knotslist.push_back(1.0);
29361 }
29362 }
29363 }
29364
29365 // write spline control points:
29366 if (!writeFitScenario) {
29367 for (auto const &v : cp) {
29368 sp.controllist.push_back(std::make_shared<DRW_Coord>(v.x, v.y));
29369 }
29370 }
29371
29372 // fit points
29373 for (auto const &v : fitPoints) {
29374 sp.fitlist.push_back(std::make_shared<DRW_Coord>(v.x, v.y));
29375 }
29376
29377 getEntityAttributes(&sp, s);
29378 if (m_dwgW) {
29379 if (!writeFitScenario) {
29380 sp.fitlist.clear();
29381 sp.nfit = 0;
29382 }
29383 if (!m_dwgW->writeSpline(&sp))
29384 m_writeFailed = true;
29385 return;
29386 }
29387 noteDxfWrite(m_dxfW->writeSpline(&sp));
29388}
29389
29390/**
29391 * Writes the given Ellipse entity to the file.
29392 */
29393void RS_FilterDXFRW::writeEllipse(const RS_Ellipse *s) {
29394 // version 12 do not support Ellipse but are
29395 // converted in polyline by library
29396 DRW_Ellipse el;
29397 getEntityAttributes(&el, s);
29398 el.basePoint.x = s->getCenter().x;
29399 el.basePoint.y = s->getCenter().y;
29400 el.secPoint.x = s->getMajorP().x;
29401 el.secPoint.y = s->getMajorP().y;
29402 el.ratio = s->getRatio();
29403 if (s->isReversed()) {
29404 el.staparam = s->getAngle2();
29405 el.endparam = s->getAngle1();
29406 } else {
29407 el.staparam = s->getAngle1();
29408 el.endparam = s->getAngle2();
29409 }
29410 if (m_dwgW) {
29411 if (!m_dwgW->writeEllipse(&el))
29412 m_writeFailed = true;
29413 return;
29414 }
29415 noteDxfWrite(m_dxfW->writeEllipse(&el));
29416}
29417
29418/**
29419 * Write a hyperbola entity as an exact rational quadratic SPLINE.
29420 *
29421 * Uses LC_HyperbolaSpline to create the standard SPLINE representation.
29422 */
29423void RS_FilterDXFRW::writeHyperbola(LC_Hyperbola *h) {
29424 if (h == nullptr || !h->isValid())
29425 return;
29426 DRW_Spline spl;
29427 getEntityAttributes(&spl, h);
29428 if (LC_HyperbolaSpline::hyperbolaToSpline(h->getData(), spl)) {
29429 if (m_dwgW) {
29430 if (!m_dwgW->writeSpline(&spl))
29431 m_writeFailed = true;
29432 return;
29433 }
29434 if (m_dxfW)
29435 noteDxfWrite(m_dxfW->writeSpline(&spl));
29436 }
29437}
29438
29439/**
29440 * Write a parabola entity as its canonical non-rational quadratic SPLINE.
29441 */
29442void RS_FilterDXFRW::writeParabola(LC_Parabola *p) {
29443 if (p == nullptr || !p->getData().isValid())
29444 return;
29445
29446 DRW_Spline spl;
29447 getEntityAttributes(&spl, p);
29448 if (LC_ParabolaSpline::parabolaToSpline(p->getData(), spl)) {
29449 if (m_dwgW) {
29450 if (!m_dwgW->writeSpline(&spl))
29451 m_writeFailed = true;
29452 return;
29453 }
29454 if (m_dxfW)
29455 noteDxfWrite(m_dxfW->writeSpline(&spl));
29456 }
29457}
29458
29459/**
29460 * Writes the given block insert entity to the file.
29461 */
29462void RS_FilterDXFRW::writeInsert(const RS_Insert *i) {
29463 DRW_Insert in;
29464 getEntityAttributes(&in, i);
29465 in.basePoint.x = i->getInsertionPoint().x;
29466 in.basePoint.y = i->getInsertionPoint().y;
29467 in.basePoint.z = i->getInsertionPoint().z;
29468 in.name = i->getName().toUtf8().data();
29469 // Inserts that bypass the RS_InsertData constructor - default constructed
29470 // data, direct field assignment, or a file that already carried a bad
29471 // value - are corrected at the boundary rather than written out.
29472 const RS_Vector scale = RS_InsertData::usableScale(i->getScale());
29473 in.xscale = scale.x;
29474 in.yscale = scale.y;
29475 in.zscale = scale.z;
29476 in.angle = i->getAngle();
29477 in.colcount = i->getCols();
29478 in.rowcount = i->getRows();
29479 in.colspace = i->getSpacing().x;
29480 in.rowspace = i->getSpacing().y;
29481 const RS_Vector extrusion = i->getData().extrusion;
29482 in.extPoint = DRW_Coord(extrusion.x, extrusion.y, extrusion.z);
29483
29484#ifdef DWGSUPPORT1
29485 DwgWriteReferenceStatus blockStatus = DwgWriteReferenceStatus::Missing;
29486 std::uint32_t blockRecordHandle = 0;
29487 DwgWriteSourceKind blockTargetKind = DwgWriteSourceKind::Block;
29488 std::uint32_t blockSourceHandle = 0;
29489 if (m_dwgW != nullptr) {
29490 if (!isDwgInsertAdmitted(i)) {
29491 m_writeFailed = true;
29492 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
29493 "RS_FilterDXFRW: DWG INSERT was not admitted by the "
29494 "frozen block graph %s",
29495 in.name.c_str());
29496 return;
29497 }
29498 blockStatus = resolveDwgWriteBlockName(normalizeDwgTableName(in.name),
29499 &blockRecordHandle);
29500 if (blockStatus != DwgWriteReferenceStatus::LegacySpaceAlias &&
29501 blockStatus != DwgWriteReferenceStatus::Resolved &&
29502 blockStatus != DwgWriteReferenceStatus::Selected &&
29503 blockStatus != DwgWriteReferenceStatus::Committed) {
29504 m_writeFailed = true;
29505 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
29506 "RS_FilterDXFRW: rejecting DWG INSERT with unresolved "
29507 "block name %s (status=%d)",
29508 in.name.c_str(), static_cast<int>(blockStatus));
29509 return;
29510 }
29511 in.blockRecH.ref = blockRecordHandle;
29512 if (blockStatus == DwgWriteReferenceStatus::LegacySpaceAlias) {
29513 blockTargetKind = DwgWriteSourceKind::FixedStructural;
29514 } else {
29515 blockSourceHandle = sourceDwgWriteIdentityHandle(
29516 DwgWriteSourceKind::Block, blockRecordHandle);
29517 }
29518 }
29519#endif
29520
29521 // RS_Insert cannot retain ATTRIB children internally: update() rebuilds
29522 // its expanded block cache. Reconstruct the native sequence from the
29523 // hidden/rendered sibling text records tagged during import, then let the
29524 // libdxfrw writer allocate and link the child handles.
29525 if (i->getParent() != nullptr) {
29526 std::vector<std::pair<InsertAttribSidecar, RS_Entity *>> attributes;
29527 for (RS_Entity *candidate : *i->getParent()) {
29528 if (candidate == nullptr || candidate == i)
29529 continue;
29530 const auto sidecar = extractInsertAttribSidecar(candidate);
29531 if (!sidecar || sidecar->ownerId != i->getId())
29532 continue;
29533 if (candidate->rtti() != RS2::EntityText &&
29534 candidate->rtti() != RS2::EntityMText)
29535 continue;
29536 attributes.emplace_back(*sidecar, candidate);
29537 }
29538 std::sort(attributes.begin(), attributes.end(),
29539 [](const auto &lhs, const auto &rhs) {
29540 if (lhs.first.ordinal != rhs.first.ordinal)
29541 return lhs.first.ordinal < rhs.first.ordinal;
29542 return lhs.second->getId() < rhs.second->getId();
29543 });
29544
29545 for (const auto &[sidecar, candidate] : attributes) {
29546 DRW_Attrib attrib;
29547 getEntityAttributes(&attrib, candidate);
29548 attrib.tag = sidecar.tag;
29549 attrib.attribFlags = static_cast<std::uint8_t>(sidecar.flags);
29550 attrib.m_fieldLength = sidecar.fieldLength;
29551 attrib.attVersion = static_cast<std::uint8_t>(sidecar.attVersion);
29552 attrib.m_attributeType = static_cast<std::uint8_t>(sidecar.attributeType);
29553 attrib.lockPosition = sidecar.lockPosition;
29554 if (sidecar.hasValue)
29555 attrib.text = sidecar.value;
29556 if (sidecar.hasBasePoint)
29557 attrib.basePoint = sidecar.basePoint;
29558 if (sidecar.hasSecPoint)
29559 attrib.secPoint = sidecar.secPoint;
29560 if (sidecar.hasExtPoint)
29561 attrib.extPoint = sidecar.extPoint;
29562 if (sidecar.hasThickness)
29563 attrib.thickness = sidecar.thickness;
29564
29565 if (candidate->rtti() == RS2::EntityMText) {
29566 const auto *source = static_cast<const RS_MText *>(candidate);
29567 const RS_MTextData data = source->getData();
29568 auto mtext = std::make_unique<DRW_MText>();
29569 mtext->basePoint =
29570 DRW_Coord(data.insertionPoint.x, data.insertionPoint.y,
29571 sidecar.hasBasePoint ? sidecar.basePoint.z : 0.0);
29572 mtext->secPoint = mtext->basePoint;
29573 mtext->extPoint =
29574 sidecar.hasExtPoint ? sidecar.extPoint : DRW_Coord{0.0, 0.0, 1.0};
29575 mtext->height = data.height;
29576 mtext->widthscale = data.width;
29577 mtext->text = data.text.toUtf8().toStdString();
29578 mtext->style = data.style.toUtf8().toStdString();
29579 mtext->angle = data.angle * 180.0 / M_PI3.14159265358979323846;
29580 mtext->textgen = static_cast<int>(data.valign) * 3 +
29581 static_cast<int>(data.halign) + 1;
29582 mtext->alignH = static_cast<DRW_Text::HAlign>(
29583 data.drawingDirection == RS_MTextData::TopToBottom ? 3 : 1);
29584 mtext->alignV = static_cast<DRW_Text::VAlign>(
29585 data.lineSpacingStyle == RS_MTextData::Exact ? 2 : 1);
29586 mtext->interlin = data.lineSpacingFactor;
29587 mtext->linespacingStyle =
29588 data.lineSpacingStyle == RS_MTextData::Exact ? 2 : 1;
29589 if (sidecar.hasMText) {
29590 const auto &saved = sidecar.mtext;
29591 if (saved.hasText)
29592 mtext->text = saved.text;
29593 mtext->textgen = saved.textgen;
29594 mtext->alignH = static_cast<DRW_Text::HAlign>(saved.alignH);
29595 mtext->alignV = static_cast<DRW_Text::VAlign>(saved.alignV);
29596 mtext->linespacingStyle =
29597 static_cast<std::uint16_t>(saved.linespacingStyle);
29598 mtext->interlin = saved.interlin;
29599 mtext->m_backgroundFlags = saved.backgroundFlags;
29600 mtext->m_backgroundScale = saved.backgroundScale;
29601 mtext->m_backgroundColor = saved.backgroundColor;
29602 mtext->m_backgroundTransparency = saved.backgroundTransparency;
29603 mtext->m_r2018IsNotAnnotative = saved.r2018IsNotAnnotative;
29604 mtext->m_r2018Version =
29605 static_cast<std::uint16_t>(saved.r2018Version);
29606 mtext->m_r2018DefaultFlag = saved.r2018DefaultFlag;
29607 mtext->m_r2018AppIdHandle = saved.r2018AppIdHandle;
29608 mtext->m_r2018Attachment = saved.r2018Attachment;
29609 mtext->m_r2018XAxisDir = saved.r2018XAxisDir;
29610 mtext->m_r2018InsertionPoint = saved.r2018InsertionPoint;
29611 mtext->m_r2018RectWidth = saved.r2018RectWidth;
29612 mtext->m_r2018RectHeight = saved.r2018RectHeight;
29613 mtext->m_r2018ExtentsHeight = saved.r2018ExtentsHeight;
29614 mtext->m_r2018ExtentsWidth = saved.r2018ExtentsWidth;
29615 mtext->m_r2018ColumnType =
29616 static_cast<std::uint16_t>(saved.r2018ColumnType);
29617 mtext->m_r2018ColumnCount = saved.r2018ColumnCount;
29618 mtext->m_r2018ColumnWidth = saved.r2018ColumnWidth;
29619 mtext->m_r2018ColumnGutter = saved.r2018ColumnGutter;
29620 mtext->m_r2018ColumnAutoHeight = saved.r2018ColumnAutoHeight;
29621 mtext->m_r2018ColumnFlowReversed = saved.r2018ColumnFlowReversed;
29622 mtext->m_r2018ColumnHeights = saved.r2018ColumnHeights;
29623 mtext->m_r2018AnnotativeData = saved.annotativeData;
29624 mtext->m_r2018AnnotativeUnknown =
29625 static_cast<std::uint16_t>(saved.annotativeUnknown);
29626 mtext->m_r2018AnnotativeAppHandle = saved.annotativeAppHandle;
29627 mtext->m_r2018ReallyLocked = saved.reallyLocked;
29628 mtext->setHasDataStorageBinaryData(saved.hasDataStorageBinaryData);
29629 mtext->reactorHandles = saved.reactorHandles;
29630 mtext->xDictHandle = saved.xDictHandle;
29631 mtext->color = saved.color;
29632 mtext->color24 = saved.color24;
29633 mtext->colorName = saved.colorName;
29634 mtext->transparency = saved.transparency;
29635 mtext->setDwgAcDbColorHandle(saved.hasAcDbColorHandle,
29636 saved.acDbColorHandle);
29637 mtext->ltypeScale = saved.ltypeScale;
29638 mtext->lineType = saved.lineType;
29639 mtext->visible = saved.visible;
29640 mtext->lWeight =
29641 static_cast<DRW_LW_Conv::lineWidth>(saved.lineWeight);
29642 mtext->material = saved.material;
29643 mtext->shadow = static_cast<DRW::ShadowMode>(saved.shadow);
29644 mtext->shadowHandle = saved.shadowHandle;
29645 mtext->plotStyle = static_cast<int>(saved.plotStyle);
29646 mtext->fullVisualStyleHandle = saved.fullVisualStyleHandle;
29647 mtext->faceVisualStyleHandle = saved.faceVisualStyleHandle;
29648 mtext->edgeVisualStyleHandle = saved.edgeVisualStyleHandle;
29649 mtext->setDwgLayerHandle(saved.layerHandle);
29650 mtext->setDwgLinetypeHandle(saved.linetypeHandle);
29651 DRW_MText cleanMText;
29652 cleanMText.extData = std::move(saved.xdata);
29653 removeInsertAttribSidecar(cleanMText);
29654 mtext->extData = std::move(cleanMText.extData);
29655 mtext->styleH.ref = saved.styleHandle;
29656 if (saved.hasBasePoint)
29657 mtext->basePoint = saved.basePoint;
29658 if (saved.hasSecPoint)
29659 mtext->secPoint = saved.secPoint;
29660 if (saved.hasExtPoint)
29661 mtext->extPoint = saved.extPoint;
29662 }
29663 if (!sidecar.hasValue)
29664 attrib.text = mtext->text;
29665 attrib.mtext = std::move(mtext);
29666 } else {
29667 const auto *source = static_cast<const RS_Text *>(candidate);
29668 attrib.basePoint.x = source->getInsertionPoint().x;
29669 attrib.basePoint.y = source->getInsertionPoint().y;
29670 attrib.secPoint.x = source->getSecondPoint().x;
29671 attrib.secPoint.y = source->getSecondPoint().y;
29672 attrib.height = source->getHeight();
29673 attrib.widthscale = source->getWidthRel();
29674 attrib.text = source->getText().toUtf8().toStdString();
29675 attrib.style = source->getStyle().toUtf8().toStdString();
29676 attrib.angle = source->getAngle() * 180.0 / M_PI3.14159265358979323846;
29677 attrib.alignH = static_cast<DRW_Text::HAlign>(
29678 static_cast<int>(source->getHAlign()));
29679 attrib.alignV = static_cast<DRW_Text::VAlign>(
29680 static_cast<int>(source->getVAlign()));
29681 switch (source->getTextGeneration()) {
29682 case RS_TextData::Backward:
29683 attrib.textgen = 2;
29684 break;
29685 case RS_TextData::UpsideDown:
29686 attrib.textgen = 4;
29687 break;
29688 case RS_TextData::None:
29689 default:
29690 attrib.textgen = 0;
29691 break;
29692 }
29693 }
29694 removeInsertAttribSidecar(attrib);
29695 in.attlist.push_back(std::make_shared<DRW_Attrib>(std::move(attrib)));
29696 }
29697 }
29698 if (m_dwgW) {
29699 if (!m_dwgW->writeInsert(&in)) {
29700 m_writeFailed = true;
29701 return;
29702 }
29703#ifdef DWGSUPPORT1
29704 const DwgWriteReferenceReceipt receipt{DwgWriteSourceKind::Entity,
29705 i->sourceHandle(),
29706 in.handle,
29707 "INSERT.BLOCK_RECORD",
29708 0,
29709 blockTargetKind,
29710 blockSourceHandle,
29711 blockRecordHandle,
29712 true,
29713 2};
29714 if (!recordDwgWriteReferenceReceipt(receipt))
29715 m_writeFailed = true;
29716#endif
29717 return;
29718 }
29719 noteDxfWrite(m_dxfW->writeInsert(&in));
29720}
29721
29722/**
29723 * Writes the given mText entity to the file.
29724 */
29725void RS_FilterDXFRW::writeMText(const RS_MText *t) {
29726 DRW_Text *text;
29727 DRW_Text txt1;
29728 DRW_MText txt2;
29729
29730 if (m_version == 1009) {
29731 text = &txt1;
29732 } else {
29733 text = &txt2;
29734 }
29735
29736 getEntityAttributes(text, t);
29737 if (m_version != 1009) {
29738 if (const auto ocs = extractMTextOcsSidecar(t)) {
29739 text->basePoint.z = ocs->elevation;
29740 text->extPoint = ocs->extrusion;
29741 static_cast<DRW_MText *>(text)->secPoint = ocs->xAxis;
29742 removeMTextOcsSidecar(*text);
29743 }
29744 }
29745 text->basePoint.x = t->getInsertionPoint().x;
29746 text->basePoint.y = t->getInsertionPoint().y;
29747 text->height = t->getHeight();
29748 text->angle = t->getAngle() * 180 / M_PI3.14159265358979323846;
29749 text->style = t->getStyle().toStdString();
29750
29751 if (m_version == 1009) {
29752 if (t->getHAlign() == RS_MTextData::HALeft) {
29753 text->alignH = DRW_Text::HLeft;
29754 } else if (t->getHAlign() == RS_MTextData::HACenter) {
29755 text->alignH = DRW_Text::HCenter;
29756 } else if (t->getHAlign() == RS_MTextData::HARight) {
29757 text->alignH = DRW_Text::HRight;
29758 }
29759 if (t->getVAlign() == RS_MTextData::VATop) {
29760 text->alignV = DRW_Text::VTop;
29761 } else if (t->getVAlign() == RS_MTextData::VAMiddle) {
29762 text->alignV = DRW_Text::VMiddle;
29763 } else if (t->getVAlign() == RS_MTextData::VABottom) {
29764 text->alignV = DRW_Text::VBaseLine;
29765 }
29766#if QT_VERSION((6<<16)|(9<<8)|(0)) >= QT_VERSION_CHECK(5, 15, 0)((5<<16)|(15<<8)|(0))
29767 QStringList txtList = t->getText().split('\n', Qt::KeepEmptyParts);
29768#else
29769 QStringList txtList = t->getText().split('\n', QString::KeepEmptyParts);
29770#endif
29771 double dist = t->getLineSpacingFactor() * 5 * t->getHeight() / 3;
29772 bool setSec = false;
29773 if (text->alignH != DRW_Text::HLeft ||
29774 text->alignV != DRW_Text::VBaseLine) {
29775 text->secPoint.x = t->getInsertionPoint().x;
29776 text->secPoint.y = t->getInsertionPoint().y;
29777 setSec = true;
29778 }
29779 if (text->alignV == DRW_Text::VTop) {
29780 dist = dist * -1;
29781 }
29782 for (int i = 0; i < txtList.size(); ++i) {
29783 if (!txtList.at(i).isEmpty()) {
29784 text->text = toDxfString(txtList.at(i)).toUtf8().data();
29785 RS_Vector inc = RS_Vector::polar(dist * i, t->getAngle() + M_PI_21.57079632679489661923);
29786 if (setSec) {
29787 text->secPoint.x += inc.x;
29788 text->secPoint.y += inc.y;
29789 } else {
29790 text->basePoint.x += inc.x;
29791 text->basePoint.y += inc.y;
29792 }
29793 noteDxfWrite(m_dxfW->writeText(text));
29794 }
29795 }
29796 } else {
29797 if (t->getHAlign() == RS_MTextData::HALeft) {
29798 text->textgen = 1;
29799 } else if (t->getHAlign() == RS_MTextData::HACenter) {
29800 text->textgen = 2;
29801 } else if (t->getHAlign() == RS_MTextData::HARight) {
29802 text->textgen = 3;
29803 }
29804 if (t->getVAlign() == RS_MTextData::VAMiddle) {
29805 text->textgen += 3;
29806 } else if (t->getVAlign() == RS_MTextData::VABottom) {
29807 text->textgen += 6;
29808 }
29809 // DXF MTEXT group 72: 1=LeftToRight, 3=TopToBottom, 5=ByStyle.
29810 // RightToLeft has no DXF encoding — fall through to LeftToRight so
29811 // other readers see a well-defined direction. The RTL preference is
29812 // round-tripped via XDATA (app id "LibreCad", code 1071) below; on
29813 // re-import we restore the flag if the marker is present, otherwise
29814 // the entity reads as LTR.
29815 switch (t->getDrawingDirection()) {
29816 case RS_MTextData::TopToBottom:
29817 text->alignH = static_cast<DRW_Text::HAlign>(3);
29818 break;
29819 case RS_MTextData::ByStyle:
29820 text->alignH = static_cast<DRW_Text::HAlign>(5);
29821 break;
29822 case RS_MTextData::LeftToRight:
29823 case RS_MTextData::RightToLeft:
29824 default:
29825 text->alignH = static_cast<DRW_Text::HAlign>(1);
29826 break;
29827 }
29828 if (t->getDrawingDirection() == RS_MTextData::RightToLeft) {
29829 text->extData.push_back(
29830 std::make_shared<DRW_Variant>(1001, std::string("LibreCad")));
29831 text->extData.push_back(
29832 std::make_shared<DRW_Variant>(1071, std::int32_t{1}));
29833 }
29834 if (t->getLineSpacingStyle() == RS_MTextData::AtLeast) {
29835 text->alignV = static_cast<DRW_Text::VAlign>(1);
29836 } else {
29837 text->alignV = static_cast<DRW_Text::VAlign>(2);
29838 }
29839
29840 text->text = toDxfString(t->getText()).toUtf8().data();
29841 // text->widthscale =t->getWidth();
29842 text->widthscale = t->getUsedTextWidth(); // getSize().x;
29843 txt2.interlin = t->getLineSpacingFactor();
29844
29845 if (m_dwgW) {
29846 if (!m_dwgW->writeMText(static_cast<DRW_MText *>(text)))
29847 m_writeFailed = true;
29848 return;
29849 }
29850 noteDxfWrite(m_dxfW->writeMText(static_cast<DRW_MText *>(text)));
29851 }
29852}
29853
29854/**
29855 * Writes the given Text entity to the file.
29856 */
29857void RS_FilterDXFRW::writeText(RS_Text *t) {
29858 DRW_Text text;
29859
29860 getEntityAttributes(&text, t);
29861 if (const auto ocs = extractTextOcsSidecar(t)) {
29862 text.basePoint.z = ocs->baseElevation;
29863 text.secPoint.z = ocs->alignmentElevation;
29864 text.extPoint = ocs->extrusion;
29865 text.thickness = ocs->thickness;
29866 removeTextOcsSidecar(text);
29867 }
29868 text.basePoint.x = t->getInsertionPoint().x;
29869 text.basePoint.y = t->getInsertionPoint().y;
29870 text.height = t->getHeight();
29871 text.angle = t->getAngle() * 180 / M_PI3.14159265358979323846;
29872 text.style = t->getStyle().toStdString();
29873 text.alignH = (DRW_Text::HAlign)t->getHAlign();
29874 text.alignV = (DRW_Text::VAlign)t->getVAlign();
29875 text.widthscale = t->getWidthRel();
29876
29877 if (text.alignV != DRW_Text::VBaseLine || text.alignH != DRW_Text::HLeft) {
29878 // if (text.alignV != DRW_Text::VBaseLine || text.alignH ==
29879 // DRW_Text::HMiddle) {
29880 // if (text.alignH != DRW_Text::HLeft) {
29881 if (text.alignH == DRW_Text::HAligned || text.alignH == DRW_Text::HFit) {
29882 text.secPoint.x = t->getSecondPoint().x;
29883 text.secPoint.y = t->getSecondPoint().y;
29884 } else {
29885 text.secPoint.x = t->getInsertionPoint().x;
29886 text.secPoint.y = t->getInsertionPoint().y;
29887 }
29888 }
29889
29890 /* if (text.alignH == DRW_Text::HAligned || text.alignH == DRW_Text::HFit)
29891 { text.secPoint.x = t->getSecondPoint().x; text.secPoint.y =
29892 t->getSecondPoint().y;
29893 }*/
29894
29895 if (!t->getText().isEmpty()) {
29896 text.text = toDxfString(t->getText()).toUtf8().data();
29897 if (m_dwgW) {
29898 if (!m_dwgW->writeText(&text))
29899 m_writeFailed = true;
29900 return;
29901 }
29902 noteDxfWrite(m_dxfW->writeText(&text));
29903 }
29904}
29905
29906/**
29907 * Writes the given dimension entity to the file.
29908 */
29909void RS_FilterDXFRW::writeDimension(RS_Dimension *d) {
29910 QString blkName;
29911 if (m_noNameBlock.contains(d)) {
29912 blkName = m_noNameBlock.take(d);
29913 }
29914
29915 // version 12 are inserts of *D blocks
29916 if (m_version == 1009) {
29917 if (!blkName.isEmpty()) {
29918 DRW_Insert in;
29919 getEntityAttributes(&in, d);
29920 in.basePoint.x = in.basePoint.y = 0.0;
29921 in.basePoint.z = 0.0;
29922 in.name = blkName.toStdString();
29923 in.xscale = in.yscale = 1.0;
29924 in.zscale = 1.0;
29925 in.angle = 0.0;
29926 in.colcount = in.rowcount = 1;
29927 in.colspace = in.rowspace = 0.0;
29928 noteDxfWrite(m_dxfW->writeInsert(&in));
29929 }
29930 return;
29931 }
29932
29933 DRW_Dimension *dim;
29934 int attachmentPoint = 1;
29935 if (d->getHAlign() == RS_MTextData::HALeft) {
29936 attachmentPoint = 1;
29937 } else if (d->getHAlign() == RS_MTextData::HACenter) {
29938 attachmentPoint = 2;
29939 } else if (d->getHAlign() == RS_MTextData::HARight) {
29940 attachmentPoint = 3;
29941 }
29942 if (d->getVAlign() == RS_MTextData::VATop) {
29943 attachmentPoint += 0;
29944 } else if (d->getVAlign() == RS_MTextData::VAMiddle) {
29945 attachmentPoint += 3;
29946 } else if (d->getVAlign() == RS_MTextData::VABottom) {
29947 attachmentPoint += 6;
29948 }
29949
29950 switch (d->rtti()) {
29951 case RS2::EntityDimAligned: {
29952 auto *da = static_cast<RS_DimAligned *>(d);
29953 auto dd = new DRW_DimAligned();
29954 dim = dd;
29955 dim->type = 1 + 32;
29956 dd->setDef1Point(
29957 DRW_Coord(da->getExtensionPoint1().x, da->getExtensionPoint1().y, 0.0));
29958 dd->setDef2Point(
29959 DRW_Coord(da->getExtensionPoint2().x, da->getExtensionPoint2().y, 0.0));
29960 break;
29961 }
29962 case RS2::EntityDimDiametric: {
29963 auto *dr = static_cast<RS_DimDiametric *>(d);
29964 auto dd = new DRW_DimDiametric();
29965 dim = dd;
29966 dim->type = 3 + 32;
29967 dd->setDiameter1Point(
29968 DRW_Coord(dr->getDefinitionPoint().x, dr->getDefinitionPoint().y, 0.0));
29969 dd->setLeaderLength(dr->getLeader());
29970 break;
29971 }
29972 case RS2::EntityDimRadial: {
29973 auto *dr = static_cast<RS_DimRadial *>(d);
29974 auto *dd = new DRW_DimRadial();
29975 dim = dd;
29976 dim->type = 4 + 32;
29977 dd->setDiameterPoint(
29978 DRW_Coord(dr->getDefinitionPoint().x, dr->getDefinitionPoint().y, 0.0));
29979 dd->setLeaderLength(dr->getLeader());
29980 break;
29981 }
29982 case RS2::EntityDimAngular: {
29983 auto *da = static_cast<RS_DimAngular *>(d);
29984 if (da->getDefinitionPoint3() == da->getGenericData().definitionPoint) {
29985 auto *dd = new DRW_DimAngular3p();
29986 dim = dd;
29987 dim->type = 5 + 32;
29988 dd->setFirstLine(DRW_Coord(da->getDefinitionPoint1().x,
29989 da->getDefinitionPoint1().y, 0.0)); // 13
29990 dd->setSecondLine(DRW_Coord(da->getDefinitionPoint2().x,
29991 da->getDefinitionPoint2().y, 0.0)); // 14
29992 dd->SetVertexPoint(DRW_Coord(da->getDefinitionPoint3().x,
29993 da->getDefinitionPoint3().y, 0.0)); // 15
29994 dd->setDimPoint(DRW_Coord(da->getDefinitionPoint().x,
29995 da->getDefinitionPoint().y, 0.0)); // 10
29996 } else {
29997 auto *dd = new DRW_DimAngular();
29998 dim = dd;
29999 dim->type = 2 + 32;
30000 dd->setFirstLine1(DRW_Coord(da->getDefinitionPoint1().x,
30001 da->getDefinitionPoint1().y, 0.0)); // 13
30002 dd->setFirstLine2(DRW_Coord(da->getDefinitionPoint2().x,
30003 da->getDefinitionPoint2().y, 0.0)); // 14
30004 dd->setSecondLine1(DRW_Coord(da->getDefinitionPoint3().x,
30005 da->getDefinitionPoint3().y, 0.0)); // 15
30006 dd->setDimPoint(DRW_Coord(da->getDefinitionPoint4().x,
30007 da->getDefinitionPoint4().y, 0.0)); // 16
30008 }
30009 break;
30010 }
30011 case RS2::EntityDimOrdinate: {
30012 auto *da = static_cast<LC_DimOrdinate *>(d);
30013 auto *dd = new DRW_DimOrdinate();
30014 dim = dd;
30015 dd->type = 6 + 32;
30016 auto dimOridinateData = da->getEData();
30017 if (dimOridinateData.ordinateForX) {
30018 dd->type = 6 + 64;
30019 }
30020 dd->setOriginPoint(
30021 DRW_Coord(da->getDefinitionPoint().x, da->getDefinitionPoint().y, 0.0));
30022 dd->setSecondLine(
30023 DRW_Coord(da->getLeaderEndPoint().x, da->getLeaderEndPoint().y, 0.0));
30024 dd->setFirstLine(
30025 DRW_Coord(da->getFeaturePoint().x, da->getFeaturePoint().y, 0.0));
30026 break;
30027 }
30028 case RS2::EntityDimArc: {
30029 auto *da = static_cast<LC_DimArc *>(d);
30030 auto *dd = new DRW_DimArc();
30031 dim = dd;
30032 RS_Vector centre = da->getCenter();
30033 double r = da->getRadius();
30034 double a0 = da->getStartAngle();
30035 double a1 = da->getEndAngle();
30036 double amid = (a0 + a1) / 2.0;
30037 dd->setArcCenter(DRW_Coord(centre.x, centre.y, 0.));
30038 dd->setArcDefPoint(DRW_Coord(centre.x + r * std::cos(amid),
30039 centre.y + r * std::sin(amid), 0.));
30040 dd->setExtLine1(DRW_Coord(centre.x + r * std::cos(a0),
30041 centre.y + r * std::sin(a0), 0.));
30042 dd->setExtLine2(DRW_Coord(centre.x + r * std::cos(a1),
30043 centre.y + r * std::sin(a1), 0.));
30044 dd->arcStartAngle = a0;
30045 dd->arcEndAngle = a1;
30046 dd->arcSymbol = da->getArcSymbol();
30047 dd->isPartial = da->getIsPartial();
30048 dd->hasLeader = da->getHasLeader();
30049 if (da->getLeaderPt1().valid)
30050 dd->setLeaderPt1(
30051 DRW_Coord(da->getLeaderPt1().x, da->getLeaderPt1().y, 0.));
30052 if (da->getLeaderPt2().valid)
30053 dd->leaderPt2 = DRW_Coord(da->getLeaderPt2().x, da->getLeaderPt2().y, 0.);
30054 break;
30055 }
30056 default: {
30057 // default to DimLinear
30058 auto dl = static_cast<RS_DimLinear *>(d);
30059 auto dd = new DRW_DimLinear();
30060 dim = dd;
30061 dim->type = 0 + 32;
30062 dd->setDef1Point(
30063 DRW_Coord(dl->getExtensionPoint1().x, dl->getExtensionPoint1().y, 0.0));
30064 dd->setDef2Point(
30065 DRW_Coord(dl->getExtensionPoint2().x, dl->getExtensionPoint2().y, 0.0));
30066 dd->setAngle(RS_Math::rad2deg(dl->getAngle()));
30067 dd->setOblique(dl->getOblique());
30068 break;
30069 }
30070 }
30071 getEntityAttributes(dim, d);
30072 dim->setDefPoint(
30073 DRW_Coord(d->getDefinitionPoint().x, d->getDefinitionPoint().y, 0));
30074 dim->setTextPoint(
30075 DRW_Coord(d->getMiddleOfText().x, d->getMiddleOfText().y, 0));
30076 dim->setStyle(d->getStyle().toUtf8().data());
30077 dim->setAlign(attachmentPoint);
30078 dim->setTextLineStyle(d->getLineSpacingStyle());
30079 dim->setText(toDxfString(d->getLabel(false)).toUtf8().data());
30080 dim->setTextLineFactor(d->getLineSpacingFactor());
30081 dim->setHDir(d->getHDir());
30082 dim->setFlipArrow1(d->isFlipArrow1());
30083 dim->setFlipArrow2(d->isFlipArrow2());
30084 if (d->hasUserDefinedTextLocation()) {
30085 dim->type = dim->type + 128;
30086 }
30087 if (!blkName.isEmpty()) {
30088 dim->setName(blkName.toStdString());
30089 }
30090 if (m_dwgW) {
30091 bool hasPredictedClass = false;
30092 if (d->rtti() == RS2::EntityDimArc && d->sourceHandle() != 0) {
30093 const auto entityIt = m_dwgWriteEntityHandleByEntity.find(d);
30094 std::uint16_t classNumber = 0;
30095 bool committed = false;
30096 hasPredictedClass = entityIt != m_dwgWriteEntityHandleByEntity.end() &&
30097 findDwgClassFrameAdmission(
30098 DwgWriteSourceKind::Entity, d->sourceHandle(),
30099 entityIt->second, classNumber, committed) &&
30100 !committed;
30101 if (hasPredictedClass &&
30102 !m_dwgW->setDwgObjectFrameProvenance(classNumber, 0, 0)) {
30103 m_writeFailed = true;
30104 delete dim;
30105 return;
30106 }
30107 }
30108 if (!m_dwgW->writeDimension(dim)) {
30109 if (hasPredictedClass)
30110 m_dwgW->clearDwgObjectFrameProvenance();
30111 m_writeFailed = true;
30112 }
30113 delete dim;
30114 return;
30115 }
30116 LC_DimStyle *override = d->getDimStyleOverride();
30117 if (override != nullptr) {
30118 LC_ExtEntityData extEntityData;
30119 addDimStyleOverrideToExtendedData(&extEntityData, override);
30120 fillEntityExtData(dim->extData, &extEntityData);
30121 }
30122 noteDxfWrite(m_dxfW->writeDimension(dim));
30123 delete dim;
30124}
30125
30126void RS_FilterDXFRW::writeTolerance(LC_Tolerance *t) {
30127 if (t == nullptr)
30128 return;
30129
30130 DRW_Tolerance tol;
30131 getEntityAttributes(&tol, t);
30132 const LC_ToleranceData data = t->getData();
30133 tol.insertionPoint =
30134 DRW_Coord(data.insertionPoint.x, data.insertionPoint.y, 0.0);
30135 tol.xAxisDirectionVector =
30136 DRW_Coord(data.directionVector.x, data.directionVector.y, 0.0);
30137 tol.extPoint = DRW_Coord(0.0, 0.0, 1.0);
30138 tol.text = toDxfString(data.textCode).toUtf8().constData();
30139 const QString style =
30140 data.dimStyleName.isEmpty() ? m_dimStyle : data.dimStyleName;
30141 tol.dimStyleName = style.toUtf8().constData();
30142
30143 if (m_dwgW) {
30144 if (!m_dwgW->writeTolerance(&tol))
30145 m_writeFailed = true;
30146 return;
30147 }
30148 if (m_dxfW) {
30149 noteDxfWrite(m_dxfW->writeTolerance(&tol));
30150 }
30151}
30152
30153/**
30154 * Writes the given leader entity to the file.
30155 */
30156void RS_FilterDXFRW::writeLeader(const RS_Leader *l) {
30157 if (l->count() <= 0) {
30158 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING, "dropping leader with no vertices");
30159 return;
30160 }
30161
30162 DRW_Leader leader;
30163 getEntityAttributes(&leader, l);
30164 const QString styleName = l->getData().styleName;
30165 leader.style =
30166 (styleName.isEmpty() ? "Standard" : styleName.toUtf8().toStdString());
30167 leader.arrow = l->hasArrowHead();
30168 leader.leadertype = 0;
30169 leader.flag = 3;
30170 leader.hookline = 0;
30171 leader.hookflag = 0;
30172 leader.textheight = 1;
30173 leader.textwidth = 10;
30174
30175 RS_Line *li = nullptr;
30176 for (RS_Entity *v :
30177 lc::LC_ContainerTraverser{*l, RS2::ResolveNone}.entities()) {
30178 if (v->rtti() == RS2::EntityLine) {
30179 li = static_cast<RS_Line *>(v);
30180 leader.vertexlist.push_back(std::make_shared<DRW_Coord>(
30181 li->getStartpoint().x, li->getStartpoint().y, 0.0));
30182 }
30183 }
30184 if (li) {
30185 leader.vertexlist.push_back(std::make_shared<DRW_Coord>(
30186 li->getEndpoint().x, li->getEndpoint().y, 0.0));
30187 }
30188
30189 if (leader.vertexlist.size() < 2) {
30190 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
30191 "dropping leader with fewer than 2 vertices");
30192 return;
30193 }
30194 leader.vertnum = static_cast<int>(leader.vertexlist.size());
30195
30196 if (m_dwgW) {
30197 if (!m_dwgW->writeLeader(&leader))
30198 m_writeFailed = true;
30199 return;
30200 }
30201 noteDxfWrite(m_dxfW->writeLeader(&leader));
30202}
30203
30204namespace {
30205
30206// Fill the clamped uniform knot vector a control-point spline must carry.
30207//
30208// A DXF SPLINE always stores its knots (group 40), and libdxfrw's payload
30209// validation requires knots == controls + degree + 1
30210// (isValidControlSplineLayout in drw_entities.cpp). Emitting none produced a
30211// boundary edge that dxfRW::validateHatchPayload() rejects, and because a
30212// rejected entity fails the whole write, a drawing containing a
30213// spline-bordered hatch could not be exported to DXF at all.
30214//
30215// The vector generated here is the one the reader used to re-derive for an
30216// empty knot list, so consumers see the same curve as before.
30217void fillClampedUniformKnots(DRW_Spline &drw) {
30218 const std::int32_t degree = drw.degree;
30219 const std::int32_t controls = static_cast<std::int32_t>(drw.controllist.size());
30220 if (degree < 1 || controls < degree + 1)
30221 return;
30222 const std::int32_t spans = controls - degree;
30223 drw.knotslist.clear();
30224 drw.knotslist.reserve(static_cast<std::size_t>(controls + degree + 1));
30225 for (std::int32_t i = 0; i <= degree; ++i)
30226 drw.knotslist.push_back(0.0);
30227 for (std::int32_t i = 1; i < spans; ++i)
30228 drw.knotslist.push_back(static_cast<double>(i));
30229 for (std::int32_t i = 0; i <= degree; ++i)
30230 drw.knotslist.push_back(static_cast<double>(spans));
30231 drw.nknots = static_cast<std::int32_t>(drw.knotslist.size());
30232}
30233
30234// Emit an LC_SplinePoints boundary edge as a degree-2 DRW_Spline. Either
30235// the control net or the fit points are populated depending on the
30236// LC_SplinePointsData mode. See plan §B.1.
30237std::shared_ptr<DRW_Spline>
30238makeDrwSplineFromSplinePoints(const LC_SplinePoints *sp) {
30239 auto drw = std::make_shared<DRW_Spline>();
30240 drw->degree = 2;
30241 // Closed boundary edges set BOTH bit 0 (DXF entity-level "closed")
30242 // AND bit 1 (DWG-boundary / DXF group code 74 "periodic"). The hatch
30243 // boundary read path accepts either bit; this keeps cross-format
30244 // round-trip consistent.
30245 drw->flags = sp->isClosed() ? 0x3 : 0x0;
30246 const auto &d = sp->getData();
30247 if (d.useControlPoints && !d.controlPoints.empty()) {
30248 for (const auto &v : d.controlPoints)
30249 drw->controllist.push_back(std::make_shared<DRW_Coord>(v.x, v.y, 0.0));
30250 drw->ncontrol = static_cast<std::int32_t>(d.controlPoints.size());
30251 fillClampedUniformKnots(*drw);
30252 } else {
30253 for (const auto &v : d.splinePoints)
30254 drw->fitlist.push_back(std::make_shared<DRW_Coord>(v.x, v.y, 0.0));
30255 drw->nfit = static_cast<std::int32_t>(d.splinePoints.size());
30256 }
30257 return drw;
30258}
30259
30260// Emit an RS_Spline (general NURBS, degree 1-3, possibly rational) boundary
30261// edge as a DRW_Spline carrying its full data. Mirrors weight into both
30262// drw->weightlist[i] AND drw->controllist[i]->z so both DXF and DWG
30263// consumers can read it correctly. See plan §C.3.
30264std::shared_ptr<DRW_Spline> makeDrwSplineFromRSSpline(const RS_Spline *sp) {
30265 auto drw = std::make_shared<DRW_Spline>();
30266 const auto &sd = sp->getData();
30267 drw->degree = static_cast<std::int32_t>(sd.degree);
30268
30269 const auto cps = sp->getControlPoints();
30270 const auto ws = sp->getWeights();
30271 const bool weightsAlign = (ws.size() == cps.size());
30272 const bool isRational =
30273 weightsAlign && std::any_of(ws.begin(), ws.end(), [](double w) {
30274 return std::abs(w - 1.0) > 1e-9;
30275 });
30276 // Set both DXF entity-level closed (bit 0) and hatch-boundary
30277 // periodic (bit 1) for cross-format consistency. See helper above.
30278 drw->flags = (sp->isClosed() ? 0x3 : 0x0) | (isRational ? 0x4 : 0x0);
30279
30280 for (size_t i = 0; i < cps.size(); ++i) {
30281 const double w = (isRational && i < ws.size()) ? ws[i] : 1.0;
30282 drw->controllist.push_back(
30283 std::make_shared<DRW_Coord>(cps[i].x, cps[i].y, w));
30284 }
30285 drw->ncontrol = static_cast<std::int32_t>(cps.size());
30286 if (isRational) {
30287 drw->weightlist = ws;
30288 }
30289 drw->knotslist = sd.knotslist;
30290 drw->nknots = static_cast<std::int32_t>(sd.knotslist.size());
30291 if (drw->knotslist.empty())
30292 fillClampedUniformKnots(*drw);
30293 return drw;
30294}
30295
30296} // namespace
30297
30298/**
30299 * Writes the given hatch entity to the file.
30300 */
30301void RS_FilterDXFRW::writeHatch(RS_Hatch *h) {
30302 // version 12 are inserts of *U blocks
30303 if (m_version == 1009) {
30304 if (m_noNameBlock.contains(h)) {
30305 DRW_Insert in;
30306 getEntityAttributes(&in, h);
30307 in.basePoint.x = in.basePoint.y = 0.0;
30308 in.basePoint.z = 0.0;
30309 in.name = m_noNameBlock.value(h).toUtf8().data();
30310 in.xscale = in.yscale = 1.0;
30311 in.zscale = 1.0;
30312 in.angle = 0.0;
30313 in.colcount = in.rowcount = 1;
30314 in.colspace = in.rowspace = 0.0;
30315 noteDxfWrite(m_dxfW->writeInsert(&in));
30316 }
30317 return;
30318 }
30319
30320 bool writeIt = true;
30321 if (h->countLoops() > 0) {
30322 // check if all of the loops contain entities:
30323 for (RS_Entity *l :
30324 lc::LC_ContainerTraverser{*h, RS2::ResolveNone}.entities()) {
30325 if (l->isContainer() && !l->getFlag(RS2::FlagTemp)) {
30326 if (l->count() == 0) {
30327 writeIt = false;
30328 }
30329 }
30330 }
30331 } else {
30332 writeIt = false;
30333 }
30334
30335 if (!writeIt) {
30336 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
30337 "RS_FilterDXF::writeHatch: Dropping Hatch");
30338 return;
30339 }
30340
30341 DRW_Hatch ha;
30342 getEntityAttributes(&ha, h);
30343 if (const auto ocs = extractHatchExtrusionSidecar(h)) {
30344 ha.basePoint.z = ocs->elevation;
30345 ha.extPoint = ocs->extrusion;
30346 removeHatchExtrusionSidecar(ha);
30347 }
30348 ha.solid = h->isSolid();
30349 ha.scale = h->getScale();
30350 ha.angle = h->getAngle();
30351 if (ha.solid) {
30352 ha.name = "SOLID";
30353 } else {
30354 ha.name = h->getPattern().toUtf8().data();
30355 }
30356 ha.loopsnum = h->countLoops();
30357
30358 for (RS_Entity *l = h->firstEntity(RS2::ResolveNone); l;
30359 l = h->nextEntity(RS2::ResolveNone)) {
30360 // Write hatch loops:
30361 if (l->isContainer() && !l->getFlag(RS2::FlagTemp)) {
30362 auto loop = static_cast<RS_EntityContainer *>(l);
30363 std::shared_ptr<DRW_HatchLoop> lData = std::make_shared<DRW_HatchLoop>(0);
30364
30365 // a container other than a spline, such as a polyline, is written as its edges
30366 std::vector<RS_Entity *> edges;
30367 for (RS_Entity *ed :
30368 lc::LC_ContainerTraverser{*loop, RS2::ResolveNone}.entities()) {
30369 if (ed->isContainer() && ed->rtti() != RS2::EntitySpline) {
30370 const auto parts = lc::LC_ContainerTraverser{
30371 *static_cast<RS_EntityContainer *>(ed), RS2::ResolveAll}.entities();
30372 edges.insert(edges.end(), parts.begin(), parts.end());
30373 } else {
30374 edges.push_back(ed);
30375 }
30376 }
30377 for (RS_Entity *ed : edges) {
30378 // Write hatch loop edges:
30379 if (ed->rtti() == RS2::EntityLine) {
30380 auto *ln = static_cast<RS_Line *>(ed);
30381 std::shared_ptr<DRW_Line> line = std::make_shared<DRW_Line>();
30382 line->basePoint.x = ln->getStartpoint().x;
30383 line->basePoint.y = ln->getStartpoint().y;
30384 line->secPoint.x = ln->getEndpoint().x;
30385 line->secPoint.y = ln->getEndpoint().y;
30386 lData->objlist.push_back(line);
30387 } else if (ed->rtti() == RS2::EntityArc) {
30388 auto ar = static_cast<RS_Arc *>(ed);
30389 std::shared_ptr<DRW_Arc> arc = std::make_shared<DRW_Arc>();
30390 arc->basePoint.x = ar->getCenter().x;
30391 arc->basePoint.y = ar->getCenter().y;
30392 arc->radious = ar->getRadius();
30393 if (!ar->isReversed()) {
30394 arc->staangle = ar->getAngle1();
30395 arc->endangle = ar->getAngle2();
30396 arc->isccw = true;
30397 } else {
30398 arc->staangle = 2 * M_PI3.14159265358979323846 - ar->getAngle1();
30399 arc->endangle = 2 * M_PI3.14159265358979323846 - ar->getAngle2();
30400 arc->isccw = false;
30401 }
30402 lData->objlist.push_back(arc);
30403 } else if (ed->rtti() == RS2::EntityCircle) {
30404 auto ci = static_cast<RS_Circle *>(ed);
30405 std::shared_ptr<DRW_Arc> arc = std::make_shared<DRW_Arc>();
30406 arc->basePoint.x = ci->getCenter().x;
30407 arc->basePoint.y = ci->getCenter().y;
30408 arc->radious = ci->getRadius();
30409 arc->staangle = 0.0;
30410 arc->endangle = 2 * M_PI3.14159265358979323846; // 2*M_PI;
30411 arc->isccw = true;
30412 lData->objlist.push_back(arc);
30413 } else if (ed->rtti() == RS2::EntityEllipse) {
30414 auto el = static_cast<RS_Ellipse *>(ed);
30415 std::shared_ptr<DRW_Ellipse> ell = std::make_shared<DRW_Ellipse>();
30416 ell->basePoint.x = el->getCenter().x;
30417 ell->basePoint.y = el->getCenter().y;
30418 ell->secPoint.x = el->getMajorP().x;
30419 ell->secPoint.y = el->getMajorP().y;
30420 ell->ratio = el->getRatio();
30421 double rot = el->getMajorP().angle();
30422 double startAng = el->getCenter().angleTo(el->getStartpoint()) - rot;
30423 double endAng = el->getCenter().angleTo(el->getEndpoint()) - rot;
30424 if (startAng < 0) {
30425 startAng = M_PI3.14159265358979323846 * 2 + startAng;
30426 }
30427 if (endAng < 0) {
30428 endAng = M_PI3.14159265358979323846 * 2 + endAng;
30429 }
30430 ell->staparam = startAng;
30431 ell->endparam = endAng;
30432 ell->isccw = !el->isReversed();
30433 lData->objlist.push_back(ell);
30434 } else if (ed->rtti() == RS2::EntitySplinePoints) {
30435 lData->objlist.push_back(makeDrwSplineFromSplinePoints(
30436 static_cast<LC_SplinePoints *>(ed)));
30437 } else if (ed->rtti() == RS2::EntitySpline) {
30438 lData->objlist.push_back(
30439 makeDrwSplineFromRSSpline(static_cast<RS_Spline *>(ed)));
30440 }
30441 }
30442 lData->update(); // change to DRW_HatchLoop
30443 ha.appendLoop(lData);
30444 }
30445 }
30446 if (m_dwgW) {
30447 if (!m_dwgW->writeHatch(&ha))
30448 m_writeFailed = true;
30449 return;
30450 }
30451 noteDxfWrite(m_dxfW->writeHatch(&ha));
30452}
30453
30454/**
30455 * Writes the given Solid entity to the file.
30456 */
30457void RS_FilterDXFRW::writeSolid(RS_Solid *s) {
30458 RS_SolidData data;
30459 DRW_Solid solid;
30460 RS_Vector corner;
30461 getEntityAttributes(&solid, s);
30462 corner = s->getCorner(0);
30463 solid.basePoint.x = corner.x;
30464 solid.basePoint.y = corner.y;
30465 corner = s->getCorner(1);
30466 solid.secPoint.x = corner.x;
30467 solid.secPoint.y = corner.y;
30468 corner = s->getCorner(2);
30469 solid.thirdPoint.x = corner.x;
30470 solid.thirdPoint.y = corner.y;
30471 if (s->isTriangle()) {
30472 solid.fourPoint.x = solid.thirdPoint.x;
30473 solid.fourPoint.y = solid.thirdPoint.y;
30474 } else {
30475 corner = s->getCorner(3);
30476 solid.fourPoint.x = corner.x;
30477 solid.fourPoint.y = corner.y;
30478 }
30479 if (m_dwgW) {
30480 if (!m_dwgW->writeSolid(&solid))
30481 m_writeFailed = true;
30482 return;
30483 }
30484 noteDxfWrite(m_dxfW->writeSolid(&solid));
30485}
30486
30487void RS_FilterDXFRW::writeImage(const RS_Image *i) {
30488 if (i == nullptr)
30489 return;
30490
30491 DRW_Image image;
30492 getEntityAttributes(&image, i);
30493
30494 if (const auto frame = extractImageFrameSidecar(i)) {
30495 image.basePoint.z = frame->insertion.z;
30496 image.secPoint.z = frame->uVector.z;
30497 image.vVector.z = frame->vVector.z;
30498 removeImageFrameSidecar(image);
30499 }
30500
30501 image.basePoint.x = i->getInsertionPoint().x;
30502 image.basePoint.y = i->getInsertionPoint().y;
30503 image.secPoint.x = i->getUVector().x;
30504 image.secPoint.y = i->getUVector().y;
30505 image.vVector.x = i->getVVector().x;
30506 image.vVector.y = i->getVVector().y;
30507 image.sizeu = i->getWidth();
30508 image.sizev = i->getHeight();
30509 image.brightness = i->getBrightness();
30510 image.contrast = i->getContrast();
30511 image.fade = i->getFade();
30512
30513 const LC_DwgAdvancedMetadata::RasterImageRecord *rasterRecord = nullptr;
30514 const LC_DwgAdvancedMetadata::ImageDefinitionRecord *definitionRecord =
30515 nullptr;
30516 const LC_DwgAdvancedMetadata::ImageDefinitionReactorRecord *reactorRecord =
30517 nullptr;
30518 DRW_ImageDef definitionTemplate;
30519 DRW_ImageDefinitionReactor reactorTemplate;
30520 if (m_graphic != nullptr) {
30521 const auto &metadata = m_graphic->dwgAdvancedMetadata();
30522 rasterRecord = metadata.findRasterImageByHandle(
30523 static_cast<std::uint32_t>(i->sourceHandle()));
30524 if (rasterRecord == nullptr) {
30525 const std::uint32_t definitionHandle =
30526 static_cast<std::uint32_t>(i->getHandle());
30527 for (const auto &record : metadata.rasterImages()) {
30528 if (record.definitionHandle == definitionHandle) {
30529 rasterRecord = &record;
30530 break;
30531 }
30532 }
30533 }
30534 if (rasterRecord != nullptr) {
30535 image.m_classVersion = rasterRecord->classVersion;
30536 image.m_displayProps = rasterRecord->displayProps;
30537 image.clip = rasterRecord->clip;
30538 image.clipMode = rasterRecord->clipMode;
30539 image.m_clipBoundaryType = rasterRecord->clipBoundaryType;
30540 image.clipPath = rasterRecord->clipPath;
30541 definitionRecord =
30542 metadata.findImageDefinitionByHandle(rasterRecord->definitionHandle);
30543 if (definitionRecord != nullptr)
30544 definitionTemplate = imageDefinitionFromMetadata(*definitionRecord);
30545 reactorRecord = metadata.findImageDefinitionReactorByHandle(
30546 rasterRecord->reactorHandle);
30547 if (reactorRecord != nullptr)
30548 reactorTemplate = imageDefinitionReactorFromMetadata(*reactorRecord);
30549 }
30550 }
30551
30552 std::string fileName = i->getFile().toUtf8().constData();
30553 if (fileName.empty()) {
30554 // IMAGE requires a reachable IMAGEDEF. A valid fallback name also keeps
30555 // multiple unnamed images distinct in the image dictionary.
30556 fileName = "librecad_image_" + std::to_string(i->getId());
30557 }
30558
30559#ifdef DWGSUPPORT1
30560 if (m_dwgW != nullptr) {
30561 if ((definitionRecord != nullptr &&
30562 hasDxfTableAppDataUnsupportedByDwg(definitionTemplate)) ||
30563 (reactorRecord != nullptr &&
30564 hasDxfTableAppDataUnsupportedByDwg(reactorTemplate))) {
30565 m_writeFailed = true;
30566 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_ERROR,
30567 "RS_FilterDXFRW::writeImage: DWG cannot preserve IMAGE "
30568 "custom application groups");
30569 return;
30570 }
30571 if (!m_dwgW->writeImage(
30572 &image, &fileName,
30573 definitionRecord != nullptr ? &definitionTemplate : nullptr,
30574 reactorRecord != nullptr ? &reactorTemplate : nullptr)) {
30575 m_writeFailed = true;
30576 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_ERROR,
30577 "RS_FilterDXFRW::writeImage: DWG writer rejected IMAGE");
30578 }
30579 return;
30580 }
30581#endif
30582 if (m_dxfW == nullptr)
30583 return;
30584
30585 DRW_ImageDef *imgDef =
30586 m_dxfW->writeImage(&image, fileName);
30587 if (imgDef == nullptr)
30588 m_writeFailed = true;
30589 if (imgDef) {
30590 if (definitionRecord != nullptr) {
30591 imgDef->imgVersion = definitionRecord->classVersion;
30592 imgDef->loaded = definitionRecord->loaded;
30593 imgDef->u = definitionRecord->imageSizeU;
30594 imgDef->v = definitionRecord->imageSizeV;
30595 imgDef->up = definitionRecord->pixelSizeU;
30596 imgDef->vp = definitionRecord->pixelSizeV;
30597 imgDef->resolution = definitionRecord->resolution;
30598 } else {
30599 imgDef->loaded = 1;
30600 imgDef->u = i->getData().size.x;
30601 imgDef->v = i->getData().size.y;
30602 imgDef->up = 1;
30603 imgDef->vp = 1;
30604 imgDef->resolution = 0;
30605 }
30606 }
30607}
30608
30609void RS_FilterDXFRW::writeWipeout(LC_Wipeout *w) {
30610 if (w == nullptr) {
30611 return;
30612 }
30613#ifdef DWGSUPPORT1
30614 if (m_dwgW != nullptr && !isDwgWipeoutAdmitted(w)) {
30615 m_writeFailed = true;
30616 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_ERROR, "RS_FilterDXFRW::writeWipeout: DWG "
30617 "cannot preserve external references");
30618 return;
30619 }
30620#endif
30621 DRW_Wipeout img;
30622 getEntityAttributes(&img, w);
30623 const LC_WipeoutData &data = w->getData();
30624 if (data.hasNativeFrame) {
30625 img.basePoint = DRW_Coord(data.insertionPoint.x, data.insertionPoint.y,
30626 data.insertionPoint.z);
30627 img.secPoint = DRW_Coord(data.uPixel.x, data.uPixel.y, data.uPixel.z);
30628 img.vVector = DRW_Coord(data.vPixel.x, data.vPixel.y, data.vPixel.z);
30629 img.sizeu = data.sizeU;
30630 img.sizev = data.sizeV;
30631 img.m_displayProps = data.displayProps;
30632 img.ref = data.imageDefHandle;
30633 img.m_imageDefReactorHandle = data.imageDefReactorHandle;
30634 img.clip = data.clip;
30635 img.brightness = data.brightness;
30636 img.contrast = data.contrast;
30637 img.fade = data.fade;
30638 img.m_clipBoundaryType = data.clipBoundaryType;
30639 img.clipMode = data.clipMode;
30640 img.clipPath.reserve(data.clipPath.size());
30641 for (const RS_Vector &clipPoint : data.clipPath)
30642 img.clipPath.emplace_back(clipPoint.x, clipPoint.y, clipPoint.z);
30643 } else {
30644 // User-created WIPEOUTs have no source frame. Emit a documented canonical
30645 // pixel basis whose inverse maps the world polygon without approximation.
30646 img.basePoint = DRW_Coord(0.0, 0.0, 0.0);
30647 img.secPoint = DRW_Coord(1.0, 0.0, 0.0);
30648 img.vVector = DRW_Coord(0.0, 1.0, 0.0);
30649 img.sizeu = 1.0;
30650 img.sizev = 1.0;
30651 img.clip = 1;
30652 img.brightness = 50;
30653 img.contrast = 50;
30654 img.fade = 0;
30655 img.m_clipBoundaryType = 2;
30656 img.clipMode = false;
30657 img.clipPath.reserve(w->getVertices().size());
30658 for (const RS_Vector &vertex : w->getVertices())
30659 img.clipPath.emplace_back(vertex.x - 0.5, vertex.y - 0.5, vertex.z);
30660 }
30661
30662 constexpr std::size_t kMaxDwgWipeoutClipVertices = 100000u;
30663 if (img.clipPath.size() > kMaxDwgWipeoutClipVertices) {
30664 m_writeFailed = true;
30665 RS_DEBUGRS_Debug::instance()->print(
30666 RS_Debug::D_ERROR,
30667 "RS_FilterDXFRW::writeWipeout: clip path exceeds DWG limit");
30668 return;
30669 }
30670 bool written = false;
30671#ifdef DWGSUPPORT1
30672 if (m_dwgW != nullptr)
30673 written = m_dwgW->writeWipeout(&img);
30674 else
30675#endif
30676 if (m_dxfW != nullptr)
30677 written = m_dxfW->writeWipeout(&img);
30678 if (!written) {
30679 m_writeFailed = true;
30680 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_ERROR,
30681 "RS_FilterDXFRW::writeWipeout: writer rejected entity");
30682 }
30683}
30684
30685/**
30686 * Serialize an LC_MLeader. DWG AC1024+ writes a native text-content
30687 * MULTILEADER subset with context roots, leader lines, and text content.
30688 * DXF keeps the older scalar/context writer. Unsupported complex content
30689 * still falls back to visible LEADER/MTEXT geometry on DWG export.
30690 */
30691RS_FilterDXFRW::DwgEntityProductionResult
30692RS_FilterDXFRW::writeMLeader(LC_MLeader *m) {
30693 if (m == nullptr)
30694 return DwgEntityProductionResult::Failed;
30695 DRW_MLeader e;
30696 getEntityAttributes(&e, m);
30697 const auto &d = m->getData();
30698 e.overrideFlags = 0; // Phase 9 doesn't track override bits;
30699 // emit defaults so a re-read picks the
30700 // entity-level values as authoritative.
30701 e.leaderType = d.leaderType;
30702 e.leaderColor = d.leaderColor;
30703 e.landingDistance = d.landingDistance;
30704 e.defaultArrowHeadSize = d.arrowSize;
30705 e.landingEnabled = d.landingEnabled;
30706 e.doglegEnabled = d.doglegEnabled;
30707 e.styleContentType = d.contentType;
30708 e.scaleFactor = d.scaleFactor;
30709 e.classVersion = 2;
30710 e.context.roots.reserve(d.roots.size());
30711 for (const auto &sourceRoot : d.roots) {
30712 DRW_MLeaderRoot root;
30713 root.isContentValid = sourceRoot.connectionPoint.valid;
30714 root.unknown291 = sourceRoot.direction.valid;
30715 root.connectionPoint =
30716 DRW_Coord(sourceRoot.connectionPoint.x, sourceRoot.connectionPoint.y,
30717 sourceRoot.connectionPoint.z);
30718 root.direction = DRW_Coord(sourceRoot.direction.x, sourceRoot.direction.y,
30719 sourceRoot.direction.z);
30720 root.leaderIndex = static_cast<std::int32_t>(e.context.roots.size());
30721 root.landingDistance = sourceRoot.landingDistance;
30722 root.attachmentDirection =
30723 static_cast<std::uint16_t>(sourceRoot.attachmentDirection);
30724 root.leaderLines.reserve(sourceRoot.leaderLines.size());
30725 for (const auto &sourceLine : sourceRoot.leaderLines) {
30726 DRW_MLeaderLeaderLine line;
30727 line.leaderLineIndex = sourceLine.leaderLineIndex;
30728 line.leaderType = static_cast<std::uint16_t>(d.leaderType);
30729 line.color = d.leaderColor;
30730 line.lineWeight = e.lWeight;
30731 line.arrowSize = d.arrowSize;
30732 line.overrideFlags = 0;
30733 line.points.reserve(sourceLine.points.size());
30734 for (const RS_Vector &point : sourceLine.points)
30735 line.points.emplace_back(point.x, point.y, point.z);
30736 root.leaderLines.push_back(std::move(line));
30737 }
30738 e.context.roots.push_back(std::move(root));
30739 }
30740 e.context.overallScale = d.scaleFactor;
30741 e.context.contentBasePoint = DRW_Coord(
30742 d.contentBasePoint.x, d.contentBasePoint.y, d.contentBasePoint.z);
30743 e.context.textHeight = d.textHeight > 0.0 ? d.textHeight : 1.0;
30744 e.context.arrowHeadSize = d.arrowSize;
30745 e.context.landingGap = d.landingDistance;
30746 e.context.hasTextContents = d.hasTextContents;
30747 e.context.hasContentsBlock = d.hasBlockContents;
30748 e.context.textLabel = toDxfString(d.textLabel).toUtf8().data();
30749 e.context.textNormal = DRW_Coord(0.0, 0.0, 1.0);
30750 const RS_Vector textLocation =
30751 d.textLocation.valid ? d.textLocation : d.contentBasePoint;
30752 e.context.textLocation =
30753 DRW_Coord(textLocation.x, textLocation.y, textLocation.z);
30754 e.context.textDirection =
30755 DRW_Coord(std::cos(d.textRotation), std::sin(d.textRotation), 0.0);
30756 e.context.textRotation = d.textRotation;
30757 e.context.boundaryWidth = d.boundaryWidth;
30758 e.context.boundaryHeight = d.boundaryHeight;
30759 e.context.lineSpacingFactor = 1.0;
30760 e.context.lineSpacingStyle = 1;
30761 e.context.textColor = d.textColor;
30762 e.context.alignment = 1;
30763 e.context.flowDirection = 1;
30764 e.context.bgScaleFactor = 1.5;
30765 e.context.basePoint = DRW_Coord(d.basePoint.x, d.basePoint.y, d.basePoint.z);
30766 e.context.baseDirection = DRW_Coord(1.0, 0.0, 0.0);
30767 e.context.baseVertical = DRW_Coord(0.0, 1.0, 0.0);
30768 e.styleHandle.ref = d.dwgStyleHandle;
30769 e.leaderLineTypeHandle.ref = d.dwgLeaderLineTypeHandle;
30770 e.arrowHeadHandle.ref = d.dwgArrowHeadHandle;
30771 e.styleTextStyleHandle.ref = d.dwgTextStyleHandle;
30772 e.styleBlockHandle.ref = d.dwgBlockHandle;
30773 e.context.textStyleHandle.ref = d.dwgTextStyleHandle;
30774 e.context.blockTableRecordHandle.ref = d.dwgBlockHandle;
30775 for (DRW_MLeaderRoot &root : e.context.roots) {
30776 for (DRW_MLeaderLeaderLine &line : root.leaderLines) {
30777 line.lineTypeHandle.ref = d.dwgLeaderLineTypeHandle;
30778 line.arrowHandle.ref = d.dwgArrowHeadHandle;
30779 }
30780 }
30781 if (m_dwgW) {
30782 const DwgMLeaderWriteRoute route =
30783 selectDwgMLeaderWriteRoute(d, m_dwgW->getVersion());
30784 if (route == DwgMLeaderWriteRoute::Invalid) {
30785 m_writeFailed = true;
30786 return DwgEntityProductionResult::Failed;
30787 }
30788 // Generated entities do not yet have a pre-seal class-instance ordinal;
30789 // keep them on the visible fallback route until that descriptor family is
30790 // migrated. Source-backed native output is admitted in writeDwgClasses().
30791 const bool useNative =
30792 route == DwgMLeaderWriteRoute::Native && m->sourceHandle() != 0;
30793 if (useNative) {
30794 struct NativeMLeaderReference {
30795 const char *name = nullptr;
30796 std::uint32_t ordinal = 0;
30797 DwgWriteSourceKind targetKind = DwgWriteSourceKind::Object;
30798 std::uint32_t targetSourceHandle = 0;
30799 std::uint32_t targetOutputHandle = 0;
30800 };
30801 std::vector<NativeMLeaderReference> references;
30802 const auto isLiveReference = [](DwgWriteReferenceStatus status) {
30803 return status == DwgWriteReferenceStatus::Resolved ||
30804 status == DwgWriteReferenceStatus::LegacySpaceAlias ||
30805 status == DwgWriteReferenceStatus::Reserved ||
30806 status == DwgWriteReferenceStatus::Selected ||
30807 status == DwgWriteReferenceStatus::Committed;
30808 };
30809 const auto addReference =
30810 [&references](const char *name, std::uint32_t ordinal,
30811 DwgWriteSourceKind targetKind,
30812 std::uint32_t targetSourceHandle,
30813 std::uint32_t targetOutputHandle) {
30814 if (name == nullptr || targetOutputHandle == 0)
30815 return false;
30816 references.push_back({name, ordinal, targetKind, targetSourceHandle,
30817 targetOutputHandle});
30818 return true;
30819 };
30820 const auto resolveObjectReference =
30821 [this, &addReference, &isLiveReference](
30822 std::uint32_t sourceHandle, const char *name,
30823 std::uint32_t ordinal, std::uint32_t &outputHandle) {
30824 outputHandle = 0;
30825 if (sourceHandle == DRW::NoHandle)
30826 return true;
30827 const DwgWriteReferenceStatus status =
30828 resolveDwgWriteObjectHandle(sourceHandle, &outputHandle);
30829 return isLiveReference(status) && outputHandle != 0 &&
30830 addReference(name, ordinal, DwgWriteSourceKind::Object,
30831 sourceHandle, outputHandle);
30832 };
30833 const auto resolveTableReference =
30834 [this, &addReference](
30835 std::uint32_t sourceHandle, const std::string &sourceName,
30836 const std::map<std::string, std::uint32_t> &outputHandles,
30837 const char *name, std::uint32_t ordinal,
30838 std::uint32_t &outputHandle) {
30839 outputHandle = 0;
30840 if (sourceHandle == DRW::NoHandle)
30841 return true;
30842 if (sourceName.empty())
30843 return false;
30844 const auto outputIt =
30845 outputHandles.find(normalizeDwgTableName(sourceName));
30846 if (outputIt == outputHandles.end())
30847 return false;
30848
30849 std::uint32_t plannedHandle = 0;
30850 const DwgWriteIdentityLookup state = lookupDwgWriteIdentity(
30851 DwgWriteSourceKind::TableRecord, sourceHandle, &plannedHandle);
30852 if (state != DwgWriteIdentityLookup::Reserved &&
30853 state != DwgWriteIdentityLookup::Selected &&
30854 state != DwgWriteIdentityLookup::Committed) {
30855 return false;
30856 }
30857 if (plannedHandle == 0 || plannedHandle != outputIt->second)
30858 return false;
30859
30860 outputHandle = plannedHandle;
30861 return addReference(name, ordinal, DwgWriteSourceKind::TableRecord,
30862 sourceHandle, outputHandle);
30863 };
30864 const auto resolveBlockReference =
30865 [this, &addReference, &isLiveReference](
30866 std::uint32_t sourceHandle, const char *name,
30867 std::uint32_t ordinal, std::uint32_t &outputHandle) {
30868 outputHandle = 0;
30869 if (sourceHandle == DRW::NoHandle)
30870 return true;
30871 const DwgWriteReferenceStatus status =
30872 resolveDwgWriteBlockOwner(sourceHandle, &outputHandle);
30873 if (!isLiveReference(status) || outputHandle == 0)
30874 return false;
30875 const bool fixed =
30876 status == DwgWriteReferenceStatus::LegacySpaceAlias;
30877 return addReference(name, ordinal,
30878 fixed ? DwgWriteSourceKind::FixedStructural
30879 : DwgWriteSourceKind::Block,
30880 fixed ? 0 : sourceHandle, outputHandle);
30881 };
30882
30883 const auto &metadata = m_graphic->dwgAdvancedMetadata();
30884 if (!resolveObjectReference(d.dwgStyleHandle, "MLEADER.STYLE", 0,
30885 e.styleHandle.ref) ||
30886 !resolveTableReference(
30887 d.dwgLeaderLineTypeHandle,
30888 metadata.lineTypeNameForHandle(d.dwgLeaderLineTypeHandle),
30889 m_dwgWriteLTypeHandleByName, "MLEADER.LTYPE", 0,
30890 e.leaderLineTypeHandle.ref) ||
30891 !resolveBlockReference(d.dwgArrowHeadHandle, "MLEADER.ARROW", 0,
30892 e.arrowHeadHandle.ref) ||
30893 !resolveTableReference(
30894 d.dwgTextStyleHandle,
30895 metadata.textStyleNameForHandle(d.dwgTextStyleHandle),
30896 m_dwgWriteTextStyleHandleByName, "MLEADER.TEXT_STYLE", 0,
30897 e.styleTextStyleHandle.ref) ||
30898 !resolveBlockReference(d.dwgBlockHandle, "MLEADER.BLOCK", 0,
30899 e.styleBlockHandle.ref)) {
30900 m_writeFailed = true;
30901 return DwgEntityProductionResult::Failed;
30902 }
30903 if (e.context.hasTextContents) {
30904 if (!resolveTableReference(
30905 d.dwgTextStyleHandle,
30906 metadata.textStyleNameForHandle(d.dwgTextStyleHandle),
30907 m_dwgWriteTextStyleHandleByName, "MLEADER.CONTEXT.TEXT_STYLE",
30908 0, e.context.textStyleHandle.ref)) {
30909 m_writeFailed = true;
30910 return DwgEntityProductionResult::Failed;
30911 }
30912 } else if (e.context.hasContentsBlock) {
30913 if (!resolveBlockReference(d.dwgBlockHandle, "MLEADER.CONTEXT.BLOCK", 0,
30914 e.context.blockTableRecordHandle.ref)) {
30915 m_writeFailed = true;
30916 return DwgEntityProductionResult::Failed;
30917 }
30918 }
30919
30920 std::uint32_t lineOrdinal = 0;
30921 for (DRW_MLeaderRoot &root : e.context.roots) {
30922 for (DRW_MLeaderLeaderLine &line : root.leaderLines) {
30923 if (!resolveTableReference(
30924 d.dwgLeaderLineTypeHandle,
30925 metadata.lineTypeNameForHandle(d.dwgLeaderLineTypeHandle),
30926 m_dwgWriteLTypeHandleByName, "MLEADER.LINE.LTYPE",
30927 lineOrdinal, line.lineTypeHandle.ref) ||
30928 !resolveBlockReference(d.dwgArrowHeadHandle, "MLEADER.LINE.ARROW",
30929 lineOrdinal, line.arrowHandle.ref)) {
30930 m_writeFailed = true;
30931 return DwgEntityProductionResult::Failed;
30932 }
30933 ++lineOrdinal;
30934 }
30935 }
30936
30937 const auto identityCheckpoint = checkpointDwgWriteIdentity();
30938 const std::uint64_t nativeTransaction =
30939 m_dwgW->beginDwgWriteTransaction(true, false, false);
30940 const auto rollback = [&]() {
30941 if (nativeTransaction != 0)
30942 (void)m_dwgW->rollbackDwgWriteTransaction(nativeTransaction);
30943 restoreDwgWriteIdentity(identityCheckpoint);
30944 m_writeFailed = true;
30945 };
30946 if (nativeTransaction == 0 || !m_dwgW->writeMLeader(&e)) {
30947 rollback();
30948 return DwgEntityProductionResult::Failed;
30949 }
30950 const std::uint32_t outputHandle =
30951 m_dwgW->getLastSuccessfulEntityHandle();
30952 if (outputHandle == 0 || outputHandle != e.handle) {
30953 rollback();
30954 return DwgEntityProductionResult::Failed;
30955 }
30956 for (const NativeMLeaderReference &reference : references) {
30957 const DwgWriteReferenceReceipt receipt{DwgWriteSourceKind::Entity,
30958 m->sourceHandle(),
30959 outputHandle,
30960 reference.name,
30961 reference.ordinal,
30962 reference.targetKind,
30963 reference.targetSourceHandle,
30964 reference.targetOutputHandle,
30965 true,
30966 DRW::DwgHardPointer};
30967 if (!recordDwgWriteReferenceReceipt(receipt)) {
30968 rollback();
30969 return DwgEntityProductionResult::Failed;
30970 }
30971 }
30972 if (!m_dwgW->commitDwgWriteTransaction(nativeTransaction)) {
30973 rollback();
30974 return DwgEntityProductionResult::Failed;
30975 }
30976 return DwgEntityProductionResult::SingleFrame;
30977 }
30978
30979 const auto identityCheckpoint = checkpointDwgWriteIdentity();
30980 const std::uint64_t transaction =
30981 m_dwgW->beginDwgWriteTransaction(false, false, false);
30982 if (transaction == 0) {
30983 m_writeFailed = true;
30984 return DwgEntityProductionResult::Failed;
30985 }
30986 const auto rollback = [&]() {
30987 (void)m_dwgW->rollbackDwgWriteTransaction(transaction);
30988 restoreDwgWriteIdentity(identityCheckpoint);
30989 m_writeFailed = true;
30990 };
30991
30992 bool wroteGeometry = false;
30993 const auto emitFallbackChild = [&](const auto &write) {
30994 if (!write())
30995 return false;
30996 const std::uint32_t outputHandle =
30997 m_dwgW->getLastSuccessfulEntityHandle();
30998 if (outputHandle == 0 || !recordDwgEntityFrame(outputHandle))
30999 return false;
31000
31001 if (!wroteGeometry) {
31002 if (m->sourceHandle() != 0) {
31003 const auto entityIt = m_dwgWriteEntityHandleByEntity.find(m);
31004 if (entityIt == m_dwgWriteEntityHandleByEntity.end() ||
31005 entityIt->second != outputHandle || !commitDwgEntityWrite(m))
31006 return false;
31007 } else if (!commitDwgGeneratedEntityWrite(outputHandle)) {
31008 return false;
31009 }
31010 } else if (!commitDwgGeneratedEntityWrite(outputHandle)) {
31011 return false;
31012 }
31013 wroteGeometry = true;
31014 return true;
31015 };
31016
31017 for (const auto &root : d.roots) {
31018 for (const auto &line : root.leaderLines) {
31019 if (line.points.size() < 2)
31020 continue;
31021 DRW_Leader leader;
31022 getEntityAttributes(&leader, m);
31023 leader.style =
31024 d.styleName.isEmpty() ? "Standard" : d.styleName.toStdString();
31025 leader.arrow = true;
31026 leader.leadertype = d.leaderType == 2 ? 1 : 0;
31027 leader.flag = 3;
31028 leader.hookline = 0;
31029 leader.hookflag = 0;
31030 leader.textheight = d.textHeight > 0.0 ? d.textHeight : 1.0;
31031 leader.textwidth = d.boundaryWidth > 0.0 ? d.boundaryWidth : 10.0;
31032 leader.vertnum = static_cast<int>(line.points.size());
31033 leader.vertexlist.reserve(line.points.size());
31034 for (const RS_Vector &point : line.points) {
31035 leader.vertexlist.push_back(
31036 std::make_shared<DRW_Coord>(point.x, point.y, point.z));
31037 }
31038 if (wroteGeometry)
31039 leader.handle = 0;
31040 if (!emitFallbackChild(
31041 [&leader, this] { return m_dwgW->writeLeader(&leader); })) {
31042 rollback();
31043 return DwgEntityProductionResult::Failed;
31044 }
31045 }
31046 }
31047 if (d.hasBlockContents) {
31048 DRW_Insert insert;
31049 getEntityAttributes(&insert, m);
31050 insert.basePoint =
31051 DRW_Coord(d.blockLocation.x, d.blockLocation.y, d.blockLocation.z);
31052 insert.name = d.blockName.toStdString();
31053 insert.xscale = d.blockScale.x;
31054 insert.yscale = d.blockScale.y;
31055 insert.zscale = d.blockScale.z;
31056 insert.angle = d.blockRotation;
31057 insert.colcount = 1;
31058 insert.rowcount = 1;
31059 insert.colspace = 0.0;
31060 insert.rowspace = 0.0;
31061 insert.extPoint = DRW_Coord(0.0, 0.0, 1.0);
31062 if (wroteGeometry)
31063 insert.handle = 0;
31064 if (!emitFallbackChild(
31065 [&insert, this] { return m_dwgW->writeInsert(&insert); })) {
31066 rollback();
31067 return DwgEntityProductionResult::Failed;
31068 }
31069 }
31070 if (d.hasTextContents && !d.textLabel.isEmpty()) {
31071 DRW_MText text;
31072 getEntityAttributes(&text, m);
31073 const RS_Vector insertion =
31074 d.textLocation.valid ? d.textLocation : d.contentBasePoint;
31075 text.basePoint.x = insertion.x;
31076 text.basePoint.y = insertion.y;
31077 text.basePoint.z = insertion.z;
31078 text.height = d.textHeight > 0.0 ? d.textHeight : 1.0;
31079 text.angle = d.textRotation * 180.0 / M_PI3.14159265358979323846;
31080 text.style = d.textStyleName.isEmpty() ? "Standard"
31081 : d.textStyleName.toStdString();
31082 text.text = toDxfString(d.textLabel).toUtf8().data();
31083 text.widthscale = d.boundaryWidth;
31084 text.interlin = 1.0;
31085 if (wroteGeometry)
31086 text.handle = 0;
31087 if (!emitFallbackChild(
31088 [&text, this] { return m_dwgW->writeMText(&text); })) {
31089 rollback();
31090 return DwgEntityProductionResult::Failed;
31091 }
31092 }
31093 if (!wroteGeometry) {
31094 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
31095 "dropping MLEADER with no writable leader/text geometry");
31096 rollback();
31097 return DwgEntityProductionResult::Failed;
31098 }
31099
31100 if (!m_dwgW->commitDwgWriteTransaction(transaction)) {
31101 rollback();
31102 return DwgEntityProductionResult::Failed;
31103 }
31104 return DwgEntityProductionResult::SelfCommittedCompound;
31105 }
31106 noteDxfWrite(m_dxfW->writeMultiLeader(&e));
31107 return DwgEntityProductionResult::SingleFrame;
31108}
31109
31110/*void RS_FilterDXFRW::writeEntityContainer(DL_WriterA& dw, RS_EntityContainer*
31111con, const DRW_Entity& attrib) { QString blkName; blkName = "__CE";
31112
31113 // Creating an unique ID from the element ID
31114 int tmp, c=1; // tmp = temporary var c = counter var
31115 tmp = con->getId();
31116
31117 while (true) {
31118 tmp = tmp/c;
31119 blkName.append((char) tmp %10 + 48);
31120 c *= 10;
31121 if (tmp < 10) {
31122 break;
31123 }
31124 }
31125
31126 //Block definition
31127 dw.sectionTables();
31128 dxf.writeBlockRecord(dw);
31129 dw.dxfString( 0, "BLOCK_RECORD");
31130
31131 dw.handle();
31132 dw.dxfHex(330, 1);
31133 dw.dxfString(100, "AcDbSymbolTableRecord");
31134 dw.dxfString(100, "AcDbBlockTableRecord");
31135 dw.dxfString( 2, blkName.toLatin1().data());
31136 dw.dxfHex(340, 0);
31137 dw.dxfString(0, "ENDTAB");
31138
31139 //Block creation
31140 RS_BlockData blkdata(blkName, RS_Vector(0,0), false);
31141
31142 RS_Block* blk = new RS_Block(graphic, blkdata);
31143
31144 for (RS_Entity* e1 = con->firstEntity(); e1 ;
31145 e1 = con->nextEntity() ) {
31146 blk->addEntity(e1);
31147 }
31148 writeBlock(dw, blk);
31149 //delete e1;
31150}*/
31151
31152/**
31153 * Writes the atomic entities of the given container to the file.
31154 */
31155/*void RS_FilterDXFRW::writeAtomicEntities(DL_WriterA& dw, RS_EntityContainer*
31156c, const DRW_Entity& attrib, RS2::ResolveLevel level) {
31157
31158 for (RS_Entity* e=c->firstEntity(level);
31159 e;
31160 e=c->nextEntity(level)) {
31161
31162 writeEntity(dw, e, attrib);
31163 }
31164}*/
31165
31166/**
31167 * Sets the entities attributes according to the attributes
31168 * that come from a DXF file.
31169 */
31170void RS_FilterDXFRW::setEntityAttributes(RS_Entity *entity,
31171 const DRW_Entity *attrib) {
31172 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::setEntityAttributes");
31173
31174 RS_Pen pen;
31175 pen.setColor(Qt::black);
31176 pen.setLineType(RS2::SolidLine);
31177 // A layer name is an identifier, NOT MTEXT content: do NOT run it through
31178 // toNativeString (which caret-decodes ^X, expands \P/%%c, strips font
31179 // tags). Decoding here corrupts names that legitimately contain '^' and,
31180 // worse, desyncs the entity from its layer record — addLayer() below
31181 // stores the RAW name (lay.name = attrib->layer), so a decoded layName
31182 // would never match it. Use the verbatim UTF-8 name on both paths.
31183 QString layName = QString::fromUtf8(attrib->layer.c_str());
31184 entity->setLayer(importLayerForEntity(layName, attrib->layer));
31185
31186 // Color:
31187 RS_Color col;
31188 if (attrib->color24 >= 0) {
31189 col = RS_Color(attrib->color24 >> 16, attrib->color24 >> 8 & 0xFF,
31190 attrib->color24 & 0xFF);
31191 } else {
31192 col = numberToColor(attrib->color);
31193 }
31194 if (!attrib->colorName.empty()) {
31195 col.setColorName(QString::fromUtf8(attrib->colorName.c_str()));
31196 }
31197 pen.setColor(col);
31198
31199 // Linetype:
31200 pen.setLineType(nameToLineType(QString::fromUtf8(attrib->lineType.c_str())));
31201
31202 // Width:
31203 pen.setWidth(numberToWidth(attrib->lWeight));
31204
31205 // Transparency (DXF code 440): alpha_raw packs alpha_type in high byte
31206 // (0/1 = ByLayer/ByBlock, inherits parent), 3 = explicit alpha (low
31207 // byte 0..255). RS_Pen.alpha is a float 0..1 where 1.0 is opaque.
31208 // libreDWG common_entity_data.spec:432-446 documents the encoding.
31209 // Only override the pen default when alpha_type == 3.
31210 if (attrib->transparency != DRW::Opaque) {
31211 const unsigned int rawAlpha =
31212 static_cast<unsigned int>(attrib->transparency);
31213 const unsigned int alphaType = (rawAlpha >> 24) & 0xFF;
31214 const unsigned int alphaByte = rawAlpha & 0xFF;
31215 if (alphaType == 3) {
31216 pen.setAlpha(static_cast<float>(alphaByte) / 255.0f);
31217 }
31218 }
31219
31220 entity->setPen(pen);
31221
31222 // Passive metadata sidecars — DXF/DWG fields that don't affect
31223 // rendering or equality but must round-trip on save. Skip the
31224 // default sentinels so unset entities stay unchanged.
31225 if (attrib->material != DRW::MaterialByLayer)
31226 entity->setMaterialHandle(attrib->material);
31227 if (attrib->plotStyle != DRW::DefaultPlotStyle)
31228 entity->setPlotStyleHandle(static_cast<quint32>(attrib->plotStyle));
31229 if (attrib->shadow != DRW::CastAndReceieveShadows)
31230 entity->setShadowMode(static_cast<int>(attrib->shadow));
31231 entity->setShadowHandle(attrib->shadowHandle);
31232 // Visual-style handles are represented by DXF group 348 and by the
31233 // corresponding DWG common-entity flags/handles.
31234 entity->setVisualStyleHandles(attrib->fullVisualStyleHandle,
31235 attrib->faceVisualStyleHandle,
31236 attrib->edgeVisualStyleHandle);
31237 entity->setReactorHandles(attrib->reactorHandles);
31238 entity->setXDictHandle(attrib->xDictHandle);
31239 // Visibility (DXF code 60 / DWG invisible bit) — import side of the
31240 // two-way fix; getEntityAttributes now exports it. (write-review P3 #11)
31241 entity->setVisible(attrib->visible);
31242
31243 // Source DXF/DWG handle (code 5). Lets the writer build an old->new
31244 // handle map for refs that target model entities (F3a; GROUP code-340).
31245 if (attrib->handle != DRW::NoHandle)
31246 entity->setSourceHandle(attrib->handle);
31247
31248 // Preserve any XDATA / EED that came in with the entity. Stored
31249 // verbatim on RS_Entity so a later getEntityAttributes() can spit
31250 // it back out unchanged. The dimension-style override path reads
31251 // the same source independently and is unaffected.
31252 if (!attrib->extData.empty()) {
31253 entity->setDrwExtData(attrib->extData);
31254 }
31255
31256 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::setEntityAttributes: OK");
31257}
31258
31259/**
31260 * Gets the entities attributes as a DL_Attributes object.
31261 */
31262void RS_FilterDXFRW::getEntityAttributes(DRW_Entity *ent,
31263 const RS_Entity *entity) {
31264 // DRW_Entity RS_FilterDXFRW::getEntityAttributes(RS_Entity* /*entity*/) {
31265
31266 // F3: DXF export seeds ent->handle with the entity's SOURCE handle as a
31267 // source->minted remap key (used to resolve GROUP 340 members). DWG export
31268 // uses the preallocated source->minted map so OBJECTS references can point
31269 // at the actual entity handle without colliding with fixed table/object
31270 // handles reserved by the writer.
31271#ifdef DWGSUPPORT1
31272 if (m_dwgW != nullptr) {
31273 ent->handle = 0;
31274 auto entityIt = m_dwgWriteEntityHandleByEntity.find(entity);
31275 const bool hasCommonReferences =
31276 entity->materialHandle() != 0 || entity->plotStyleHandle() != 0 ||
31277 entity->shadowHandle() != 0 || entity->fullVisualStyleHandle() != 0 ||
31278 entity->faceVisualStyleHandle() != 0 ||
31279 entity->edgeVisualStyleHandle() != 0 ||
31280 !entity->reactorHandles().empty() || entity->xDictHandle() != 0;
31281 if (entityIt == m_dwgWriteEntityHandleByEntity.end() &&
31282 hasCommonReferences) {
31283 const std::uint32_t sourceHandle = entity->sourceHandle();
31284 const std::uint32_t emittedHandle = m_dwgW->allocNextHandle();
31285 if (emittedHandle == 0 ||
31286 !selectDwgWriteIdentity(DwgWriteSourceKind::Entity, sourceHandle,
31287 emittedHandle)) {
31288 m_writeFailed = true;
31289 } else {
31290 entityIt =
31291 m_dwgWriteEntityHandleByEntity.emplace(entity, emittedHandle).first;
31292 if (sourceHandle != 0) {
31293 const bool sourceInserted =
31294 m_dwgWriteEntityHandleRemap.emplace(sourceHandle, emittedHandle)
31295 .second;
31296 if (!sourceInserted)
31297 m_dwgWriteDuplicateEntityHandles.insert(sourceHandle);
31298 }
31299 }
31300 }
31301 if (entityIt != m_dwgWriteEntityHandleByEntity.end()) {
31302 ent->handle = entityIt->second;
31303 }
31304 } else
31305#endif
31306 {
31307 ent->handle = entity->sourceHandle();
31308 }
31309
31310 // Layer:
31311 RS_Layer *layer = entity->getLayer();
31312 QString layerName;
31313 if (layer) {
31314 layerName = layer->getName();
31315 } else {
31316 layerName = "0";
31317 }
31318
31319 RS_Pen pen = entity->getPen(false);
31320
31321#ifdef DWGSUPPORT1
31322 // Entity common references use the global DWG handle namespace. Resolve
31323 // either a preallocated entity handle or a preserved/native OBJECTS
31324 // handle, and reject ambiguous or unavailable targets before encoding.
31325 auto remapDwgReference = [this, ent, entity](
31326 std::uint32_t source, const char *referenceKind,
31327 std::uint32_t referenceOrdinal = 0) {
31328 std::uint32_t outputHandle = 0;
31329 DwgWriteSourceKind targetKind = DwgWriteSourceKind::Object;
31330 const DwgWriteReferenceStatus status =
31331 resolveDwgWriteCommonHandle(source, &outputHandle, &targetKind);
31332 if (status == DwgWriteReferenceStatus::Ambiguous) {
31333 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
31334 "RS_FilterDXFRW: dropping ambiguous DWG %s target 0x%X",
31335 referenceKind, source);
31336 if (!recordDwgWriteOptionalDrop(
31337 DwgWriteSourceKind::Entity, entity->sourceHandle(), ent->handle,
31338 referenceKind, referenceOrdinal, targetKind, source, status,
31339 m_dwgW->getVersion(), "DWG ENTITIES",
31340 "ambiguous common reference"))
31341 m_writeFailed = true;
31342 return std::uint32_t{0};
31343 }
31344 if (status == DwgWriteReferenceStatus::Missing ||
31345 status == DwgWriteReferenceStatus::Suppressed) {
31346 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
31347 "RS_FilterDXFRW: dropping dangling DWG %s target 0x%X",
31348 referenceKind, source);
31349 if (!recordDwgWriteOptionalDrop(
31350 DwgWriteSourceKind::Entity, entity->sourceHandle(), ent->handle,
31351 referenceKind, referenceOrdinal, targetKind, source, status,
31352 m_dwgW->getVersion(), "DWG ENTITIES",
31353 "unresolved common reference"))
31354 m_writeFailed = true;
31355 return std::uint32_t{0};
31356 }
31357 return outputHandle;
31358 };
31359#endif
31360
31361 // Color:
31362 int exact_rgb;
31363 int color = colorToNumber(pen.getColor(), &exact_rgb);
31364 // printf("Color is: %s -> %d\n", pen.getColor().name().toLatin1().data(),
31365 // color);
31366
31367 // Linetype:
31368 QString lineType = lineTypeToName(pen.getLineType());
31369
31370 // Width:
31371 DRW_LW_Conv::lineWidth width = widthToNumber(pen.getWidth());
31372
31373 ent->layer = toDxfString(layerName).toUtf8().data();
31374 ent->color = color;
31375 ent->color24 = exact_rgb;
31376 if (pen.getColor().hasColorName()) {
31377 ent->colorName = pen.getColor().colorName().toUtf8().data();
31378 }
31379 ent->lWeight = width;
31380 ent->lineType = lineType.toUtf8().data();
31381
31382 // Transparency export: encode pen alpha < 1.0 as DXF code 440
31383 // (alpha_raw). High byte 0x03 = "explicit alpha", low byte = 0..255.
31384 // Mirrors the import decoder above.
31385 if (pen.getAlpha() < 1.0f) {
31386 int alphaByte = static_cast<int>(pen.getAlpha() * 255.0f + 0.5f);
31387 ent->transparency = (0x03 << 24) | (alphaByte & 0xFF);
31388 }
31389
31390 // Visibility (DXF code 60 / DWG invisible bit). The writer honors
31391 // ent->visible (writeEntity emits 60 when invisible; encodeDwgCommon sets
31392 // the DWG bit), but this boundary never populated it, so an invisible
31393 // entity round-tripped as visible. (write-review P3 #11)
31394 ent->visible = entity->isVisible();
31395
31396 // Passive metadata sidecars — emit only when overridden. DWG references
31397 // must be remapped after the writer reserves its final handle graph;
31398 // DXF keeps the source handle namespace unchanged.
31399 ent->material = DRW::MaterialByLayer;
31400 ent->plotStyle = DRW::DefaultPlotStyle;
31401 ent->shadow = DRW::CastAndReceieveShadows;
31402 ent->shadowHandle = 0;
31403 ent->fullVisualStyleHandle = 0;
31404 ent->faceVisualStyleHandle = 0;
31405 ent->edgeVisualStyleHandle = 0;
31406 ent->reactorHandles.clear();
31407 ent->xDictHandle = 0;
31408 if (entity->materialHandle() != 0) {
31409#ifdef DWGSUPPORT1
31410 ent->material =
31411 m_dwgW != nullptr
31412 ? remapDwgReference(entity->materialHandle(), "material")
31413 : entity->materialHandle();
31414#else
31415 ent->material = entity->materialHandle();
31416#endif
31417 }
31418 if (entity->plotStyleHandle() != 0) {
31419#ifdef DWGSUPPORT1
31420 const auto handle =
31421 m_dwgW != nullptr
31422 ? remapDwgReference(entity->plotStyleHandle(), "plot-style")
31423 : entity->plotStyleHandle();
31424#else
31425 const auto handle = entity->plotStyleHandle();
31426#endif
31427 ent->plotStyle = static_cast<int>(handle);
31428 }
31429 if (entity->shadowMode() != 0) {
31430 ent->shadow = static_cast<DRW::ShadowMode>(entity->shadowMode());
31431 if (entity->shadowHandle() != 0) {
31432#ifdef DWGSUPPORT1
31433 ent->shadowHandle =
31434 m_dwgW != nullptr
31435 ? remapDwgReference(entity->shadowHandle(), "shadow")
31436 : entity->shadowHandle();
31437#else
31438 ent->shadowHandle = entity->shadowHandle();
31439#endif
31440 }
31441 }
31442 const auto mapVisualStyle = [&](quint32 source, const char *referenceKind) {
31443#ifdef DWGSUPPORT1
31444 return m_dwgW != nullptr ? remapDwgReference(source, referenceKind)
31445 : source;
31446#else
31447 (void)referenceKind;
31448 return source;
31449#endif
31450 };
31451 ent->fullVisualStyleHandle =
31452 mapVisualStyle(entity->fullVisualStyleHandle(), "full visual-style");
31453 ent->faceVisualStyleHandle =
31454 mapVisualStyle(entity->faceVisualStyleHandle(), "face visual-style");
31455 ent->edgeVisualStyleHandle =
31456 mapVisualStyle(entity->edgeVisualStyleHandle(), "edge visual-style");
31457 std::uint32_t reactorOrdinal = 0;
31458 for (quint32 source : entity->reactorHandles()) {
31459#ifdef DWGSUPPORT1
31460 const auto mapped =
31461 m_dwgW != nullptr ? remapDwgReference(source, "reactor", reactorOrdinal)
31462 : source;
31463#else
31464 const auto mapped = source;
31465#endif
31466 if (mapped != 0)
31467 ent->reactorHandles.push_back(mapped);
31468 ++reactorOrdinal;
31469 }
31470 ent->xDictHandle = entity->xDictHandle();
31471#ifdef DWGSUPPORT1
31472 if (m_dwgW != nullptr)
31473 ent->xDictHandle =
31474 remapDwgReference(ent->xDictHandle, "extension-dictionary");
31475#endif
31476
31477 // Re-emit any XDATA / EED that was attached on import. Skipped if
31478 // the dimension-export path (or any other caller) already populated
31479 // ent->extData with its own structured override.
31480 if (entity->hasDrwExtData() && ent->extData.empty()) {
31481 ent->extData = entity->getDrwExtData();
31482 }
31483}
31484
31485/**
31486 * @return Pen with the same attributes as 'attrib'.
31487 */
31488RS_Pen RS_FilterDXFRW::attributesToPen(const DRW_Layer *att) const {
31489 RS_Color col;
31490 if (att->color24 >= 0) {
31491 col = RS_Color(att->color24 >> 16, att->color24 >> 8 & 0xFF,
31492 att->color24 & 0xFF);
31493 } else {
31494 col = numberToColor(att->color);
31495 }
31496 if (!att->colorName.empty()) {
31497 col.setColorName(QString::fromUtf8(att->colorName.c_str()));
31498 }
31499
31500 RS_Pen pen(col, numberToWidth(att->lWeight),
31501 nameToLineType(QString::fromUtf8(att->lineType.c_str())));
31502 return pen;
31503}
31504
31505/**
31506 * Converts a color index (num) into a RS_Color object.
31507 * Please refer to the dxflib documentation for details.
31508 *
31509 * @param num Color number.
31510 */
31511RS_Color RS_FilterDXFRW::numberToColor(int num) {
31512 if (num == 0) {
31513 return RS_Color(RS2::FlagByBlock);
31514 } else if (num == 256) {
31515 return RS_Color(RS2::FlagByLayer);
31516 } else if (num <= 255 && num >= 0) {
31517 return RS_Color(DRW::dxfColors[num][0], DRW::dxfColors[num][1],
31518 DRW::dxfColors[num][2]);
31519 } else {
31520 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
31521 "RS_FilterDXF::numberToColor: Invalid color number given.");
31522 return RS_Color(RS2::FlagByLayer);
31523 }
31524
31525 return RS_Color();
31526}
31527
31528/**
31529 * Converts a color into a color number in the DXF palette.
31530 * The color that fits best is chosen.
31531 */
31532int RS_FilterDXFRW::colorToNumber(const RS_Color &col, int *rgb) {
31533 // printf("Searching color for %s\n", col.name().toLatin1().data());
31534 *rgb = -1;
31535 // Special color BYBLOCK:
31536 if (col.getFlag(RS2::FlagByBlock)) {
31537 return 0;
31538 }
31539 // Special color BYLAYER
31540 else if (col.getFlag(RS2::FlagByLayer)) {
31541 return 256;
31542 }
31543 // Special color black is not in the table but white represents both
31544 // black and white
31545 else {
31546 int red = col.red();
31547 int green = col.green();
31548 int blue = col.blue();
31549 if (red == 0 && green == 0 && blue == 0) {
31550 return 7;
31551 }
31552 // All other colors
31553 else {
31554 int num = 0;
31555 int diff =
31556 255 * 3; // smallest difference to a color in the table found so far
31557
31558 // Run through the whole table and compare
31559 for (int i = 1; i <= 255; i++) {
31560 int d = abs(red - DRW::dxfColors[i][0]) +
31561 abs(green - DRW::dxfColors[i][1]) +
31562 abs(blue - DRW::dxfColors[i][2]);
31563
31564 if (d < diff) {
31565 /*
31566 printf("color %f,%f,%f is closer\n",
31567 dxfColors[i][0],
31568 dxfColors[i][1],
31569 dxfColors[i][2]);
31570 */
31571 diff = d;
31572 num = i;
31573 if (d == 0) {
31574 break;
31575 }
31576 }
31577 }
31578 // printf(" Found: %d, diff: %d\n", num, diff);
31579 if (diff != 0) {
31580 *rgb = 0;
31581 *rgb = red << 16 | green << 8 | blue;
31582 }
31583 return num;
31584 }
31585 }
31586}
31587
31588void RS_FilterDXFRW::add3dFace(const DRW_3Dface &data) {
31589 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::add3dFace");
31590 RS_PolylineData d(RS_Vector(false), RS_Vector(false), !data.invisibleflag);
31591 auto *polyline = new RS_Polyline(m_currentContainer, d);
31592 setEntityAttributes(polyline, &data);
31593 RS_Vector v1(data.basePoint.x, data.basePoint.y);
31594 RS_Vector v2(data.secPoint.x, data.secPoint.y);
31595 RS_Vector v3(data.thirdPoint.x, data.thirdPoint.y);
31596 RS_Vector v4(data.fourPoint.x, data.fourPoint.y);
31597
31598 polyline->addVertex(v1, 0.0);
31599 polyline->addVertex(v2, 0.0);
31600 polyline->addVertex(v3, 0.0);
31601 polyline->addVertex(v4, 0.0);
31602
31603 // F2 sidecar: the RS_Polyline drops Z + the invisible-edge flags, so store
31604 // all 4 corners with Z and the flag (code 70) for native 3DFACE rebuild.
31605 std::vector<std::shared_ptr<DRW_Variant>> payload;
31606 payload.push_back(std::make_shared<DRW_Variant>(
31607 1010, DRW_Coord(data.basePoint.x, data.basePoint.y, data.basePoint.z)));
31608 payload.push_back(std::make_shared<DRW_Variant>(
31609 1011, DRW_Coord(data.secPoint.x, data.secPoint.y, data.secPoint.z)));
31610 payload.push_back(std::make_shared<DRW_Variant>(
31611 1012,
31612 DRW_Coord(data.thirdPoint.x, data.thirdPoint.y, data.thirdPoint.z)));
31613 payload.push_back(std::make_shared<DRW_Variant>(
31614 1013, DRW_Coord(data.fourPoint.x, data.fourPoint.y, data.fourPoint.z)));
31615 payload.push_back(
31616 std::make_shared<DRW_Variant>(1070, std::int32_t{data.invisibleflag}));
31617 appendTypeSidecar(polyline, k3dFaceMarker, std::move(payload));
31618
31619 m_currentContainer->addEntity(polyline);
31620}
31621
31622void RS_FilterDXFRW::addMesh(const DRW_Mesh &data) {
31623 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addMesh: %zu vertices, %zu faces",
31624 data.vertices.size(), data.faces.size());
31625 // Render the base-cage faces as closed polylines (LibreCAD is 2D; Z dropped,
31626 // matching add3dFace). Each face is a list of vertex indices into vertices[].
31627 for (const auto &face : data.faces) {
31628 if (face.size() < 2)
31629 continue;
31630 RS_PolylineData d(RS_Vector(false), RS_Vector(false), /*closed=*/true);
31631 auto *polyline = new RS_Polyline(m_currentContainer, d);
31632 setEntityAttributes(polyline, &data);
31633 bool any = false;
31634 for (std::int32_t idx : face) {
31635 if (idx < 0 || static_cast<size_t>(idx) >= data.vertices.size())
31636 continue;
31637 const DRW_Coord &v = data.vertices[static_cast<size_t>(idx)];
31638 polyline->addVertex(RS_Vector(v.x, v.y), 0.0);
31639 any = true;
31640 }
31641 if (any)
31642 m_currentContainer->addEntity(polyline);
31643 else
31644 delete polyline;
31645 }
31646}
31647
31648void RS_FilterDXFRW::addComment(const char *) {
31649 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addComment(const char*) not yet implemented.");
31650}
31651
31652void RS_FilterDXFRW::addPlotSettings(const DRW_PlotSettings *data) {
31653 if (m_graphic == nullptr || data == nullptr)
31654 return;
31655 m_graphic->dwgAdvancedMetadata().addPlotSettings(*data);
31656 LC_PlotSettings *ps = m_graphic->getPlotSettings();
31657 if (ps == nullptr)
31658 return;
31659 // fixme - review and rework this. It should not be stored there from DXF
31660 // point of view! may be use current style name instead of view name..?
31661 ps->setPagesNum(QString::fromStdString(data->plotViewName));
31662 ps->setMarginsInMm(data->marginLeft, data->marginTop, data->marginRight,
31663 data->marginBottom);
31664
31665 ps->setPaperWidthMm(data->paperWidth);
31666 ps->setPaperHeightMm(data->paperHeight);
31667 ps->setCustomPrintScaleDrawingUnitsDenominator(data->drawingUnits);
31668 ps->setCustomPrintScalePaperUnitsNumerator(data->realWorldUnits);
31669 ps->setOriginOffsetXMm(data->paperImageOriginX);
31670 ps->setOriginOffsetYMm(data->paperImageOriginY);
31671 ps->setPlotWindowLowerLeftX(data->windowMinX);
31672 ps->setPlotWindowLowerLeftY(data->windowMinY);
31673 ps->setPlotWindowUpperRightX(data->windowMaxX);
31674 ps->setPlotWindowUpperRightY(data->windowMaxY);
31675 ps->setPlotType(data->plotType);
31676 ps->setPlotLayoutFlag(data->plotLayoutFlags);
31677 ps->setPlotPaperUnits(data->paperUnits);
31678 ps->setPlotRotation(data->plotRotation);
31679 ps->setStandardScaleType(data->scaleType);
31680 ps->setShadePlotMode(data->shadePlotMode);
31681 ps->setShadePlotResolutionMode(data->shadePlotResLevel);
31682 ps->setShadePlotCustomDpi(data->shadePlotCustomDPI);
31683
31684 ps->setStandardScaleFactor(data->scaleFactor);
31685 ps->setPaperImageOriginX(data->paperImageOriginX);
31686 ps->setPaperImageOriginY(data->paperImageOriginY);
31687
31688 ps->setPaperSizeName(QString::fromStdString(data->paperSize));
31689 ps->setCurrentStyleName(QString::fromStdString(data->currentStyleSheet));
31690}
31691
31692/**
31693 * Converts a line type name (e.g. "CONTINUOUS") into a RS2::LineType
31694 * object.
31695 */
31696RS2::LineType RS_FilterDXFRW::nameToLineType(const QString &name) {
31697
31698 QString uName = name.toUpper();
31699
31700 // Standard linetypes for QCad II / AutoCAD
31701 if (uName.isEmpty() || uName == "BYLAYER") {
31702 return RS2::LineByLayer;
31703 }
31704 if (uName == "BYBLOCK") {
31705 return RS2::LineByBlock;
31706 }
31707 if (uName == "CONTINUOUS" || uName == "ACAD_ISO01W100") {
31708 return RS2::SolidLine;
31709 }
31710 if (uName == "ACAD_ISO07W100" || uName == "DOT") {
31711 return RS2::DotLine;
31712 }
31713 if (uName == "DOTTINY") {
31714 return RS2::DotLineTiny;
31715 }
31716 if (uName == "DOT2") {
31717 return RS2::DotLine2;
31718 }
31719 if (uName == "DOTX2") {
31720 return RS2::DotLineX2;
31721 }
31722 if (uName == "ACAD_ISO02W100" || uName == "ACAD_ISO03W100" ||
31723 uName == "DASHED") {
31724 return RS2::DashLine;
31725 }
31726 if (uName == "DASHEDTINY") {
31727 return RS2::DashLineTiny;
31728 }
31729 if (uName == "DASHED2") {
31730 return RS2::DashLine2;
31731 }
31732 if (uName == "DASHEDX2") {
31733 return RS2::DashLineX2;
31734 }
31735 if (uName == "HIDDEN") {
31736 return RS2::HiddenLine;
31737 }
31738 if (uName == "HIDDENTINY") {
31739 return RS2::HiddenLineTiny;
31740 }
31741 if (uName == "HIDDEN2") {
31742 return RS2::HiddenLine2;
31743 }
31744 if (uName == "HIDDENX2") {
31745 return RS2::HiddenLineX2;
31746 }
31747 if (uName == "ACAD_ISO10W100" || uName == "DASHDOT") {
31748 return RS2::DashDotLine;
31749 }
31750 if (uName == "DASHDOTTINY") {
31751 return RS2::DashDotLineTiny;
31752 }
31753 if (uName == "DASHDOT2") {
31754 return RS2::DashDotLine2;
31755 }
31756 if (uName == "ACAD_ISO04W100" || uName == "DASHDOTX2") {
31757 return RS2::DashDotLineX2;
31758 }
31759 if (uName == "ACAD_ISO12W100" || uName == "DIVIDE") {
31760 return RS2::DivideLine;
31761 }
31762 if (uName == "DIVIDETINY") {
31763 return RS2::DivideLineTiny;
31764 }
31765 if (uName == "DIVIDE2") {
31766 return RS2::DivideLine2;
31767 }
31768 if (uName == "ACAD_ISO05W100" || uName == "DIVIDEX2") {
31769 return RS2::DivideLineX2;
31770 }
31771 if (uName == "CENTER") {
31772 return RS2::CenterLine;
31773 }
31774 if (uName == "CENTERTINY") {
31775 return RS2::CenterLineTiny;
31776 }
31777 if (uName == "CENTER2") {
31778 return RS2::CenterLine2;
31779 }
31780 if (uName == "CENTERX2") {
31781 return RS2::CenterLineX2;
31782 }
31783 // ISO 128-20 type 09 "long-dashed double-short-dashed" (24,-3,6,-3,6,-3)
31784 // is PHANTOM's shape at PHANTOM's scale, like the ISO04/ISO05 aliases.
31785 if (uName == "ACAD_ISO09W100" || uName == "PHANTOM") {
31786 return RS2::PhantomLine;
31787 }
31788 if (uName == "PHANTOMTINY") {
31789 return RS2::PhantomLineTiny;
31790 }
31791 if (uName == "PHANTOM2") {
31792 return RS2::PhantomLine2;
31793 }
31794 if (uName == "PHANTOMX2") {
31795 return RS2::PhantomLineX2;
31796 }
31797 if (uName == "BORDER") {
31798 return RS2::BorderLine;
31799 }
31800 if (uName == "BORDERTINY") {
31801 return RS2::BorderLineTiny;
31802 }
31803 if (uName == "BORDER2") {
31804 return RS2::BorderLine2;
31805 }
31806 if (uName == "BORDERX2") {
31807 return RS2::BorderLineX2;
31808 }
31809
31810 return RS2::SolidLine;
31811}
31812
31813/**
31814 * Converts a RS_LineType into a name for a line type.
31815 */
31816QString RS_FilterDXFRW::lineTypeToName(RS2::LineType lineType) {
31817 // Standard linetypes for QCad II / AutoCAD
31818 switch (lineType) {
31819 case RS2::SolidLine:
31820 return "CONTINUOUS";
31821 case RS2::DotLine:
31822 return "DOT";
31823 case RS2::DotLineTiny:
31824 return "DOTTINY";
31825 case RS2::DotLine2:
31826 return "DOT2";
31827 case RS2::DotLineX2:
31828 return "DOTX2";
31829 case RS2::DashLine:
31830 return "DASHED";
31831 case RS2::DashLineTiny:
31832 return "DASHEDTINY";
31833 case RS2::DashLine2:
31834 return "DASHED2";
31835 case RS2::DashLineX2:
31836 return "DASHEDX2";
31837 case RS2::HiddenLine:
31838 return "HIDDEN";
31839 case RS2::HiddenLineTiny:
31840 return "HIDDENTINY";
31841 case RS2::HiddenLine2:
31842 return "HIDDEN2";
31843 case RS2::HiddenLineX2:
31844 return "HIDDENX2";
31845 case RS2::DashDotLine:
31846 return "DASHDOT";
31847 case RS2::DashDotLineTiny:
31848 return "DASHDOTTINY";
31849 case RS2::DashDotLine2:
31850 return "DASHDOT2";
31851 case RS2::DashDotLineX2:
31852 return "DASHDOTX2";
31853 case RS2::DivideLine:
31854 return "DIVIDE";
31855 case RS2::DivideLineTiny:
31856 return "DIVIDETINY";
31857 case RS2::DivideLine2:
31858 return "DIVIDE2";
31859 case RS2::DivideLineX2:
31860 return "DIVIDEX2";
31861 case RS2::CenterLine:
31862 return "CENTER";
31863 case RS2::CenterLineTiny:
31864 return "CENTERTINY";
31865 case RS2::CenterLine2:
31866 return "CENTER2";
31867 case RS2::CenterLineX2:
31868 return "CENTERX2";
31869 case RS2::PhantomLine:
31870 return "PHANTOM";
31871 case RS2::PhantomLineTiny:
31872 return "PHANTOMTINY";
31873 case RS2::PhantomLine2:
31874 return "PHANTOM2";
31875 case RS2::PhantomLineX2:
31876 return "PHANTOMX2";
31877 case RS2::BorderLine:
31878 return "BORDER";
31879 case RS2::BorderLineTiny:
31880 return "BORDERTINY";
31881 case RS2::BorderLine2:
31882 return "BORDER2";
31883 case RS2::BorderLineX2:
31884 return "BORDERX2";
31885 case RS2::LineByLayer:
31886 return "ByLayer";
31887 case RS2::LineByBlock:
31888 return "ByBlock";
31889 default:
31890 break;
31891 }
31892 return "CONTINUOUS";
31893}
31894
31895/**
31896 * Converts a RS_LineType into a name for a line type.
31897 */
31898/*QString RS_FilterDXFRW::lineTypeToDescription(RS2::LineType lineType) {
31899
31900 // Standard linetypes for QCad II / AutoCAD
31901 switch (lineType) {
31902 case RS2::SolidLine:
31903 return "Solid line";
31904 case RS2::DotLine:
31905 return "ISO Dashed __ __ __ __ __ __ __ __ __ __ _";
31906 case RS2::DashLine:
31907 return "ISO Dashed with Distance __ __ __ _";
31908 case RS2::DashDotLine:
31909 return "ISO Long Dashed Dotted ____ . ____ . __";
31910 case RS2::DashDotDotLine:
31911 return "ISO Long Dashed Double Dotted ____ .. __";
31912 case RS2::LineByLayer:
31913 return "";
31914 case RS2::LineByBlock:
31915 return "";
31916 default:
31917 break;
31918 }
31919
31920 return "CONTINUOUS";
31921}*/
31922
31923namespace {
31924// The DXF/DWG line-weight encoding and LibreCAD's RS2::LineWidth are a
31925// one-to-one mapping. numberToWidth() and widthToNumber() are the two
31926// directions of it; they used to be a pair of 27-case switches that had to be
31927// kept in step by hand, so state the pairs once here instead.
31928struct LineWidthPair {
31929 DRW_LW_Conv::lineWidth drw;
31930 RS2::LineWidth rs;
31931};
31932
31933constexpr LineWidthPair kLineWidthPairs[] = {
31934 {DRW_LW_Conv::widthByLayer, RS2::WidthByLayer},
31935 {DRW_LW_Conv::widthByBlock, RS2::WidthByBlock},
31936 {DRW_LW_Conv::widthDefault, RS2::WidthDefault},
31937 {DRW_LW_Conv::width00, RS2::Width00},
31938 {DRW_LW_Conv::width01, RS2::Width01},
31939 {DRW_LW_Conv::width02, RS2::Width02},
31940 {DRW_LW_Conv::width03, RS2::Width03},
31941 {DRW_LW_Conv::width04, RS2::Width04},
31942 {DRW_LW_Conv::width05, RS2::Width05},
31943 {DRW_LW_Conv::width06, RS2::Width06},
31944 {DRW_LW_Conv::width07, RS2::Width07},
31945 {DRW_LW_Conv::width08, RS2::Width08},
31946 {DRW_LW_Conv::width09, RS2::Width09},
31947 {DRW_LW_Conv::width10, RS2::Width10},
31948 {DRW_LW_Conv::width11, RS2::Width11},
31949 {DRW_LW_Conv::width12, RS2::Width12},
31950 {DRW_LW_Conv::width13, RS2::Width13},
31951 {DRW_LW_Conv::width14, RS2::Width14},
31952 {DRW_LW_Conv::width15, RS2::Width15},
31953 {DRW_LW_Conv::width16, RS2::Width16},
31954 {DRW_LW_Conv::width17, RS2::Width17},
31955 {DRW_LW_Conv::width18, RS2::Width18},
31956 {DRW_LW_Conv::width19, RS2::Width19},
31957 {DRW_LW_Conv::width20, RS2::Width20},
31958 {DRW_LW_Conv::width21, RS2::Width21},
31959 {DRW_LW_Conv::width22, RS2::Width22},
31960 {DRW_LW_Conv::width23, RS2::Width23},
31961};
31962
31963// The pair of switches this table replaced could not carry a duplicate case
31964// label - the compiler rejected it. Keep that guarantee here: both lookups
31965// return the first matching row, so a repeated value on either side would
31966// silently shadow a later one instead of failing the build.
31967constexpr bool lineWidthPairsAreOneToOne() {
31968 constexpr std::size_t count =
31969 sizeof(kLineWidthPairs) / sizeof(kLineWidthPairs[0]);
31970 for (std::size_t i = 0; i < count; ++i) {
31971 for (std::size_t j = i + 1; j < count; ++j) {
31972 if (kLineWidthPairs[i].drw == kLineWidthPairs[j].drw
31973 || kLineWidthPairs[i].rs == kLineWidthPairs[j].rs)
31974 return false;
31975 }
31976 }
31977 return true;
31978}
31979
31980static_assert(lineWidthPairsAreOneToOne(),
31981 "kLineWidthPairs: every DRW_LW_Conv::lineWidth and every "
31982 "RS2::LineWidth may appear once, so neither direction of the "
31983 "lookup can shadow a later row");
31984} // namespace
31985
31986/**
31987 * Converts a DRW_LW_Conv::lineWidth into a RS2::LineWidth.
31988 */
31989RS2::LineWidth RS_FilterDXFRW::numberToWidth(DRW_LW_Conv::lineWidth lw) {
31990 for (const LineWidthPair &pair : kLineWidthPairs) {
31991 if (pair.drw == lw)
31992 return pair.rs;
31993 }
31994 return RS2::WidthDefault;
31995}
31996
31997/**
31998 * Converts a RS2::LineWidth into an DRW_LW_Conv::lineWidth.
31999 */
32000DRW_LW_Conv::lineWidth RS_FilterDXFRW::widthToNumber(RS2::LineWidth width) {
32001 for (const LineWidthPair &pair : kLineWidthPairs) {
32002 if (pair.rs == width)
32003 return pair.drw;
32004 }
32005 return DRW_LW_Conv::widthDefault;
32006}
32007
32008/**
32009 * Converts a native unicode string into a DXF encoded string.
32010 *
32011 * DXF endoding includes the following special sequences:
32012 * - %%%c for a diameter sign
32013 * - %%%d for a degree sign
32014 * - %%%p for a plus/minus sign
32015 */
32016QString RS_FilterDXFRW::toDxfString(const QString &str) {
32017 QString res;
32018 res.reserve(str.length() + 16);
32019
32020 for (const QChar &qchar : str) {
32021 switch (qchar.unicode()) {
32022 case 0x0A:
32023 res.append(uR"(\P)");
32024 break;
32025 case 0x2205:
32026 case 0x2300:
32027 res.append(u"%%C");
32028 break;
32029 case 0x00B0:
32030 res.append(u"%%D");
32031 break;
32032 case 0x00B1:
32033 res.append(u"%%P");
32034 break;
32035 default:
32036 res.append(qchar);
32037 break;
32038 }
32039 }
32040 return res;
32041}
32042
32043/**
32044 * Converts a DXF encoded string into a native Unicode string.
32045 */
32046QString RS_FilterDXFRW::toNativeString(const QString &data) {
32047 QString res;
32048 const int n = data.length();
32049
32050 // Ignore font tags:
32051 int j = 0;
32052 for (int i = 0; i < n; ++i) {
32053 // Need at least "{\X" available — bounds-check before any at(i+N).
32054 if (i + 2 >= n)
32055 break;
32056 if (data.at(i).unicode() != 0x7B)
32057 continue; // is '{' ?
32058 if (data.at(i + 1).unicode() != 0x5c)
32059 continue; // and is "{\" ?
32060 const ushort tagChar = data.at(i + 2).unicode();
32061 if (tagChar != 0x66 && tagChar != 0x48 && tagChar != 0x43)
32062 continue; // "\f" "\H" "\C"
32063
32064 // found tag, append parsed part
32065 res.append(data.mid(j, i - j));
32066 qsizetype pos = data.indexOf(QChar(0x7D), i + 3); // find '}'
32067 if (pos < 0)
32068 break; //'}' not found
32069 QString tmp = data.mid(i + 1, pos - i - 1);
32070 do {
32071 qsizetype semi = tmp.indexOf(QChar(0x3B), 0); // find ';'
32072 if (semi < 0)
32073 break; // malformed: no ';' — bail to avoid infinite loop
32074 tmp.remove(0, semi + 1);
32075 } while (tmp.startsWith(QLatin1StringView("\\f")) ||
32076 tmp.startsWith(QLatin1StringView("\\H")) ||
32077 tmp.startsWith(QLatin1StringView("\\C")));
32078 res.append(tmp);
32079 i = pos;
32080 j = pos + 1;
32081 }
32082 res.append(data.mid(j));
32083
32084 // AutoCAD caret convention: ^X → chr((X-64) mod 126); ^Space → '^'
32085 // literal. Mirrors ezdxf tools/text.py:501. Run BEFORE the \P/\~ pass
32086 // so ^J (LF), ^M (CR), ^I (TAB) decode first; also subsumes the prior
32087 // hard-coded "^I → 4 spaces" rule with the general formula (^I → TAB).
32088 QString caretDecoded;
32089 caretDecoded.reserve(res.size());
32090 for (int k = 0; k < res.size(); ++k) {
32091 const QChar ch = res.at(k);
32092 if (ch.unicode() == 0x5E /* '^' */ && k + 1 < res.size()) {
32093 const QChar nx = res.at(k + 1);
32094 if (nx.unicode() == 0x20) {
32095 caretDecoded.append(QChar(0x5E)); // ^space → literal ^
32096 } else {
32097 const int code = (static_cast<int>(nx.unicode()) - 64) % 126;
32098 caretDecoded.append(
32099 QChar(static_cast<ushort>(code < 0 ? code + 126 : code)));
32100 }
32101 ++k; // consume the X
32102 } else {
32103 caretDecoded.append(ch);
32104 }
32105 }
32106 res = std::move(caretDecoded);
32107
32108 // Replace literal escape sequences. Each replace() mutates in place;
32109 // the prior `res = res.replace(...)` was an unnecessary self-assignment.
32110 res.replace(QLatin1StringView("\\P"), QLatin1StringView("\n")); // line feed
32111 res.replace(QLatin1StringView("\\~"), QLatin1StringView(" ")); // space
32112 // diameter / degree / plus-minus — case-insensitive match of %%c / %%C etc.
32113 // (RLZ: Empty_set is 0x2205, diameter is 0x2300 — needs to be in all fonts.)
32114 res.replace(QLatin1StringView("%%c"), QStringLiteral("⌀")(QString(QtPrivate::qMakeStringPrivate(u"" "⌀"))),
32115 Qt::CaseInsensitive);
32116 res.replace(QLatin1StringView("%%d"), QStringLiteral("°")(QString(QtPrivate::qMakeStringPrivate(u"" "°"))),
32117 Qt::CaseInsensitive);
32118 res.replace(QLatin1StringView("%%p"), QStringLiteral("±")(QString(QtPrivate::qMakeStringPrivate(u"" "±"))),
32119 Qt::CaseInsensitive);
32120
32121 return res;
32122}
32123
32124/**
32125 * Converts the given number from a DXF file into an AngleFormat enum.
32126 *
32127 * @param num $DIMAUNIT from DXF (0: decimal deg, 1: deg/min/sec, 2: gradians,
32128 * 3: radians, 4: surveyor's units)
32129 *
32130 * @ret Matching AngleFormat enum value.
32131 */
32132RS2::AngleFormat RS_FilterDXFRW::numberToAngleFormat(int num) {
32133 switch (num) {
32134 default:
32135 case 0:
32136 return RS2::DegreesDecimal;
32137 case 1:
32138 return RS2::DegreesMinutesSeconds;
32139 case 2:
32140 return RS2::Gradians;
32141 case 3:
32142 return RS2::Radians;
32143 case 4:
32144 return RS2::Surveyors;
32145 }
32146}
32147
32148/**
32149 * Converts AngleFormat enum to DXF number.
32150 */
32151int RS_FilterDXFRW::angleFormatToNumber(RS2::AngleFormat af) {
32152 switch (af) {
32153 default:
32154 case RS2::DegreesDecimal:
32155 return 0;
32156 case RS2::DegreesMinutesSeconds:
32157 return 1;
32158 case RS2::Gradians:
32159 return 2;
32160 case RS2::Radians:
32161 return 3;
32162 case RS2::Surveyors:
32163 return 4;
32164 }
32165}
32166
32167/**
32168 * converts a DXF unit setting (e.g. INSUNITS) to a unit enum.
32169 */
32170RS2::Unit RS_FilterDXFRW::numberToUnit(int num) {
32171 switch (num) {
32172 default:
32173 case 0:
32174 return RS2::None;
32175 case 1:
32176 return RS2::Inch;
32177 case 2:
32178 return RS2::Foot;
32179 case 3:
32180 return RS2::Mile;
32181 case 4:
32182 return RS2::Millimeter;
32183 case 5:
32184 return RS2::Centimeter;
32185 case 6:
32186 return RS2::Meter;
32187 case 7:
32188 return RS2::Kilometer;
32189 case 8:
32190 return RS2::Microinch;
32191 case 9:
32192 return RS2::Mil;
32193 case 10:
32194 return RS2::Yard;
32195 case 11:
32196 return RS2::Angstrom;
32197 case 12:
32198 return RS2::Nanometer;
32199 case 13:
32200 return RS2::Micron;
32201 case 14:
32202 return RS2::Decimeter;
32203 case 15:
32204 return RS2::Decameter;
32205 case 16:
32206 return RS2::Hectometer;
32207 case 17:
32208 return RS2::Gigameter;
32209 case 18:
32210 return RS2::Astro;
32211 case 19:
32212 return RS2::Lightyear;
32213 case 20:
32214 return RS2::Parsec;
32215 }
32216 return RS2::None;
32217}
32218
32219/**
32220 * Converts a unit enum into a DXF unit number e.g. for INSUNITS.
32221 */
32222int RS_FilterDXFRW::unitToNumber(RS2::Unit unit) {
32223 switch (unit) {
32224 default:
32225 case RS2::None:
32226 return 0;
32227 case RS2::Inch:
32228 return 1;
32229 case RS2::Foot:
32230 return 2;
32231 case RS2::Mile:
32232 return 3;
32233 case RS2::Millimeter:
32234 return 4;
32235 case RS2::Centimeter:
32236 return 5;
32237 case RS2::Meter:
32238 return 6;
32239 case RS2::Kilometer:
32240 return 7;
32241 case RS2::Microinch:
32242 return 8;
32243 case RS2::Mil:
32244 return 9;
32245 case RS2::Yard:
32246 return 10;
32247 case RS2::Angstrom:
32248 return 11;
32249 case RS2::Nanometer:
32250 return 12;
32251 case RS2::Micron:
32252 return 13;
32253 case RS2::Decimeter:
32254 return 14;
32255 case RS2::Decameter:
32256 return 15;
32257 case RS2::Hectometer:
32258 return 16;
32259 case RS2::Gigameter:
32260 return 17;
32261 case RS2::Astro:
32262 return 18;
32263 case RS2::Lightyear:
32264 return 19;
32265 case RS2::Parsec:
32266 return 20;
32267 }
32268 return 0;
32269}
32270
32271/**
32272 * Checks if the given variable is two-dimensional (e.g. $LIMMIN).
32273 */
32274bool RS_FilterDXFRW::isVariableTwoDimensional(const QString &var) {
32275 if (var == "$LIMMIN" || var == "$LIMMAX" || var == "$PLIMMIN" ||
32276 var == "$PLIMMAX" || var == "$GRIDUNIT" || var == "$VIEWCTR") {
32277 return true;
32278 } else {
32279 return false;
32280 }
32281}
32282
32283void RS_FilterDXFRW::addPolylineSegment(
32284 RS_Polyline &polyline, const RS_Vector &prev_pos, const RS_Vector &curr_pos,
32285 const double segmentBulge, const double nextBulge,
32286 const std::vector<std::shared_ptr<DRW_Variant>> &extData) {
32287 bool isLcData = false;
32288 double yRadius = 0.0;
32289
32290 // Issue #1946: LibreCAD may use elliptic arcs internally, but dxf file saving
32291 // uses arcs only. Minor radius of elliptic is saved as DRW_Variant::content
32292 // The content is flagged as "LibreCAD"
32293 for (const std::shared_ptr<DRW_Variant> &var : extData) {
32294 if (var == nullptr)
32295 continue;
32296 if (var->code() == 1001) {
32297 std::string marker;
32298 isLcData = readXDataMarker(var.get(), marker) == XDataReadResult::Valid &&
32299 marker == "LibreCad";
32300 } else if (isLcData && var->code() == 1040) {
32301 double radius = 0.0;
32302 if (readXDataDouble(var.get(), 1040, radius) == XDataReadResult::Valid) {
32303 yRadius = radius;
32304 } else {
32305 isLcData = false;
32306 }
32307 }
32308 }
32309 bool isElliptic = yRadius > RS_TOLERANCE1.0e-10;
32310
32311 if (isElliptic) {
32312 std::unique_ptr<RS_Arc> arc{
32313 RS_Polyline::arcFromBulge(prev_pos, curr_pos, segmentBulge)};
32314 if (arc != nullptr && arc->getRadius() >= RS_TOLERANCE1.0e-10) {
32315 double radius = arc->getRadius();
32316 double scaleRatio = yRadius / radius;
32317 RS_Ellipse *ellipse =
32318 RS_Polyline::convertToEllipse(std::make_pair(arc.get(), scaleRatio));
32319 if (ellipse != nullptr) {
32320 ellipse->setParent(&polyline);
32321 // ellipse->setSelected(polyline.isSelected());
32322 ellipse->setPen(RS_Pen(RS2::FlagInvalid));
32323 ellipse->setLayer(nullptr);
32324 polyline.addEntity(ellipse);
32325 polyline.getData().endpoint = curr_pos;
32326 polyline.setNextBulge(nextBulge);
32327 return;
32328 }
32329 }
32330 }
32331
32332 // A malformed LibreCAD elliptic sidecar must not leave a gap in an
32333 // otherwise valid polyline. addVertex consumes the current outgoing bulge
32334 // and stores the following vertex's bulge for the next segment.
32335 polyline.addVertex(curr_pos, nextBulge, false);
32336}
32337
32338#ifdef DWGSUPPORT1
32339QString RS_FilterDXFRW::printDwgVersion(int v) {
32340 switch (v) {
32341 case DRW::MC00:
32342 return "MC0.0/R1.1";
32343 case DRW::AC12:
32344 return "AC1.2/R1.2";
32345 case DRW::AC14:
32346 return "AC1.4/R1.4";
32347 case DRW::AC150:
32348 return "AC1.50/R2.0";
32349 case DRW::AC210:
32350 return "AC2.10/R2.10";
32351 case DRW::AC1002:
32352 return "AC1002/R2.5";
32353 case DRW::AC1003:
32354 return "AC1003/R2.6";
32355 case DRW::AC1004:
32356 return "AC1004/R9";
32357 case DRW::AC1006:
32358 return "AC1006/R10";
32359 case DRW::AC1009:
32360 return "AC1009/R11-R12";
32361 case DRW::AC1012:
32362 return "AC1012/R13";
32363 case DRW::AC1014:
32364 return "AC1014/R14";
32365 case DRW::AC1015:
32366 return "AC1015/R2000";
32367 case DRW::AC1018:
32368 return "AC1018/R2004";
32369 case DRW::AC1021:
32370 return "AC1021/R2007";
32371 case DRW::AC1024:
32372 return "AC1024/R2010";
32373 case DRW::AC1027:
32374 return "AC1027/R2013";
32375 case DRW::AC1032:
32376 return "AC1032/R2018";
32377 default:
32378 return "unknown";
32379 }
32380}
32381
32382bool RS_FilterDXFRW::sameRawObjectEncodingFamily(DRW::Version src,
32383 DRW::Version tgt) {
32384 // STRICT source==target only. The earlier "encoding family" widening (Alt-A)
32385 // grouped {AC1015,AC1018} and {AC1024,AC1027,AC1032} as cross-version
32386 // replayable, but a deep review proved the raw OBJECT/ENTITY bytes are NOT
32387 // safely replayable across a version boundary: the common object/entity
32388 // header layout differs (AC1015 lacks the R2004 xDictFlag bit and carries
32389 // entity prev/next link handles AC1018 drops; AC1024 lacks the has_ds_data
32390 // bit AC1027/AC1032 add), and even where common headers match the per-version
32391 // object BODY encoding can differ -- the raw bytes were written FOR the
32392 // source version. Replaying them into another version's frame (with a fresh
32393 // valid CRC) yields silently-malformed output. So raw replay is allowed only
32394 // when the target version equals the source version; a cross-version save
32395 // cleanly drops the raw-preserved metadata instead of corrupting it.
32396 return src == tgt;
32397}
32398
32399void RS_FilterDXFRW::printDwgError(int le) {
32400 switch (le) {
32401 case DRW::BAD_UNKNOWN:
32402 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
32403 QObject::tr("unknown error opening dwg file"));
32404 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_UNKNOWN");
32405 break;
32406 case DRW::BAD_OPEN:
32407 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(QObject::tr("can't open this dwg file"));
32408 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_OPEN");
32409 break;
32410 case DRW::BAD_VERSION:
32411 if (m_dwgVersion != DRW::UNKNOWNV) {
32412 const QString actual = dwgVersionDisplay(m_dwgVersion);
32413 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
32414 QObject::tr(
32415 "Cannot open DWG: file is %1; LibreCAD supports %2 and "
32416 "newer. "
32417 "Convert with GNU LibreDWG (dwgread / dwg2dxf) or re-save "
32418 "from a recent CAD tool.")
32419 .arg(actual)
32420 .arg(dwgVersionDisplay(DRW::AC1012)));
32421 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: BAD_VERSION (%s)",
32422 actual.toUtf8().constData());
32423 } else {
32424 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(QObject::tr("unsupported dwg version"));
32425 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_VERSION");
32426 }
32427 break;
32428 case DRW::BAD_READ_METADATA:
32429 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
32430 QObject::tr("error reading file metadata in dwg file"));
32431 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_READ_FILE_HEADER");
32432 break;
32433 case DRW::BAD_READ_FILE_HEADER:
32434 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
32435 QObject::tr("error reading file header in dwg file"));
32436 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_READ_FILE_HEADER");
32437 break;
32438 case DRW::BAD_READ_HEADER:
32439 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
32440 QObject::tr("error reading header vars in dwg file"));
32441 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_READ_HEADER");
32442 break;
32443 case DRW::BAD_READ_CLASSES:
32444 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
32445 QObject::tr("error reading classes in dwg file"));
32446 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_READ_CLASSES");
32447 break;
32448 case DRW::BAD_READ_HANDLES:
32449 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
32450 QObject::tr("error reading offsets in dwg file"));
32451 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_READ_OFFSETS");
32452 break;
32453 case DRW::BAD_READ_TABLES:
32454 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
32455 QObject::tr("error reading tables in dwg file"));
32456 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_READ_TABLES");
32457 break;
32458 case DRW::BAD_READ_BLOCKS:
32459 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
32460 QObject::tr("error reading blocks in dwg file"));
32461 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_READ_OFFSETS");
32462 break;
32463 case DRW::BAD_READ_ENTITIES:
32464 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
32465 QObject::tr("error reading entities in dwg file"));
32466 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_READ_ENTITIES");
32467 break;
32468 case DRW::BAD_READ_OBJECTS:
32469 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
32470 QObject::tr("error reading objects in dwg file"));
32471 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_READ_OBJECTS");
32472 break;
32473 default:
32474 break;
32475 }
32476}
32477
32478QString RS_FilterDXFRW::strVal(const DRW_Variant *var) {
32479 if (var == nullptr || var->type() != DRW_Variant::STRING ||
32480 var->content.s == nullptr)
32481 return {};
32482 return QString::fromUtf8(var->content.s->c_str());
32483}
32484
32485LC_DimStyle *RS_FilterDXFRW::createDimStyle(const DRW_Dimstyle &s) {
32486 auto *result = new LC_DimStyle();
32487 QString name = QString::fromUtf8(s.name.c_str());
32488 result->setName(name);
32489
32490 bool styleForEntityType = !result->isBaseStyle();
32491 if (styleForEntityType) {
32492 result->setModifyCheckMode(LC_DimStyle::ModificationAware::ALL);
32493 }
32494
32495 auto arrowStyle = result->arrowhead();
32496
32497 DRW_Variant *var = checkedDimStyleVariable(s, "$DIMBLK");
32498 if (var != nullptr) {
32499 arrowStyle->setSameBlockName(strVal(var));
32500 }
32501 var = checkedDimStyleVariable(s, "$DIMBLK1");
32502 if (var != nullptr) {
32503 arrowStyle->setArrowHeadBlockNameFirst(strVal(var));
32504 }
32505 var = checkedDimStyleVariable(s, "$DIMBLK2");
32506 if (var != nullptr) {
32507 arrowStyle->setArrowHeadBlockNameSecond(strVal(var));
32508 }
32509 var = checkedDimStyleVariable(s, "$DIMASZ");
32510 if (var != nullptr) {
32511 arrowStyle->setSize(var->d_val());
32512 }
32513 var = checkedDimStyleVariable(s, "$DIMTSZ");
32514 if (var != nullptr) {
32515 arrowStyle->setTickSize(var->d_val());
32516 }
32517 var = checkedDimStyleVariable(s, "$DIMSAH");
32518 if (var != nullptr) {
32519 arrowStyle->setUseSeparateArrowHeads(var->i_val());
32520 }
32521 var = checkedDimStyleVariable(s, "$DIMSOXD");
32522 if (var != nullptr) {
32523 arrowStyle->setSuppressionsRaw(var->i_val());
32524 }
32525
32526 auto scaleStyle = result->scaling();
32527 var = checkedDimStyleVariable(s, "$DIMSCALE");
32528 if (var != nullptr) {
32529 scaleStyle->setScale(var->d_val());
32530 }
32531 var = checkedDimStyleVariable(s, "$DIMLFAC");
32532 if (var != nullptr) {
32533 scaleStyle->setLinearFactor(var->d_val());
32534 }
32535
32536 auto extLineStyle = result->extensionLine();
32537
32538 var = checkedDimStyleVariable(s, "$DIMEXO");
32539 if (var != nullptr) {
32540 extLineStyle->setDistanceFromOriginPoint(var->d_val());
32541 }
32542 var = checkedDimStyleVariable(s, "$DIMEXE");
32543 if (var != nullptr) {
32544 extLineStyle->setDistanceBeyondDimLine(var->d_val());
32545 }
32546 var = checkedDimStyleVariable(s, "$DIMFXL");
32547 if (var != nullptr) {
32548 extLineStyle->setFixedLength(var->d_val());
32549 }
32550 var = checkedDimStyleVariable(s, "$DIMFXLON");
32551 if (var != nullptr) {
32552 extLineStyle->setHasFixedLength(var->i_val() == 1);
32553 }
32554 var = checkedDimStyleVariable(s, "$DIMLWE");
32555 if (var != nullptr) {
32556 extLineStyle->setLineWidthRaw(var->i_val());
32557 }
32558 var = checkedDimStyleVariable(s, "$DIMCLRE");
32559 if (var != nullptr) {
32560 extLineStyle->setColor(numberToColor(var->i_val()));
32561 }
32562 var = checkedDimStyleVariable(s, "$DIMSE1");
32563 if (var != nullptr) {
32564 extLineStyle->setSuppressFirstRaw(var->i_val());
32565 }
32566 var = checkedDimStyleVariable(s, "$DIMSE2");
32567 if (var != nullptr) {
32568 extLineStyle->setSuppressSecondRaw(var->i_val());
32569 }
32570 var = checkedDimStyleVariable(s, "$DIMLTEX1");
32571 if (var != nullptr) {
32572 auto dimltex1 = strVal(var);
32573 if (!dimltex1.isEmpty()) {
32574 extLineStyle->setLineTypeFirst(dimltex1);
32575 }
32576 }
32577 var = checkedDimStyleVariable(s, "$DIMLTEX2");
32578 if (var != nullptr) {
32579 auto dimltex2 = strVal(var);
32580 if (!dimltex2.isEmpty()) {
32581 extLineStyle->setLineTypeSecond(dimltex2);
32582 }
32583 }
32584
32585 auto dimLineStyle = result->dimensionLine();
32586 var = checkedDimStyleVariable(s, "$DIMLWD");
32587 if (var != nullptr) {
32588 dimLineStyle->setLineWidthRaw(var->i_val());
32589 }
32590 var = checkedDimStyleVariable(s, "$DIMDLE");
32591 if (var != nullptr) {
32592 dimLineStyle->setDistanceBeyondExtLinesForObliqueStroke(var->d_val());
32593 }
32594 var = checkedDimStyleVariable(s, "$DIMDLI");
32595 if (var != nullptr) {
32596 dimLineStyle->setBaselineDimLinesSpacing(var->d_val());
32597 }
32598 var = checkedDimStyleVariable(s, "$DIMGAP");
32599 if (var != nullptr) {
32600 dimLineStyle->setLineGap(var->d_val());
32601 }
32602 var = checkedDimStyleVariable(s, "$DIMCLRD");
32603 if (var != nullptr) {
32604 dimLineStyle->setColor(numberToColor(var->i_val()));
32605 }
32606 var = checkedDimStyleVariable(s, "$DIMSD1");
32607 if (var != nullptr) {
32608 dimLineStyle->setSuppressFirstLineRaw(var->i_val());
32609 }
32610 var = checkedDimStyleVariable(s, "$DIMSD2");
32611 if (var != nullptr) {
32612 dimLineStyle->setSuppressSecondLineRaw(var->i_val());
32613 }
32614 var = checkedDimStyleVariable(s, "$DIMTOFL");
32615 if (var != nullptr) {
32616 dimLineStyle->setDrawPolicyForOutsideTextRaw(var->i_val());
32617 }
32618 var = checkedDimStyleVariable(s, "$DIMLTYPE");
32619 if (var != nullptr) {
32620 auto dimltype = strVal(var);
32621 if (!dimltype.isEmpty()) {
32622 dimLineStyle->setLineType(dimltype);
32623 }
32624 }
32625
32626 auto textStyle = result->text();
32627 var = checkedDimStyleVariable(s, "$DIMTXT");
32628 if (var != nullptr) {
32629 textStyle->setHeight(var->d_val());
32630 }
32631 var = checkedDimStyleVariable(s, "$DIMTXSTY");
32632 if (var != nullptr) {
32633 auto dimtxsty = strVal(var);
32634 prepareTextStyleName(dimtxsty);
32635 textStyle->setStyle(dimtxsty); // fixme - resolve via table?
32636 }
32637 var = checkedDimStyleVariable(s, "$DIMTOH");
32638 if (var != nullptr) {
32639 textStyle->setOrientationOutsideRaw(var->i_val());
32640 }
32641 var = checkedDimStyleVariable(s, "$DIMTIH");
32642 if (var != nullptr) {
32643 textStyle->setOrientationInsideRaw(var->i_val());
32644 }
32645 var = checkedDimStyleVariable(s, "$DIMJUST");
32646 if (var != nullptr) {
32647 textStyle->setHorizontalPositioningRaw(var->i_val());
32648 }
32649 var = checkedDimStyleVariable(s, "$DIMCLRT");
32650 if (var != nullptr) {
32651 textStyle->setColor(numberToColor(var->i_val()));
32652 }
32653 var = checkedDimStyleVariable(s, "$DIMTAD");
32654 if (var != nullptr) {
32655 textStyle->setVerticalPositioningRaw(var->i_val());
32656 }
32657 var = checkedDimStyleVariable(s, "$DIMTIX");
32658 if (var != nullptr) {
32659 textStyle->setExtLinesRelativePlacementRaw(var->i_val());
32660 }
32661 var = checkedDimStyleVariable(s, "$DIMTFILL");
32662 if (var != nullptr) {
32663 textStyle->setBackgroundFillModeRaw(var->i_val());
32664 }
32665 var = checkedDimStyleVariable(s, "$DIMTFILLCLR");
32666 if (var != nullptr) {
32667 textStyle->setExplicitBackgroundFillColor(numberToColor(var->i_val()));
32668 }
32669 var = checkedDimStyleVariable(s, "$DIMTXTDIRECTION");
32670 if (var != nullptr) {
32671 textStyle->setReadingDirectionRaw(var->i_val());
32672 }
32673 var = checkedDimStyleVariable(s, "$DIMTVP");
32674 if (var != nullptr) {
32675 textStyle->setVerticalDistanceToDimLine(var->d_val());
32676 }
32677 var = checkedDimStyleVariable(s, "$DIMUPT");
32678 if (var != nullptr) {
32679 textStyle->setCursorControlPolicyRaw(var->i_val());
32680 }
32681 var = checkedDimStyleVariable(s, "$DIMTMOVE");
32682 if (var != nullptr) {
32683 textStyle->setPositionMovementPolicyRaw(var->i_val());
32684 }
32685 var = checkedDimStyleVariable(s, "$DIMATFIT");
32686 if (var != nullptr) {
32687 textStyle->setUnsufficientSpacePolicyRaw(var->i_val());
32688 }
32689
32690 auto zerosSuppression = result->zerosSuppression();
32691
32692 var = checkedDimStyleVariable(s, "$DIMZIN");
32693 if (var != nullptr) {
32694 zerosSuppression->setLinearRaw(var->i_val());
32695 }
32696 var = checkedDimStyleVariable(s, "$DIMAZIN");
32697 if (var != nullptr) {
32698 zerosSuppression->setAngularRaw(var->i_val());
32699 }
32700 var = checkedDimStyleVariable(s, "$DIMTZIN");
32701 if (var != nullptr) {
32702 zerosSuppression->setToleranceRaw(var->i_val());
32703 }
32704 var = checkedDimStyleVariable(s, "$DIMALTZ");
32705 if (var != nullptr) {
32706 zerosSuppression->setAltLinearRaw(var->i_val());
32707 }
32708 var = checkedDimStyleVariable(s, "$DIMALTTZ");
32709 if (var != nullptr) {
32710 zerosSuppression->setAltToleranceRaw(var->i_val());
32711 }
32712
32713 auto linearFormat = result->linearFormat();
32714
32715 var = checkedDimStyleVariable(s, "$DIMLUNIT");
32716 if (var != nullptr) {
32717 linearFormat->setFormatRaw(var->i_val());
32718 }
32719 var = checkedDimStyleVariable(s, "$DIMDSEP");
32720 if (var != nullptr) {
32721 linearFormat->setDecimalFormatSeparatorChar(var->i_val());
32722 }
32723 var = checkedDimStyleVariable(s, "$DIMDEC");
32724 if (var != nullptr) {
32725 linearFormat->setDecimalPlaces(var->i_val());
32726 }
32727 var = checkedDimStyleVariable(s, "$DIMPOST");
32728 if (var != nullptr) {
32729 linearFormat->setPrefixOrSuffix(strVal(var));
32730 }
32731 var = checkedDimStyleVariable(s, "$DIMALT");
32732 if (var != nullptr) {
32733 linearFormat->setAlternateUnitsRaw(var->i_val());
32734 }
32735 var = checkedDimStyleVariable(s, "$DIMALTU");
32736 if (var != nullptr) {
32737 linearFormat->setAltFormatRaw(var->i_val());
32738 }
32739 var = checkedDimStyleVariable(s, "$DIMALTD");
32740 if (var != nullptr) {
32741 linearFormat->setAltDecimalPlaces(var->i_val());
32742 }
32743 var = checkedDimStyleVariable(s, "$DIMALTF");
32744 if (var != nullptr) {
32745 linearFormat->setAltUnitsMultiplier(var->d_val());
32746 }
32747 var = checkedDimStyleVariable(s, "$DIMAPOST");
32748 if (var != nullptr) {
32749 linearFormat->setAltPrefixOrSuffix(strVal(var));
32750 }
32751
32752 auto angularFormat = result->angularFormat();
32753 var = checkedDimStyleVariable(s, "$DIMAUNIT");
32754 if (var != nullptr) {
32755 angularFormat->setFormatRaw(var->i_val());
32756 }
32757 var = checkedDimStyleVariable(s, "$DIMADEC");
32758 if (var != nullptr) {
32759 angularFormat->setDecimalPlaces(var->i_val());
32760 }
32761
32762 auto linearRoundOff = result->roundOff();
32763 var = checkedDimStyleVariable(s, "$DIMRND");
32764 if (var != nullptr) {
32765 linearRoundOff->setRoundToValue(var->d_val());
32766 }
32767 var = checkedDimStyleVariable(s, "$DIMALTRND");
32768 if (var != nullptr) {
32769 linearRoundOff->setAltRoundToValue(var->d_val());
32770 }
32771
32772 auto fractionStyle = result->fractions();
32773 var = checkedDimStyleVariable(s, "$DIMFRAC");
32774 if (var != nullptr) {
32775 fractionStyle->setStyleRaw(var->i_val());
32776 }
32777
32778 auto radialStyle = result->radial();
32779 var = checkedDimStyleVariable(s, "$DIMCEN");
32780 if (var != nullptr) {
32781 radialStyle->setCenterMarkOrLineSize(var->d_val());
32782 }
32783
32784 auto toleranceStyle = result->latteralTolerance();
32785
32786 var = checkedDimStyleVariable(s, "$DIMTDEC");
32787 if (var != nullptr) {
32788 toleranceStyle->setDecimalPlaces(var->i_val());
32789 }
32790 var = checkedDimStyleVariable(s, "$DIMALTTD");
32791 if (var != nullptr) {
32792 toleranceStyle->setDecimalPlacesAltDim(var->i_val());
32793 }
32794 var = checkedDimStyleVariable(s, "$DIMTOL");
32795 if (var != nullptr) {
32796 toleranceStyle->setAppendTolerancesToDimText(
32797 var->i_val()); // fixme - review
32798 }
32799 var = checkedDimStyleVariable(s, "$DIMTOLJ");
32800 if (var != nullptr) {
32801 toleranceStyle->setVerticalJustificationRaw(var->i_val());
32802 }
32803 var = checkedDimStyleVariable(s, "$DIMTM");
32804 if (var != nullptr) {
32805 toleranceStyle->setLowerToleranceLimit(var->d_val()); // fixme - review
32806 }
32807 var = checkedDimStyleVariable(s, "$DIMTP");
32808 if (var != nullptr) {
32809 toleranceStyle->setUpperToleranceLimit(var->d_val());
32810 }
32811 var = checkedDimStyleVariable(s, "$DIMTFAC");
32812 if (var != nullptr) {
32813 toleranceStyle->setHeightScaleFactorToDimText(var->d_val());
32814 }
32815 var = checkedDimStyleVariable(s, "$DIMLIM");
32816 if (var != nullptr) {
32817 toleranceStyle->setLimitsAreGeneratedAsDefaultText(
32818 var->i_val()); // fixme - review
32819 }
32820
32821 auto leaderStyle = result->leader();
32822 var = checkedDimStyleVariable(s, "$DIMLDRBLK");
32823 if (var != nullptr) {
32824 leaderStyle->setArrowBlockName(strVal(var));
32825 }
32826
32827 auto arc = result->arc();
32828 var = checkedDimStyleVariable(s, "$DIMARCSYM");
32829 if (var != nullptr) {
32830 arc->setArcSymbolPositionRaw(var->i_val());
32831 }
32832
32833 // fixme - remove to MLeaderStyle
32834 /*auto mleaderStyle = result->mleader();
32835 var = s.get("$MLEADERSCALE");
32836 if (var != nullptr) {
32837 mleaderStyle->setScale(var->d_val());
32838 }*/
32839 // mleaderStyle->setScale(s.mleaderscale);
32840
32841 parseDimStyleExtData(s, result);
32842
32843 if (styleForEntityType) {
32844 result->setModifyCheckMode(LC_DimStyle::ModificationAware::SET);
32845 }
32846 return result;
32847}
32848
32849bool RS_FilterDXFRW::resolveBlockNameByHandle(std::uint32_t blockHandle,
32850 QString &blockName) const {
32851 // The reading context lives on the DXF reader; it is null on the DWG read
32852 // path (and after the reader is torn down). Resolution by handle is a
32853 // DXF-only facility — bail out safely otherwise.
32854 if (m_dxfR == nullptr)
32855 return false;
32856 std::string name =
32857 m_dxfR->getReadingContext()->resolveBlockRecordName(blockHandle);
32858 if (name.empty()) {
32859 return false;
32860 }
32861 blockName = name.c_str();
32862 return true;
32863}
32864
32865void RS_FilterDXFRW::parseDimStyleExtData(const DRW_Dimstyle &s,
32866 LC_DimStyle *result) {
32867 std::vector<DRW_Variant> tagData;
32868 int currentValType = 0;
32869 QString applicationName = "";
32870 bool expectType = false;
32871 // https://help.autodesk.com/view/OARX/2024/ENU/?guid=GUID-3F0380A5-1C15-464D-BC66-2C5F094BCFB9
32872 // https://documentation.help/AutoCAD-DXF/WS1a9193826455f5ff18cb41610ec0a2e719-7943.htm
32873 for (const auto &v : s.extData) {
32874 if (v == nullptr)
32875 continue;
32876 const int code = v->code();
32877 switch (code) {
32878 case 1001: {
32879 // application name
32880 if (!applicationName.isEmpty()) {
32881 applyParsedDimStyleExtData(result, applicationName, tagData);
32882 tagData.clear();
32883 }
32884 std::string value;
32885 if (readXDataString(v, 1001, value) != XDataReadResult::Valid) {
32886 applicationName.clear();
32887 tagData.clear();
32888 expectType = false;
32889 continue;
32890 }
32891 applicationName = QString::fromStdString(value);
32892 expectType = false; // for later "not", as actually we do expect it
32893 continue;
32894 }
32895 case 1002: {
32896 // control name
32897 continue;
32898 }
32899 case 1070: // integer
32900 case 1071: {
32901 // long
32902 std::int64_t integerValue = 0;
32903 if (readXDataInteger(v, code, integerValue) != XDataReadResult::Valid ||
32904 !fitsXDataInt32(integerValue)) {
32905 continue;
32906 }
32907 const int val = static_cast<int>(integerValue);
32908 if (expectType) {
32909 // code of var
32910 currentValType = val;
32911 } else {
32912 // it fields
32913 auto intVar = DRW_Variant(currentValType, val);
32914 tagData.push_back(intVar);
32915 expectType = true;
32916 }
32917 break;
32918 }
32919 case 1040: // real
32920 case 1041: // distance
32921 case 1042: {
32922 // scale factor
32923 double value = 0.0;
32924 if (readXDataDouble(v, code, value) != XDataReadResult::Valid)
32925 continue;
32926 auto doubleVar = DRW_Variant(currentValType, value);
32927 tagData.push_back(doubleVar);
32928 expectType = true;
32929 break;
32930 }
32931 default:
32932 continue;
32933 }
32934 }
32935 if (!applicationName.isEmpty()) {
32936 // process last app name setion
32937 applyParsedDimStyleExtData(result, applicationName, tagData);
32938 tagData.clear();
32939 }
32940}
32941
32942void RS_FilterDXFRW::applyParsedDimStyleExtData(
32943 const LC_DimStyle *dimStyle, const QString &appName,
32944 const std::vector<DRW_Variant> &vector) {
32945 if (vector.empty()) {
32946 return;
32947 }
32948 const DRW_Variant *var = &vector.at(0);
32949 int code = var->code();
32950
32951 if (appName == "ACAD_DSTYLE_DIMJAG") {
32952 if (code == 388) {
32953 // double val = var->d_val();
32954 // fixme - decide where to store it. this is "Jog Height Factor"
32955 // dimStyle->dimensionLine()->set
32956 }
32957
32958 // code 388, float
32959 } else if (appName == "ACAD_DSTYLE_DIMTALN") {
32960 // code 392, int
32961 if (code == 392) {
32962 int val = var->i_val();
32963 dimStyle->latteralTolerance()->setAdjustmentRaw(val);
32964 }
32965 } else if (appName == "ACAD_DSTYLE_DIMBREAK") {
32966 // code 391, float
32967 if (code == 391) {
32968 // double val = var->d_val();
32969 // fixme - decide where to store it. This is "Dimension Break" in acad.
32970 }
32971 }
32972}
32973
32974#endif // DWGSUPPORT