1977 lines
53 KiB
C++
1977 lines
53 KiB
C++
/****************************************************************************
|
|
**
|
|
** This file is part of the LibreCAD project, a 2D CAD program
|
|
**
|
|
** Copyright (C) 2010 R. van Twisk (librecad@rvt.dds.nl)
|
|
** Copyright (C) 2001-2003 RibbonSoft. All rights reserved.
|
|
**
|
|
**
|
|
** This file may be distributed and/or modified under the terms of the
|
|
** GNU General Public License version 2 as published by the Free Software
|
|
** Foundation and appearing in the file gpl-2.0.txt included in the
|
|
** packaging of this file.
|
|
**
|
|
** This program is distributed in the hope that it will be useful,
|
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
** GNU General Public License for more details.
|
|
**
|
|
** You should have received a copy of the GNU General Public License
|
|
** along with this program; if not, write to the Free Software
|
|
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
**
|
|
** This copyright notice MUST APPEAR in all copies of the script!
|
|
**
|
|
**********************************************************************/
|
|
|
|
#include <iostream>
|
|
#include <cmath>
|
|
#include <set>
|
|
#include <QObject>
|
|
|
|
#include "rs_dialogfactory.h"
|
|
#include "qg_dialogfactory.h"
|
|
#include "rs_entitycontainer.h"
|
|
|
|
#include "rs_debug.h"
|
|
#include "rs_dimension.h"
|
|
#include "rs_layer.h"
|
|
#include "rs_arc.h"
|
|
#include "rs_ellipse.h"
|
|
#include "rs_line.h"
|
|
#include "rs_insert.h"
|
|
#include "rs_spline.h"
|
|
#include "rs_solid.h"
|
|
#include "rs_information.h"
|
|
#include "rs_graphicview.h"
|
|
#include "rs_constructionline.h"
|
|
|
|
bool RS_EntityContainer::autoUpdateBorders = true;
|
|
|
|
/**
|
|
* Default constructor.
|
|
*
|
|
* @param owner True if we own and also delete the entities.
|
|
*/
|
|
RS_EntityContainer::RS_EntityContainer(RS_EntityContainer* parent,
|
|
bool owner)
|
|
: RS_Entity(parent) {
|
|
|
|
autoDelete=owner;
|
|
// RS_DEBUG->print("RS_EntityContainer::RS_EntityContainer: "
|
|
// "owner: %d", (int)owner);
|
|
subContainer = nullptr;
|
|
//autoUpdateBorders = true;
|
|
entIdx = -1;
|
|
}
|
|
|
|
|
|
/**
|
|
* Copy constructor. Makes a deep copy of all entities.
|
|
*/
|
|
/*
|
|
RS_EntityContainer::RS_EntityContainer(const RS_EntityContainer& ec)
|
|
: RS_Entity(ec) {
|
|
|
|
}
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
* Destructor.
|
|
*/
|
|
RS_EntityContainer::~RS_EntityContainer() {
|
|
if (autoDelete) {
|
|
while (!entities.isEmpty())
|
|
delete entities.takeFirst();
|
|
} else
|
|
entities.clear();
|
|
}
|
|
|
|
|
|
|
|
RS_Entity* RS_EntityContainer::clone() const{
|
|
RS_DEBUG->print("RS_EntityContainer::clone: ori autoDel: %d",
|
|
autoDelete);
|
|
|
|
RS_EntityContainer* ec = new RS_EntityContainer(*this);
|
|
ec->setOwner(autoDelete);
|
|
|
|
RS_DEBUG->print("RS_EntityContainer::clone: clone autoDel: %d",
|
|
ec->isOwner());
|
|
|
|
ec->detach();
|
|
ec->initId();
|
|
|
|
return ec;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Detaches shallow copies and creates deep copies of all subentities.
|
|
* This is called after cloning entity containers.
|
|
*/
|
|
void RS_EntityContainer::detach() {
|
|
QList<RS_Entity*> tmp;
|
|
bool autoDel = isOwner();
|
|
RS_DEBUG->print("RS_EntityContainer::detach: autoDel: %d",
|
|
(int)autoDel);
|
|
setOwner(false);
|
|
|
|
// make deep copies of all entities:
|
|
for(auto e: entities){
|
|
if (!e->getFlag(RS2::FlagTemp)) {
|
|
tmp.append(e->clone());
|
|
}
|
|
}
|
|
|
|
// clear shared pointers:
|
|
entities.clear();
|
|
setOwner(autoDel);
|
|
|
|
// point to new deep copies:
|
|
for(auto e: tmp){
|
|
entities.append(e);
|
|
e->reparent(this);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void RS_EntityContainer::reparent(RS_EntityContainer* parent) {
|
|
RS_Entity::reparent(parent);
|
|
|
|
// All sub-entities:
|
|
|
|
for(auto e: entities){
|
|
e->reparent(parent);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void RS_EntityContainer::setVisible(bool v) {
|
|
// RS_DEBUG->print("RS_EntityContainer::setVisible: %d", v);
|
|
RS_Entity::setVisible(v);
|
|
|
|
// All sub-entities:
|
|
|
|
for(auto e: entities){
|
|
// RS_DEBUG->print("RS_EntityContainer::setVisible: subentity: %d", v);
|
|
e->setVisible(v);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @return Total length of all entities in this container.
|
|
*/
|
|
double RS_EntityContainer::getLength() const {
|
|
double ret = 0.0;
|
|
|
|
for(auto e: entities){
|
|
if (e->isVisible()) {
|
|
double l = e->getLength();
|
|
if (l<0.0) {
|
|
ret = -1.0;
|
|
break;
|
|
} else {
|
|
ret += l;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* Selects this entity.
|
|
*/
|
|
bool RS_EntityContainer::setSelected(bool select) {
|
|
// This entity's select:
|
|
if (RS_Entity::setSelected(select)) {
|
|
|
|
// All sub-entity's select:
|
|
for(auto e: entities){
|
|
if (e->isVisible()) {
|
|
e->setSelected(select);
|
|
}
|
|
}
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Toggles select on this entity.
|
|
*/
|
|
bool RS_EntityContainer::toggleSelected() {
|
|
// Toggle this entity's select:
|
|
if (RS_Entity::toggleSelected()) {
|
|
|
|
// Toggle all sub-entity's select:
|
|
/*for (RS_Entity* e=firstEntity(RS2::ResolveNone);
|
|
e;
|
|
e=nextEntity(RS2::ResolveNone)) {
|
|
e->toggleSelected();
|
|
}*/
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Selects all entities within the given area.
|
|
*
|
|
* @param select True to select, False to deselect the entities.
|
|
*/
|
|
void RS_EntityContainer::selectWindow(RS_Vector v1, RS_Vector v2,
|
|
bool select, bool cross) {
|
|
|
|
bool included;
|
|
|
|
for(auto e: entities){
|
|
|
|
included = false;
|
|
|
|
if (e->isVisible()) {
|
|
if (e->isInWindow(v1, v2)) {
|
|
//e->setSelected(select);
|
|
included = true;
|
|
} else if (cross) {
|
|
RS_EntityContainer l;
|
|
l.addRectangle(v1, v2);
|
|
RS_VectorSolutions sol;
|
|
|
|
if (e->isContainer()) {
|
|
RS_EntityContainer* ec = (RS_EntityContainer*)e;
|
|
for (RS_Entity* se=ec->firstEntity(RS2::ResolveAll);
|
|
se && included==false;
|
|
se=ec->nextEntity(RS2::ResolveAll)) {
|
|
|
|
if (se->rtti() == RS2::EntitySolid){
|
|
included = static_cast<RS_Solid*>(se)->isInCrossWindow(v1,v2);
|
|
} else {
|
|
for (auto line: l) {
|
|
sol = RS_Information::getIntersection(
|
|
se, line, true);
|
|
if (sol.hasValid()) {
|
|
included = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if (e->rtti() == RS2::EntitySolid){
|
|
included = static_cast<RS_Solid*>(e)->isInCrossWindow(v1,v2);
|
|
} else {
|
|
for (auto line: l) {
|
|
sol = RS_Information::getIntersection(e, line, true);
|
|
if (sol.hasValid()) {
|
|
included = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (included) {
|
|
e->setSelected(select);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Adds a entity to this container and updates the borders of this
|
|
* entity-container if autoUpdateBorders is true.
|
|
*/
|
|
void RS_EntityContainer::addEntity(RS_Entity* entity) {
|
|
/*
|
|
if (isDocument()) {
|
|
RS_LayerList* lst = getDocument()->getLayerList();
|
|
if (lst) {
|
|
RS_Layer* l = lst->getActive();
|
|
if (l && l->isLocked()) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
|
|
if (!entity) return;
|
|
|
|
if (entity->rtti()==RS2::EntityImage ||
|
|
entity->rtti()==RS2::EntityHatch) {
|
|
entities.prepend(entity);
|
|
} else {
|
|
entities.append(entity);
|
|
}
|
|
if (autoUpdateBorders) {
|
|
adjustBorders(entity);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Insert a entity at the end of entities list and updates the
|
|
* borders of this entity-container if autoUpdateBorders is true.
|
|
*/
|
|
void RS_EntityContainer::appendEntity(RS_Entity* entity){
|
|
if (!entity)
|
|
return;
|
|
entities.append(entity);
|
|
if (autoUpdateBorders)
|
|
adjustBorders(entity);
|
|
}
|
|
|
|
/**
|
|
* Insert a entity at the start of entities list and updates the
|
|
* borders of this entity-container if autoUpdateBorders is true.
|
|
*/
|
|
void RS_EntityContainer::prependEntity(RS_Entity* entity){
|
|
if (!entity) return;
|
|
entities.prepend(entity);
|
|
if (autoUpdateBorders)
|
|
adjustBorders(entity);
|
|
}
|
|
|
|
/**
|
|
* Move a entity list in this container at the given position,
|
|
* the borders of this entity-container if autoUpdateBorders is true.
|
|
*/
|
|
void RS_EntityContainer::moveEntity(int index, QList<RS_Entity *>& entList){
|
|
if (entList.isEmpty()) return;
|
|
int ci = 0; //current index for insert without invert order
|
|
bool ret, into = false;
|
|
RS_Entity *mid = nullptr;
|
|
if (index < 1) {
|
|
ci = 0;
|
|
} else if (index >= entities.size() ) {
|
|
ci = entities.size() - entList.size();
|
|
} else {
|
|
into = true;
|
|
mid = entities.at(index);
|
|
}
|
|
|
|
for (int i = 0; i < entList.size(); ++i) {
|
|
RS_Entity *e = entList.at(i);
|
|
ret = entities.removeOne(e);
|
|
//if e not exist in entities list remove from entList
|
|
if (!ret) {
|
|
entList.removeAt(i);
|
|
}
|
|
}
|
|
if (into) {
|
|
ci = entities.indexOf(mid);
|
|
}
|
|
|
|
for(auto e: entList){
|
|
entities.insert(ci++, e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Inserts a entity to this container at the given position and updates
|
|
* the borders of this entity-container if autoUpdateBorders is true.
|
|
*/
|
|
void RS_EntityContainer::insertEntity(int index, RS_Entity* entity) {
|
|
if (!entity) return;
|
|
|
|
entities.insert(index, entity);
|
|
|
|
if (autoUpdateBorders) {
|
|
adjustBorders(entity);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Replaces the entity at the given index with the given entity
|
|
* and updates the borders of this entity-container if autoUpdateBorders is true.
|
|
*/
|
|
/*RLZ unused function
|
|
void RS_EntityContainer::replaceEntity(int index, RS_Entity* entity) {
|
|
//RLZ TODO: is needed to delete the old entity? not documented in Q3PtrList
|
|
// investigate in qt3support code if reactivate this function.
|
|
if (!entity) {
|
|
return;
|
|
}
|
|
|
|
entities.replace(index, entity);
|
|
|
|
if (autoUpdateBorders) {
|
|
adjustBorders(entity);
|
|
}
|
|
}RLZ*/
|
|
|
|
|
|
|
|
/**
|
|
* Removes an entity from this container and updates the borders of
|
|
* this entity-container if autoUpdateBorders is true.
|
|
*/
|
|
bool RS_EntityContainer::removeEntity(RS_Entity* entity) {
|
|
//RLZ TODO: in Q3PtrList if 'entity' is nullptr remove the current item-> at.(entIdx)
|
|
// and sets 'entIdx' in next() or last() if 'entity' is the last item in the list.
|
|
// in LibreCAD is never called with nullptr
|
|
bool ret;
|
|
ret = entities.removeOne(entity);
|
|
|
|
if (autoDelete && ret) {
|
|
delete entity;
|
|
}
|
|
if (autoUpdateBorders) {
|
|
calculateBorders();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Erases all entities in this container and resets the borders..
|
|
*/
|
|
void RS_EntityContainer::clear() {
|
|
if (autoDelete) {
|
|
while (!entities.isEmpty())
|
|
delete entities.takeFirst();
|
|
} else
|
|
entities.clear();
|
|
resetBorders();
|
|
}
|
|
|
|
unsigned int RS_EntityContainer::count() const{
|
|
return entities.size();
|
|
}
|
|
|
|
|
|
/**
|
|
* Counts all entities (leaves of the tree).
|
|
*/
|
|
unsigned int RS_EntityContainer::countDeep() const{
|
|
unsigned int c=0;
|
|
for(auto t: *this){
|
|
c += t->countDeep();
|
|
}
|
|
return c;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Counts the selected entities in this container.
|
|
*/
|
|
unsigned int RS_EntityContainer::countSelected(bool deep, std::initializer_list<RS2::EntityType> const& types) {
|
|
unsigned int c=0;
|
|
std::set<RS2::EntityType> type = types;
|
|
|
|
for (RS_Entity* t: entities){
|
|
|
|
if (t->isSelected())
|
|
if (!types.size() || type.count(t->rtti()))
|
|
c++;
|
|
|
|
if (t->isContainer())
|
|
c += static_cast<RS_EntityContainer*>(t)->countSelected(deep);
|
|
}
|
|
|
|
return c;
|
|
}
|
|
|
|
/**
|
|
* Counts the selected entities in this container.
|
|
*/
|
|
double RS_EntityContainer::totalSelectedLength() {
|
|
double ret(0.0);
|
|
for (RS_Entity* e: entities){
|
|
|
|
if (e->isVisible() && e->isSelected()) {
|
|
double l = e->getLength();
|
|
if (l>=0.) {
|
|
ret += l;
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* Adjusts the borders of this graphic (max/min values)
|
|
*/
|
|
void RS_EntityContainer::adjustBorders(RS_Entity* entity) {
|
|
//RS_DEBUG->print("RS_EntityContainer::adjustBorders");
|
|
//resetBorders();
|
|
|
|
if (entity) {
|
|
// make sure a container is not empty (otherwise the border
|
|
// would get extended to 0/0):
|
|
if (!entity->isContainer() || entity->count()>0) {
|
|
minV = RS_Vector::minimum(entity->getMin(),minV);
|
|
maxV = RS_Vector::maximum(entity->getMax(),maxV);
|
|
}
|
|
|
|
// Notify parents. The border for the parent might
|
|
// also change TODO: Check for efficiency
|
|
//if(parent) {
|
|
//parent->adjustBorders(this);
|
|
//}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Recalculates the borders of this entity container.
|
|
*/
|
|
void RS_EntityContainer::calculateBorders() {
|
|
RS_DEBUG->print("RS_EntityContainer::calculateBorders");
|
|
|
|
resetBorders();
|
|
for (RS_Entity* e: entities){
|
|
|
|
RS_Layer* layer = e->getLayer();
|
|
|
|
// RS_DEBUG->print("RS_EntityContainer::calculateBorders: "
|
|
// "isVisible: %d", (int)e->isVisible());
|
|
|
|
if (e->isVisible() && !(layer && layer->isFrozen())) {
|
|
e->calculateBorders();
|
|
adjustBorders(e);
|
|
}
|
|
}
|
|
|
|
RS_DEBUG->print("RS_EntityContainer::calculateBorders: size 1: %f,%f",
|
|
getSize().x, getSize().y);
|
|
|
|
// needed for correcting corrupt data (PLANS.dxf)
|
|
if (minV.x>maxV.x || minV.x>RS_MAXDOUBLE || maxV.x>RS_MAXDOUBLE
|
|
|| minV.x<RS_MINDOUBLE || maxV.x<RS_MINDOUBLE) {
|
|
|
|
minV.x = 0.0;
|
|
maxV.x = 0.0;
|
|
}
|
|
if (minV.y>maxV.y || minV.y>RS_MAXDOUBLE || maxV.y>RS_MAXDOUBLE
|
|
|| minV.y<RS_MINDOUBLE || maxV.y<RS_MINDOUBLE) {
|
|
|
|
minV.y = 0.0;
|
|
maxV.y = 0.0;
|
|
}
|
|
|
|
RS_DEBUG->print("RS_EntityContainer::calculateBorders: size: %f,%f",
|
|
getSize().x, getSize().y);
|
|
|
|
//RS_DEBUG->print(" borders: %f/%f %f/%f", minV.x, minV.y, maxV.x, maxV.y);
|
|
|
|
//printf("borders: %lf/%lf %lf/%lf\n", minV.x, minV.y, maxV.x, maxV.y);
|
|
//RS_Entity::calculateBorders();
|
|
}
|
|
|
|
//namespace {
|
|
//bool isBoundingBoxValid(RS_Entity* e) {
|
|
// if (!(e->getMin() && e->getMax())) return false;
|
|
// if (!(e->getMin().x <= e->getMax().x)) return false;
|
|
// if (!(e->getMin().y <= e->getMax().y)) return false;
|
|
// if ((e->getMin() - e->getMax()).magnitude() > RS_MAXDOUBLE) return false;
|
|
// return true;
|
|
//}
|
|
//}
|
|
|
|
/**
|
|
* Recalculates the borders of this entity container including
|
|
* invisible entities.
|
|
*/
|
|
void RS_EntityContainer::forcedCalculateBorders() {
|
|
//RS_DEBUG->print("RS_EntityContainer::calculateBorders");
|
|
|
|
resetBorders();
|
|
for (RS_Entity* e: entities){
|
|
|
|
//RS_Layer* layer = e->getLayer();
|
|
|
|
if (e->isContainer()) {
|
|
((RS_EntityContainer*)e)->forcedCalculateBorders();
|
|
} else {
|
|
e->calculateBorders();
|
|
}
|
|
adjustBorders(e);
|
|
}
|
|
|
|
// needed for correcting corrupt data (PLANS.dxf)
|
|
if (minV.x>maxV.x || minV.x>RS_MAXDOUBLE || maxV.x>RS_MAXDOUBLE
|
|
|| minV.x<RS_MINDOUBLE || maxV.x<RS_MINDOUBLE) {
|
|
|
|
minV.x = 0.0;
|
|
maxV.x = 0.0;
|
|
}
|
|
if (minV.y>maxV.y || minV.y>RS_MAXDOUBLE || maxV.y>RS_MAXDOUBLE
|
|
|| minV.y<RS_MINDOUBLE || maxV.y<RS_MINDOUBLE) {
|
|
|
|
minV.y = 0.0;
|
|
maxV.y = 0.0;
|
|
}
|
|
|
|
//RS_DEBUG->print(" borders: %f/%f %f/%f", minV.x, minV.y, maxV.x, maxV.y);
|
|
|
|
//printf("borders: %lf/%lf %lf/%lf\n", minV.x, minV.y, maxV.x, maxV.y);
|
|
//RS_Entity::calculateBorders();
|
|
}
|
|
|
|
/**
|
|
* Updates all Dimension entities in this container and / or
|
|
* reposition their labels.
|
|
*
|
|
* @param autoText Automatically reposition the text label bool autoText=true
|
|
*/
|
|
void RS_EntityContainer::updateDimensions(bool autoText) {
|
|
|
|
RS_DEBUG->print("RS_EntityContainer::updateDimensions()");
|
|
|
|
//for (RS_Entity* e=firstEntity(RS2::ResolveNone);
|
|
// e;
|
|
// e=nextEntity(RS2::ResolveNone)) {
|
|
|
|
for (RS_Entity* e: entities){
|
|
if (RS_Information::isDimension(e->rtti())) {
|
|
// update and reposition label:
|
|
((RS_Dimension*)e)->updateDim(autoText);
|
|
} else if(e->rtti()==RS2::EntityDimLeader)
|
|
e->update();
|
|
else if (e->isContainer()) {
|
|
((RS_EntityContainer*)e)->updateDimensions(autoText);
|
|
}
|
|
}
|
|
|
|
RS_DEBUG->print("RS_EntityContainer::updateDimensions() OK");
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Updates all Insert entities in this container.
|
|
*/
|
|
void RS_EntityContainer::updateInserts() {
|
|
|
|
RS_DEBUG->print("RS_EntityContainer::updateInserts() ID/type: %d/%d", getId(), rtti());
|
|
|
|
for (RS_Entity* e: entities){
|
|
//// Only update our own inserts and not inserts of inserts
|
|
if (e->rtti()==RS2::EntityInsert /*&& e->getParent()==this*/) {
|
|
((RS_Insert*)e)->update();
|
|
RS_DEBUG->print("RS_EntityContainer::updateInserts: updated ID/type: %d/%d", e->getId(), e->rtti());
|
|
} else if (e->isContainer()) {
|
|
if (e->rtti()==RS2::EntityHatch) {
|
|
RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_EntityContainer::updateInserts: skip hatch ID/type: %d/%d", e->getId(), e->rtti());
|
|
} else {
|
|
RS_DEBUG->print("RS_EntityContainer::updateInserts: update container ID/type: %d/%d", e->getId(), e->rtti());
|
|
((RS_EntityContainer*)e)->updateInserts();
|
|
}
|
|
} else {
|
|
RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_EntityContainer::updateInserts: skip entity ID/type: %d/%d", e->getId(), e->rtti());
|
|
}
|
|
}
|
|
RS_DEBUG->print("RS_EntityContainer::updateInserts() ID/type: %d/%d OK", getId(), rtti());
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Renames all inserts with name 'oldName' to 'newName'. This is
|
|
* called after a block was rename to update the inserts.
|
|
*/
|
|
void RS_EntityContainer::renameInserts(const QString& oldName,
|
|
const QString& newName) {
|
|
RS_DEBUG->print("RS_EntityContainer::renameInserts()");
|
|
|
|
//for (RS_Entity* e=firstEntity(RS2::ResolveNone);
|
|
// e;
|
|
// e=nextEntity(RS2::ResolveNone)) {
|
|
|
|
for (RS_Entity* e: entities){
|
|
if (e->rtti()==RS2::EntityInsert) {
|
|
RS_Insert* i = ((RS_Insert*)e);
|
|
if (i->getName()==oldName) {
|
|
i->setName(newName);
|
|
}
|
|
}
|
|
if (e->isContainer()) {
|
|
((RS_EntityContainer*)e)->renameInserts(oldName, newName);
|
|
}
|
|
}
|
|
|
|
RS_DEBUG->print("RS_EntityContainer::renameInserts() OK");
|
|
|
|
}
|
|
|
|
/**
|
|
* Updates all Spline entities in this container.
|
|
*/
|
|
void RS_EntityContainer::updateSplines() {
|
|
|
|
RS_DEBUG->print("RS_EntityContainer::updateSplines()");
|
|
|
|
for (RS_Entity* e: entities){
|
|
//// Only update our own inserts and not inserts of inserts
|
|
if (e->rtti()==RS2::EntitySpline /*&& e->getParent()==this*/) {
|
|
((RS_Spline*)e)->update();
|
|
} else if (e->isContainer() && e->rtti()!=RS2::EntityHatch) {
|
|
((RS_EntityContainer*)e)->updateSplines();
|
|
}
|
|
}
|
|
|
|
RS_DEBUG->print("RS_EntityContainer::updateSplines() OK");
|
|
}
|
|
|
|
|
|
/**
|
|
* Updates the sub entities of this container.
|
|
*/
|
|
void RS_EntityContainer::update() {
|
|
for (RS_Entity* e: entities){
|
|
e->update();
|
|
}
|
|
}
|
|
|
|
void RS_EntityContainer::addRectangle(RS_Vector const& v0, RS_Vector const& v1)
|
|
{
|
|
addEntity(new RS_Line{this, v0, {v1.x, v0.y}});
|
|
addEntity(new RS_Line{this, {v1.x, v0.y}, v1});
|
|
addEntity(new RS_Line{this, v1, {v0.x, v1.y}});
|
|
addEntity(new RS_Line{this, {v0.x, v1.y}, v0});
|
|
}
|
|
|
|
/**
|
|
* Returns the first entity or nullptr if this graphic is empty.
|
|
* @param level
|
|
*/
|
|
RS_Entity* RS_EntityContainer::firstEntity(RS2::ResolveLevel level) {
|
|
RS_Entity* e = nullptr;
|
|
entIdx = -1;
|
|
switch (level) {
|
|
case RS2::ResolveNone:
|
|
if (!entities.isEmpty()) {
|
|
entIdx = 0;
|
|
return entities.first();
|
|
}
|
|
break;
|
|
|
|
case RS2::ResolveAllButInserts: {
|
|
subContainer=nullptr;
|
|
if (!entities.isEmpty()) {
|
|
entIdx = 0;
|
|
e = entities.first();
|
|
}
|
|
if (e && e->isContainer() && e->rtti()!=RS2::EntityInsert) {
|
|
subContainer = (RS_EntityContainer*)e;
|
|
e = ((RS_EntityContainer*)e)->firstEntity(level);
|
|
// empty container:
|
|
if (!e) {
|
|
subContainer = nullptr;
|
|
e = nextEntity(level);
|
|
}
|
|
}
|
|
return e;
|
|
}
|
|
break;
|
|
|
|
case RS2::ResolveAllButTextImage:
|
|
case RS2::ResolveAllButTexts: {
|
|
subContainer=nullptr;
|
|
if (!entities.isEmpty()) {
|
|
entIdx = 0;
|
|
e = entities.first();
|
|
}
|
|
if (e && e->isContainer() && e->rtti()!=RS2::EntityText && e->rtti()!=RS2::EntityMText) {
|
|
subContainer = (RS_EntityContainer*)e;
|
|
e = ((RS_EntityContainer*)e)->firstEntity(level);
|
|
// empty container:
|
|
if (!e) {
|
|
subContainer = nullptr;
|
|
e = nextEntity(level);
|
|
}
|
|
}
|
|
return e;
|
|
}
|
|
break;
|
|
|
|
case RS2::ResolveAll: {
|
|
subContainer=nullptr;
|
|
if (!entities.isEmpty()) {
|
|
entIdx = 0;
|
|
e = entities.first();
|
|
}
|
|
if (e && e->isContainer()) {
|
|
subContainer = (RS_EntityContainer*)e;
|
|
e = ((RS_EntityContainer*)e)->firstEntity(level);
|
|
// empty container:
|
|
if (!e) {
|
|
subContainer = nullptr;
|
|
e = nextEntity(level);
|
|
}
|
|
}
|
|
return e;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Returns the last entity or \p nullptr if this graphic is empty.
|
|
*
|
|
* @param level \li \p 0 Groups are not resolved
|
|
* \li \p 1 (default) only Groups are resolved
|
|
* \li \p 2 all Entity Containers are resolved
|
|
*/
|
|
RS_Entity* RS_EntityContainer::lastEntity(RS2::ResolveLevel level) {
|
|
RS_Entity* e = nullptr;
|
|
if(!entities.size()) return nullptr;
|
|
entIdx = entities.size()-1;
|
|
switch (level) {
|
|
case RS2::ResolveNone:
|
|
if (!entities.isEmpty())
|
|
return entities.last();
|
|
break;
|
|
|
|
case RS2::ResolveAllButInserts: {
|
|
if (!entities.isEmpty())
|
|
e = entities.last();
|
|
subContainer = nullptr;
|
|
if (e && e->isContainer() && e->rtti()!=RS2::EntityInsert) {
|
|
subContainer = (RS_EntityContainer*)e;
|
|
e = ((RS_EntityContainer*)e)->lastEntity(level);
|
|
}
|
|
return e;
|
|
}
|
|
break;
|
|
case RS2::ResolveAllButTextImage:
|
|
case RS2::ResolveAllButTexts: {
|
|
if (!entities.isEmpty())
|
|
e = entities.last();
|
|
subContainer = nullptr;
|
|
if (e && e->isContainer() && e->rtti()!=RS2::EntityText && e->rtti()!=RS2::EntityMText) {
|
|
subContainer = (RS_EntityContainer*)e;
|
|
e = ((RS_EntityContainer*)e)->lastEntity(level);
|
|
}
|
|
return e;
|
|
}
|
|
break;
|
|
|
|
case RS2::ResolveAll: {
|
|
if (!entities.isEmpty())
|
|
e = entities.last();
|
|
subContainer = nullptr;
|
|
if (e && e->isContainer()) {
|
|
subContainer = (RS_EntityContainer*)e;
|
|
e = ((RS_EntityContainer*)e)->lastEntity(level);
|
|
}
|
|
return e;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the next entity or container or \p nullptr if the last entity
|
|
* returned by \p next() was the last entity in the container.
|
|
*/
|
|
RS_Entity* RS_EntityContainer::nextEntity(RS2::ResolveLevel level) {
|
|
|
|
//set entIdx pointing in next entity and check if is out of range
|
|
++entIdx;
|
|
switch (level) {
|
|
case RS2::ResolveNone:
|
|
if ( entIdx < entities.size() )
|
|
return entities.at(entIdx);
|
|
break;
|
|
|
|
case RS2::ResolveAllButInserts: {
|
|
RS_Entity* e=nullptr;
|
|
if (subContainer) {
|
|
e = subContainer->nextEntity(level);
|
|
if (e) {
|
|
--entIdx; //return a sub-entity, index not advanced
|
|
return e;
|
|
} else {
|
|
if ( entIdx < entities.size() )
|
|
e = entities.at(entIdx);
|
|
}
|
|
} else {
|
|
if ( entIdx < entities.size() )
|
|
e = entities.at(entIdx);
|
|
}
|
|
if (e && e->isContainer() && e->rtti()!=RS2::EntityInsert) {
|
|
subContainer = (RS_EntityContainer*)e;
|
|
e = ((RS_EntityContainer*)e)->firstEntity(level);
|
|
// empty container:
|
|
if (!e) {
|
|
subContainer = nullptr;
|
|
e = nextEntity(level);
|
|
}
|
|
}
|
|
return e;
|
|
}
|
|
break;
|
|
|
|
case RS2::ResolveAllButTextImage:
|
|
case RS2::ResolveAllButTexts: {
|
|
RS_Entity* e=nullptr;
|
|
if (subContainer) {
|
|
e = subContainer->nextEntity(level);
|
|
if (e) {
|
|
--entIdx; //return a sub-entity, index not advanced
|
|
return e;
|
|
} else {
|
|
if ( entIdx < entities.size() )
|
|
e = entities.at(entIdx);
|
|
}
|
|
} else {
|
|
if ( entIdx < entities.size() )
|
|
e = entities.at(entIdx);
|
|
}
|
|
if (e && e->isContainer() && e->rtti()!=RS2::EntityText && e->rtti()!=RS2::EntityMText ) {
|
|
subContainer = (RS_EntityContainer*)e;
|
|
e = ((RS_EntityContainer*)e)->firstEntity(level);
|
|
// empty container:
|
|
if (!e) {
|
|
subContainer = nullptr;
|
|
e = nextEntity(level);
|
|
}
|
|
}
|
|
return e;
|
|
}
|
|
break;
|
|
|
|
case RS2::ResolveAll: {
|
|
RS_Entity* e=nullptr;
|
|
if (subContainer) {
|
|
e = subContainer->nextEntity(level);
|
|
if (e) {
|
|
--entIdx; //return a sub-entity, index not advanced
|
|
return e;
|
|
} else {
|
|
if ( entIdx < entities.size() )
|
|
e = entities.at(entIdx);
|
|
}
|
|
} else {
|
|
if ( entIdx < entities.size() )
|
|
e = entities.at(entIdx);
|
|
}
|
|
if (e && e->isContainer()) {
|
|
subContainer = (RS_EntityContainer*)e;
|
|
e = ((RS_EntityContainer*)e)->firstEntity(level);
|
|
// empty container:
|
|
if (!e) {
|
|
subContainer = nullptr;
|
|
e = nextEntity(level);
|
|
}
|
|
}
|
|
return e;
|
|
}
|
|
break;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Returns the prev entity or container or \p nullptr if the last entity
|
|
* returned by \p prev() was the first entity in the container.
|
|
*/
|
|
RS_Entity* RS_EntityContainer::prevEntity(RS2::ResolveLevel level) {
|
|
//set entIdx pointing in prev entity and check if is out of range
|
|
--entIdx;
|
|
switch (level) {
|
|
|
|
case RS2::ResolveNone:
|
|
if (entIdx >= 0)
|
|
return entities.at(entIdx);
|
|
break;
|
|
|
|
case RS2::ResolveAllButInserts: {
|
|
RS_Entity* e=nullptr;
|
|
if (subContainer) {
|
|
e = subContainer->prevEntity(level);
|
|
if (e) {
|
|
return e;
|
|
} else {
|
|
if (entIdx >= 0)
|
|
e = entities.at(entIdx);
|
|
}
|
|
} else {
|
|
if (entIdx >= 0)
|
|
e = entities.at(entIdx);
|
|
}
|
|
if (e && e->isContainer() && e->rtti()!=RS2::EntityInsert) {
|
|
subContainer = (RS_EntityContainer*)e;
|
|
e = ((RS_EntityContainer*)e)->lastEntity(level);
|
|
// empty container:
|
|
if (!e) {
|
|
subContainer = nullptr;
|
|
e = prevEntity(level);
|
|
}
|
|
}
|
|
return e;
|
|
}
|
|
|
|
case RS2::ResolveAllButTextImage:
|
|
case RS2::ResolveAllButTexts: {
|
|
RS_Entity* e=nullptr;
|
|
if (subContainer) {
|
|
e = subContainer->prevEntity(level);
|
|
if (e) {
|
|
return e;
|
|
} else {
|
|
if (entIdx >= 0)
|
|
e = entities.at(entIdx);
|
|
}
|
|
} else {
|
|
if (entIdx >= 0)
|
|
e = entities.at(entIdx);
|
|
}
|
|
if (e && e->isContainer() && e->rtti()!=RS2::EntityText && e->rtti()!=RS2::EntityMText) {
|
|
subContainer = (RS_EntityContainer*)e;
|
|
e = ((RS_EntityContainer*)e)->lastEntity(level);
|
|
// empty container:
|
|
if (!e) {
|
|
subContainer = nullptr;
|
|
e = prevEntity(level);
|
|
}
|
|
}
|
|
return e;
|
|
}
|
|
|
|
case RS2::ResolveAll: {
|
|
RS_Entity* e=nullptr;
|
|
if (subContainer) {
|
|
e = subContainer->prevEntity(level);
|
|
if (e) {
|
|
++entIdx; //return a sub-entity, index not advanced
|
|
return e;
|
|
} else {
|
|
if (entIdx >= 0)
|
|
e = entities.at(entIdx);
|
|
}
|
|
} else {
|
|
if (entIdx >= 0)
|
|
e = entities.at(entIdx);
|
|
}
|
|
if (e && e->isContainer()) {
|
|
subContainer = (RS_EntityContainer*)e;
|
|
e = ((RS_EntityContainer*)e)->lastEntity(level);
|
|
// empty container:
|
|
if (!e) {
|
|
subContainer = nullptr;
|
|
e = prevEntity(level);
|
|
}
|
|
}
|
|
return e;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @return Entity at the given index or nullptr if the index is out of range.
|
|
*/
|
|
RS_Entity* RS_EntityContainer::entityAt(int index) {
|
|
if (entities.size() > index && index >= 0)
|
|
return entities.at(index);
|
|
else
|
|
return nullptr;
|
|
}
|
|
|
|
void RS_EntityContainer::setEntityAt(int index,RS_Entity* en){
|
|
if(autoDelete && entities.at(index)) {
|
|
delete entities.at(index);
|
|
}
|
|
entities[index] = en;
|
|
}
|
|
|
|
/**
|
|
* @return Current index.
|
|
*/
|
|
/*RLZ unused
|
|
int RS_EntityContainer::entityAt() {
|
|
return entIdx;
|
|
} RLZ unused*/
|
|
|
|
|
|
/**
|
|
* Finds the given entity and makes it the current entity if found.
|
|
*/
|
|
int RS_EntityContainer::findEntity(RS_Entity const* const entity) {
|
|
entIdx = entities.indexOf(const_cast<RS_Entity*>(entity));
|
|
return entIdx;
|
|
}
|
|
|
|
/**
|
|
* @return The point which is closest to 'coord'
|
|
* (one of the vertices)
|
|
*/
|
|
RS_Vector RS_EntityContainer::getNearestEndpoint(const RS_Vector& coord,
|
|
double* dist )const {
|
|
|
|
double minDist = RS_MAXDOUBLE; // minimum measured distance
|
|
double curDist; // currently measured distance
|
|
RS_Vector closestPoint(false); // closest found endpoint
|
|
RS_Vector point; // endpoint found
|
|
|
|
for (RS_Entity* en: entities){
|
|
|
|
if (en->isVisible()
|
|
&& !en->getParent()->ignoredOnModification()
|
|
){//no end point for Insert, text, Dim
|
|
point = en->getNearestEndpoint(coord, &curDist);
|
|
if (point.valid && curDist<minDist) {
|
|
closestPoint = point;
|
|
minDist = curDist;
|
|
if (dist) {
|
|
*dist = minDist;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return closestPoint;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @return The point which is closest to 'coord'
|
|
* (one of the vertices)
|
|
*/
|
|
RS_Vector RS_EntityContainer::getNearestEndpoint(const RS_Vector& coord,
|
|
double* dist, RS_Entity** pEntity)const {
|
|
|
|
double minDist = RS_MAXDOUBLE; // minimum measured distance
|
|
double curDist; // currently measured distance
|
|
RS_Vector closestPoint(false); // closest found endpoint
|
|
RS_Vector point; // endpoint found
|
|
|
|
//QListIterator<RS_Entity> it = createIterator();
|
|
//RS_Entity* en;
|
|
//while ( (en = it.current()) ) {
|
|
// ++it;
|
|
|
|
unsigned i0=0;
|
|
for(auto en: entities){
|
|
if (!en->getParent()->ignoredOnModification() ){//no end point for Insert, text, Dim
|
|
// std::cout<<"find nearest for entity "<<i0<<std::endl;
|
|
point = en->getNearestEndpoint(coord, &curDist);
|
|
if (point.valid && curDist<minDist) {
|
|
closestPoint = point;
|
|
minDist = curDist;
|
|
if (dist) {
|
|
*dist = minDist;
|
|
}
|
|
if(pEntity){
|
|
*pEntity=en;
|
|
}
|
|
}
|
|
}
|
|
i0++;
|
|
}
|
|
|
|
// std::cout<<__FILE__<<" : "<<__func__<<" : line "<<__LINE__<<std::endl;
|
|
// std::cout<<"count()="<<const_cast<RS_EntityContainer*>(this)->count()<<"\tminDist= "<<minDist<<"\tclosestPoint="<<closestPoint;
|
|
// if(pEntity ) std::cout<<"\t*pEntity="<<*pEntity;
|
|
// std::cout<<std::endl;
|
|
return closestPoint;
|
|
}
|
|
|
|
|
|
|
|
RS_Vector RS_EntityContainer::getNearestPointOnEntity(const RS_Vector& coord,
|
|
bool onEntity, double* dist, RS_Entity** entity)const {
|
|
|
|
RS_Vector point(false);
|
|
|
|
RS_Entity* en = getNearestEntity(coord, dist, RS2::ResolveNone);
|
|
|
|
if (en && en->isVisible()
|
|
&& !en->getParent()->ignoredSnap()
|
|
){
|
|
point = en->getNearestPointOnEntity(coord, onEntity, dist, entity);
|
|
}
|
|
|
|
return point;
|
|
}
|
|
|
|
|
|
|
|
RS_Vector RS_EntityContainer::getNearestCenter(const RS_Vector& coord,
|
|
double* dist) const{
|
|
double minDist = RS_MAXDOUBLE; // minimum measured distance
|
|
double curDist = RS_MAXDOUBLE; // currently measured distance
|
|
RS_Vector closestPoint(false); // closest found endpoint
|
|
RS_Vector point; // endpoint found
|
|
|
|
for(auto en: entities){
|
|
|
|
if (en->isVisible()
|
|
&& !en->getParent()->ignoredSnap()
|
|
){//no center point for spline, text, Dim
|
|
point = en->getNearestCenter(coord, &curDist);
|
|
if (point.valid && curDist<minDist) {
|
|
closestPoint = point;
|
|
minDist = curDist;
|
|
}
|
|
}
|
|
}
|
|
if (dist) {
|
|
*dist = minDist;
|
|
}
|
|
|
|
return closestPoint;
|
|
}
|
|
|
|
/** @return the nearest of equidistant middle points of the line. */
|
|
|
|
RS_Vector RS_EntityContainer::getNearestMiddle(const RS_Vector& coord,
|
|
double* dist,
|
|
int middlePoints
|
|
) const{
|
|
double minDist = RS_MAXDOUBLE; // minimum measured distance
|
|
double curDist = RS_MAXDOUBLE; // currently measured distance
|
|
RS_Vector closestPoint(false); // closest found endpoint
|
|
RS_Vector point; // endpoint found
|
|
|
|
for(auto en: entities){
|
|
|
|
if (en->isVisible()
|
|
&& !en->getParent()->ignoredSnap()
|
|
){//no midle point for spline, text, Dim
|
|
point = en->getNearestMiddle(coord, &curDist, middlePoints);
|
|
if (point.valid && curDist<minDist) {
|
|
closestPoint = point;
|
|
minDist = curDist;
|
|
}
|
|
}
|
|
}
|
|
if (dist) {
|
|
*dist = minDist;
|
|
}
|
|
|
|
return closestPoint;
|
|
}
|
|
|
|
|
|
|
|
RS_Vector RS_EntityContainer::getNearestDist(double distance,
|
|
const RS_Vector& coord,
|
|
double* dist) const{
|
|
|
|
RS_Vector point(false);
|
|
RS_Entity* closestEntity;
|
|
|
|
closestEntity = getNearestEntity(coord, nullptr, RS2::ResolveNone);
|
|
|
|
if (closestEntity) {
|
|
point = closestEntity->getNearestDist(distance, coord, dist);
|
|
}
|
|
|
|
return point;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @return The intersection which is closest to 'coord'
|
|
*/
|
|
RS_Vector RS_EntityContainer::getNearestIntersection(const RS_Vector& coord,
|
|
double* dist) {
|
|
|
|
double minDist = RS_MAXDOUBLE; // minimum measured distance
|
|
double curDist = RS_MAXDOUBLE; // currently measured distance
|
|
RS_Vector closestPoint(false); // closest found endpoint
|
|
RS_Vector point; // endpoint found
|
|
RS_VectorSolutions sol;
|
|
RS_Entity* closestEntity;
|
|
|
|
closestEntity = getNearestEntity(coord, nullptr, RS2::ResolveAllButTextImage);
|
|
|
|
if (closestEntity) {
|
|
for (RS_Entity* en = firstEntity(RS2::ResolveAllButTextImage);
|
|
en;
|
|
en = nextEntity(RS2::ResolveAllButTextImage)) {
|
|
if (
|
|
!en->isVisible()
|
|
|| en->getParent()->ignoredSnap()
|
|
){
|
|
continue;
|
|
}
|
|
|
|
sol = RS_Information::getIntersection(closestEntity,
|
|
en,
|
|
true);
|
|
|
|
point=sol.getClosest(coord,&curDist,nullptr);
|
|
if(sol.getNumber()>0 && curDist<minDist){
|
|
closestPoint=point;
|
|
minDist=curDist;
|
|
}
|
|
|
|
}
|
|
}
|
|
if(dist && closestPoint.valid) {
|
|
*dist = minDist;
|
|
}
|
|
|
|
return closestPoint;
|
|
}
|
|
|
|
RS_Vector RS_EntityContainer::getNearestVirtualIntersection(const RS_Vector& coord,
|
|
const double& angle,
|
|
double* dist)
|
|
{
|
|
|
|
RS_Vector point; // endpoint found
|
|
RS_VectorSolutions sol;
|
|
RS_Entity* closestEntity;
|
|
RS_Vector second_coord;
|
|
|
|
second_coord.set(angle);
|
|
closestEntity = getNearestEntity(coord, nullptr, RS2::ResolveAllButTextImage);
|
|
|
|
if (closestEntity)
|
|
{
|
|
RS_ConstructionLineData data(coord,coord + second_coord);
|
|
auto line = new RS_ConstructionLine(this,data);
|
|
|
|
sol = RS_Information::getIntersection(closestEntity,line,true);
|
|
if (sol.getVector().empty())
|
|
{
|
|
return coord;
|
|
}
|
|
else
|
|
{
|
|
point=sol.getClosest(coord,dist,nullptr);
|
|
return point;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return coord;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
RS_Vector RS_EntityContainer::getNearestRef(const RS_Vector& coord,
|
|
double* dist) const{
|
|
|
|
double minDist = RS_MAXDOUBLE; // minimum measured distance
|
|
double curDist; // currently measured distance
|
|
RS_Vector closestPoint(false); // closest found endpoint
|
|
RS_Vector point; // endpoint found
|
|
|
|
for(auto en: entities){
|
|
|
|
if (en->isVisible()) {
|
|
point = en->getNearestRef(coord, &curDist);
|
|
if (point.valid && curDist<minDist) {
|
|
closestPoint = point;
|
|
minDist = curDist;
|
|
if (dist) {
|
|
*dist = minDist;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return closestPoint;
|
|
}
|
|
|
|
|
|
RS_Vector RS_EntityContainer::getNearestSelectedRef(const RS_Vector& coord,
|
|
double* dist) const{
|
|
|
|
double minDist = RS_MAXDOUBLE; // minimum measured distance
|
|
double curDist; // currently measured distance
|
|
RS_Vector closestPoint(false); // closest found endpoint
|
|
RS_Vector point; // endpoint found
|
|
|
|
for(auto en: entities){
|
|
|
|
if (en->isVisible() && en->isSelected() && !en->isParentSelected()) {
|
|
point = en->getNearestSelectedRef(coord, &curDist);
|
|
if (point.valid && curDist<minDist) {
|
|
closestPoint = point;
|
|
minDist = curDist;
|
|
if (dist) {
|
|
*dist = minDist;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return closestPoint;
|
|
}
|
|
|
|
|
|
double RS_EntityContainer::getDistanceToPoint(const RS_Vector& coord,
|
|
RS_Entity** entity,
|
|
RS2::ResolveLevel level,
|
|
double solidDist) const{
|
|
|
|
RS_DEBUG->print("RS_EntityContainer::getDistanceToPoint");
|
|
|
|
|
|
double minDist = RS_MAXDOUBLE; // minimum measured distance
|
|
double curDist; // currently measured distance
|
|
RS_Entity* closestEntity = nullptr; // closest entity found
|
|
RS_Entity* subEntity = nullptr;
|
|
|
|
for(auto e: entities){
|
|
|
|
if (e->isVisible()) {
|
|
RS_DEBUG->print("entity: getDistanceToPoint");
|
|
RS_DEBUG->print("entity: %d", e->rtti());
|
|
// bug#426, need to ignore Images to find nearest intersections
|
|
if(level==RS2::ResolveAllButTextImage && e->rtti()==RS2::EntityImage) continue;
|
|
curDist = e->getDistanceToPoint(coord, &subEntity, level, solidDist);
|
|
|
|
RS_DEBUG->print("entity: getDistanceToPoint: OK");
|
|
|
|
/*
|
|
* By using '<=', we will prefer the *last* item in the container if there are multiple
|
|
* entities that are *exactly* the same distance away, which should tend to be the one
|
|
* drawn most recently, and the one most likely to be visible (as it is also the order
|
|
* that the software draws the entities). This makes a difference when one entity is
|
|
* drawn directly over top of another, and it's reasonable to assume that humans will
|
|
* tend to want to reference entities that they see or have recently drawn as opposed
|
|
* to deeper more forgotten and invisible ones...
|
|
*/
|
|
if (curDist<=minDist)
|
|
{
|
|
switch(level){
|
|
case RS2::ResolveAll:
|
|
case RS2::ResolveAllButTextImage:
|
|
closestEntity = subEntity;
|
|
break;
|
|
default:
|
|
closestEntity = e;
|
|
}
|
|
minDist = curDist;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (entity) {
|
|
*entity = closestEntity;
|
|
}
|
|
RS_DEBUG->print("RS_EntityContainer::getDistanceToPoint: OK");
|
|
|
|
return minDist;
|
|
}
|
|
|
|
|
|
|
|
RS_Entity* RS_EntityContainer::getNearestEntity(const RS_Vector& coord,
|
|
double* dist,
|
|
RS2::ResolveLevel level) const{
|
|
|
|
RS_DEBUG->print("RS_EntityContainer::getNearestEntity");
|
|
|
|
RS_Entity* e = nullptr;
|
|
|
|
// distance for points inside solids:
|
|
double solidDist = RS_MAXDOUBLE;
|
|
if (dist) {
|
|
solidDist = *dist;
|
|
}
|
|
|
|
double d = getDistanceToPoint(coord, &e, level, solidDist);
|
|
|
|
if (e && e->isVisible()==false) {
|
|
e = nullptr;
|
|
}
|
|
|
|
// if d is negative, use the default distance (used for points inside solids)
|
|
if (dist) {
|
|
*dist = d;
|
|
}
|
|
RS_DEBUG->print("RS_EntityContainer::getNearestEntity: OK");
|
|
|
|
return e;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Rearranges the atomic entities in this container in a way that connected
|
|
* entities are stored in the right order and direction.
|
|
* Non-recoursive. Only affects atomic entities in this container.
|
|
*
|
|
* @retval true all contours were closed
|
|
* @retval false at least one contour is not closed
|
|
|
|
* to do: find closed contour by flood-fill
|
|
*/
|
|
bool RS_EntityContainer::optimizeContours() {
|
|
// std::cout<<"RS_EntityContainer::optimizeContours: begin"<<std::endl;
|
|
|
|
// DEBUG_HEADER
|
|
// std::cout<<"loop with count()="<<count()<<std::endl;
|
|
RS_DEBUG->print("RS_EntityContainer::optimizeContours");
|
|
|
|
RS_EntityContainer tmp;
|
|
tmp.setAutoUpdateBorders(false);
|
|
bool closed=true;
|
|
|
|
/** accept all full circles **/
|
|
QList<RS_Entity*> enList;
|
|
for(auto e1: entities){
|
|
if (!e1->isEdge() || e1->isContainer() ) {
|
|
enList<<e1;
|
|
continue;
|
|
}
|
|
|
|
//detect circles and whole ellipses
|
|
switch(e1->rtti()){
|
|
case RS2::EntityEllipse:
|
|
if(static_cast<RS_Ellipse*>(e1)->isEllipticArc())
|
|
continue;
|
|
// fall-through
|
|
case RS2::EntityCircle:
|
|
//directly detect circles, bug#3443277
|
|
tmp.addEntity(e1->clone());
|
|
enList<<e1;
|
|
// fall-through
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
}
|
|
// std::cout<<"RS_EntityContainer::optimizeContours: 1"<<std::endl;
|
|
|
|
/** remove unsupported entities */
|
|
for(RS_Entity* it: enList)
|
|
removeEntity(it);
|
|
|
|
/** check and form a closed contour **/
|
|
// std::cout<<"RS_EntityContainer::optimizeContours: 2"<<std::endl;
|
|
/** the first entity **/
|
|
RS_Entity* current(nullptr);
|
|
if(count()>0) {
|
|
current=entityAt(0)->clone();
|
|
tmp.addEntity(current);
|
|
removeEntity(entityAt(0));
|
|
}else {
|
|
if(tmp.count()==0) return false;
|
|
}
|
|
// std::cout<<"RS_EntityContainer::optimizeContours: 3"<<std::endl;
|
|
RS_Vector vpStart;
|
|
RS_Vector vpEnd;
|
|
if(current){
|
|
vpStart=current->getStartpoint();
|
|
vpEnd=current->getEndpoint();
|
|
}
|
|
RS_Entity* next(nullptr);
|
|
// std::cout<<"RS_EntityContainer::optimizeContours: 4"<<std::endl;
|
|
/** connect entities **/
|
|
const QString errMsg=QObject::tr("Hatch failed due to a gap=%1 between (%2, %3) and (%4, %5)");
|
|
|
|
while(count()>0) {
|
|
double dist(0.);
|
|
RS_Vector&& vpTmp=getNearestEndpoint(vpEnd,&dist,&next);
|
|
if(dist>1e-8) {
|
|
if(vpEnd.squaredTo(vpStart) < 1e-8) {
|
|
RS_Entity* e2=entityAt(0);
|
|
tmp.addEntity(e2->clone());
|
|
vpStart=e2->getStartpoint();
|
|
vpEnd=e2->getEndpoint();
|
|
removeEntity(e2);
|
|
continue;
|
|
}
|
|
else {
|
|
QG_DIALOGFACTORY->commandMessage(
|
|
errMsg.arg(dist).arg(vpTmp.x).arg(vpTmp.y).arg(vpEnd.x).arg(vpEnd.y)
|
|
);
|
|
RS_DEBUG->print(RS_Debug::D_ERROR, "RS_EntityContainer::optimizeContours: hatch failed due to a gap");
|
|
closed=false;
|
|
break;
|
|
}
|
|
}
|
|
if(!next) { //workaround if next is nullptr
|
|
// std::cout<<"RS_EntityContainer::optimizeContours: next is nullptr" <<std::endl;
|
|
RS_DEBUG->print("RS_EntityContainer::optimizeContours: next is nullptr");
|
|
// closed=false; //workaround if next is nullptr
|
|
break; //workaround if next is nullptr
|
|
} //workaround if next is nullptr
|
|
if(closed) {
|
|
next->setProcessed(true);
|
|
RS_Entity* eTmp = next->clone();
|
|
if(vpEnd.squaredTo(eTmp->getStartpoint())>vpEnd.squaredTo(eTmp->getEndpoint()))
|
|
eTmp->revertDirection();
|
|
vpEnd=eTmp->getEndpoint();
|
|
tmp.addEntity(eTmp);
|
|
removeEntity(next);
|
|
}
|
|
}
|
|
// DEBUG_HEADER
|
|
// if(vpEnd.valid && vpEnd.squaredTo(vpStart) > 1e-8) {
|
|
// QG_DIALOGFACTORY->commandMessage(errMsg.arg(vpEnd.distanceTo(vpStart))
|
|
// .arg(vpStart.x).arg(vpStart.y).arg(vpEnd.x).arg(vpEnd.y));
|
|
// RS_DEBUG->print("RS_EntityContainer::optimizeContours: hatch failed due to a gap");
|
|
// closed=false;
|
|
// }
|
|
// std::cout<<"RS_EntityContainer::optimizeContours: 5"<<std::endl;
|
|
|
|
|
|
// add new sorted entities:
|
|
for(auto en: tmp){
|
|
en->setProcessed(false);
|
|
addEntity(en->clone());
|
|
}
|
|
// std::cout<<"RS_EntityContainer::optimizeContours: 6"<<std::endl;
|
|
|
|
if(closed) {
|
|
RS_DEBUG->print("RS_EntityContainer::optimizeContours: OK");
|
|
}
|
|
else {
|
|
RS_DEBUG->print("RS_EntityContainer::optimizeContours: bad");
|
|
}
|
|
// std::cout<<"RS_EntityContainer::optimizeContours: end: count()="<<count()<<std::endl;
|
|
// std::cout<<"RS_EntityContainer::optimizeContours: closed="<<closed<<std::endl;
|
|
return closed;
|
|
}
|
|
|
|
|
|
bool RS_EntityContainer::hasEndpointsWithinWindow(const RS_Vector& v1, const RS_Vector& v2) {
|
|
for(auto e: entities){
|
|
if (e->hasEndpointsWithinWindow(v1, v2)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void RS_EntityContainer::move(const RS_Vector& offset) {
|
|
for(auto e: entities){
|
|
|
|
e->move(offset);
|
|
if (autoUpdateBorders) {
|
|
e->moveBorders(offset);
|
|
}
|
|
}
|
|
if (autoUpdateBorders) {
|
|
moveBorders(offset);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void RS_EntityContainer::rotate(const RS_Vector& center, const double& angle) {
|
|
RS_Vector angleVector(angle);
|
|
|
|
for(auto e: entities){
|
|
e->rotate(center, angleVector);
|
|
}
|
|
if (autoUpdateBorders) {
|
|
calculateBorders();
|
|
}
|
|
}
|
|
|
|
|
|
void RS_EntityContainer::rotate(const RS_Vector& center, const RS_Vector& angleVector) {
|
|
|
|
for(auto e: entities){
|
|
e->rotate(center, angleVector);
|
|
}
|
|
if (autoUpdateBorders) {
|
|
calculateBorders();
|
|
}
|
|
}
|
|
|
|
|
|
void RS_EntityContainer::scale(const RS_Vector& center, const RS_Vector& factor) {
|
|
if (fabs(factor.x)>RS_TOLERANCE && fabs(factor.y)>RS_TOLERANCE) {
|
|
|
|
for(auto e: entities){
|
|
e->scale(center, factor);
|
|
}
|
|
}
|
|
if (autoUpdateBorders) {
|
|
calculateBorders();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void RS_EntityContainer::mirror(const RS_Vector& axisPoint1, const RS_Vector& axisPoint2) {
|
|
if (axisPoint1.distanceTo(axisPoint2)>RS_TOLERANCE) {
|
|
|
|
for(auto e: entities){
|
|
e->mirror(axisPoint1, axisPoint2);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void RS_EntityContainer::stretch(const RS_Vector& firstCorner,
|
|
const RS_Vector& secondCorner,
|
|
const RS_Vector& offset) {
|
|
|
|
if (getMin().isInWindow(firstCorner, secondCorner) &&
|
|
getMax().isInWindow(firstCorner, secondCorner)) {
|
|
|
|
move(offset);
|
|
} else {
|
|
|
|
for(auto e: entities){
|
|
e->stretch(firstCorner, secondCorner, offset);
|
|
}
|
|
}
|
|
|
|
// some entitiycontainers might need an update (e.g. RS_Leader):
|
|
update();
|
|
}
|
|
|
|
|
|
|
|
void RS_EntityContainer::moveRef(const RS_Vector& ref,
|
|
const RS_Vector& offset) {
|
|
|
|
|
|
for(auto e: entities){
|
|
e->moveRef(ref, offset);
|
|
}
|
|
if (autoUpdateBorders) {
|
|
calculateBorders();
|
|
}
|
|
}
|
|
|
|
|
|
void RS_EntityContainer::moveSelectedRef(const RS_Vector& ref,
|
|
const RS_Vector& offset) {
|
|
|
|
|
|
for(auto e: entities){
|
|
e->moveSelectedRef(ref, offset);
|
|
}
|
|
if (autoUpdateBorders) {
|
|
calculateBorders();
|
|
}
|
|
}
|
|
|
|
void RS_EntityContainer::revertDirection() {
|
|
for(int k = 0; k < entities.size() / 2; ++k) {
|
|
entities.swap(k, entities.size() - 1 - k);
|
|
}
|
|
|
|
for(RS_Entity*const entity: entities) {
|
|
entity->revertDirection();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief RS_EntityContainer::draw() draw entities in order
|
|
* @param painter
|
|
* @param view
|
|
*/
|
|
void RS_EntityContainer::draw(RS_Painter* painter, RS_GraphicView* view,
|
|
double& /*patternOffset*/) {
|
|
|
|
if (!(painter && view)) {
|
|
return;
|
|
}
|
|
|
|
foreach (auto e, entities)
|
|
{
|
|
view->drawEntity(painter, e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief areaLineIntegral, line integral for contour area calculation by Green's Theorem
|
|
* Contour Area =\oint x dy
|
|
* @return line integral \oint x dy along the entity
|
|
*/
|
|
double RS_EntityContainer::areaLineIntegral() const
|
|
{
|
|
//TODO make sure all contour integral is by counter-clockwise
|
|
double contourArea=0.;
|
|
//closed area is always positive
|
|
double closedArea=0.;
|
|
|
|
// edges:
|
|
|
|
for(auto e: entities){
|
|
e->setLayer(getLayer());
|
|
switch (e->rtti()) {
|
|
case RS2::EntityLine:
|
|
case RS2::EntityArc:
|
|
contourArea += e->areaLineIntegral();
|
|
break;
|
|
case RS2::EntityCircle:
|
|
closedArea += e->areaLineIntegral();
|
|
break;
|
|
case RS2::EntityEllipse:
|
|
if(static_cast<RS_Ellipse*>(e)->isArc())
|
|
contourArea += e->areaLineIntegral();
|
|
else
|
|
closedArea += e->areaLineIntegral();
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return fabs(contourArea)+closedArea;
|
|
}
|
|
|
|
bool RS_EntityContainer::ignoredOnModification() const
|
|
{
|
|
switch(rtti()){
|
|
// commented out Insert to allow snapping on block, bug#523
|
|
// case RS2::EntityInsert: /**Insert*/
|
|
case RS2::EntitySpline:
|
|
case RS2::EntityMText: /**< Text 15*/
|
|
case RS2::EntityText: /**< Text 15*/
|
|
case RS2::EntityDimAligned: /**< Aligned Dimension */
|
|
case RS2::EntityDimLinear: /**< Linear Dimension */
|
|
case RS2::EntityDimRadial: /**< Radial Dimension */
|
|
case RS2::EntityDimDiametric: /**< Diametric Dimension */
|
|
case RS2::EntityDimAngular: /**< Angular Dimension */
|
|
case RS2::EntityDimLeader: /**< Leader Dimension */
|
|
case RS2::EntityHatch:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool RS_EntityContainer::ignoredSnap() const
|
|
{
|
|
// issue #652 , disable snap for hatch
|
|
// TODO, should snapping on hatch be a feature enabled by settings?
|
|
if (getParent() && getParent()->rtti() == RS2::EntityHatch)
|
|
return true;
|
|
return ignoredOnModification();
|
|
}
|
|
|
|
QList<RS_Entity *>::const_iterator RS_EntityContainer::begin() const
|
|
{
|
|
return entities.begin();
|
|
}
|
|
|
|
QList<RS_Entity *>::const_iterator RS_EntityContainer::end() const
|
|
{
|
|
return entities.end();
|
|
}
|
|
|
|
QList<RS_Entity *>::iterator RS_EntityContainer::begin()
|
|
{
|
|
return entities.begin();
|
|
}
|
|
|
|
QList<RS_Entity *>::iterator RS_EntityContainer::end()
|
|
{
|
|
return entities.end();
|
|
}
|
|
|
|
/**
|
|
* Dumps the entities to stdout.
|
|
*/
|
|
std::ostream& operator << (std::ostream& os, RS_EntityContainer& ec) {
|
|
|
|
static int indent = 0;
|
|
|
|
char* tab = new char[indent*2+1];
|
|
for(int i=0; i<indent*2; ++i) {
|
|
tab[i] = ' ';
|
|
}
|
|
tab[indent*2] = '\0';
|
|
|
|
++indent;
|
|
|
|
unsigned long int id = ec.getId();
|
|
|
|
os << tab << "EntityContainer[" << id << "]: \n";
|
|
os << tab << "Borders[" << id << "]: "
|
|
<< ec.minV << " - " << ec.maxV << "\n";
|
|
//os << tab << "Unit[" << id << "]: "
|
|
//<< RS_Units::unit2string (ec.unit) << "\n";
|
|
if (ec.getLayer()) {
|
|
os << tab << "Layer[" << id << "]: "
|
|
<< ec.getLayer()->getName().toLatin1().data() << "\n";
|
|
} else {
|
|
os << tab << "Layer[" << id << "]: <nullptr>\n";
|
|
}
|
|
//os << ec.layerList << "\n";
|
|
|
|
os << tab << " Flags[" << id << "]: "
|
|
<< (ec.getFlag(RS2::FlagVisible) ? "RS2::FlagVisible" : "");
|
|
os << (ec.getFlag(RS2::FlagUndone) ? " RS2::FlagUndone" : "");
|
|
os << (ec.getFlag(RS2::FlagSelected) ? " RS2::FlagSelected" : "");
|
|
os << "\n";
|
|
|
|
|
|
os << tab << "Entities[" << id << "]: \n";
|
|
for(auto t: ec){
|
|
|
|
|
|
switch (t->rtti()) {
|
|
case RS2::EntityInsert:
|
|
os << tab << *((RS_Insert*)t);
|
|
os << tab << *((RS_Entity*)t);
|
|
os << tab << *((RS_EntityContainer*)t);
|
|
break;
|
|
default:
|
|
if (t->isContainer()) {
|
|
os << tab << *((RS_EntityContainer*)t);
|
|
} else {
|
|
os << tab << *t;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
os << tab << "\n\n";
|
|
--indent;
|
|
|
|
delete[] tab;
|
|
return os;
|
|
}
|
|
|
|
|
|
RS_Entity* RS_EntityContainer::first() const
|
|
{
|
|
return entities.first();
|
|
}
|
|
|
|
RS_Entity* RS_EntityContainer::last() const
|
|
{
|
|
return entities.last();
|
|
}
|
|
|
|
const QList<RS_Entity*>& RS_EntityContainer::getEntityList()
|
|
{
|
|
return entities;
|
|
}
|