Bug Summary

File:librecad/src/lib/engine/document/container/rs_entitycontainer.cpp
Warning:line 2131, column 21
Value stored to 'countOfEntities' during its initialization is never read

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_entitycontainer.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/engine/document/container/rs_entitycontainer.cpp
1/****************************************************************************
2**
3** This file is part of the LibreCAD project, a 2D CAD program
4**
5** Copyright (C) 2025 Dongxu Li (github.com/dxli)
6** Copyright (C) 2010 R. van Twisk (librecad@rvt.dds.nl)
7** Copyright (C) 2001-2003 RibbonSoft. All rights reserved.
8**
9**
10** This file may be distributed and/or modified under the terms of the
11** GNU General Public License version 2 as published by the Free Software
12** Foundation and appearing in the file gpl-2.0.txt included in the
13** packaging of this file.
14**
15** This program is distributed in the hope that it will be useful,
16** but WITHOUT ANY WARRANTY; without even the implied warranty of
17** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18** GNU General Public License for more details.
19**
20** You should have received a copy of the GNU General Public License
21** along with this program; if not, write to the Free Software
22** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23**
24** This copyright notice MUST APPEAR in all copies of the script!
25**
26**********************************************************************/
27
28#include "rs_entitycontainer.h"
29
30#include <algorithm>
31#include <cmath>
32#include <iostream>
33#include <optional>
34
35#include "lc_containertraverser.h"
36#include "lc_looputils.h"
37#include "qg_dialogfactory.h"
38#include "rs_block.h"
39#include "rs_blocklist.h"
40#include "rs_constructionline.h"
41#include "rs_debug.h"
42#include "rs_dialogfactory.h"
43#include "rs_dimension.h"
44#include "rs_document.h"
45#include "rs_ellipse.h"
46#include "rs_graphic.h"
47#include "rs_information.h"
48#include "rs_insert.h"
49#include "rs_layer.h"
50#include "rs_layerlist.h"
51#include "rs_line.h"
52#include "rs_painter.h"
53#include "rs_solid.h"
54#include "rs_vector.h"
55
56class RS_Dimension;
57
58namespace {
59 // the tolerance used to check topology of contours in hatching
60 constexpr double CONTOUR_TOLERANCE = 1e-8; // fixme - sand - to options?
61
62 // For validate hatch contours, whether an entity in the contour is a closed
63 // loop itself
64 bool isClosedLoop(RS_Entity& entity) {
65 switch (entity.rtti()) {
66 case RS2::EntityCircle:
67 // Sub containers are always closed
68 case RS2::EntityContainer:
69 return true;
70 case RS2::EntityEllipse:
71 return !static_cast<RS_Ellipse*>(&entity)->isArc();
72 default:
73 return false;
74 }
75 }
76
77 // Find the nearest distance between the endpoints of an entity to a given point
78 double endPointDistance(const RS_Vector& point, const RS_Entity& entity) {
79 double distance = RS_MAXDOUBLE1.0E+10;
80 entity.getNearestEndpoint(point, nullptr, &distance);
81 return distance;
82 }
83
84 void clearSelectionBeforeDeletion(RS_Entity* entity) {
85 if (entity == nullptr || !entity->getFlag(RS2::FlagSelected))
86 return;
87 if (RS_Document* document = entity->getDocument()) {
88 document->unselect(entity);
89 } else {
90 entity->setSelectionFlag(false);
91 }
92 }
93}
94
95/**
96 * Default constructor.
97 *
98 * @param parent
99 * @param owner True if we own and also delete the entities.
100 */
101RS_EntityContainer::RS_EntityContainer(RS_EntityContainer* parent, const bool owner) : RS_Entity(parent) {
102 m_autoDelete = owner;
103 // RS_DEBUG->print("RS_EntityContainer::RS_EntityContainer: "
104 // "owner: %d", (int)owner);
105 m_subContainer = nullptr;
106 //autoUpdateBorders = true;
107 m_entIdx = -1;
108}
109
110/**
111 * Copy constructor. Makes a deep copy of all entities.
112 */
113
114RS_EntityContainer::RS_EntityContainer(const RS_EntityContainer& other) : RS_Entity{other}, m_subContainer{other.m_subContainer},
115 m_entities{other.m_entities},
116 m_autoUpdateBorders{other.m_autoUpdateBorders},
117 m_entIdx{other.m_entIdx}, m_autoDelete{other.m_autoDelete} {
118 if (m_autoDelete) {
119 // fixme - sand - check this logic, looks suspicious!
120 for (auto& it : *this) {
121 if (it == nullptr) {
122 continue;
123 }
124 if (it->isContainer()) {
125 it = it->clone();
126 }
127 }
128 }
129}
130
131RS_EntityContainer::RS_EntityContainer(const RS_EntityContainer& other, const bool copyChildren) : RS_Entity{other} {
132 m_subContainer = nullptr;
133 m_autoUpdateBorders = other.m_autoUpdateBorders;
134 m_entIdx = other.m_entIdx;
135 m_autoDelete = other.m_autoDelete;
136 if (copyChildren) {
137 m_entities = other.m_entities;
138 if (m_autoDelete) {
139 // fixme - sand - check this logic, looks suspicious!
140 for (auto& it : *this) {
141 if (it == nullptr) {
142 continue;
143 }
144 if (it->isContainer()) {
145 it = it->clone();
146 }
147 }
148 }
149 }
150}
151
152RS_EntityContainer& RS_EntityContainer::operator =(const RS_EntityContainer& other) {
153 this->RS_Entity::operator =(other);
154 m_subContainer = other.m_subContainer;
155 m_entities = other.m_entities;
156 m_autoUpdateBorders = other.m_autoUpdateBorders;
157 m_entIdx = other.m_entIdx;
158 m_autoDelete = other.m_autoDelete;
159 if (m_autoDelete) {
160 for (auto& it : *this) {
161 if (it == nullptr) {
162 continue;
163 }
164 if (it->isContainer()) {
165 it = it->clone();
166 }
167 }
168 }
169 return *this;
170}
171
172RS_EntityContainer::RS_EntityContainer(RS_EntityContainer&& other) noexcept : RS_Entity{other}, m_subContainer{other.m_subContainer},
173 m_entities{std::move(other.m_entities)},
174 m_autoUpdateBorders{other.m_autoUpdateBorders},
175 m_entIdx{other.m_entIdx}, m_autoDelete{other.m_autoDelete} {
176}
177
178RS_EntityContainer& RS_EntityContainer::operator =(RS_EntityContainer&& other) noexcept {
179 this->RS_Entity::operator =(other);
180 m_subContainer = other.m_subContainer;
181 m_entities = std::move(other.m_entities);
182 m_autoUpdateBorders = other.m_autoUpdateBorders;
183 m_entIdx = other.m_entIdx;
184 m_autoDelete = other.m_autoDelete;
185 return *this;
186}
187
188/**
189 * Destructor.
190 */
191RS_EntityContainer::~RS_EntityContainer() {
192 if (m_autoDelete) {
193 while (!m_entities.isEmpty()) {
194 delete m_entities.takeFirst();
195 }
196 }
197 else {
198 m_entities.clear();
199 }
200}
201
202RS_Entity* RS_EntityContainer::clone() const {
203 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::clone: ori autoDel: %d", m_autoDelete);
204
205 auto* ec = new RS_EntityContainer(getParent(), isOwner());
206 if (isOwner()) {
207 for (const RS_Entity* entity : std::as_const(m_entities)) {
208 if (entity != nullptr) {
209 ec->m_entities.push_back(entity->clone());
210 }
211 }
212 }
213 else {
214 ec->m_entities = m_entities;
215 }
216
217 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::clone: clone autoDel: %d", ec->isOwner());
218
219 ec->detach();
220 return ec;
221}
222
223RS_Entity* RS_EntityContainer::cloneProxy() const {
224 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::cloneproxy: ori autoDel: %d", m_autoDelete);
225
226 auto* ec = new RS_EntityContainer(getParent(), isOwner());
227 if (isOwner()) {
228 for (const RS_Entity* entity : std::as_const(m_entities)) {
229 if (entity != nullptr) {
230 ec->m_entities.push_back(entity->cloneProxy());
231 }
232 }
233 }
234 else {
235 ec->m_entities = m_entities;
236 }
237
238 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::cloneproxy: clone autoDel: %d", ec->isOwner());
239
240 ec->detach(); // fixme - review whether detach is always need... looks like a double clone() ??
241 return ec;
242}
243
244/**
245 * Detaches shallow copies and creates deep copies of all subentities.
246 * This is called after cloning entity containers.
247 */
248void RS_EntityContainer::detach() {
249 QList<RS_Entity*> clonesList;
250 const bool autoDel = isOwner();
251 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::detach: autoDel: %d", autoDel);
252 setOwner(false);
253
254 // make deep copies of all entities:
255 for (const RS_Entity* e : *this) {
256 if (e == nullptr) {
257 continue;
258 }
259 if (!e->getFlag(RS2::FlagTemp)) {
260 clonesList.append(e->clone());
261 }
262 }
263
264 // clear shared pointers:
265 clear();
266 setOwner(autoDel);
267
268 // point to new deep copies:
269 for (RS_Entity* e : clonesList) {
270 push_back(e);
271 e->reparent(this);
272 }
273}
274
275void RS_EntityContainer::reparent(RS_EntityContainer* newParent) {
276 RS_Entity::reparent(newParent);
277
278 // All sub-entities:
279 for (RS_Entity* e : *this) {
280 if (e == nullptr) {
281 continue;
282 }
283 e->reparent(newParent);
284 }
285}
286
287void RS_EntityContainer::setVisible(const bool v) {
288 RS_Entity::setVisible(v);
289
290 // All sub-entities:
291 for (const auto e : std::as_const(m_entities)) {
292 if (e == nullptr) {
293 continue;
294 }
295 e->setVisible(v);
296 }
297}
298
299/**
300 * @return Total length of all m_entities in this container.
301 */
302double RS_EntityContainer::getLength() const {
303 double ret = 0.0;
304
305 for (const RS_Entity* e : *this) {
306 if (e == nullptr) {
307 continue;
308 }
309 if (e->isVisible()) {
310 const double length = e->getLength();
311 if (std::signbit(length)) {
312 ret = -1.0;
313 break;
314 }
315 ret += length;
316 }
317 }
318
319 return ret;
320}
321
322/**
323 * Selects this entity.
324 */
325bool RS_EntityContainer::setSelected(const bool select) {
326 // layer is locked:
327 if (select && isLocked()) {
328 return false;
329 }
330 RS_Entity::setSelectionFlag(select);
331 const auto doc = getDocument();
332 addToSelectionSet(select, doc);
333
334 // All sub-entity's select:
335 for (RS_Entity* e : *this) {
336 if (e == nullptr) {
337 continue;
338 }
339 if (e->isVisible()) {
340 e->setSelectionFlag(select);
341 }
342 }
343 return true;
344}
345
346bool RS_EntityContainer::doSelectInDocument(const bool select, RS_Document* doc) {
347 if (select && isLocked()) {
348 return false;
349 }
350 RS_Entity::setSelectionFlag(select);
351 addToSelectionSet(select, doc);
352
353 // All sub-entity's select:
354 for (RS_Entity* e : *this) {
355 if (e == nullptr) {
356 continue;
357 }
358 if (e->isVisible()) {
359 e->setSelectionFlag(select);
360 }
361 }
362 return true;
363}
364
365void RS_EntityContainer::setSelectionFlag(const bool select) {
366 // layer is locked:
367 if (select && isLocked()) {
368 return;
369 }
370 RS_Entity::setSelectionFlag(select);
371 // All sub-entity's select:
372 for (RS_Entity* e : *this) {
373 if (e == nullptr) {
374 continue;
375 }
376 if (e->isVisible()) {
377 e->setSelectionFlag(select);
378 }
379 }
380}
381
382void RS_EntityContainer::setHighlighted(const bool on) {
383 for (RS_Entity* e : *this) {
384 if (e == nullptr) {
385 continue;
386 }
387 e->setHighlighted(on);
388 }
389 RS_Entity::setHighlighted(on);
390}
391
392/**
393 * Adds a entity to this container and updates the borders of this
394 * entity-container if autoUpdateBorders is true.
395 */
396void RS_EntityContainer::addEntity(const RS_Entity* entity) {
397 if (entity == nullptr) {
398 return;
399 }
400 debugEntityAlreadyPresentExists(entity);
401 const auto ent = const_cast<RS_Entity*>(entity);
402 if (entity->rtti() == RS2::EntityImage || entity->rtti() == RS2::EntityHatch) {
403 m_entities.prepend(ent);
404 }
405 else {
406 m_entities.append(ent);
407 }
408 adjustBordersIfNeeded(entity);
409}
410
411/**
412 * Insert a entity at the end of entities list and updates the
413 * borders of this entity-container if autoUpdateBorders is true.
414 */
415void RS_EntityContainer::appendEntity(RS_Entity* entity) {
416 if (entity == nullptr) {
417 return;
418 }
419 debugEntityAlreadyPresentExists(entity);
420 m_entities.append(entity);
421 adjustBordersIfNeeded(entity);
422}
423
424/**
425 * Insert a entity at the start of entities list and updates the
426 * borders of this entity-container if autoUpdateBorders is true.
427 */
428void RS_EntityContainer::prependEntity(RS_Entity* entity) {
429 if (entity == nullptr) {
430 return;
431 }
432 debugEntityAlreadyPresentExists(entity);
433 m_entities.prepend(entity);
434 adjustBordersIfNeeded(entity);
435}
436
437/**
438 * Move a entity list in this container at the given position,
439 * the borders of this entity-container if autoUpdateBorders is true.
440 */
441void RS_EntityContainer::moveEntity(const int index, QList<RS_Entity*>& entList) {
442 if (entList.isEmpty()) {
443 return;
444 }
445 int ci = 0; //current index for insert without invert order
446 bool into = false;
447 RS_Entity* mid = nullptr;
448 if (index < 1) {
449 ci = 0;
450 }
451 else if (index >= m_entities.size()) {
452 ci = m_entities.size() - entList.size();
453 }
454 else {
455 into = true;
456 mid = m_entities.at(index);
457 }
458
459 for (int i = 0; i < entList.size(); ++i) {
460 RS_Entity* e = entList.at(i);
461 const bool ret = m_entities.removeOne(e);
462 //if e not exist in entities list remove from entList
463 if (!ret) {
464 entList.removeAt(i);
465 }
466 }
467 if (into) {
468 ci = m_entities.indexOf(mid);
469 }
470
471 for (const auto e : entList) {
472 m_entities.insert(ci++, e);
473 }
474}
475
476void RS_EntityContainer::adjustBordersIfNeeded(const RS_Entity* entity) {
477 if (m_autoUpdateBorders) {
478 adjustBorders(entity);
479 }
480}
481
482/**
483 * Inserts a entity to this container at the given position and updates
484 * the borders of this entity-container if autoUpdateBorders is true.
485 */
486void RS_EntityContainer::insertEntity(const int index, RS_Entity* entity) {
487 if (entity == nullptr) {
488 return;
489 }
490 debugEntityAlreadyPresentExists(entity);
491 m_entities.insert(index, entity);
492 adjustBordersIfNeeded(entity);
493}
494
495/**
496 * Removes an entity from this container and updates the borders of
497 * this entity-container if autoUpdateBorders is true.
498 */
499bool RS_EntityContainer::removeEntity(RS_Entity* entity) {
500 if (entity != nullptr) {
501 const bool ret = m_entities.removeOne(entity);
502 if (ret) {
503 // actually was contained in container
504 const bool mayAffectBorders = entity->isVisible();
505 // A selected child may be owned by a transient container such as
506 // an expanded INSERT. Remove it from the document selection before
507 // its storage is released, so selection never retains a dangling
508 // entity pointer.
509 clearSelectionBeforeDeletion(entity);
510 if (m_autoDelete) {
511 delete entity;
512 }
513 if (mayAffectBorders) {
514 calculateBordersIfNeeded();
515 }
516 }
517 return ret;
518 }
519 return false;
520}
521
522/**
523 * Erases all entities in this container and resets the borders..
524 */
525void RS_EntityContainer::clear() {
526 if (m_autoDelete) {
527 while (!m_entities.isEmpty()) {
528 RS_Entity* en = m_entities.takeFirst();
529 clearSelectionBeforeDeletion(en);
530 delete en;
531 }
532 }
533 else {
534 m_entities.clear();
535 }
536 resetBorders();
537}
538
539unsigned int RS_EntityContainer::count() const {
540 return m_entities.size();
541}
542
543/**
544 * Counts all entities (leaves of the tree).
545 */
546unsigned int RS_EntityContainer::countDeep() const {
547 unsigned int c = 0;
548 for (const auto t : *this) {
549 if (t == nullptr) {
550 continue;
551 }
552 c += t->countDeep();
553 }
554 return c;
555}
556
557/**
558 * Adjusts the borders of this graphic (max/min values)
559 */
560void RS_EntityContainer::adjustBorders(const RS_Entity* entity) {
561 //RS_DEBUG->print("RS_EntityContainer::adjustBorders");
562 //resetBorders();
563
564 if (entity != nullptr) {
565 // make sure a container is not empty (otherwise the border
566 // would get extended to 0/0):
567 if (!entity->isContainer() || entity->count() > 0) {
568 const RS_Vector emin = entity->getMin();
569 const RS_Vector emax = entity->getMax();
570 if (!emin.valid || !emax.valid)
571 return;
572 m_minV = RS_Vector::minimum(emin, m_minV);
573 m_maxV = RS_Vector::maximum(emax, m_maxV);
574 }
575
576 // Notify parents. The border for the parent might
577 // also change TODO: Check for efficiency
578 //if(parent) {
579 //parent->adjustBorders(this);
580 //}
581 }
582}
583
584/**
585 * Recalculates the borders of this entity container.
586 */
587void RS_EntityContainer::calculateBorders() {
588 // fixme - sand verify that there are no not needed borders calculations!!!
589 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::calculateBorders");
590
591 resetBorders();
592 for (RS_Entity* e : *this) {
593 if (e != nullptr && e->isVisible()) {
594 e->calculateBorders();
595 adjustBorders(e);
596 }
597 }
598
599 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::calculateBorders: size 1: %f,%f", getSize().x, getSize().y);
600
601 if (!m_minV.valid || !m_maxV.valid || m_minV.x > m_maxV.x
602 || m_minV.y > m_maxV.y || !std::isfinite(m_minV.x)
603 || !std::isfinite(m_minV.y) || !std::isfinite(m_maxV.x)
604 || !std::isfinite(m_maxV.y)) {
605 m_minV = RS_Vector(false);
606 m_maxV = RS_Vector(false);
607 }
608
609 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::calculateBorders: size: %f,%f", getSize().x, getSize().y);
610}
611
612/**
613 * Recalculates the borders of this entity container including
614 * invisible entities.
615 */
616void RS_EntityContainer::forcedCalculateBorders() {
617 resetBorders();
618 for (RS_Entity* e : *this) {
619 if (e == nullptr)
620 continue;
621 // INSERT overrides calculateBorders (origin-pin / empty expand). Prefer
622 // that path over a blind recursive force that collapses empty children
623 // back to (0,0) and inflates MDI resize scroll ranges.
624 if (e->rtti() == RS2::EntityInsert) {
625 e->calculateBorders();
626 } else if (e->isContainer()) {
627 const auto container = static_cast<RS_EntityContainer*>(e);
628 container->forcedCalculateBorders();
629 } else {
630 e->calculateBorders();
631 }
632 adjustBorders(e);
633 }
634
635 if (!m_minV.valid || !m_maxV.valid || m_minV.x > m_maxV.x
636 || m_minV.y > m_maxV.y || !std::isfinite(m_minV.x)
637 || !std::isfinite(m_minV.y) || !std::isfinite(m_maxV.x)
638 || !std::isfinite(m_maxV.y)) {
639 m_minV = RS_Vector(false);
640 m_maxV = RS_Vector(false);
641 }
642}
643
644/**
645 * Updates all Dimension entities in this container and / or
646 * reposition their labels.
647 *
648 * @param autoText Automatically reposition the text label bool autoText=true
649 */
650int RS_EntityContainer::updateDimensions(const bool autoText) {
651 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::updateDimensions()");
652 int updatedDimsCount = 0;
653
654 for (RS_Entity* e : *this) {
655 if (e == nullptr) {
656 continue;
657 }
658 if (e->isDeleted()) {
659 continue;
660 }
661 if (e->rtti() == RS2::EntityDimLeader) {
662 updatedDimsCount++;
663 e->update();
664 }
665 if (RS_Information::isDimension(e->rtti())) {
666 const auto dimension = static_cast<RS_Dimension*>(e);
667 // update and reposition label:
668 dimension->update();
669 updatedDimsCount++;
670 }
671 else if (e->isContainer()) {
672 const auto container = static_cast<RS_EntityContainer*>(e);
673 updatedDimsCount += container->updateDimensions(autoText);
674 }
675 }
676
677 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::updateDimensions() OK");
678 return updatedDimsCount;
679}
680
681int RS_EntityContainer::updateVisibleDimensions(const bool autoText) {
682 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::updateVisibleDimensions()");
683 int updatedDimsCount = 0;
684 for (RS_Entity* e : *this) {
685 if (e == nullptr) {
686 continue;
687 }
688 if (e->isVisible()) {
689 if (e->rtti() == RS2::EntityDimLeader) {
690 e->update();
691 updatedDimsCount++;
692 }
693 else if (RS_Information::isDimension(e->rtti())) {
694 const auto dimension = static_cast<RS_Dimension*>(e);
695 // update and reposition label:
696 dimension->updateDim(autoText);
697 updatedDimsCount++;
698 }
699 else if (e->isContainer()) {
700 const auto container = static_cast<RS_EntityContainer*>(e);
701 updatedDimsCount += container->updateVisibleDimensions(autoText);
702 }
703 }
704 }
705
706 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::updateVisibleDimensions() OK");
707 return updatedDimsCount;
708}
709
710/**
711 * Updates all Insert entities in this container.
712 */
713void RS_EntityContainer::updateInserts() {
714 const std::string idTypeId = std::to_string(getId()) + "/" + std::to_string(rtti());
715 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::updateInserts() ID/type: %s", idTypeId.c_str());
716
717 for (RS_Entity* e : std::as_const(*this)) {
718 //// Only update our own inserts and not inserts of inserts
719 if (e != nullptr) {
720 if (e->getId() != 0 && e->rtti() == RS2::EntityInsert /*&& e->getParent()==this*/) {
721 static_cast<RS_Insert*>(e)->update();
722
723 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::updateInserts: updated ID/type: %s", idTypeId.c_str());
724 }
725 else if (e->isContainer()) {
726 if (e->rtti() == RS2::EntityHatch) {
727 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_DEBUGGING, "RS_EntityContainer::updateInserts: skip hatch ID/type: %s", idTypeId.c_str());
728 }
729 else {
730 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::updateInserts: update container ID/type: %s", idTypeId.c_str());
731
732 static_cast<RS_EntityContainer*>(e)->updateInserts();
733 }
734 }
735 else {
736 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_DEBUGGING, "RS_EntityContainer::updateInserts: skip entity ID/type: %s", idTypeId.c_str());
737 }
738 }
739 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::updateInserts() ID/type: %s", idTypeId.c_str());
740 }
741}
742
743/**
744 * Renames all inserts with name 'oldName' to 'newName'. This is
745 * called after a block was rename to update the inserts.
746 */
747void RS_EntityContainer::renameInserts(const QString& oldName, const QString& newName) {
748 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::renameInserts()");
749 for (RS_Entity* e : std::as_const(m_entities)) {
750 if (e == nullptr) {
751 continue;
752 }
753 if (e->rtti() == RS2::EntityInsert) {
754 auto* i = static_cast<RS_Insert*>(e);
755 if (i->getName() == oldName) {
756 i->setName(newName);
757 }
758 }
759 if (e->isContainer()) {
760 const auto container = static_cast<RS_EntityContainer*>(e);
761 container->renameInserts(oldName, newName);
762 }
763 }
764 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::renameInserts() OK");
765}
766
767/**
768 * Updates all Spline entities in this container.
769 */
770void RS_EntityContainer::updateSplines() {
771 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::updateSplines()");
772 for (RS_Entity* e : *this) {
773 //// Only update our own inserts and not inserts of inserts
774 if (e->rtti() == RS2::EntitySpline /*&& e->getParent()==this*/) {
775 e->update();
776 }
777 else if (e->isContainer() && e->rtti() != RS2::EntityHatch) {
778 static_cast<RS_EntityContainer*>(e)->updateSplines();
779 }
780 }
781 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::updateSplines() OK");
782}
783
784/**
785 * Updates the sub entities of this container.
786 */
787void RS_EntityContainer::update() {
788 for (RS_Entity* e : *this) {
789 e->update();
790 }
791}
792
793void RS_EntityContainer::addRectangle(const RS_Vector& v0, const RS_Vector& v1) {
794 addEntity(new RS_Line{this, v0, {v1.x, v0.y}});
795 addEntity(new RS_Line{this, {v1.x, v0.y}, v1});
796 addEntity(new RS_Line{this, v1, {v0.x, v1.y}});
797 addEntity(new RS_Line{this, {v0.x, v1.y}, v0});
798}
799
800void RS_EntityContainer::addRectangle(const RS_Vector& v0, const RS_Vector& v1, const RS_Vector& v2, const RS_Vector& v3) {
801 addEntity(new RS_Line(this, v0, v1));
802 addEntity(new RS_Line(this, v1, v2));
803 addEntity(new RS_Line(this, v2, v3));
804 addEntity(new RS_Line(this, v3, v0));
805}
806
807/**
808 * Returns the first entity or nullptr if this graphic is empty.
809 * @param level
810 */
811RS_Entity* RS_EntityContainer::firstEntity(const RS2::ResolveLevel level) const {
812 RS_Entity* e = nullptr;
813 m_entIdx = -1;
814 switch (level) {
815 case RS2::ResolveNone: {
816 if (!m_entities.isEmpty()) {
817 m_entIdx = 0;
818 return m_entities.first();
819 }
820 break;
821 }
822 case RS2::ResolveAllButInserts: {
823 m_subContainer = nullptr;
824 if (!m_entities.isEmpty()) {
825 m_entIdx = 0;
826 e = m_entities.first();
827 }
828 if (e != nullptr && e->isContainer() && e->rtti() != RS2::EntityInsert) {
829 m_subContainer = static_cast<RS_EntityContainer*>(e);
830 e = m_subContainer->firstEntity(level);
831 // empty container:
832 if (e == nullptr) {
833 m_subContainer = nullptr;
834 e = nextEntity(level);
835 }
836 }
837 return e;
838 }
839 case RS2::ResolveAllButTextImage:
840 case RS2::ResolveAllButTexts: {
841 m_subContainer = nullptr;
842 if (!m_entities.isEmpty()) {
843 m_entIdx = 0;
844 e = m_entities.first();
845 }
846 if (e != nullptr && e->isContainer() && e->rtti() != RS2::EntityText && e->rtti() != RS2::EntityMText) {
847 m_subContainer = static_cast<RS_EntityContainer*>(e);
848 e = m_subContainer->firstEntity(level);
849 // empty container:
850 if (e == nullptr) {
851 m_subContainer = nullptr;
852 e = nextEntity(level);
853 }
854 }
855 return e;
856 }
857 case RS2::ResolveAll: {
858 m_subContainer = nullptr;
859 if (!m_entities.isEmpty()) {
860 m_entIdx = 0;
861 e = m_entities.first();
862 }
863 if (e != nullptr && e->isContainer()) {
864 m_subContainer = static_cast<RS_EntityContainer*>(e);
865 e = m_subContainer->firstEntity(level);
866 // empty container:
867 if (e == nullptr) {
868 m_subContainer = nullptr;
869 e = nextEntity(level);
870 }
871 }
872 return e;
873 }
874 }
875 return nullptr;
876}
877
878/**
879 * Returns the last entity or \p nullptr if this graphic is empty.
880 *
881 * @param level \li \p 0 Groups are not resolved
882 * \li \p 1 (default) only Groups are resolved
883 * \li \p 2 all Entity Containers are resolved
884 */
885RS_Entity* RS_EntityContainer::lastEntity(const RS2::ResolveLevel level) const {
886 RS_Entity* e = nullptr;
887 if (m_entities.empty()) {
888 return nullptr;
889 }
890 m_entIdx = m_entities.size() - 1;
891 switch (level) {
892 case RS2::ResolveNone: {
893 if (!m_entities.isEmpty()) {
894 return m_entities.last();
895 }
896 break;
897 }
898 case RS2::ResolveAllButInserts: {
899 if (!m_entities.isEmpty()) {
900 e = m_entities.last();
901 }
902 m_subContainer = nullptr;
903 if (e != nullptr && e->isContainer() && e->rtti() != RS2::EntityInsert) {
904 m_subContainer = static_cast<RS_EntityContainer*>(e);
905 e = m_subContainer->lastEntity(level);
906 }
907 return e;
908 }
909 case RS2::ResolveAllButTextImage:
910 case RS2::ResolveAllButTexts: {
911 if (!m_entities.isEmpty()) {
912 e = m_entities.last();
913 }
914 m_subContainer = nullptr;
915 if (e != nullptr && e->isContainer() && e->rtti() != RS2::EntityText && e->rtti() != RS2::EntityMText) {
916 m_subContainer = static_cast<RS_EntityContainer*>(e);
917 e = m_subContainer->lastEntity(level);
918 }
919 return e;
920 }
921 case RS2::ResolveAll: {
922 if (!m_entities.isEmpty()) {
923 e = m_entities.last();
924 }
925 m_subContainer = nullptr;
926 if (e != nullptr && e->isContainer()) {
927 m_subContainer = static_cast<RS_EntityContainer*>(e);
928 e = m_subContainer->lastEntity(level);
929 }
930 return e;
931 }
932 }
933 return nullptr;
934}
935
936/**
937 * Returns the next entity or container or \p nullptr if the last entity
938 * returned by \p next() was the last entity in the container.
939 */
940RS_Entity* RS_EntityContainer::nextEntity(const RS2::ResolveLevel level) const {
941 //set entIdx pointing in next entity and check if is out of range
942 ++m_entIdx;
943 switch (level) {
944 case RS2::ResolveNone: {
945 if (m_entIdx < m_entities.size()) {
946 return m_entities.at(m_entIdx);
947 }
948 break;
949 }
950 case RS2::ResolveAllButInserts: {
951 RS_Entity* e = nullptr;
952 if (m_subContainer != nullptr) {
953 e = m_subContainer->nextEntity(level);
954 if (e != nullptr) {
955 --m_entIdx; //return a sub-entity, index not advanced
956 return e;
957 }
958 if (m_entIdx < m_entities.size()) {
959 e = m_entities.at(m_entIdx);
960 }
961 }
962 else {
963 if (m_entIdx < m_entities.size()) {
964 e = m_entities.at(m_entIdx);
965 }
966 }
967 if (e != nullptr && e->isContainer() && e->rtti() != RS2::EntityInsert) {
968 m_subContainer = static_cast<RS_EntityContainer*>(e);
969 e = m_subContainer->firstEntity(level);
970 // empty container:
971 if (e == nullptr) {
972 m_subContainer = nullptr;
973 e = nextEntity(level);
974 }
975 }
976 return e;
977 }
978 case RS2::ResolveAllButTextImage:
979 case RS2::ResolveAllButTexts: {
980 RS_Entity* e = nullptr;
981 if (m_subContainer != nullptr) {
982 e = m_subContainer->nextEntity(level);
983 if (e != nullptr) {
984 --m_entIdx; //return a sub-entity, index not advanced
985 return e;
986 }
987 if (m_entIdx < m_entities.size()) {
988 e = m_entities.at(m_entIdx);
989 }
990 }
991 else {
992 if (m_entIdx < m_entities.size()) {
993 e = m_entities.at(m_entIdx);
994 }
995 }
996 if (e != nullptr && e->isContainer() && e->rtti() != RS2::EntityText && e->rtti() != RS2::EntityMText) {
997 m_subContainer = static_cast<RS_EntityContainer*>(e);
998 e = m_subContainer->firstEntity(level);
999 // empty container:
1000 if (e == nullptr) {
1001 m_subContainer = nullptr;
1002 e = nextEntity(level);
1003 }
1004 }
1005 return e;
1006 }
1007 case RS2::ResolveAll: {
1008 RS_Entity* e = nullptr;
1009 if (m_subContainer != nullptr) {
1010 e = m_subContainer->nextEntity(level);
1011 if (e != nullptr) {
1012 --m_entIdx; //return a sub-entity, index not advanced
1013 return e;
1014 }
1015 if (m_entIdx < m_entities.size()) {
1016 e = m_entities.at(m_entIdx);
1017 }
1018 }
1019 else {
1020 if (m_entIdx < m_entities.size()) {
1021 e = m_entities.at(m_entIdx);
1022 }
1023 }
1024 if (e != nullptr && e->isContainer()) {
1025 m_subContainer = static_cast<RS_EntityContainer*>(e);
1026 e = m_subContainer->firstEntity(level);
1027 // empty container:
1028 if (e == nullptr) {
1029 m_subContainer = nullptr;
1030 e = nextEntity(level);
1031 }
1032 }
1033 return e;
1034 }
1035 }
1036 return nullptr;
1037}
1038
1039/**
1040 * Returns the prev entity or container or \p nullptr if the last entity
1041 * returned by \p prev() was the first entity in the container.
1042 */
1043RS_Entity* RS_EntityContainer::prevEntity(const RS2::ResolveLevel level) const {
1044 //set entIdx pointing in prev entity and check if is out of range
1045 --m_entIdx;
1046 switch (level) {
1047 case RS2::ResolveNone: {
1048 if (m_entIdx >= 0) {
1049 return m_entities.at(m_entIdx);
1050 }
1051 break;
1052 }
1053 case RS2::ResolveAllButInserts: {
1054 RS_Entity* e = nullptr;
1055 if (m_subContainer != nullptr) {
1056 e = m_subContainer->prevEntity(level);
1057 if (e != nullptr) {
1058 return e;
1059 }
1060 if (m_entIdx >= 0) {
1061 e = m_entities.at(m_entIdx);
1062 }
1063 }
1064 else {
1065 if (m_entIdx >= 0) {
1066 e = m_entities.at(m_entIdx);
1067 }
1068 }
1069 if (e != nullptr && e->isContainer() && e->rtti() != RS2::EntityInsert) {
1070 m_subContainer = static_cast<RS_EntityContainer*>(e);
1071 e = m_subContainer->lastEntity(level);
1072 // empty container:
1073 if (e == nullptr) {
1074 m_subContainer = nullptr;
1075 e = prevEntity(level);
1076 }
1077 }
1078 return e;
1079 }
1080 case RS2::ResolveAllButTextImage:
1081 case RS2::ResolveAllButTexts: {
1082 RS_Entity* e = nullptr;
1083 if (m_subContainer != nullptr) {
1084 e = m_subContainer->prevEntity(level);
1085 if (e != nullptr) {
1086 return e;
1087 }
1088 if (m_entIdx >= 0) {
1089 e = m_entities.at(m_entIdx);
1090 }
1091 }
1092 else {
1093 if (m_entIdx >= 0) {
1094 e = m_entities.at(m_entIdx);
1095 }
1096 }
1097 if (e != nullptr && e->isContainer() && e->rtti() != RS2::EntityText && e->rtti() != RS2::EntityMText) {
1098 m_subContainer = static_cast<RS_EntityContainer*>(e);
1099 e = m_subContainer->lastEntity(level);
1100 // empty container:
1101 if (e == nullptr) {
1102 m_subContainer = nullptr;
1103 e = prevEntity(level);
1104 }
1105 }
1106 return e;
1107 }
1108 case RS2::ResolveAll: {
1109 RS_Entity* e = nullptr;
1110 if (m_subContainer != nullptr) {
1111 e = m_subContainer->prevEntity(level);
1112 if (e != nullptr) {
1113 ++m_entIdx; //return a sub-entity, index not advanced
1114 return e;
1115 }
1116 if (m_entIdx >= 0) {
1117 e = m_entities.at(m_entIdx);
1118 }
1119 }
1120 else {
1121 if (m_entIdx >= 0) {
1122 e = m_entities.at(m_entIdx);
1123 }
1124 }
1125 if (e != nullptr && e->isContainer()) {
1126 m_subContainer = static_cast<RS_EntityContainer*>(e);
1127 e = m_subContainer->lastEntity(level);
1128 // empty container:
1129 if (e == nullptr) {
1130 m_subContainer = nullptr;
1131 e = prevEntity(level);
1132 }
1133 }
1134 return e;
1135 }
1136 }
1137 return nullptr;
1138}
1139
1140/**
1141 * @return Entity at the given index or nullptr if the index is out of range.
1142 */
1143RS_Entity* RS_EntityContainer::entityAt(const int index) const {
1144 if (m_entities.size() > index && index >= 0) {
1145 return m_entities.at(index);
1146 }
1147 return nullptr;
1148}
1149
1150void RS_EntityContainer::setEntityAt(const int index, RS_Entity* en) {
1151 if (m_autoDelete && (m_entities.at(index) != nullptr)) {
1152 clearSelectionBeforeDeletion(m_entities.at(index));
1153 delete m_entities.at(index);
1154 }
1155 debugEntityAlreadyPresentExists(en);
1156 m_entities[index] = en;
1157}
1158
1159/**
1160 * Finds the given entity and makes it the current entity if found.
1161 */
1162int RS_EntityContainer::findEntity(const RS_Entity* const entity) {
1163 m_entIdx = m_entities.indexOf(const_cast<RS_Entity*>(entity));
1164 return m_entIdx;
1165}
1166
1167int RS_EntityContainer::findEntityIndex(const RS_Entity* const entity) const {
1168 return m_entities.indexOf(const_cast<RS_Entity*>(entity));
1169}
1170
1171bool RS_EntityContainer::areNeighborsEntities(RS_Entity const *const e1, RS_Entity const *const e2) const {
1172 return abs(m_entities.indexOf(const_cast<RS_Entity *>(e1)) - m_entities.indexOf(const_cast<RS_Entity *>(e2))) <= 1;
1173}
1174
1175/**
1176 * @return The point which is closest to 'coord'
1177 * (one of the vertices)
1178 */
1179RS_Vector RS_EntityContainer::doGetNearestEndpoint(const RS_Vector& coord, double* dist, RS_Entity** entity) const {
1180 double minDist = RS_MAXDOUBLE1.0E+10; // minimum measured distance
1181 double curDist = 0.; // currently measured distance
1182 RS_Vector closestPoint(false); // closest found endpoint
1183 RS_Entity* closestEntity {nullptr};
1184
1185 // fixme - actually, there could be improvement for snapping - by takind into consideration only entities within visual area
1186 for (const RS_Entity* en : *this) {
1187 if (en != nullptr && en->getId() != 0 && en->isVisible()) {
1188 const auto parent = en->getParent();
1189 bool checkForEndpoint = true;
1190 if (parent != nullptr) {
1191 checkForEndpoint = !parent->ignoredOnModification();
1192 }
1193 if (checkForEndpoint) {
1194 //no end point for Insert, text, Dim
1195 RS_Entity* closestCandidate = nullptr;
1196 const RS_Vector point = en->getNearestEndpoint(coord, &closestCandidate, &curDist);
1197 if (point.valid && curDist < minDist) {
1198 closestPoint = point;
1199 closestEntity = closestCandidate;
1200 minDist = curDist;
1201 if (dist != nullptr) {
1202 *dist = minDist;
1203 }
1204 }
1205 }
1206 }
1207 }
1208 if (entity != nullptr) {
1209 *entity = closestEntity;
1210 }
1211 return closestPoint;
1212}
1213
1214/**
1215 * @return The point which is closest to 'coord'
1216 * (one of the vertices)
1217 */
1218RS_Vector RS_EntityContainer::obtainNearestEndpoint(const RS_Vector& coord, double* dist, RS_Entity** pEntity) const {
1219 double minDist = RS_MAXDOUBLE1.0E+10; // minimum measured distance
1220 double curDist; // currently measured distance
1221 RS_Vector closestPoint(false); // closest found endpoint
1222
1223 for (const auto en : m_entities) {
1224 if (en == nullptr) {
1225 continue;
1226 }
1227 if (en->getParent() == nullptr || !en->getParent()->ignoredOnModification()) {
1228 //no end point for Insert, text, Dim
1229 // std::cout<<"find nearest for entity "<<i0<<std::endl;
1230 const RS_Vector point = en->getNearestEndpoint(coord, nullptr, &curDist);
1231 if (point.valid && curDist < minDist) {
1232 closestPoint = point;
1233 minDist = curDist;
1234 if (dist != nullptr) {
1235 *dist = minDist;
1236 }
1237 if (pEntity != nullptr) {
1238 *pEntity = en;
1239 }
1240 }
1241 }
1242 }
1243 return closestPoint;
1244}
1245
1246RS_Vector RS_EntityContainer::doGetNearestPointOnEntity(const RS_Vector& coord, const bool onEntity, double* dist, RS_Entity** entity) const {
1247 RS_Vector point(false);
1248 const RS_Entity *en = getNearestEntity(coord, dist, RS2::ResolveNone);
1249 // Issue #2670: A container that overrides getDistanceToPoint() for hit-testing (e.g.
1250 // RS_Hatch reporting a solid-fill hit) can return itself as the nearest
1251 // entity. Recursing into such a self-reference loops forever and overflows
1252 // the stack (crash observed when snapping inside a filled hatch), so only
1253 // descend into a genuine child entity.
1254 // Null parent (orphan / mid-import / dummy container): treat as snap-eligible,
1255 // matching obtainNearestEndpoint's ignoredOnModification guard. Never
1256 // dereference getParent() without a null check (SIGSEGV on hover/snap).
1257 if (en && en != this && en->isVisible()
1258 && (en->getParent() == nullptr || !en->getParent()->ignoredSnap())) {
1259 point = en->getNearestPointOnEntity(coord, onEntity, dist, entity);
1260 }
1261 return point;
1262}
1263
1264RS_Vector RS_EntityContainer::doGetNearestCenter(const RS_Vector& coord, double* dist, RS_Entity** centerEntity) const {
1265 double minDist = RS_MAXDOUBLE1.0E+10; // minimum measured distance
1266 double curDist = RS_MAXDOUBLE1.0E+10; // currently measured distance
1267 RS_Vector closestPoint(false); // closest found endpoint
1268 RS_Entity* closestCenterEntity{nullptr};
1269
1270 for (const auto en : m_entities) {
1271 if (en != nullptr && en->getId() != 0 && en->isVisible()
1272 && (en->getParent() == nullptr || !en->getParent()->ignoredSnap())) {
1273 //no center point for spline, text, Dim
1274 RS_Entity* centerEnt;
1275 const RS_Vector point = en->getNearestCenter(coord, &curDist, &centerEnt);
1276 if (point.valid && curDist < minDist) {
1277 closestPoint = point;
1278 closestCenterEntity = centerEnt;
1279 minDist = curDist;
1280 }
1281 }
1282 }
1283 if (dist != nullptr) {
1284 *dist = minDist;
1285 }
1286 if (centerEntity != nullptr) {
1287 *centerEntity = closestCenterEntity;
1288 }
1289 return closestPoint;
1290}
1291
1292/** @return the nearest of equidistant middle points of the line. */
1293
1294RS_Vector RS_EntityContainer::doGetNearestMiddle(const RS_Vector& coord, double* dist, const int middlePoints) const {
1295 double minDist = RS_MAXDOUBLE1.0E+10; // minimum measured distance
1296 double curDist = RS_MAXDOUBLE1.0E+10; // currently measured distance
1297 RS_Vector closestPoint(false); // closest found endpoint
1298
1299 for (const auto en : m_entities) {
1300 if (en != nullptr && en->isVisible()
1301 && (en->getParent() == nullptr || !en->getParent()->ignoredSnap())) {
1302 //no midle point for spline, text, Dim
1303 const RS_Vector point = en->getNearestMiddle(coord, &curDist, middlePoints);
1304 if (point.valid && curDist < minDist) {
1305 closestPoint = point;
1306 minDist = curDist;
1307 }
1308 }
1309 }
1310 if (dist != nullptr) {
1311 *dist = minDist;
1312 }
1313 return closestPoint;
1314}
1315
1316RS_Vector RS_EntityContainer::doGetNearestDist(const double distance, const RS_Vector& coord, double* dist) const {
1317 const RS_Entity* closestEntity = getNearestEntity(coord, nullptr, RS2::ResolveNone);
1318 return (closestEntity != nullptr) ? closestEntity->getNearestDist(distance, coord, dist) : RS_Vector{false};
1319}
1320
1321/**
1322 * @return The intersection which is closest to 'coord'
1323 */
1324RS_Vector RS_EntityContainer::getNearestIntersection(const RS_Vector& coord, double* dist, RS_Entity** entity, RS_Entity** otherEntity) const {
1325 double minDist = RS_MAXDOUBLE1.0E+10; // minimum measured distance
1326 RS_Vector closestPoint(false); // closest found endpoint
1327 const RS_Entity* closestEntity = getNearestEntity(coord, nullptr, RS2::ResolveAllButTextImage);
1328
1329 if (closestEntity != nullptr) {
1330 // fixme - sand - why not via traverser?
1331 for (const RS_Entity* en = firstEntity(RS2::ResolveAllButTextImage); en != nullptr; en = nextEntity(RS2::ResolveAllButTextImage)) {
1332 const auto parent = en->getParent();
1333 bool ignoredSnap = false;
1334 if (parent != nullptr) {
1335 // may be null in block editing?
1336 ignoredSnap = parent->ignoredSnap();
1337 }
1338 if (!en->isVisible() || ignoredSnap) {
1339 continue;
1340 }
1341
1342 RS_VectorSolutions sol = RS_Information::getIntersection(closestEntity, en, true);
1343 double curDist = RS_MAXDOUBLE1.0E+10; // currently measured distance
1344 const RS_Vector point = sol.getClosest(coord, &curDist, nullptr);
1345 if (sol.getNumber() > 0 && curDist < minDist) {
1346 closestPoint = point;
1347 minDist = curDist;
1348 if (entity != nullptr) {
1349 *entity = const_cast<RS_Entity*>(closestEntity);
1350 }
1351 if (otherEntity != nullptr) {
1352 *otherEntity = const_cast<RS_Entity*>(en);
1353 }
1354 }
1355 }
1356 }
1357 if ((dist != nullptr) && closestPoint.valid) {
1358 *dist = minDist;
1359 }
1360 return closestPoint;
1361}
1362
1363RS_Vector RS_EntityContainer::getNearestVirtualIntersection(const RS_Vector& coord, const double angle, double* dist) const {
1364 const RS_Entity* closestEntity = getNearestEntity(coord, nullptr, RS2::ResolveAllButTextImage);
1365 if (closestEntity != nullptr) {
1366 const RS_Vector second_coord{angle};
1367 const RS_ConstructionLineData data(coord, coord + second_coord);
1368 const auto line = RS_ConstructionLine(nullptr, data);
1369
1370 const RS_VectorSolutions sol = RS_Information::getIntersection(closestEntity, &line, true);
1371 if (sol.getVector().empty()) {
1372 return coord;
1373 }
1374 return sol.getClosest(coord, dist, nullptr);
1375 }
1376 return coord;
1377}
1378
1379RS_Vector RS_EntityContainer::doGetNearestRef(const RS_Vector& coord, double* dist) const {
1380 double minDist = RS_MAXDOUBLE1.0E+10; // minimum measured distance
1381 double curDist; // currently measured distance
1382 RS_Vector closestPoint(false); // closest found endpoint
1383
1384 for (const RS_Entity* en : *this) {
1385 if (en == nullptr) {
1386 continue;
1387 }
1388 if (en->isVisible()) {
1389 const RS_Vector point = en->getNearestRef(coord, &curDist);
1390 if (point.valid && curDist < minDist) {
1391 closestPoint = point;
1392 minDist = curDist;
1393 if (dist != nullptr) {
1394 *dist = minDist;
1395 }
1396 }
1397 }
1398 }
1399 return closestPoint;
1400}
1401
1402RS_Vector RS_EntityContainer::doGetNearestSelectedRef(const RS_Vector& coord, double* dist) const {
1403 const RefInfo info = getNearestSelectedRefInfo(coord, dist);
1404 return info.ref;
1405}
1406
1407RS_EntityContainer::RefInfo RS_EntityContainer::getNearestSelectedRefInfo(const RS_Vector& coord, double* dist) const {
1408 double minDist = RS_MAXDOUBLE1.0E+10; // minimum measured distance
1409 RS_Vector closestPoint(false); // closest found endpoint
1410 RS_Entity* closestPointEntity = nullptr;
1411
1412 for (RS_Entity* en : *this) {
1413 if (en == nullptr) {
1414 continue;
1415 }
1416 // fixme - sand - iteration of ver all entities
1417 if (en->isVisible() && en->isSelected() && !en->isParentSelected()) {
1418 // fixme - SELECTION - selection collection!
1419 double curDist = 0.; // currently measured distance
1420 const RS_Vector point = en->getNearestSelectedRef(coord, &curDist);
1421 if (point.valid && curDist < minDist) {
1422 closestPoint = point;
1423 closestPointEntity = en;
1424 minDist = curDist;
1425 if (dist != nullptr) {
1426 *dist = minDist;
1427 }
1428 }
1429 }
1430 }
1431 const RefInfo result{closestPoint, closestPointEntity};
1432 return result;
1433}
1434
1435namespace {
1436 // ResolveAllButTextImage looks for an entity to intersect: images (bug#426) and text have no
1437 // intersections. On-entity snapping (ResolveAllButTexts) never lands on text either, and
1438 // measuring text would walk every glyph.
1439 bool isSkippedAt(const RS2::EntityType type, const RS2::ResolveLevel level) {
1440 return (level == RS2::ResolveAllButTextImage && (type == RS2::EntityImage || RS2::isTextEntity(type)))
1441 || (level == RS2::ResolveAllButTexts && RS2::isTextEntity(type));
1442 }
1443
1444 // Whether a construction line lies in the container, other than in the block references it holds.
1445 bool holdsConstructionLine(const RS_EntityContainer& container) {
1446 return std::any_of(container.begin(), container.end(), [](const RS_Entity* entity) {
1447 if (entity == nullptr) {
1448 return false;
1449 }
1450 switch (entity->rtti()) {
1451 case RS2::EntityConstructionLine:
1452 return true;
1453 // a block reference shows a block definition, and each definition is checked on its own
1454 case RS2::EntityInsert:
1455 return false;
1456 default:
1457 return entity->isContainer() && holdsConstructionLine(static_cast<const RS_EntityContainer&>(*entity));
1458 }
1459 });
1460 }
1461
1462 /**
1463 * Tells from cached borders, without measuring any geometry, when an entity of a drawing lies
1464 * farther from a point than a distance. RS_Entity::getDistanceToPoint() never reports less than
1465 * the distance to the borders, or to the center of an atomic entity (selection by center point),
1466 * except for construction lines and lines on a construction layer, which reach past their
1467 * borders. Containers are searched for those only when the drawing has a construction layer or a
1468 * block definition holding a construction line.
1469 */
1470 class DistanceLowerBound {
1471 public:
1472 explicit DistanceLowerBound(const RS_EntityContainer& drawing) : m_graphic{drawing.getGraphic()} {
1473 const RS_LayerList* layers = m_graphic != nullptr ? m_graphic->getLayerList() : nullptr;
1474 m_constructionLayers = layers == nullptr || std::any_of(layers->begin(), layers->end(), [](const RS_Layer* layer) {
1475 return layer != nullptr && layer->isConstruction();
1476 });
1477 }
1478
1479 // whether entity.getDistanceToPoint(coord) is certainly larger than distance
1480 bool exceeds(const RS_Entity& entity, const RS_Vector& coord, const double distance) const {
1481 const RS_Vector min = entity.getMin();
1482 const RS_Vector max = entity.getMax();
1483 // unknown, reset or non-finite borders bound nothing
1484 if (!coord.valid || !min.valid || !max.valid || !(min.x <= max.x && min.y <= max.y)
1485 || !std::isfinite(min.x + min.y + max.x + max.y)) {
1486 return false;
1487 }
1488 // the slack absorbs rounding, so an entity measured exactly as far away is never skipped
1489 const double limit = distance + RS_TOLERANCE1.0e-10 + 1e-12 * (std::abs(coord.x) + std::abs(coord.y) + distance);
1490 const double dx = std::max({min.x - coord.x, 0., coord.x - max.x});
1491 const double dy = std::max({min.y - coord.y, 0., coord.y - max.y});
1492 if (dx * dx + dy * dy <= limit * limit) {
1493 return false;
1494 }
1495 const RS_Vector center = entity.isContainer() ? RS_Vector(false) : entity.getCenter();
1496 return !(center.valid && center.distanceTo(coord) <= limit) && !reachesPastBorders(entity);
1497 }
1498
1499 private:
1500 bool reachesPastBorders(const RS_Entity& entity) const {
1501 switch (entity.rtti()) {
1502 case RS2::EntityConstructionLine:
1503 case RS2::EntityRefConstructionLine:
1504 case RS2::EntitySnapConstructionLine:
1505 return true;
1506 case RS2::EntityLine:
1507 // drawn and hit-tested as infinite on a construction layer
1508 return m_constructionLayers && entity.isConstruction();
1509 // glyphs have no layer of their own, and a hatch is measured by its filled area
1510 case RS2::EntityText:
1511 case RS2::EntityMText:
1512 case RS2::EntityHatch:
1513 return false;
1514 default:
1515 break;
1516 }
1517 if (!entity.isContainer() || !containersMayReach()) {
1518 return false;
1519 }
1520 const auto& container = static_cast<const RS_EntityContainer&>(entity);
1521 return std::any_of(container.begin(), container.end(), [this](const RS_Entity* child) {
1522 return child != nullptr && reachesPastBorders(*child);
1523 });
1524 }
1525
1526 bool containersMayReach() const {
1527 if (!m_containersMayReach.has_value()) {
1528 const RS_BlockList* blocks = m_graphic != nullptr ? m_graphic->getBlockList() : nullptr;
1529 m_containersMayReach = m_constructionLayers || blocks == nullptr
1530 || std::any_of(blocks->begin(), blocks->end(), [](const RS_Block* block) {
1531 return block != nullptr && holdsConstructionLine(*block);
1532 });
1533 }
1534 return *m_containersMayReach;
1535 }
1536
1537 RS_Graphic* m_graphic = nullptr;
1538 bool m_constructionLayers = true;
1539 mutable std::optional<bool> m_containersMayReach;
1540 };
1541}
1542
1543void RS_EntityContainer::appendNearby(const RS_Vector& coord, const double range, RS_EntityContainer& nearby) const {
1544 const DistanceLowerBound bound{*this};
1545 for (RS_Entity* e : *this) {
1546 // push_back keeps the drawing order that settles ties; addEntity() would move hatches and images to the front
1547 if (e != nullptr && e->isVisible() && !bound.exceeds(*e, coord, range)) {
1548 nearby.push_back(e);
1549 }
1550 }
1551}
1552
1553double RS_EntityContainer::doGetDistanceToPoint(const RS_Vector& coord, RS_Entity** entity, const RS2::ResolveLevel level,
1554 const double solidDist) const {
1555 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::getDistanceToPoint");
1556 double minDist = RS_MAXDOUBLE1.0E+10; // minimum measured distance
1557 double curDist = 0.; // currently measured distance
1558 RS_Entity* closestEntity = nullptr; // closest entity found
1559 RS_Entity* subEntity = nullptr;
1560
1561 // a drawing skips every entity whose borders lie farther away than the nearest entity found so far
1562 std::optional<DistanceLowerBound> bound;
1563 if (isDocument()) {
1564 bound.emplace(*this);
1565 }
1566
1567 for (RS_Entity* e : *this) {
1568 if (e == nullptr) {
1569 continue;
1570 }
1571 const auto entityLayer = e->getLayer();
1572 if (e->isVisible() && (entityLayer == nullptr || !entityLayer->isLocked())) {
1573 RS_DEBUGRS_Debug::instance()->print("entity: getDistanceToPoint");
1574 RS_DEBUGRS_Debug::instance()->print("entity: %d", e->rtti());
1575 if (isSkippedAt(e->rtti(), level) || (bound.has_value() && bound->exceeds(*e, coord, minDist))) {
1576 continue;
1577 }
1578 curDist = e->getDistanceToPoint(coord, &subEntity, level, solidDist);
1579
1580 RS_DEBUGRS_Debug::instance()->print("entity: getDistanceToPoint: OK");
1581
1582 /*
1583 * By using '<=', we will prefer the *last* item in the container if there are multiple
1584 * entities that are *exactly* the same distance away, which should tend to be the one
1585 * drawn most recently, and the one most likely to be visible (as it is also the order
1586 * that the software draws the entities). This makes a difference when one entity is
1587 * drawn directly over top of another, and it's reasonable to assume that humans will
1588 * tend to want to reference entities that they see or have recently drawn as opposed
1589 * to deeper more forgotten and invisible ones...
1590 */
1591 if (curDist <= minDist) {
1592 switch (level) {
1593 case RS2::ResolveAll:
1594 case RS2::ResolveAllButTextImage:
1595 case RS2::ResolveAllButTexts:
1596 closestEntity = subEntity;
1597 break;
1598 default:
1599 closestEntity = e;
1600 break;
1601 }
1602 minDist = curDist;
1603 }
1604 }
1605 }
1606
1607 if (entity != nullptr) {
1608 *entity = closestEntity;
1609 }
1610 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::getDistanceToPoint: OK");
1611
1612 return minDist;
1613}
1614
1615RS_Entity* RS_EntityContainer::getNearestEntity(const RS_Vector& coord, double* dist, const RS2::ResolveLevel level) const {
1616 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::getNearestEntity");
1617
1618 RS_Entity* e = nullptr;
1619
1620 // distance for points inside solids:
1621 double solidDist = RS_MAXDOUBLE1.0E+10;
1622 if (dist != nullptr) {
1623 solidDist = *dist;
1624 }
1625
1626 const double d = getDistanceToPoint(coord, &e, level, solidDist);
1627 if (e != nullptr && e->isVisible() == false) {
1628 e = nullptr;
1629 }
1630
1631 // if d is negative, use the default distance (used for points inside solids)
1632 if (dist != nullptr) {
1633 *dist = d;
1634 }
1635 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::getNearestEntity: OK");
1636 return e;
1637}
1638
1639/**
1640 * Rearranges the atomic entities in this container in a way that connected
1641 * entities are stored in the right order and direction.
1642 * Non-recoursive. Only affects atomic entities in this container.
1643 *
1644 * @retval true all contours were closed
1645 * @retval false at least one contour is not closed
1646
1647 * to do: find closed contour by flood-fill
1648 */
1649bool RS_EntityContainer::optimizeContours() {
1650 // std::cout<<"RS_EntityContainer::optimizeContours: begin"<<std::endl;
1651 // DEBUG_HEADER
1652 // std::cout<<"loop with count()="<<count()<<std::endl;
1653 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::optimizeContours");
1654
1655 RS_EntityContainer tmp;
1656 tmp.setAutoUpdateBorders(false);
1657 bool closed = true;
1658
1659 /** accept all full circles **/
1660 QList<RS_Entity*> enList;
1661 for (RS_Entity* e1 : *this) {
1662 if (e1 == nullptr) {
1663 continue;
1664 }
1665 if (!e1->isEdge() || e1->isContainer()) {
1666 enList << e1;
1667 continue;
1668 }
1669
1670 //detect circles and whole ellipses
1671 switch (e1->rtti()) {
1672 case RS2::EntityEllipse: {
1673 if (static_cast<RS_Ellipse*>(e1)->isEllipticArc()) {
1674 continue;
1675 }
1676 // fall-through
1677 [[fallthrough]];
1678 }
1679 case RS2::EntityCircle: {
1680 //directly detect circles, bug#3443277
1681 tmp.addEntity(e1->clone());
1682 enList << e1;
1683 // fall-through
1684 [[fallthrough]];
1685 }
1686 default:
1687 break;
1688 }
1689 }
1690
1691 /** remove unsupported entities */
1692 for (RS_Entity* it : std::as_const(enList)) {
1693 removeEntity(it);
1694 }
1695
1696 /** check and form a closed contour **/
1697 // std::cout<<"RS_EntityContainer::optimizeContours: 2"<<std::endl;
1698 /** the first entity **/
1699 const RS_Entity* current(nullptr);
1700 if (count() > 0) {
1701 current = entityAt(0)->clone();
1702 tmp.addEntity(current);
1703 removeEntity(entityAt(0));
1704 }
1705 else {
1706 if (tmp.count() == 0) {
1707 return false;
1708 }
1709 }
1710 // std::cout<<"RS_EntityContainer::optimizeContours: 3"<<std::endl;
1711 RS_Vector vpStart;
1712 RS_Vector vpEnd;
1713 if (current != nullptr) {
1714 vpStart = current->getStartpoint();
1715 vpEnd = current->getEndpoint();
1716 }
1717 RS_Entity* next = nullptr;
1718 // std::cout<<"RS_EntityContainer::optimizeContours: 4"<<std::endl;
1719 /** connect entities **/
1720 const auto errMsg = QObject::tr("Hatch failed due to a gap=%1 between (%2, %3) and (%4, %5)");
1721
1722 while (count() > 0) {
1723 double dist = 0.;
1724 const RS_Vector vpTmp = obtainNearestEndpoint(vpEnd, &dist, &next);
1725 if (dist > CONTOUR_TOLERANCE) {
1726 if (vpEnd.squaredTo(vpStart) < CONTOUR_TOLERANCE) {
1727 RS_Entity* e2 = entityAt(0);
1728 tmp.addEntity(e2->clone());
1729 vpStart = e2->getStartpoint();
1730 vpEnd = e2->getEndpoint();
1731 removeEntity(e2);
1732 continue;
1733 }
1734 // fixme - sand - isn't it a really bad dependency? check later and remove
1735 QG_DIALOGFACTORY(RS_DialogFactory::instance()->getFactoryObject())->commandMessage(errMsg.arg(dist).arg(vpTmp.x).arg(vpTmp.y).arg(vpEnd.x).arg(vpEnd.y));
1736 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_ERROR, "RS_EntityContainer::optimizeContours: hatch failed due to a gap");
1737 closed = false;
1738 break;
1739 }
1740 if (next == nullptr) {
1741 //workaround if next is nullptr
1742 // std::cout<<"RS_EntityContainer::optimizeContours: next is nullptr" <<std::endl;
1743 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::optimizeContours: next is nullptr");
1744 // closed=false; //workaround if next is nullptr
1745 break; //workaround if next is nullptr
1746 } // workaround if next is nullptr
1747 if (closed) {
1748 next->setProcessed(true);
1749 RS_Entity* eTmp = next->clone();
1750 if (vpEnd.squaredTo(eTmp->getStartpoint()) > vpEnd.squaredTo(eTmp->getEndpoint())) {
1751 eTmp->revertDirection();
1752 }
1753 vpEnd = eTmp->getEndpoint();
1754 tmp.addEntity(eTmp);
1755 removeEntity(next);
1756 }
1757 }
1758 // DEBUG_HEADER
1759 // if(vpEnd.valid && vpEnd.squaredTo(vpStart) > 1e-8) {
1760 // QG_DIALOGFACTORY->commandMessage(errMsg.arg(vpEnd.distanceTo(vpStart))
1761 // .arg(vpStart.x).arg(vpStart.y).arg(vpEnd.x).arg(vpEnd.y));
1762 // RS_DEBUG->print("RS_EntityContainer::optimizeContours: hatch failed due to a gap");
1763 // closed=false;
1764 // }
1765 // std::cout<<"RS_EntityContainer::optimizeContours: 5"<<std::endl;
1766
1767 // add new sorted entities:
1768 for (RS_Entity* en : tmp) {
1769 en->setProcessed(false);
1770 addEntity(en->clone());
1771 en->reparent(this);
1772 }
1773 // std::cout<<"RS_EntityContainer::optimizeContours: 6"<<std::endl;
1774
1775 if (closed) {
1776 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::optimizeContours: OK");
1777 }
1778 else {
1779 RS_DEBUGRS_Debug::instance()->print("RS_EntityContainer::optimizeContours: bad");
1780 }
1781 // std::cout<<"RS_EntityContainer::optimizeContours: end: count()="<<count()<<std::endl;
1782 // std::cout<<"RS_EntityContainer::optimizeContours: closed="<<closed<<std::endl;
1783 return closed;
1784}
1785
1786bool RS_EntityContainer::hasEndpointsWithinWindow(const RS_Vector& v1, const RS_Vector& v2) const {
1787 return std::any_of(cbegin(), cend(), [&v1, &v2](const RS_Entity* entity) {
1788 return entity != nullptr && entity->hasEndpointsWithinWindow(v1, v2);
1789 });
1790}
1791
1792void RS_EntityContainer::move(const RS_Vector& offset) {
1793 moveBorders(offset);
1794 for (RS_Entity* e : *this) {
1795 if (e == nullptr) {
1796 continue;
1797 }
1798 e->move(offset);
1799 adjustBorders(e);
1800 }
1801 calculateBordersIfNeeded();
1802}
1803
1804void RS_EntityContainer::rotate(const RS_Vector& center, const double angle) {
1805 RS_EntityContainer::rotate(center, RS_Vector{angle});
1806}
1807
1808void RS_EntityContainer::rotate(const RS_Vector& center, const RS_Vector& angleVector) {
1809 resetBorders();
1810 for (RS_Entity* e : *this) {
1811 if (e == nullptr) {
1812 continue;
1813 }
1814 e->rotate(center, angleVector);
1815 adjustBorders(e);
1816 }
1817 calculateBordersIfNeeded();
1818}
1819
1820void RS_EntityContainer::scale(const RS_Vector& center, const RS_Vector& factor) {
1821 if (std::abs(factor.x) > RS_TOLERANCE1.0e-10 && std::abs(factor.y) > RS_TOLERANCE1.0e-10) {
1822 scaleBorders(center, factor);
1823 for (RS_Entity* e : *this) {
1824 if (e == nullptr) {
1825 continue;
1826 }
1827 e->scale(center, factor);
1828 adjustBorders(e);
1829 }
1830 calculateBordersIfNeeded();
1831 }
1832}
1833
1834void RS_EntityContainer::mirror(const RS_Vector& axisPoint1, const RS_Vector& axisPoint2) {
1835 if (axisPoint1.distanceTo(axisPoint2) > RS_TOLERANCE1.0e-10) {
1836 resetBorders();
1837 for (RS_Entity* e : *this) {
1838 if (e == nullptr) {
1839 continue;
1840 }
1841 e->mirror(axisPoint1, axisPoint2);
1842 adjustBorders(e);
1843 }
1844 }
1845}
1846
1847RS_Entity& RS_EntityContainer::shear(const double k) {
1848 for (RS_Entity* e : *this) {
1849 if (e == nullptr) {
1850 continue;
1851 }
1852 e->shear(k);
1853 }
1854 calculateBorders();
1855 return *this;
1856}
1857
1858void RS_EntityContainer::stretch(const RS_Vector& firstCorner, const RS_Vector& secondCorner, const RS_Vector& offset) {
1859 if (getMin().isInWindow(firstCorner, secondCorner) && getMax().isInWindow(firstCorner, secondCorner)) {
1860 move(offset);
1861 }
1862 else {
1863 for (RS_Entity* e : *this) {
1864 if (e == nullptr) {
1865 continue;
1866 }
1867 e->stretch(firstCorner, secondCorner, offset);
1868 }
1869 }
1870 // some entitiycontainers might need an update (e.g. RS_Leader):
1871 update();
1872 // children stretched in place leave the borders of the container behind
1873 calculateBorders();
1874}
1875
1876void RS_EntityContainer::calculateBordersIfNeeded() {
1877 if (m_autoUpdateBorders) {
1878 calculateBorders();
1879 }
1880}
1881
1882void RS_EntityContainer::moveRef(const RS_Vector& ref, const RS_Vector& offset) {
1883 resetBorders();
1884 for (RS_Entity* e : *this) {
1885 if (e == nullptr) {
1886 continue;
1887 }
1888 e->moveRef(ref, offset);
1889 adjustBorders(e);
1890 }
1891 calculateBordersIfNeeded();
1892}
1893
1894void RS_EntityContainer::moveSelectedRef(const RS_Vector& ref, const RS_Vector& offset) {
1895 resetBorders();
1896 for (RS_Entity* e : *this) {
1897 if (e == nullptr) {
1898 continue;
1899 }
1900 e->moveSelectedRef(ref, offset);
1901 adjustBorders(e);
1902 }
1903 calculateBordersIfNeeded();
1904}
1905
1906void RS_EntityContainer::revertDirection() {
1907 // revert entity order in the container
1908 for (int k = 0; k < m_entities.size() / 2; ++k) {
1909#if (QT_VERSION((6<<16)|(9<<8)|(0)) >= QT_VERSION_CHECK(5, 13, 0)((5<<16)|(13<<8)|(0)))
1910 m_entities.swapItemsAt(k, m_entities.size() - 1 - k);
1911#else
1912 entities.swap(k, entities.size() - 1 - k);
1913#endif
1914 }
1915
1916 // revert each entity itself
1917 for (RS_Entity* entity : std::as_const(m_entities)) {
1918 if (entity == nullptr) {
1919 continue;
1920 }
1921 entity->revertDirection();
1922 }
1923}
1924
1925/**
1926 * @brief draw m_entities in order
1927 * @param painter
1928 */
1929void RS_EntityContainer::draw(RS_Painter* painter) {
1930 for (RS_Entity* e : *this) {
1931 if (e != nullptr && e->getId() != 0) {
1932 painter->drawEntity(e);
1933 }
1934 }
1935}
1936
1937void RS_EntityContainer::drawAsChild(RS_Painter* painter) {
1938 for (RS_Entity* e : *this) {
1939 if (e != nullptr && e->getId() != 0) {
1940 painter->drawAsChild(e);
1941 }
1942 }
1943}
1944
1945/**
1946 * @brief areaLineIntegral, line integral for contour area calculation by Green's Theorem
1947 * Contour Area =\oint x dy
1948 * @return line integral \oint x dy along the entity
1949 */
1950double RS_EntityContainer::areaLineIntegral() const {
1951 //TODO make sure all contour integral is by counter-clockwise
1952 double contourArea = 0.;
1953 //closed area is always positive
1954 double closedArea = 0.;
1955 double subArea = 0.;
1956
1957 // edges:
1958 RS_Vector previousPoint(false);
1959 for (unsigned i = 0; i < count(); ++i) {
1960 RS_Entity* e = m_entities.at(i);
1961 if (isClosedLoop(*e)) {
1962 if (e->isContainer()) {
1963 subArea += e->areaLineIntegral();
1964 }
1965 else {
1966 closedArea += e->areaLineIntegral();
1967 }
1968 continue;
1969 }
1970 e->setLayer(getLayer());
1971 const double lineIntegral = e->areaLineIntegral();
1972 RS_Vector startPoint = e->getStartpoint();
1973 RS_Vector endPoint = e->getEndpoint();
1974 // LC_ERR << e->getId() << ": int = " << lineIntegral << ": " << startPoint.x << " - " << endPoint.x;
1975
1976 // the line integral is always by the direction: from the start point to the end point
1977 if (previousPoint.valid) {
1978 const double distance = endPointDistance(previousPoint, *e);
1979 if (distance > CONTOUR_TOLERANCE) {
1980 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_ERROR, "%s(): contour area calculation maybe incorrect: gap of %lg found at (%lg, %lg)",
1981 __func__, distance, previousPoint.x, previousPoint.y);
1982 }
1983 }
1984 // assume the contour is a simple connected loop
1985 if (previousPoint.valid && endPoint.squaredTo(previousPoint) <= RS_TOLERANCE151.5e-15) {
1986 contourArea -= lineIntegral;
1987 previousPoint = startPoint;
1988 }
1989 else {
1990 bool useEndPoint = true;
1991 if (!previousPoint.valid && i + 1 < count()) {
1992 const auto currEntity = m_entities.at(i + 1);
1993 useEndPoint = endPointDistance(endPoint, *currEntity) < endPointDistance(startPoint, *currEntity);
1994 }
1995 contourArea += useEndPoint ? lineIntegral : -lineIntegral;
1996 previousPoint = useEndPoint ? endPoint : startPoint;
1997 }
1998 }
1999 return std::abs(contourArea) + closedArea - subArea;
2000}
2001
2002LC_FirstMoment RS_EntityContainer::firstMomentLineIntegral() const {
2003 // Mirrors secondMomentLineIntegral() traversal; accumulates (∬ x dA, ∬ y dA).
2004 LC_FirstMoment contourMoment;
2005 double contourArea = 0.0;
2006 LC_FirstMoment closedMoment;
2007 LC_FirstMoment subMoment;
2008
2009 RS_Vector previousPoint(false);
2010 for (unsigned i = 0; i < count(); ++i) {
2011 RS_Entity* e = m_entities.at(i);
2012 if (isClosedLoop(*e)) {
2013 if (e->isContainer()) {
2014 subMoment += e->firstMomentLineIntegral();
2015 }
2016 else {
2017 closedMoment += e->firstMomentLineIntegral();
2018 }
2019 continue;
2020 }
2021 e->setLayer(getLayer());
2022 LC_FirstMoment m = e->firstMomentLineIntegral();
2023 double integral = e->areaLineIntegral();
2024 RS_Vector startPt = e->getStartpoint();
2025 RS_Vector endPt = e->getEndpoint();
2026
2027 if (previousPoint.valid && endPt.squaredTo(previousPoint) <= RS_TOLERANCE151.5e-15) {
2028 contourArea -= integral;
2029 contourMoment -= m;
2030 previousPoint = startPt;
2031 } else {
2032 bool useEndPoint = true;
2033 if (!previousPoint.valid && i + 1 < count()) {
2034 auto currEntity = m_entities.at(i + 1);
2035 useEndPoint = endPointDistance(endPt, *currEntity) < endPointDistance(startPt, *currEntity);
2036 }
2037 if (useEndPoint) {
2038 contourArea += integral;
2039 contourMoment += m;
2040 previousPoint = endPt;
2041 } else {
2042 contourArea -= integral;
2043 contourMoment -= m;
2044 previousPoint = startPt;
2045 }
2046 }
2047 }
2048 if (contourArea < 0.0) {
2049 contourMoment = -contourMoment;
2050 }
2051
2052 return contourMoment + closedMoment - subMoment;
2053}
2054
2055LC_SecondMoment RS_EntityContainer::secondMomentLineIntegral() const {
2056 // Follows the same traversal logic as areaLineIntegral(), applying identical
2057 // sign flips so the moments are consistent with the signed area convention.
2058 LC_SecondMoment contourMoment;
2059 double contourArea = 0.0;
2060 LC_SecondMoment closedMoment;
2061 LC_SecondMoment subMoment;
2062
2063 RS_Vector previousPoint(false);
2064 for (unsigned i = 0; i < count(); ++i) {
2065 RS_Entity* e = m_entities.at(i);
2066 if (isClosedLoop(*e)) {
2067 if (e->isContainer()) {
2068 subMoment += e->secondMomentLineIntegral();
2069 }
2070 else {
2071 closedMoment += e->secondMomentLineIntegral();
2072 }
2073 continue;
2074 }
2075 e->setLayer(getLayer());
2076 LC_SecondMoment m = e->secondMomentLineIntegral();
2077 double integral = e->areaLineIntegral();
2078 RS_Vector startPt = e->getStartpoint();
2079 RS_Vector endPt = e->getEndpoint();
2080
2081 if (previousPoint.valid && endPt.squaredTo(previousPoint) <= RS_TOLERANCE151.5e-15) {
2082 contourArea -= integral;
2083 contourMoment -= m;
2084 previousPoint = startPt;
2085 } else {
2086 bool useEndPoint = true;
2087 if (!previousPoint.valid && i + 1 < count()) {
2088 auto currEntity = m_entities.at(i + 1);
2089 useEndPoint = endPointDistance(endPt, *currEntity) < endPointDistance(startPt, *currEntity);
2090 }
2091 if (useEndPoint) {
2092 contourArea += integral;
2093 contourMoment += m;
2094 previousPoint = endPt;
2095 } else {
2096 contourArea -= integral;
2097 contourMoment -= m;
2098 previousPoint = startPt;
2099 }
2100 }
2101 }
2102 // Match the abs-value convention of areaLineIntegral()
2103 if (contourArea < 0.0) {
2104 contourMoment = -contourMoment;
2105 }
2106
2107 return contourMoment + closedMoment - subMoment;
2108}
2109
2110bool RS_EntityContainer::ignoredOnModification() const {
2111 const RS2::EntityType ownType = rtti();
2112 if (RS2::isDimensionalEntity(ownType) || RS2::isTextEntity(ownType) || ownType == RS2::EntityHatch) {
2113 return true;
2114 }
2115 return isParentIgnoredOnModifications();
2116}
2117
2118bool RS_EntityContainer::ignoredSnap() const {
2119 // issue #652 , disable snap for hatch
2120 // TODO, should snapping on hatch be a feature enabled by settings?
2121 if ((getParent() != nullptr) && getParent()->rtti() == RS2::EntityHatch) {
2122 return true;
2123 }
2124 return ignoredOnModification();
2125}
2126
2127#define DEBUG_CONTAINER_DUPLICATE // fixme - sand - disable before push!
2128
2129void RS_EntityContainer::debugEntityAlreadyPresentExists(const RS_Entity* entity) const {
2130#ifdef DEBUG_CONTAINER_DUPLICATE
2131 const qsizetype countOfEntities = m_entities.count(entity);
Value stored to 'countOfEntities' during its initialization is never read
2132 Q_ASSERT(countOfEntities == 0)static_cast<void>(false && (countOfEntities == 0
))
;
2133#endif
2134}
2135
2136QList<RS_Entity*>::const_iterator RS_EntityContainer::begin() const {
2137 return m_entities.begin();
2138}
2139
2140QList<RS_Entity*>::const_iterator RS_EntityContainer::end() const {
2141 return m_entities.end();
2142}
2143
2144QList<RS_Entity*>::const_iterator RS_EntityContainer::cbegin() const {
2145 return m_entities.cbegin();
2146}
2147
2148QList<RS_Entity*>::const_iterator RS_EntityContainer::cend() const {
2149 return m_entities.cend();
2150}
2151
2152QList<RS_Entity*>::iterator RS_EntityContainer::begin() {
2153 return m_entities.begin();
2154}
2155
2156QList<RS_Entity*>::iterator RS_EntityContainer::end() {
2157 return m_entities.end();
2158}
2159
2160/**
2161 * Dumps the entities to stdout.
2162 */
2163std::ostream& operator<<(std::ostream& os, RS_EntityContainer& ec) {
2164 static int indent = 0;
2165 const std::string tab(indent * 2, ' ');
2166 ++indent;
2167 const unsigned long int id = ec.getId();
2168 os << tab << "EntityContainer[" << id << "]: \n";
2169 os << tab << "Borders[" << id << "]: " << ec.m_minV << " - " << ec.m_maxV << "\n";
2170 if (ec.getLayer() != nullptr) {
2171 os << tab << "Layer[" << id << "]: " << ec.getLayer()->getName().toLatin1().data() << "\n";
2172 }
2173 else {
2174 os << tab << "Layer[" << id << "]: <nullptr>\n";
2175 }
2176 //os << ec.layerList << "\n";
2177
2178 os << tab << " Flags[" << id << "]: " << (ec.getFlag(RS2::FlagVisible) ? "RS2::FlagVisible" : "");
2179 os << (ec.getFlag(RS2::FlagDeleted) ? " RS2::FlagUndone" : "");
2180 os << (ec.getFlag(RS2::FlagSelected) ? " RS2::FlagSelected" : "");
2181 os << "\n";
2182
2183 os << tab << "Entities[" << id << "]: \n";
2184 for (const auto t : ec) {
2185 switch (t->rtti()) {
2186 case RS2::EntityInsert:
2187 os << tab << *static_cast<RS_Insert*>(t);
2188 os << tab << *t;
2189 os << tab << *static_cast<RS_EntityContainer*>(t);
2190 break;
2191 default:
2192 if (t->isContainer()) {
2193 os << tab << *static_cast<RS_EntityContainer*>(t);
2194 }
2195 else {
2196 os << tab << *t;
2197 }
2198 break;
2199 }
2200 }
2201 os << tab << "\n\n";
2202 --indent;
2203 return os;
2204}
2205
2206RS_Entity* RS_EntityContainer::first() const {
2207 return m_entities.first();
2208}
2209
2210RS_Entity* RS_EntityContainer::last() const {
2211 return m_entities.last();
2212}
2213
2214const QList<RS_Entity*>& RS_EntityContainer::getEntityList() const {
2215 return m_entities;
2216}
2217
2218std::vector<std::unique_ptr<RS_EntityContainer>> RS_EntityContainer::getLoops() const {
2219 if (m_entities.empty()) {
2220 return {};
2221 }
2222 std::vector<std::unique_ptr<RS_EntityContainer>> loops;
2223 RS_EntityContainer edges(nullptr, false);
2224 for(RS_Entity* en: *this){
2225 if (en != nullptr && en->isContainer()){
2226 if (en->isContainer()){
2227 auto subLoops = static_cast<RS_EntityContainer*>(en)->getLoops();
2228 for (auto& subLoop: subLoops) {
2229 loops.push_back(std::move(subLoop));
2230 }
2231 }
2232 continue;
2233 }
2234
2235 if (en == nullptr || !en->isEdge()) {
2236 continue;
2237 }
2238
2239 //detect circles and whole ellipses
2240 switch (en->rtti()) {
2241 case RS2::EntityEllipse:
2242 if (static_cast<RS_Ellipse*>(en)->isEllipticArc()) {
2243 edges.addEntity(en);
2244 break;
2245 }
2246 [[fallthrough]];
2247 case RS2::EntityCircle: {
2248 auto ec = std::make_unique<RS_EntityContainer>(nullptr, false);
2249 ec->addEntity(en);
2250 loops.push_back(std::move(ec));
2251 break;
2252 }
2253 default:
2254 edges.addEntity(en);
2255 }
2256 }
2257 //find loops
2258 while (!edges.isEmpty()) {
2259 LC_LoopUtils::LoopExtractor extractor{edges};
2260 auto subLoops = extractor.extract();
2261 for (auto& loop : subLoops) {
2262 loops.push_back(std::move(loop));
2263 }
2264 }
2265 return loops;
2266}