/**************************************************************************** ** ** 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. ** Copyright LibreCAD librecad.org ** ** ** 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! ** **********************************************************************/ // File: rs_modification.cpp #include #include "lc_containertraverser.h" #include "lc_graphicviewport.h" #include "lc_linemath.h" #include "lc_splinepoints.h" #include "lc_undosection.h" #include "rs_arc.h" #include "rs_atomicentity.h" #include "rs_block.h" #include "rs_circle.h" #include "rs_clipboard.h" #include "rs_creation.h" #include "rs_debug.h" #include "rs_ellipse.h" #include "rs_graphic.h" #include "rs_information.h" #include "rs_insert.h" #include "rs_layer.h" #include "rs_line.h" #include "rs_math.h" #include "rs_modification.h" #include "rs_mtext.h" #include "rs_polyline.h" #include "rs_settings.h" #include "rs_text.h" #include "rs_units.h" #ifdef EMU_C99 #include "emu_c99.h" #endif class LC_SplinePoints; namespace { // fixme - hm, is it actually needed to mix the logic of modification and ui/undo? /** * @brief getPasteScale - find scaling factor for pasting * @param const RS_PasteData& data - RS_PasteData * @param RS_Graphic *& source - source graphic. If source is nullptr, the graphic on the clipboard is used instead * @param const RS_Graphic& graphic - the target graphic * @return */ RS_Vector getPasteScale(const RS_PasteData &data, RS_Graphic *&source, const RS_Graphic &graphic){ // adjust scaling factor for units conversion in case of clipboard paste double factor = (RS_TOLERANCE < std::abs(data.factor)) ? data.factor : 1.0; // select source for paste if (source == nullptr){ RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::paste: add graphic source from clipboard"); source = RS_CLIPBOARD->getGraphic(); // graphics from the clipboard need to be scaled. From the part lib not: RS2::Unit sourceUnit = source->getUnit(); RS2::Unit targetUnit = graphic.getUnit(); factor = RS_Units::convert(factor, sourceUnit, targetUnit); } RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::paste: pasting scale factor: %g", factor); // scale factor as vector return {factor, factor}; } /** * @brief addNewBlock() - create a new block * @param name - name of the new block to create * @param graphic - the target graphic * @return RS_Block - the block created */ RS_Block *addNewBlock(const QString &name, RS_Graphic &graphic){ RS_BlockData db = RS_BlockData(name, {0.0, 0.0}, false); auto *b = new RS_Block(&graphic, db); b->reparent(&graphic); graphic.addBlock(b); return b; } RS_VectorSolutions findIntersection(const RS_Entity &trimEntity, const RS_Entity &limitEntity, double tolerance = 1e-4){ RS_VectorSolutions sol; if (limitEntity.isAtomic()){ // intersection(s) of the two entities: return RS_Information::getIntersection(&trimEntity, &limitEntity, false); } if (limitEntity.isContainer()){ auto ec = static_cast(&limitEntity); for(RS_Entity* e: lc::LC_ContainerTraverser{*ec, RS2::ResolveAll}.entities()) { RS_VectorSolutions s2 = RS_Information::getIntersection(&trimEntity, e, false); std::copy_if(s2.begin(), s2.end(), std::back_inserter(sol), [e, tolerance](const RS_Vector &vp){ return vp.valid && e->isPointOnEntity(vp, tolerance); }); } } return sol; } RS_Arc *trimCircle(RS_Circle& circle, const RS_Vector &trimCoord, const RS_VectorSolutions &sol){ double aStart = 0.; double aEnd = 2. * M_PI; switch (sol.size()) { case 0: break; case 1: aStart = circle.getCenter().angleTo(sol.at(0)); aEnd = aStart + 2. * M_PI; break; default: case 2: //trim according to intersections const RS_Vector& center0 = circle.getCenter(); std::vector angles { {center0.angleTo(sol[0]), center0.angleTo(sol[1])}}; const double a0 = center0.angleTo(trimCoord); aStart = angles.front(); aEnd = angles.back(); if (!RS_Math::isAngleBetween(a0, aStart, aEnd, false)) { std::swap(aStart, aEnd); } break; } RS_ArcData arcData(circle.getCenter(), circle.getRadius(), aStart, aEnd, false); return new RS_Arc(circle.getParent(), arcData); } /** * @brief getIdFlagString create a string by the entity and ID and type ID. * @param entity - entity, could be nullptr * @return std::string - "ID/typeID", or an empty string, if the input entity is nullptr */ std::string getIdFlagString(RS_Entity *entity){ if (entity == nullptr) return {}; return std::to_string(entity->getId()) + "/" + std::to_string(entity->rtti()); } // Support fillet trimming for whole ellipses RS_AtomicEntity *trimEllipseForRound(RS_AtomicEntity *entity, const RS_Arc &arcFillet){ if (entity == nullptr) { return entity; } if (entity->rtti() != RS2::EntityEllipse) { return entity; } auto* ellipse = static_cast(entity); if (ellipse->isEllipticArc()) { return entity; } RS_Vector tangent = entity->getNearestPointOnEntity(arcFillet.getCenter(), false); RS_Line line{nullptr, {arcFillet.getCenter(), tangent}}; RS_Vector middle = arcFillet.getMiddlePoint(); RS_Vector opposite = arcFillet.getCenter() + (arcFillet.getCenter() - middle).normalized() * ellipse->getMinorRadius() * 0.01; RS_Vector trimCoord = ellipse->getNearestPointOnEntity(opposite, false); RS_VectorSolutions sol = RS_Information::getIntersection(entity, &line, false); ellipse->prepareTrim(trimCoord, sol); return entity; } // A quick fix for rounding on circles RS_AtomicEntity *trimCircleForRound(RS_AtomicEntity *entity, const RS_Arc &arcFillet){ if (entity == nullptr) { return entity; } if (entity->rtti() == RS2::EntityEllipse) { return trimEllipseForRound(entity, arcFillet); } if (entity->rtti() != RS2::EntityCircle) { return entity; } RS_Line line{nullptr, {arcFillet.getCenter(), entity->getCenter()}}; RS_Vector middle = arcFillet.getMiddlePoint(); // prefer acute angle for fillet // Use a trimCoord at the opposite side of the arc wrt to the RS_Vector opposite = arcFillet.getCenter() + (arcFillet.getCenter() - middle).normalized() * entity->getRadius() * 0.01; RS_Vector trimCoord = entity->getNearestPointOnEntity(opposite, true); RS_VectorSolutions sol = RS_Information::getIntersection(entity, &line, false); RS_Arc *arc = trimCircle(*static_cast(entity), trimCoord, sol); delete entity; return arc; } inline bool isOneOfPoints(const RS_Vector& candidate, const RS_Vector& point1, const RS_Vector& point2) { bool result = point1.distanceTo(candidate) < RS_TOLERANCE || point2.distanceTo(candidate) < RS_TOLERANCE; return result; } /** * @brief getUniqueBlockName - Generates unique block name like "PASTE_0" * @param graphic - Target graphic * @param baseName - Base ("PASTE" default) * @return Unique name */ QString getUniqueBlockName(RS_Graphic* graphic, const QString& baseName = QStringLiteral("PASTE")) { if (!graphic) return baseName; RS_BlockList* bl = graphic->getBlockList(); if (!bl) return baseName; int i = 0; QString candidate; do { candidate = QString("%1_%2").arg(baseName).arg(i++); } while (bl->find(candidate) != nullptr); return candidate; } } // namespace RS_PasteData::RS_PasteData(RS_Vector _insertionPoint, double _factor, double _angle, bool _asInsert, const QString& _blockName): insertionPoint(_insertionPoint) ,factor(_factor) ,angle(_angle) ,asInsert(_asInsert) ,blockName(_blockName) { } /** * Default constructor. * * @param container The container to which we will add * entities. Usually that's an RS_Graphic entity but * it can also be a polyline, text, ... * @param graphicView Pointer to graphic view or nullptr if you don't want the * any views to be updated. * @param handleUndo true: Handle undo functionalitiy. */ RS_Modification::RS_Modification(RS_EntityContainer& container, LC_GraphicViewport* vp, bool handleUndo) { this->container = &container; this->handleUndo = handleUndo; viewport = vp; graphic = container.getGraphic(); document = container.getDocument(); } /** * Deletes all selected entities. */ void RS_Modification::remove() { RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::remove"); if (container == nullptr) { RS_DEBUG->print(RS_Debug::D_ERROR, "RS_Modification::remove: no valid container"); return; } std::vector selectedEntities; container->collectSelected(selectedEntities, false); if (!selectedEntities.empty()) { remove(selectedEntities); } else{ RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::remove: no valid container is selected"); } } void RS_Modification::remove(const std::vector &entitiesList){ LC_UndoSection undo(document,viewport); for(auto e: entitiesList) { e->setSelected(false); e->changeUndoState(); undo.addUndoable(e); } RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::remove: OK"); } /** * Revert direction of selected entities. */ void RS_Modification::revertDirection(bool keepSelected) { RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::revertDirection"); if (!container) { RS_DEBUG->print(RS_Debug::D_ERROR, "RS_Modification::revertDirection: no valid container"); return; } std::vector selectedEntities; container->collectSelected(selectedEntities, false); if (!selectedEntities.empty()) { revertDirection(selectedEntities, keepSelected); } } void RS_Modification::revertDirection(const std::vector &entitiesList, [[maybe_unused]]bool keepSelected){ std::vector clonesList; for(auto e: entitiesList) { RS_Entity* ec = e->clone(); ec->revertDirection(); clonesList.push_back(ec); } deleteOriginalAndAddNewEntities(clonesList, entitiesList, false, true); clonesList.clear(); RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::revertDirection: OK"); } /** * Changes the attributes of all selected */ bool RS_Modification::changeAttributes(RS_AttributesData& data, const bool keepSelected){ return changeAttributes(data, container, keepSelected); } bool RS_Modification::changeAttributes( RS_AttributesData& data, RS_EntityContainer* cont, const bool keepSelected) { if (cont == nullptr) { return false; } std::vector selectedEntities; container->collectSelected(selectedEntities, false); return changeAttributes(data, selectedEntities, cont, keepSelected); } bool RS_Modification::changeAttributes(RS_AttributesData& data, const std::vector &entitiesList, RS_EntityContainer *cont, bool keepSelected){ LC_UndoSection undo(document, viewport); QList clones; QSet blocks; for (auto en: entitiesList) { if (data.applyBlockDeep && en->rtti() == RS2::EntityInsert) { RS_Block *bl = dynamic_cast(en)->getBlockForInsert(); blocks << bl; } RS_Entity *cl = en->clone(); RS_Pen pen = cl->getPen(false); if (data.changeLayer) { cl->setLayer(data.layer); } if (data.changeColor) { pen.setColor(data.pen.getColor()); } if (data.changeLineType) { pen.setLineType(data.pen.getLineType()); } if (data.changeWidth) { pen.setWidth(data.pen.getWidth()); } cl->setPen(pen); en->setSelected(false); cl->setSelected(keepSelected); clones << cl; // if (graphic != nullptr) { // en->setUndoState(true); // graphic->addUndoable(en); // undo.addUndoable(en); // } en->setUndoState(true); undo.addUndoable(en); } for (const auto& block: std::as_const(blocks)) { for (auto en: *block) { if (en != nullptr) en->setSelected(true); } changeAttributes(data, block, keepSelected); } for (const auto& cl: std::as_const(clones)) { RS2::EntityType rtti = cl->rtti(); if (RS2::isDimensionalEntity(rtti)) { cl->update(); } cont->addEntity(cl); // if (graphic != nullptr) { // graphic->addUndoable(cl); // } undo.addUndoable(cl); } if (graphic != nullptr) { graphic->updateInserts(); } cont->calculateBorders(); viewport->notifyChanged(); return true; } /** * Copies all selected entities from the given container to the clipboard. * Layers and blocks that are needed are also copied if the container is * or is part of an RS_Graphic. * * @param container The entity container. * @param ref Reference point. The entities will be moved by -ref. * @param cut true: cut instead of copying, false: copy */ void RS_Modification::copy(const RS_Vector& ref, const bool cut) { RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::copy"); if (!container) { RS_DEBUG->print(RS_Debug::D_ERROR, "RS_Modification::copy: no valid container"); return; } RS_CLIPBOARD->clear(); if (graphic) { RS_CLIPBOARD->getGraphic()->setUnit(graphic->getUnit()); } else { RS_CLIPBOARD->getGraphic()->setUnit(RS2::None); } // start undo cycle for the container if we're cutting LC_UndoSection undo( document, viewport, cut && handleUndo); bool selectedEntityFound{false}; std::vector selected; collectSelectedEntities(selected); selectedEntityFound = !selected.empty(); if (selectedEntityFound) { RS_Vector refPoint; if (ref.valid) { refPoint = ref; } else { // no ref-point set, determine center of selection RS_BoundData bound = getBoundingRect(selected); refPoint = bound.getCenter(); } for (auto e: selected) { copyEntity(e, refPoint, cut); } selected.clear(); RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::copy: OK"); } else{ RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::copy: no valid container is selected"); } } void RS_Modification::collectSelectedEntities(std::vector &selected) const{ for (auto e: *container) { if (e != nullptr && e->isSelected()) { selected.push_back(e); } } } RS_BoundData RS_Modification::getBoundingRect(std::vector &selected) { RS_Vector min = RS_Vector(10e10, 10e10,0); RS_Vector max = RS_Vector(-10e10, -10e10,0); for (auto e: selected) { const RS_Vector &entityMin = e->getMin(); const RS_Vector &entityMax = e->getMax(); min.x = std::min(min.x, entityMin.x); min.y = std::min(min.y, entityMin.y); max.x = std::max(max.x, entityMax.x); max.y = std::max(max.y, entityMax.y); } RS_BoundData result(min, max); return result; } /** * Copies the given entity from the given container to the clipboard. * Layers and blocks that are needed are also copied if the container is * or is part of an RS_Graphic. * * @param e The entity. * @param ref Reference point. The entities will be moved by -ref. * @param cut true: cut instead of copying, false: copy */ void RS_Modification::copyEntity(RS_Entity* e, const RS_Vector& ref, const bool cut) { RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::copyEntity"); if (!e || !e->isSelected()) { RS_DEBUG->print(RS_Debug::D_ERROR, "RS_Modification::copyEntity: no entity is selected"); return; } // Ensure the insert is updated before copying to populate the container with transformed entities if (e->rtti() == RS2::EntityInsert) { dynamic_cast(e)->update(); } // add entity to clipboard: RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::copyEntity: to clipboard: %s", getIdFlagString(e).c_str()); RS_Entity* c = e->clone(); c->move(-ref); // issue #1616: copy&paste a rotated block results in a double rotated block // At this point the copied block entities are already rotated, but at // pasting, RS_Insert::update() would still rotate the entities again and // cause double rotation. bool isBlock = c->rtti() == RS2::EntityInsert; double angle = isBlock ? dynamic_cast(c)->getAngle() : 0.; // issue #1616: A quick fix: rotate back all block entities in the clipboard back by the // rotation angle before pasting if (isBlock && std::abs(std::remainder(angle, 2. * M_PI)) > RS_TOLERANCE_ANGLE) { auto* insert = dynamic_cast(c); //insert->rotate(insert->getData().insertionPoint, - angle); insert->setAngle(0.); } RS_CLIPBOARD->addEntity(c); copyLayers(e); copyBlocks(e); // set layer to the layer clone: auto originalLayer = e->getLayer(); // layer could be null if copy is performed in font file, where block is open. LibreCAD#2110 if (originalLayer != nullptr) { c->setLayer(e->getLayer()->getName()); } if (cut) { LC_UndoSection undo(document, viewport); RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::copyEntity: cut ID/flag: %s", getIdFlagString(e).c_str()); e->changeUndoState(); undo.addUndoable(e); e->setSelected(false); } else { RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::copyEntity: delete in view ID/flag: %s", getIdFlagString(e).c_str()); // delete entity in graphic view: e->setSelected(false); } viewport->notifyChanged(); RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::copyEntity: OK"); } /** * Copies all layers of the given entity to the clipboard. */ void RS_Modification::copyLayers(RS_Entity* e) { RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::copyLayers"); if (!e) { RS_DEBUG->print(RS_Debug::D_ERROR, "RS_Modification::copyLayers: no entity is selected"); return; } // add layer(s) of the entity insert can also be into any layer RS_Layer* l = e->getLayer(); if (!l) { RS_DEBUG->print(RS_Debug::D_ERROR, "RS_Modification::copyLayers: no valid layer found"); return; } if (!RS_CLIPBOARD->hasLayer(l->getName())) { RS_CLIPBOARD->addLayer(l->clone()); } // special handling of inserts: if (e->rtti()==RS2::EntityInsert) { // insert: add layer(s) of subentities: RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::copyLayers: copy insert entity ID/flag layers: %s", getIdFlagString(e).c_str()); RS_Block* b = ((RS_Insert*)e)->getBlockForInsert(); if (!b) { RS_DEBUG->print(RS_Debug::D_ERROR, "RS_Modification::copyLayers: could not find block for insert entity"); return; } for(auto e2: *b) { copyLayers(e2); } } else { RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::copyLayers: skip noninsert entity"); } RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::copyLayers: OK"); } /** * Copies all blocks of the given entity to the clipboard. */ void RS_Modification::copyBlocks(RS_Entity* e) { RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::copyBlocks"); if (!e) { RS_DEBUG->print(RS_Debug::D_ERROR, "RS_Modification::copyBlocks: no entity to process"); return; } // add block of the entity only if it's an insert if (e->rtti()!=RS2::EntityInsert) { RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::copyBlocks: skip non-insert entity"); return; } RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::copyBlocks: get insert entity ID/flag block: %s", getIdFlagString(e).c_str()); RS_Block* b = ((RS_Insert*)e)->getBlockForInsert(); if (!b) { RS_DEBUG->print(RS_Debug::D_ERROR, "RS_Modification::copyBlocks: could not find block for insert entity"); return; } // add block of an insert QString bn = b->getName(); if (!RS_CLIPBOARD->hasBlock(bn)) { RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::copyBlocks: add block name: %s", bn.toLatin1().data()); RS_CLIPBOARD->addBlock((RS_Block*)b->clone()); } //find insert into insert for(auto e2: *b) { //call copyBlocks only if entity are insert if (e2->rtti()==RS2::EntityInsert) { RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::copyBlocks: process insert-into-insert blocks for %s", getIdFlagString(e).c_str()); copyBlocks(e2); } } RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::copyBlocks: OK"); } /** * Pastes all entities from the clipboard into the container. * Layers and blocks that are needed are also copied if the container is * or is part of an RS_Graphic. * * @param data Paste data. * @param source The source from where to paste. nullptr means the source * is the clipboard. */ void RS_Modification::paste(const RS_PasteData& data, RS_Graphic* source) { RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::paste:"); if (container == nullptr || container->isLocked() || !container->isVisible()) { RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::paste: invalid container"); return; } RS_Graphic* src = (source != nullptr) ? source : RS_CLIPBOARD->getGraphic(); if (src == nullptr) { RS_DEBUG->print(RS_Debug::D_ERROR, "RS_Modification::paste: no source"); return; } // Scale (units) RS_Graphic* srcRef = source; RS_Vector scaleV = getPasteScale(data, srcRef, *graphic); src = srcRef; src->calculateBorders(); RS_Vector center = (src->getMin() + src->getMax()) * 0.5; RS_Vector offset = data.insertionPoint - center; LC_UndoSection undo(document, viewport, handleUndo); if (data.asInsert) { // === BLOCK: Bake β†’ angle=0 === QString bname = data.blockName.isEmpty() ? getUniqueBlockName(graphic) : data.blockName; RS_Block* block = addNewBlock(bname, *graphic); auto entities = lc::LC_ContainerTraverser{*src, RS2::ResolveAll}.entities(); for (RS_Entity* e : entities) { if (e == nullptr || e->isUndone()) continue; RS_Entity* clone = e->clone(); // Bake: centerβ†’0 β†’ scale/rot β†’ block@0 clone->move(-center); clone->scale(RS_Vector{}, scaleV); clone->rotate(RS_Vector{}, data.angle); block->addByBlockEntity(clone); // **ByBlock** πŸ‘Œ } // Insert (baked, **angle=0**) RS_InsertData idata(bname, data.insertionPoint, {1., 1.}, 0.0, 1, 1, {}); RS_Insert* insert = new RS_Insert(container, idata); insert->reparent(container); container->addEntity(insert); // Props (inherit) RS_Entity* first = src->firstEntity(RS2::ResolveNone); if (first) { insert->setLayer(first->getLayer()); insert->setPen(first->getPen(true)); } insert->setSelected(true); insert->update(); undo.addUndoable(block); undo.addUndoable(insert); RS_DEBUG->print(RS_Debug::D_DEBUGGING, "paste: block '%s'", bname.toLatin1().data()); } else { // === EXPLODED === std::vector newEnts; auto entities = lc::LC_ContainerTraverser{*src, RS2::ResolveAll}.entities(); for (RS_Entity* e : entities) { if (e == nullptr || e->isUndone()) continue; RS_Entity* clone = e->clone(); // **Symmetric**: scale/rot **around center** β†’ move clone->scale(center, scaleV); clone->rotate(center, data.angle); clone->move(offset + center); clone->setSelected(true); newEnts.push_back(clone); } // Add (match explode/moveRef) for (RS_Entity* ne : newEnts) { ne->reparent(container); container->addEntity(ne); undo.addUndoable(ne); } } graphic->updateInserts(); viewport->notifyChanged(); RS_DEBUG->print(RS_Debug::D_DEBUGGING, "paste: OK βœ…"); } /** * Create layers in destination graphic corresponding to entity to be copied * **/ bool RS_Modification::pasteLayers(RS_Graphic* source) { RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::pasteLayers"); if (!source) { RS_DEBUG->print(RS_Debug::D_ERROR, "RS_Modification::pasteLayers: no valid graphic found"); return false; } RS_LayerList* lrs=source->getLayerList(); for(RS_Layer* l: *lrs) { if(!l) { RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::pasteLayers: nullptr layer in source"); continue; } // add layers if absent QString ln = l->getName(); if (!graphic->findLayer(ln)) { graphic->addLayer(l->clone()); RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::pasteLayers: layer added: %s", ln.toLatin1().data()); } } RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::pasteLayers: OK"); return true; } /** * Create inserts and blocks in destination graphic corresponding to entity to be copied * **/ bool RS_Modification::pasteContainer(RS_Entity* entity, RS_EntityContainer* containerToPaste, QHashblocksDict, RS_Vector insertionPoint) { RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::pasteInsert"); auto* insert = dynamic_cast(entity); if (insert == nullptr) { RS_DEBUG->print(RS_Debug::D_ERROR, "RS_Modification::pasteInsert: no container to process"); return false; } // get block for this insert object RS_Block* insertBlock = insert->getBlockForInsert(); if (insertBlock == nullptr) { RS_DEBUG->print(RS_Debug::D_ERROR, "RS_Modification::pasteInsert: no block to process"); return false; } // get name for this insert object QString name_old = insertBlock->getName(); QString name_new = name_old; if (name_old != insert->getName()) { RS_DEBUG->print(RS_Debug::D_ERROR, "RS_Modification::pasteInsert: block and insert names don't coincide"); return false; } RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::pasteInsert: processing container: %s", name_old.toLatin1().data()); // rename if needed if (graphic->findBlock(name_old)) { if (insertBlock->getParent() == graphic) { // If block is already in graphic, only paste a new insert pasteEntity(entity, container); return true; } else { name_new = graphic->getBlockList()->newName(name_old); RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::pasteInsert: new block name: %s", name_new.toLatin1().data()); } } blocksDict[name_old] = name_new; // make new block in the destination RS_Block* blockClone = addNewBlock(name_new, *graphic); // create insert for the new block RS_InsertData di = RS_InsertData(name_new, insertionPoint, RS_Vector(1.0, 1.0), 0.0, 1, 1, RS_Vector(0.0,0.0)); auto* insertClone = new RS_Insert(containerToPaste, di); insertClone->reparent(containerToPaste); containerToPaste->addEntity(insertClone); // set the same layer in clone as in source QString ln = entity->getLayer()->getName(); RS_Layer* layer = graphic->getLayerList()->find(ln); if (!layer) { RS_DEBUG->print(RS_Debug::D_ERROR, "RS_Modification::pasteInsert: unable to select layer to paste in"); return false; } RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::pasteInsert: selected layer: %s", layer->getName().toLatin1().data()); insertClone->setLayer(layer); insertClone->setPen(entity->getPen(false)); // get relative insertion point RS_Vector ip{0.0, 0.0}; if (containerToPaste->getId() != graphic->getId()) { ip = blockClone->getBasePoint(); } // copy content of block/insert to destination RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::pasteInsert: copy content to the subcontainer"); for(auto* e: *insert) { if(!e) { RS_DEBUG->print(RS_Debug::D_NOTICE, "RS_Modification::pasteInsert: nullptr entity in block"); continue; } if (e->rtti() == RS2::EntityInsert) { RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::pasteInsert: process sub-insert for %s", ((RS_Insert*)e)->getName().toLatin1().data()); if (!pasteContainer(e, blockClone, blocksDict, ip)) { RS_DEBUG->print(RS_Debug::D_ERROR, "RS_Modification::pasteInsert: unable to paste entity to sub-insert"); return false; } } else { if (!pasteEntity(e, blockClone)) { RS_DEBUG->print(RS_Debug::D_ERROR, "RS_Modification::pasteInsert: unable to paste entity"); return false; } } } insertClone->update(); insertClone->setSelected(false); RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::pasteInsert: OK"); return true; } /** * Paste entity in supplied container * **/ bool RS_Modification::pasteEntity(RS_Entity* entity, RS_EntityContainer* containerToPaste) { RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::pasteEntity"); if (!entity) { RS_DEBUG->print(RS_Debug::D_ERROR, "RS_Modification::pasteEntity: no entity to process"); return false; } // create entity copy to paste RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::pasteEntity ID/flag: %s", getIdFlagString(entity).c_str()); RS_Entity* e = entity->clone(); // set the same layer in clone as in source QString ln = entity->getLayer()->getName(); RS_Layer* layer = graphic->getLayerList()->find(ln); // fixme - perf- layer search is not needed if copy paste within the same document if (!layer) { RS_DEBUG->print(RS_Debug::D_ERROR, "RS_Modification::pasteInsert: unable to select layer to paste in"); return false; } RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::pasteInsert: selected layer: %s", layer->getName().toLatin1().data()); e->setLayer(layer); e->setPen(entity->getPen(false)); // scaling entity doesn't needed as it scaled with insert object // paste entity e->reparent(containerToPaste); containerToPaste->addEntity(e); e->setSelected(false); RS_DEBUG->print(RS_Debug::D_DEBUGGING, "RS_Modification::pasteEntity: OK"); return true; } /** * Splits a polyline into two leaving out a gap. * * @param polyline The original polyline * @param e1 1st entity on which the first cutting point is. * @param v1 1st cutting point. * @param e2 2nd entity on which the first cutting point is. * @param v2 2nd cutting point. * @param polyline1 Pointer to a polyline pointer which will hold the * 1st resulting new polyline. Pass nullptr if you don't * need those pointers. * @param polyline2 Pointer to a polyline pointer which will hold the * 2nd resulting new polyline. Pass nullptr if you don't * need those pointers. * * @todo Support arcs in polylines, check for wrong parameters * * @return true */ bool RS_Modification::splitPolyline(RS_Polyline& polyline, RS_Entity& e1, RS_Vector v1, RS_Entity& e2, RS_Vector v2, RS_Polyline** polyline1, RS_Polyline** polyline2) const { if (!container) { RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::splitPolyline: no valid container"); return false; } RS_Entity* firstEntity = polyline.firstEntity(); RS_Vector firstPoint(false); if (firstEntity->rtti()==RS2::EntityLine) { firstPoint = ((RS_Line*)firstEntity)->getStartpoint(); } auto* pl1 = new RS_Polyline(container, RS_PolylineData(firstPoint, RS_Vector(0.0,0.0), 0)); auto* pl2 = new RS_Polyline(container); RS_Polyline* pl = pl1; // Current polyline RS_Line* line = nullptr; [[maybe_unused]] RS_Arc* arc = nullptr; if (polyline1) { *polyline1 = pl1; } if (polyline2) { *polyline2 = pl2; } for(auto e: polyline){ if (e->rtti()==RS2::EntityLine) { line = (RS_Line*)e; arc = nullptr; } else if (e->rtti()==RS2::EntityArc) { arc = (RS_Arc*)e; line = nullptr; } else { line = nullptr; arc = nullptr; } if (line /*|| arc*/) { if (e==&e1 && e==&e2) { // Trim within a single entity: RS_Vector sp = line->getStartpoint(); double dist1 = (v1-sp).magnitude(); double dist2 = (v2-sp).magnitude(); pl->addVertex(dist1setStartpoint(dist1addVertex(line->getEndpoint(), 0.0); } else if (e==&e1 || e==&e2) { // Trim entities: RS_Vector v = (e==&e1 ? v1 : v2); if (pl==pl1) { // Trim endpoint of entity to first vector pl->addVertex(v, 0.0); // pl = nullptr; } else { // Trim startpoint of entity to second vector pl = pl2; pl->setStartpoint(v); pl->addVertex(line->getEndpoint(), 0.0); } } else { // Add entities to polylines if (line && pl) { pl->addVertex(line->getEndpoint(), 0.0); } } } } container->addEntity(pl1); container->addEntity(pl2); //container->removeEntity(&polyline); polyline.changeUndoState(); return true; } /** * Adds a node to the given polyline. The new node is placed between * the start and end point of the given segment. * * @param node The position of the new node. * * @return Pointer to the new polyline or nullptr. */ RS_Polyline* RS_Modification::addPolylineNode(RS_Polyline& polyline, const RS_AtomicEntity& segment, const RS_Vector& node) { RS_DEBUG->print("RS_Modification::addPolylineNode"); if (!container) { RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::addPolylineNode: no valid container"); return nullptr; } if (segment.getParent()!=&polyline) { RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::addPolylineNode: segment not part of the polyline"); return nullptr; } auto newPolyline = new RS_Polyline(container); newPolyline->setClosed(polyline.isClosed()); newPolyline->setSelected(polyline.isSelected()); newPolyline->setLayer(polyline.getLayer()); newPolyline->setPen(polyline.getPen(false)); // copy polyline and add new node: bool first = true; RS_Entity* lastEntity = polyline.lastEntity(); for(auto e: polyline){ if (e->isAtomic()) { auto ae = (RS_AtomicEntity*)e; double bulge = 0.0; if (ae->rtti()==RS2::EntityArc) { RS_DEBUG->print("RS_Modification::addPolylineNode: arc segment"); bulge = ((RS_Arc*)ae)->getBulge(); } else { RS_DEBUG->print("RS_Modification::addPolylineNode: line segment"); bulge = 0.0; } if (first) { RS_DEBUG->print("RS_Modification::addPolylineNode: first segment: %f/%f", ae->getStartpoint().x, ae->getStartpoint().y); newPolyline->setNextBulge(bulge); newPolyline->addVertex(ae->getStartpoint()); first = false; } // segment to split: if (ae==&segment) { RS_DEBUG->print("RS_Modification::addPolylineNode: split segment found"); RS_DEBUG->print("RS_Modification::addPolylineNode: node: %f/%f", node.x, node.y); newPolyline->setNextBulge(0.0); newPolyline->addVertex(node); RS_DEBUG->print("RS_Modification::addPolylineNode: after node: %f/%f", ae->getEndpoint().x, ae->getEndpoint().y); if (ae!=lastEntity || !polyline.isClosed()) { newPolyline->setNextBulge(0.0); newPolyline->addVertex(ae->getEndpoint()); } } else { RS_DEBUG->print("RS_Modification::addPolylineNode: normal vertex found: %f/%f", ae->getEndpoint().x, ae->getEndpoint().y); if (ae!=lastEntity || !polyline.isClosed()) { newPolyline->setNextBulge(bulge); newPolyline->addVertex(ae->getEndpoint()); } } } else { RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::addPolylineNode: Polyline contains non-atomic entities"); } } newPolyline->setNextBulge(polyline.getClosingBulge()); newPolyline->endPolyline(); // add new polyline: container->addEntity(newPolyline); if (handleUndo) { LC_UndoSection undo( document,viewport); polyline.setUndoState(true); undo.addUndoable(&polyline); undo.addUndoable(newPolyline); } viewport->notifyChanged(); return newPolyline; } /** * Deletes a node from a polyline. * * @param node The node to delete. * * @return Pointer to the new polyline or nullptr. */ RS_Polyline* RS_Modification::deletePolylineNode(RS_Polyline& polyline, const RS_Vector& node, bool createOnly) { RS_DEBUG->print("RS_Modification::deletePolylineNode"); if (!container){ RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::addPolylineNode: no valid container"); return nullptr; } if (!node.valid){ RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::deletePolylineNode: node not valid"); return nullptr; } // check if the polyline is no longer there after deleting the node: if (polyline.count() == 1){ RS_Entity *e = polyline.firstEntity(); if (e && e->isAtomic()){ auto ae = dynamic_cast(e); if (node.distanceTo(ae->getStartpoint()) < 1.0e-6 || node.distanceTo(ae->getEndpoint()) < 1.0e-6){ if (handleUndo){ LC_UndoSection undo(document,viewport); polyline.setUndoState(true); undo.addUndoable(&polyline); } } } return nullptr; } auto* newPolyline = new RS_Polyline(container); newPolyline->setClosed(polyline.isClosed()); if (!createOnly){ newPolyline->setSelected(polyline.isSelected()); newPolyline->setLayer(polyline.getLayer()); newPolyline->setPen(polyline.getPen(false)); } // copy polyline and drop deleted node: bool first = true; bool lastDropped = false; RS_Entity* lastEntity = polyline.lastEntity(); for (auto e: polyline) { if (e->isAtomic()){ auto ae = dynamic_cast(e); double bulge = 0.0; if (ae->rtti() == RS2::EntityArc){ RS_DEBUG->print("RS_Modification::deletePolylineNode: arc segment"); bulge = ((RS_Arc *) ae)->getBulge(); } else { RS_DEBUG->print("RS_Modification::deletePolylineNode: line segment"); bulge = 0.0; } // last entity is closing entity and will be added below with endPolyline() if (e == lastEntity && polyline.isClosed()){ continue; } // first vertex (startpoint) if (first && node.distanceTo(ae->getStartpoint()) > 1.0e-6){ RS_DEBUG->print("RS_Modification::deletePolylineNode: first node: %f/%f", ae->getStartpoint().x, ae->getStartpoint().y); newPolyline->setNextBulge(bulge); newPolyline->addVertex(ae->getStartpoint()); first = false; } // normal node (not deleted): if (first == false && node.distanceTo(ae->getEndpoint()) > 1.0e-6){ RS_DEBUG->print("RS_Modification::deletePolylineNode: normal vertex found: %f/%f", ae->getEndpoint().x, ae->getEndpoint().y); if (lastDropped){ //bulge = 0.0; } newPolyline->setNextBulge(bulge); newPolyline->addVertex(ae->getEndpoint()); lastDropped = false; } // drop deleted node: else { RS_DEBUG->print("RS_Modification::deletePolylineNode: deleting vertex: %f/%f", ae->getEndpoint().x, ae->getEndpoint().y); lastDropped = true; } } else { RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::deletePolylineNode: Polyline contains non-atomic entities"); } } RS_DEBUG->print("RS_Modification::deletePolylineNode: ending polyline"); newPolyline->setNextBulge(polyline.getClosingBulge()); newPolyline->endPolyline(); // add new polyline: RS_DEBUG->print("RS_Modification::deletePolylineNode: adding new polyline"); container->addEntity(newPolyline); if (!createOnly){ RS_DEBUG->print("RS_Modification::deletePolylineNode: handling undo"); if (handleUndo){ LC_UndoSection undo(document, viewport); polyline.setUndoState(true); undo.addUndoable(&polyline); undo.addUndoable(newPolyline); } } viewport->notifyChanged(); return newPolyline; } /** * Deletes all nodes between the two given nodes (exclusive). * * @param node1 First limiting node. * @param node2 Second limiting node. * * @return Pointer to the new polyline or nullptr. */ RS_Polyline *RS_Modification::deletePolylineNodesBetween( RS_Polyline &polyline, const RS_Vector &node1, const RS_Vector &node2, bool createOnly){ RS_DEBUG->print("RS_Modification::deletePolylineNodesBetween"); if (!container){ RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::addPolylineNodesBetween: no valid container"); return nullptr; } if (node1.valid == false || node2.valid == false){ RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::deletePolylineNodesBetween: node not valid"); return nullptr; } if (node1.distanceTo(node2) < 1.0e-6){ RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::deletePolylineNodesBetween: nodes are identical"); return nullptr; } // check if there's nothing to delete: for (auto e: polyline) { if (e->isAtomic()){ auto *ae = dynamic_cast(e); /// FIXME- RS_TOLERANCE? if ((node1.distanceTo(ae->getStartpoint()) < RS_TOLERANCE && node2.distanceTo(ae->getEndpoint()) < 1.0e-6) || (node2.distanceTo(ae->getStartpoint()) < 1.0e-6 && node1.distanceTo(ae->getEndpoint()) < 1.0e-6)){ RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::deletePolylineNodesBetween: nothing to delete"); return nullptr; } } } // check if the start point is involved: const RS_Vector &polylineStartpoint = polyline.getStartpoint(); bool startpointInvolved = isOneOfPoints(polylineStartpoint, node1, node2); // check which part of the polyline has to be deleted: bool deleteStart = false; if (polyline.isClosed()){ bool found = false; double length1 = 0.0; double length2 = 0.0; RS_Entity *e = polyline.firstEntity(); if (startpointInvolved){ if (e->isAtomic()){ auto *ae = dynamic_cast(e); length1 += ae->getLength(); } e = polyline.nextEntity(); } for (; e; e = polyline.nextEntity()) { if (e->isAtomic()){ auto *ae = dynamic_cast(e); if (isOneOfPoints(ae->getStartpoint(), node1, node2)){ found = !found; } if (found){ length2 += ae->getLength(); } else { length1 += ae->getLength(); } } } if (length1 < length2){ deleteStart = true; } else { deleteStart = false; } } auto *newPolyline = new RS_Polyline(container); newPolyline->setClosed(polyline.isClosed()); if (!createOnly){ newPolyline->setSelected(polyline.isSelected()); newPolyline->setLayer(polyline.getLayer()); newPolyline->setPen(polyline.getPen(false)); } if (startpointInvolved && deleteStart && polyline.isClosed()){ newPolyline->setNextBulge(0.0); newPolyline->addVertex(polylineStartpoint); } // copy polyline and drop deleted nodes: bool first = true; bool removing = deleteStart; bool done = false; bool nextIsStraight = false; RS_Entity *lastEntity = polyline.lastEntity(); int i = 0; double bulge = 0.0; for (auto e: polyline) { RS_DEBUG->print("RS_Modification::deletePolylineNodesBetween: entity: %d", i++); RS_DEBUG->print("RS_Modification::deletePolylineNodesBetween: removing: %d", (int) removing); if (e->isAtomic()){ auto ae = dynamic_cast(e); if (ae->rtti() == RS2::EntityArc){ RS_DEBUG->print("RS_Modification::deletePolylineNodesBetween: arc segment"); auto* arc = static_cast(ae); bulge = arc->getBulge(); } else { RS_DEBUG->print("RS_Modification::deletePolylineNodesBetween: line segment"); bulge = 0.0; } const RS_Vector &endpoint = ae->getEndpoint(); const RS_Vector &startpoint = ae->getStartpoint(); // last entity is closing entity and will be added below with endPolyline() if (e == lastEntity && polyline.isClosed()){ RS_DEBUG->print("RS_Modification::deletePolylineNodesBetween: dropping last vertex of closed polyline"); continue; } // first vertex (startpoint) if (first){ if (!removing){ RS_DEBUG->print("RS_Modification::deletePolylineNodesBetween: first node: %f/%f", startpoint.x, startpoint.y); newPolyline->setNextBulge(bulge); newPolyline->addVertex(startpoint); first = false; } } // stop removing nodes: if (removing == true && isOneOfPoints(endpoint, node1, node2)){ RS_DEBUG->print("RS_Modification::deletePolylineNodesBetween: stop removing at: %f/%f",endpoint.x, endpoint.y); removing = false; done = true; if (first == false){ nextIsStraight = true; } } // normal node (not deleted): if (removing == false && (done == false || deleteStart == false)){ RS_DEBUG->print("RS_Modification::deletePolylineNodesBetween: normal vertex shouldRemove: %f/%f", endpoint.x, endpoint.y); if (nextIsStraight){ bulge = 0.0; nextIsStraight = false; } newPolyline->setNextBulge(bulge); newPolyline->addVertex(endpoint); } // drop deleted node: else { RS_DEBUG->print("RS_Modification::deletePolylineNodesBetween: deleting vertex: %f/%f", endpoint.x, endpoint.y); } // start to remove nodes from now on: if (done == false && removing == false && isOneOfPoints(endpoint, node1, node2)){ RS_DEBUG->print("RS_Modification::deletePolylineNodesBetween: start removing at: %f/%f", endpoint.x, endpoint.y); removing = true; } if (done){ done = false; } } else { RS_DEBUG->print(RS_Debug::D_WARNING,"RS_Modification::deletePolylineNodesBetween: Polyline contains non-atomic entities"); } } RS_DEBUG->print("RS_Modification::deletePolylineNodesBetween: ending polyline"); newPolyline->setNextBulge(polyline.getClosingBulge()); newPolyline->endPolyline(); // add new polyline: RS_DEBUG->print("RS_Modification::deletePolylineNodesBetween: adding new polyline"); container->addEntity(newPolyline); if (!createOnly){ RS_DEBUG->print("RS_Modification::deletePolylineNodesBetween: handling undo"); if (handleUndo){ LC_UndoSection undo(document, viewport); polyline.setUndoState(true); undo.addUndoable(&polyline); undo.addUndoable(newPolyline); } } viewport->notifyChanged(); return newPolyline; } /** * Trims two segments of a polyline all nodes between the two trim segments * are removed. * * @param polyline The polyline entity. * @param segment1 First segment to trim. * @param segment2 Second segment to trim. * * @return Pointer to the new polyline or nullptr. */ RS_Polyline *RS_Modification::polylineTrim( RS_Polyline &polyline, RS_AtomicEntity &segment1, RS_AtomicEntity &segment2, bool createOnly){ RS_DEBUG->print("RS_Modification::polylineTrim"); if (!container){ RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::addPolylineNodesBetween: no valid container"); return nullptr; } if (segment1.getParent() != &polyline || segment2.getParent() != &polyline){ RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::polylineTrim: segments not in polyline"); return nullptr; } if (&segment1 == &segment2){ RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::polylineTrim: segments are identical"); return nullptr; } RS_VectorSolutions sol; sol = RS_Information::getIntersection(&segment1, &segment2, false); if (sol.getNumber() == 0){ RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::polylineTrim: segments cannot be trimmed"); return nullptr; } // check which segment comes first in the polyline: RS_AtomicEntity *firstSegment; if (polyline.findEntity(&segment1) > polyline.findEntity(&segment2)){ firstSegment = &segment2; } else { firstSegment = &segment1; } // find out if we need to trim towards the open part of the polyline bool reverseTrim; reverseTrim = !RS_Math::isSameDirection(firstSegment->getDirection1(), firstSegment->getStartpoint().angleTo(sol.get(0)), M_PI_2); //reverseTrim = reverseTrim || !RS_Math::isSameDirection(segment2.getDirection1(), // segment2.getStartpoint().angleTo(sol.get(0)), M_PI_2); auto *newPolyline = new RS_Polyline(container); newPolyline->setClosed(polyline.isClosed()); if (!createOnly){ newPolyline->setSelected(polyline.isSelected()); newPolyline->setLayer(polyline.getLayer()); newPolyline->setPen(polyline.getPen(false)); } // normal trimming: start removing nodes at trim segment. ends stay the same if (!reverseTrim){ // copy polyline, trim segments and drop between nodes: bool first = true; bool removing = false; bool nextIsStraight = false; RS_Entity *lastEntity = polyline.lastEntity(); for (auto e: polyline) { if (e->isAtomic()){ auto ae = dynamic_cast(e); double bulge = 0.0; if (ae->rtti() == RS2::EntityArc){ RS_DEBUG->print("RS_Modification::polylineTrim: arc segment"); bulge = ((RS_Arc *) ae)->getBulge(); } else { RS_DEBUG->print("RS_Modification::polylineTrim: line segment"); bulge = 0.0; } // last entity is closing entity and will be added below with endPolyline() if (e == lastEntity && polyline.isClosed()){ RS_DEBUG->print("RS_Modification::polylineTrim: " "dropping last vertex of closed polyline"); continue; } // first vertex (startpoint) if (first){ RS_DEBUG->print("RS_Modification::polylineTrim: first node: %f/%f", ae->getStartpoint().x, ae->getStartpoint().y); newPolyline->setNextBulge(bulge); newPolyline->addVertex(ae->getStartpoint()); first = false; } // trim and start removing nodes: if (!removing && (ae == &segment1 || ae == &segment2)){ RS_DEBUG->print("RS_Modification::polylineTrim: " "start removing at trim point %f/%f", sol.get(0).x, sol.get(0).y); newPolyline->setNextBulge(0.0); newPolyline->addVertex(sol.get(0)); removing = true; nextIsStraight = true; } // stop removing nodes: else if (removing && (ae == &segment1 || ae == &segment2)){ RS_DEBUG->print("RS_Modification::polylineTrim: stop removing at: %f/%f", ae->getEndpoint().x, ae->getEndpoint().y); removing = false; } // normal node (not deleted): if (!removing){ RS_DEBUG->print("RS_Modification::polylineTrim: normal vertex found: %f/%f", ae->getEndpoint().x, ae->getEndpoint().y); if (nextIsStraight){ newPolyline->setNextBulge(0.0); nextIsStraight = false; } else { newPolyline->setNextBulge(bulge); } newPolyline->addVertex(ae->getEndpoint()); } } else { RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::polylineTrim: Polyline contains non-atomic entities"); } } } // reverse trimming: remove nodes at the ends and keep those in between else { // copy polyline, trim segments and drop between nodes: //bool first = true; bool removing = true; bool nextIsStraight = false; RS_Entity *lastEntity = polyline.lastEntity(); for (auto e: polyline) { if (e->isAtomic()){ auto *ae = dynamic_cast(e); double bulge = 0.0; if (ae->rtti() == RS2::EntityArc){ RS_DEBUG->print("RS_Modification::polylineTrim: arc segment"); auto* arc = static_cast(ae); bulge = arc ->getBulge(); } else { RS_DEBUG->print("RS_Modification::polylineTrim: line segment"); bulge = 0.0; } // last entity is closing entity and will be added below with endPolyline() if (e == lastEntity && polyline.isClosed()){ RS_DEBUG->print("RS_Modification::polylineTrim: " "dropping last vertex of closed polyline"); continue; } // trim and stop removing nodes: if (removing == true && (ae == &segment1 || ae == &segment2)){ RS_DEBUG->print("RS_Modification::polylineTrim: " "stop removing at trim point %f/%f", sol.get(0).x, sol.get(0).y); newPolyline->setNextBulge(0.0); // start of new polyline: newPolyline->addVertex(sol.get(0)); removing = false; nextIsStraight = true; } // start removing nodes again: else if (removing == false && (ae == &segment1 || ae == &segment2)){ RS_DEBUG->print("RS_Modification::polylineTrim: start removing at: %f/%f", ae->getEndpoint().x, ae->getEndpoint().y); newPolyline->setNextBulge(0.0); // start of new polyline: newPolyline->addVertex(sol.get(0)); removing = true; } // normal node (not deleted): if (removing == false){ RS_DEBUG->print("RS_Modification::polylineTrim: normal vertex found: %f/%f", ae->getEndpoint().x, ae->getEndpoint().y); if (nextIsStraight){ newPolyline->setNextBulge(0.0); nextIsStraight = false; } else { newPolyline->setNextBulge(bulge); } newPolyline->addVertex(ae->getEndpoint()); } } else { RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::polylineTrim: Polyline contains non-atomic entities"); } } } RS_DEBUG->print("RS_Modification::polylineTrim: ending polyline"); newPolyline->setNextBulge(polyline.getClosingBulge()); newPolyline->endPolyline(); // add new polyline: RS_DEBUG->print("RS_Modification::polylineTrim: adding new polyline"); container->addEntity(newPolyline); if (!createOnly){ RS_DEBUG->print("RS_Modification::polylineTrim: handling undo"); if (handleUndo){ LC_UndoSection undo(document, viewport); polyline.setUndoState(true); undo.addUndoable(&polyline); undo.addUndoable(newPolyline); } } viewport->notifyChanged(); return newPolyline; } /** * Moves all selected entities with the given data for the move * modification. */ bool RS_Modification::move(RS_MoveData &data, bool previewOnly, [[maybe_unused]]RS_EntityContainer* previewContainer){ if (!container) { RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::move: no valid container"); return false; } std::vector selectedEntities; container->collectSelected(selectedEntities, false); return move(data, selectedEntities, previewOnly, true); } bool RS_Modification::move(RS_MoveData& data, const std::vector &entitiesList, bool forPreviewOnly, bool keepSelected) { int numberOfCopies = data.obtainNumberOfCopies(); std::vector clonesList; for(auto e: entitiesList){ // Create new entities for (int num = 1; num <= numberOfCopies; num++) { RS_Entity* ec = getClone(forPreviewOnly, e); ec->move(data.offset*num); clonesList.push_back(ec); } } setupModifiedClones(clonesList, data, forPreviewOnly, keepSelected); deleteOriginalAndAddNewEntities(clonesList, entitiesList, forPreviewOnly, !data.keepOriginals); clonesList.clear(); return true; } RS_Entity *RS_Modification::getClone(bool forPreviewOnly, const RS_Entity *e) const { RS_Entity* result = nullptr; if (forPreviewOnly){ int rtti = e->rtti(); switch (rtti){ case RS2::EntityText: case RS2::EntityMText:{ // fixme - sand - ucs - BAD dependency, rework. bool drawTextAsDraftInPreview = LC_GET_ONE_BOOL("Render","DrawTextsAsDraftInPreview", true); if (drawTextAsDraftInPreview) { result = e->cloneProxy(); } else{ result = e->clone(); } break; } case RS2::EntityImage:{ result = e->cloneProxy(); break; } default: result = e->clone(); } } else{ result = e->clone(); } return result; } void RS_Modification::setupModifiedClones( std::vector &addList, const LC_ModifyOperationFlags &data, bool forPreviewOnly, bool keepSelected) const { if (!forPreviewOnly && (data.useCurrentLayer || data.useCurrentAttributes)){ RS_Layer* layer = nullptr; RS_Pen pen; if (data.useCurrentLayer) { layer = viewport->getGraphic()->getActiveLayer(); } if (data.useCurrentAttributes) { pen = document->getActivePen(); } for (auto e: addList){ if (data.useCurrentLayer) { e->setLayer(layer); } if (data.useCurrentAttributes) { e->setPen(pen); } } } for (auto e: addList){ if (e->rtti()==RS2::EntityInsert) { ((RS_Insert*)e)->update(); } // since 2.0.4.0: keep selection e->setSelected(keepSelected); } } bool RS_Modification::alignRef(LC_AlignRefData & data, const std::vector &entitiesList, bool forPreviewOnly, bool keepSelected) { int numberOfCopies = 1; /*data.obtainNumberOfCopies();*/ std::vector clonesList; RS_Vector offset = data.offset; // too slow: for(auto e: entitiesList){ // Create new entities for (int num = 1; num <= numberOfCopies; num++) { RS_Entity* ec = getClone(forPreviewOnly, e); ec->rotate(data.rotationCenter, data.rotationAngle); if (data.scale && LC_LineMath::isMeaningful(data.scaleFactor - 1.0)){ ec->scale(data.rotationCenter, data.scaleFactor); } ec->move(offset*num); clonesList.push_back(ec); } } setupModifiedClones(clonesList, data, forPreviewOnly, keepSelected); deleteOriginalAndAddNewEntities(clonesList, entitiesList, forPreviewOnly, !data.keepOriginals); clonesList.clear(); return true; } /** * Offset all selected entities with the given mouse position and distance * *@Author: Dongxu Li */ bool RS_Modification::offset(const RS_OffsetData& data, [[maybe_unused]]bool previewOnly,[[maybe_unused]] RS_EntityContainer* previewContainer) { if (container == nullptr) { RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::offset: no valid container"); return false; } std::vector selectedEntities; container->collectSelected(selectedEntities, false); return offset(data, selectedEntities, false, true); } bool RS_Modification::offset(const RS_OffsetData& data, const std::vector &entitiesList, bool forPreviewOnly, bool keepSelected) { std::vector clonesList; int numberOfCopies = data.obtainNumberOfCopies(); // Create new entities // too slow: for(auto e: entitiesList){ for (int num=1; num<= numberOfCopies; num++) { auto ec = e->clone(); //highlight is used by trim actions. do not carry over flag ec->setHighlighted(false); if (!ec->offset(data.coord, num*data.distance)) { delete ec; continue; } clonesList.push_back(ec); } } setupModifiedClones(clonesList, data, forPreviewOnly, keepSelected); deleteOriginalAndAddNewEntities(clonesList, entitiesList, forPreviewOnly, !data.keepOriginals); clonesList.clear(); return true; } /** * Rotates all selected entities with the given data for the rotation. */ bool RS_Modification::rotate(RS_RotateData &data, bool forPreviewOnly, bool keepSelected){ if (!container){ RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::rotate: no valid container"); return false; } std::vector selectedEntities; container->collectSelected(selectedEntities, false); return rotate(data, selectedEntities, forPreviewOnly, keepSelected); } bool RS_Modification::rotate(RS_RotateData& data, const std::vector &entitiesList, bool forPreviewOnly, bool keepSelected) { std::vector clonesList; // Create new entities int numberOfCopies = data.obtainNumberOfCopies(); for (auto e: entitiesList) { for (int num = 1; num <= numberOfCopies; num++) { RS_Entity* ec = getClone(forPreviewOnly, e); double rotationAngle = data.angle * num; ec->rotate(data.center, rotationAngle); bool rotateTwice = data.twoRotations; double distance = data.refPoint.distanceTo(data.center); if (distance < RS_TOLERANCE){ rotateTwice = false; } if (rotateTwice) { RS_Vector rotatedRefPoint = data.refPoint; rotatedRefPoint.rotate(data.center, rotationAngle); double secondRotationAngle = data.secondAngle; if (data.secondAngleIsAbsolute){ secondRotationAngle -= rotationAngle; } ec->rotate(rotatedRefPoint, secondRotationAngle); } clonesList.push_back(ec); } } setupModifiedClones(clonesList, data, forPreviewOnly, keepSelected); deleteOriginalAndAddNewEntities(clonesList, entitiesList, forPreviewOnly, !data.keepOriginals); clonesList.clear(); return true; } /** * Moves all selected entities with the given data for the scale * modification. */ bool RS_Modification::scale(RS_ScaleData& data, bool forPreviewOnly, const bool keepSelected) { if (container == nullptr) { RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::scale: no valid container"); return false; } std::vector selectedEntities; container->collectSelected(selectedEntities, false); return scale(data, selectedEntities, forPreviewOnly, keepSelected); } /** * Moves all selected entities with the given data for the scale * modification. */ bool RS_Modification::scale(RS_ScaleData& data, const std::vector &entitiesList, bool forPreviewOnly, const bool keepSelected) { std::vector selectedList,clonesList; for(auto ec: entitiesList){ if ( !data.isotropicScaling ) { RS2::EntityType rtti = ec->rtti(); if (rtti == RS2::EntityCircle ) { //non-isotropic scaling, replacing selected circles with ellipses auto* c=dynamic_cast(ec); ec= new RS_Ellipse{container, {c->getCenter(), {c->getRadius(),0.}, 1., 0., 0., false}}; } else if (rtti == RS2::EntityArc ) { //non-isotropic scaling, replacing selected arcs with ellipses auto *c=dynamic_cast(ec); ec= new RS_Ellipse{container, {c->getCenter(), {c->getRadius(),0.}, 1.0, c->getAngle1(), c->getAngle2(), c->isReversed()}}; } } if (ec->rtti() == RS2::EntityImage) { ec = getClone(forPreviewOnly, ec); } selectedList.push_back(ec); } int numberOfCopies = data.obtainNumberOfCopies(); // Create new entities for(RS_Entity* e: selectedList) { if (e != nullptr) { for (int num= 1; num <= numberOfCopies; num++) { RS_Entity* ec = getClone(forPreviewOnly, e); ec->scale(data.referencePoint, RS_Math::pow(data.factor, num)); clonesList.push_back(ec); } } } selectedList.clear(); setupModifiedClones(clonesList, data, forPreviewOnly, keepSelected); deleteOriginalAndAddNewEntities(clonesList, entitiesList, forPreviewOnly, !data.keepOriginals); clonesList.clear(); return true; } /** * Mirror all selected entities with the given data for the mirror * modification. */ bool RS_Modification::mirror(RS_MirrorData& data, bool keepSelected) { if (!container) { RS_DEBUG->print(RS_Debug::D_WARNING,"RS_Modification::mirror: no valid container"); return false; } std::vector selectedEntities; container->collectSelected(selectedEntities, false); return mirror(data, selectedEntities, false, keepSelected); } bool RS_Modification::mirror(RS_MirrorData& data, const std::vector &entitiesList, bool forPreviewOnly, bool keepSelected) { std::vector clonesList; // int numberOfCopies = obtainNumberOfCopies(data); int numberOfCopies = 1; // fixme - think about support of multiple copies.... may it be be something like moving the central point of selection? Like mirror+move? // Create new entities for(auto e: entitiesList){ for (int num=1; num<=numberOfCopies; ++num) { RS_Entity* ec = getClone(forPreviewOnly, e); ec->mirror(data.axisPoint1, data.axisPoint2); clonesList.push_back(ec); } } setupModifiedClones(clonesList, data, forPreviewOnly, keepSelected); deleteOriginalAndAddNewEntities(clonesList, entitiesList, forPreviewOnly, !data.keepOriginals); clonesList.clear(); return true; } /** * Rotates entities around two centers with the given parameters. */ bool RS_Modification::rotate2(RS_Rotate2Data& data,[[maybe_unused]] bool forPreviewOnly, bool keepSelected) { if (!container) { RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::rotate2: no valid container"); return false; } std::vector selectedEntities; container->collectSelected(selectedEntities, false); return rotate2(data, selectedEntities, false, keepSelected); } bool RS_Modification::rotate2(RS_Rotate2Data& data, const std::vector &entitiesList, bool forPreviewOnly, bool keepSelected) { std::vector clonesList; int numberOfCopies = data.obtainNumberOfCopies(); // Create new entities for(auto e: entitiesList){ for (int num= 1; num <= numberOfCopies; num++) { RS_Entity* ec = getClone(forPreviewOnly, e); double angle1ForCopy = /*data.sameAngle1ForCopies ? data.angle1 :*/ data.angle1 * num; double angle2ForCopy = data.sameAngle2ForCopies ? data.angle2 : data.angle2 * num; ec->rotate(data.center1, angle1ForCopy); RS_Vector center2 = data.center2; center2.rotate(data.center1, angle1ForCopy); ec->rotate(center2, angle2ForCopy); clonesList.push_back(ec); } } setupModifiedClones(clonesList, data, forPreviewOnly, keepSelected); deleteOriginalAndAddNewEntities(clonesList, entitiesList, forPreviewOnly, !data.keepOriginals); clonesList.clear(); return true; } void RS_Modification::deleteOriginalAndAddNewEntities(const std::vector &addList, const std::vector &originalEntities, bool addOnly, bool deleteOriginals, bool forceUndoable){ LC_UndoSection undo(document, viewport, handleUndo); // bundle remove/add entities in one undoCycle if (addOnly) { for (RS_Entity *e: addList) { if (e != nullptr) { container->addEntity(e); undo.addUndoable(e); } } } else { deselectOriginals(originalEntities,deleteOriginals); addNewEntities(addList, forceUndoable); } } /** * Moves and rotates entities with the given parameters. */ bool RS_Modification::moveRotate(RS_MoveRotateData& data, bool previewOnly, [[maybe_unused]]RS_EntityContainer* previewContainer, bool keepSelected) { if (!container) { RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::moveRotate: no valid container"); return false; } std::vector selectedEntities; container->collectSelected(selectedEntities, false); return moveRotate(data, selectedEntities, previewOnly, keepSelected); } bool RS_Modification::moveRotate(RS_MoveRotateData &data, const std::vector &entitiesList, bool forPreviewOnly, bool keepSelected){ std::vector clonesList; int numberOfCopies = data.obtainNumberOfCopies(); // Create new entities for(auto e: entitiesList){ for (int num=1; num <= numberOfCopies; ++num) { RS_Entity* ec = getClone(forPreviewOnly, e); const RS_Vector &offset = data.offset * num; ec->move(offset); double angleForCopy = data.sameAngleForCopies ? data.angle : data.angle * num; ec->rotate(data.referencePoint + offset, angleForCopy); clonesList.push_back(ec); } } setupModifiedClones(clonesList, data, forPreviewOnly, keepSelected); deleteOriginalAndAddNewEntities(clonesList, entitiesList, forPreviewOnly, !data.keepOriginals); clonesList.clear(); return true; } /** * Deselects all selected entities and removes them if remove is true; * * @param remove true: Remove entities. */ void RS_Modification::deselectOriginals(bool remove) { LC_UndoSection undo(document, viewport,handleUndo); for (auto e: *container) { if (e != nullptr) { if (e->isSelected()) { e->setSelected(false); if (remove) { e->changeUndoState(); undo.addUndoable(e); } } } } } void RS_Modification::deselectOriginals(const std::vector &entitiesList, bool remove) { LC_UndoSection undo(document,viewport,handleUndo); for (auto e: entitiesList) { e->setSelected(false); if (remove) { e->changeUndoState(); undo.addUndoable(e); } } } /** * Adds the given entities to the container and draws the entities if * there's a graphic view available. * * @param addList Entities to add. */ void RS_Modification::addNewEntities(const std::vector& addList, bool forceUndoable) { LC_UndoSection undo( document, viewport, handleUndo || forceUndoable); for (RS_Entity* e: addList) { if (e) { container->addEntity(e); undo.addUndoable(e); } } container->calculateBorders(); viewport->notifyChanged(); } /** * Trims or extends the given trimEntity to the intersection point of the * trimEntity and the limitEntity. * * @param trimCoord Coordinate which defines which endpoint of the * trim entity to trim. * @param trimEntity Entity which will be trimmed. * @param limitCoord Coordinate which defines the intersection to which the * trim entity will be trimmed. * @param limitEntity Entity to which the trim entity will be trimmed. * @param both true: Trim both entities. false: trim trimEntity only. * @param forPreview true: used in preview, no entities are added to the document. * */ LC_TrimResult RS_Modification::trim(const RS_Vector& trimCoord, RS_AtomicEntity* trimEntity, const RS_Vector& limitCoord, RS_Entity* limitEntity, bool both, bool forPreview) { LC_TrimResult result; if (trimEntity == nullptr || limitEntity == nullptr) { RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::trim: At least one entity is nullptr"); return result; } if (both && !limitEntity->isAtomic()) { RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::trim: limitEntity is not atomic"); } if(trimEntity->isLocked()|| !trimEntity->isVisible()) return result; RS_VectorSolutions sol = findIntersection(*trimEntity, *limitEntity); //if intersection are in start or end point can't trim/extend in this point, remove from solution. sf.net #3537053 if (trimEntity->rtti()==RS2::EntityLine){ auto *lin = static_cast(trimEntity); for (unsigned int i=0; i< sol.size(); i++) { RS_Vector v = sol.at(i); if (v == lin->getStartpoint()) { sol.removeAt(i); } else if (v == lin->getEndpoint()) { sol.removeAt(i); } } } if (!sol.hasValid()) { return both ? trim( limitCoord, static_cast(limitEntity), trimCoord, trimEntity, false, forPreview) : result; } RS_AtomicEntity* trimmed1 = nullptr; RS_AtomicEntity* trimmed2 = nullptr; // nullpointer fix? - yet check is added later in action, and setting trimmed2 to limitEntity is misleading on preview. So restored to previous version here. // auto* trimmed2 = static_cast(limitEntity); if (trimEntity->rtti()==RS2::EntityCircle) { // convert a circle into a trimmable arc, need to start from intersections trimmed1 = trimCircle(*static_cast(trimEntity), trimCoord, sol); } else { trimmed1 = static_cast(trimEntity->clone()); trimmed1->setHighlighted(false); } // trim trim entity size_t ind = 0; RS_Vector is(false), is2(false); //RS2::Ending ending = trimmed1->getTrimPoint(trimCoord, is); if ( trimEntity->trimmable() ) { is = trimmed1->prepareTrim(trimCoord, sol); } else { is = sol.getClosest(limitCoord, nullptr, &ind); //sol.getClosest(limitCoord, nullptr, &ind); RS_DEBUG->print("RS_Modification::trim: limitCoord: %f/%f", limitCoord.x, limitCoord.y); RS_DEBUG->print("RS_Modification::trim: sol.get(0): %f/%f", sol.get(0).x, sol.get(0).y); RS_DEBUG->print("RS_Modification::trim: sol.get(1): %f/%f", sol.get(1).x, sol.get(1).y); RS_DEBUG->print("RS_Modification::trim: ind: %lu", ind); is2 = sol.get(ind==0 ? 1 : 0); //RS_Vector is2 = sol.get(ind); RS_DEBUG->print("RS_Modification::trim: is2: %f/%f", is2.x, is2.y); } if (!forPreview) { // remove trim entity from view: /*if (graphicView) { graphicView->deleteEntity(trimEntity); }*/ } // remove limit entity from view: bool trimBoth= both && !limitEntity->isLocked() && limitEntity->isVisible(); if (trimBoth) { trimmed2 = (RS_AtomicEntity*)limitEntity->clone(); if (!forPreview) { trimmed2->setHighlighted(false); } } trimEnding(trimCoord, trimmed1, is); // trim limit entity: if (trimBoth) { if ( trimmed2->trimmable()) { is2 = trimmed2->prepareTrim(limitCoord, sol); } else { is2 = sol.getClosest(trimCoord); } trimEnding(limitCoord, trimmed2, is2); } if (!forPreview) { // add new trimmed trim entity: container->addEntity(trimmed1); // add new trimmed limit entity: if (trimBoth) { container->addEntity(trimmed2); } } if (!forPreview) { if (handleUndo) { LC_UndoSection undo(document,viewport); undo.addUndoable(trimmed1); trimEntity->setUndoState(true); undo.addUndoable(trimEntity); if (trimBoth) { undo.addUndoable(trimmed2); limitEntity->setUndoState(true); undo.addUndoable(limitEntity); } } } result.result = true; result.trimmed1 = trimmed1; result.trimmed2 = trimmed2; result.intersection1 = is; result.intersection2 = is2; if (trimmed1->isArc()){ result.intersection1 = trimmed1->getStartpoint(); result.intersection2 = trimmed1->getEndpoint(); } viewport->notifyChanged(); return result; } void RS_Modification::trimEnding(const RS_Vector &trimCoord, RS_AtomicEntity *trimmed1, const RS_Vector &is) const { RS2::Ending ending = trimmed1->getTrimPoint(trimCoord, is); switch (ending) { case RS2::EndingStart: { trimmed1->trimStartpoint(is); break; } case RS2::EndingEnd: { trimmed1->trimEndpoint(is); break; } default: break; } } /** * Trims or extends the given trimEntity by the given amount. * * @param trimCoord Coordinate which defines which endpoint of the * trim entity to trim. * @param trimEntity Entity which will be trimmed. * @param dist Amount to trim by. */ RS_Entity* RS_Modification::trimAmount(const RS_Vector& trimCoord, RS_AtomicEntity* trimEntity, double dist, bool trimBoth, bool &trimStart, bool &trimEnd, bool forPreview) { if (!trimEntity){ RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::trimAmount: Entity is nullptr"); return nullptr; } if (trimEntity->isLocked() || !trimEntity->isVisible()) { return nullptr; } RS_AtomicEntity *trimmed = nullptr; // remove trim entity: trimmed = (RS_AtomicEntity *) trimEntity->clone(); // trim trim entity trimStart = false; trimEnd = false; if (trimBoth){ RS_Vector isStart = trimmed->getNearestDist(-dist, trimmed->getStartpoint()); RS_Vector isEnd = trimmed->getNearestDist(-dist, trimmed->getEndpoint()); trimmed->trimStartpoint(isStart); trimmed->trimEndpoint(isEnd ); trimStart = true; trimEnd = true; } else { RS_Vector is = trimmed->getNearestDist(-dist, trimCoord); if (trimCoord.distanceTo(trimmed->getStartpoint()) < trimCoord.distanceTo(trimmed->getEndpoint())){ trimmed->trimStartpoint(is); trimStart = true; } else { trimmed->trimEndpoint(is); trimEnd = true; } } if (!forPreview){ // add new trimmed trim entity: if (container != nullptr){ container->addEntity(trimmed); } if (handleUndo){ LC_UndoSection undo(document,viewport); undo.addUndoable(trimmed); trimEntity->setUndoState(true); undo.addUndoable(trimEntity); } } viewport->notifyChanged(); return trimmed; } /** * Cuts the given entity at the given point. */ bool RS_Modification::cut(const RS_Vector& cutCoord, RS_AtomicEntity* cutEntity) { #ifndef EMU_C99 using std::isnormal; #endif if (cutEntity == nullptr) { RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::cut: Entity is nullptr"); return false; } if(cutEntity->isLocked() || ! cutEntity->isVisible()) return false; if (!cutCoord.valid) { RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::cut: Point invalid."); return false; } // cut point is at endpoint of entity: if (cutCoord.distanceTo(cutEntity->getStartpoint())getEndpoint())print(RS_Debug::D_WARNING, "RS_Modification::cut: Cutting point on endpoint"); return false; } RS_AtomicEntity* cut1 = nullptr; RS_AtomicEntity* cut2 = nullptr; double a; const RS_Pen &originalPen = cutEntity->getPen(false); RS_Layer *originalLayer = cutEntity->getLayer(false); switch (cutEntity->rtti()) { case RS2::EntityCircle: { // convert to a whole 2 pi range arc auto *originalCircle = dynamic_cast(cutEntity); a = originalCircle->getCenter().angleTo(cutCoord); cut1 = new RS_Arc(cutEntity->getParent(), RS_ArcData(originalCircle->getCenter(), originalCircle->getRadius(), a, a + 2. * M_PI, false)); cut1->setPen(originalPen); cut1->setLayer(originalLayer); // cut2 is nullptr by default break; } case RS2::EntitySplinePoints: { // interpolation spline can be closed // so we cannot use the default implementation cut2 = static_cast(cutEntity)->cut(cutCoord); cut1 = (RS_AtomicEntity *) cutEntity->clone(); cut1->setPen(originalPen); cut1->setLayer(originalLayer); if (cut2) { cut2->setPen(originalPen); cut2->setLayer(originalLayer); } break; } case RS2::EntityEllipse:{ // handle ellipse arc the using the default method // ToDo, to really handle Ellipse Arcs properly, we need to create a new class RS_EllipseArc, keep RS_Ellipse for whole range Ellipses const auto* const ellipse=static_cast(cutEntity); if(RS_Math::isSameDirection( ellipse ->getAngle1(), ellipse ->getAngle2(), RS_TOLERANCE_ANGLE) && ! /*std::*/isnormal(ellipse->getAngle1()) && ! /*std::*/isnormal(ellipse->getAngle2()) ) { // whole ellipse, convert to a whole range elliptic arc a=ellipse->getEllipseAngle(cutCoord); cut1 = new RS_Ellipse{cutEntity->getParent(), RS_EllipseData{ellipse ->getCenter(), ellipse ->getMajorP(), ellipse ->getRatio(), a,a+2.*M_PI, ellipse ->isReversed() } }; cut1->setPen(originalPen); cut1->setLayer(originalLayer); break; }else{ //elliptic arc //missing "break;" line is on purpose //elliptic arc should be handled by default: //do not insert between here and default: } } // fall-through default: cut1 = (RS_AtomicEntity*)cutEntity->clone(); cut2 = (RS_AtomicEntity*)cutEntity->clone(); cut1->trimEndpoint(cutCoord); cut2->trimStartpoint(cutCoord); } // add new cut entity: container->addEntity(cut1); if (cut2 != nullptr) { container->addEntity(cut2); } if (handleUndo) { LC_UndoSection undo(document, viewport); undo.addUndoable(cut1); if (cut2 != nullptr) { undo.addUndoable(cut2); } cutEntity->setUndoState(true); undo.addUndoable(cutEntity); } viewport->notifyChanged(); return true; } /** * Stretching. */ bool RS_Modification::stretch(const RS_Vector& firstCorner, const RS_Vector& secondCorner, const RS_Vector& offset, bool removeOriginals) { if (!offset.valid) { RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::stretch: Offset invalid"); return false; } std::vector addList; // Create new entities for(auto e: *container){ // fixme - sand - iteration over all entities in container if (e && e->isVisible() && !e->isLocked()) { if ((e->isInWindow(firstCorner, secondCorner) || e->hasEndpointsWithinWindow(firstCorner, secondCorner))) { RS_Entity* ec = e->clone(); // fixme - sand - should we use proxy there? ec->stretch(firstCorner, secondCorner, offset); addList.push_back(ec); e->setSelected(true); } } } LC_UndoSection undo( document,viewport, handleUndo); // bundle remove/add entities in one undoCycle if (removeOriginals) { // todo - so far, it seems better to stay with deselectOriginals(true); // fixme - entities are not selected, so this is error - fix it } addNewEntities(addList); return true; } /** * Bevels a corner. * * @param coord1 Mouse coordinate to specify direction from intersection. * @param entity1 First entity of the corner. * @param coord2 Mouse coordinate to specify direction from intersection. * @param entity2 Second entity of the corner. * @param data Lengths and trim flag. */ std::unique_ptr RS_Modification::bevel( const RS_Vector &coord1, RS_AtomicEntity *entity1, const RS_Vector &coord2, RS_AtomicEntity *entity2, RS_BevelData &data, bool previewOnly){ RS_DEBUG->print("RS_Modification::bevel"); if (!(entity1 && entity2)){ RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::bevel: At least one entity is nullptr"); return nullptr; } if (entity1->isLocked() || !entity1->isVisible()) { return nullptr; } if (entity2->isLocked() || !entity2->isVisible()) { return nullptr; } RS_EntityContainer *baseContainer = container; bool isPolyline = false; // bool isClosedPolyline = false; LC_UndoSection undo(document, viewport,handleUndo); // find out whether we're bevelling within a polyline: auto result = std::make_unique(); //fixme - that check should be in action too if (entity1->getParent() && entity1->getParent()->rtti() == RS2::EntityPolyline){ RS_DEBUG->print("RS_Modification::bevel: trimming polyline segments"); if (entity1->getParent() != entity2->getParent()){ RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::bevel: entities not in the same polyline"); result->error = LC_BevelResult::ERR_NOT_THE_SAME_POLYLINE; return result; } //TODO: check if entity1 & entity2 are lines. //bevel only can be with lines. // clone polyline for undo auto *cl = dynamic_cast(entity1->getParent()->clone()); baseContainer = cl; if (handleUndo && !previewOnly){ container->addEntity(cl); //cl->setUndoState(true); undo.addUndoable(cl); undo.addUndoable(entity1->getParent()); entity1->getParent()->setUndoState(true); } entity1 = (RS_AtomicEntity *) baseContainer->entityAt(entity1->getParent()->findEntity(entity1)); entity2 = (RS_AtomicEntity *) baseContainer->entityAt(entity2->getParent()->findEntity(entity2)); //baseContainer = entity1->getParent(); isPolyline = true; // isClosedPolyline = ((RS_Polyline*)entity1)->isClosed(); } RS_DEBUG->print("RS_Modification::bevel: getting intersection"); RS_VectorSolutions sol = RS_Information::getIntersection(entity1, entity2, false); if (sol.getNumber() == 0){ result->error = LC_BevelResult::ERR_NO_INTERSECTION; return result; } RS_AtomicEntity *trimmed1 = nullptr; RS_AtomicEntity *trimmed2 = nullptr; result->polyline = isPolyline; //if (data.trim || isPolyline) { if (isPolyline){ trimmed1 = entity1; trimmed2 = entity2; //Always trim if are working with a polyline, to work with trim==false //bevel can't be part of the polyline data.trim = true; } else { trimmed1 = (RS_AtomicEntity *) entity1->clone(); trimmed2 = (RS_AtomicEntity *) entity2->clone(); } // // remove trim entity (on screen): // if (data.trim || isPolyline){ // if (graphicView){ // if (isPolyline){ // graphicView->deleteEntity(baseContainer); // } else { // graphicView->deleteEntity(entity1); // graphicView->deleteEntity(entity2); // } // } // } // trim entities to intersection RS_DEBUG->print("RS_Modification::bevel: trim entities to intersection 01"); bool start1 = false; RS_Vector is = sol.getClosest(coord2); result->intersectionPoint = is; RS2::Ending ending1 = trimmed1->getTrimPoint(coord1, is); switch (ending1) { case RS2::EndingStart: trimmed1->trimStartpoint(is); start1 = true; break; case RS2::EndingEnd: trimmed1->trimEndpoint(is); start1 = false; break; default: break; } result->trimStart1 = start1; RS_DEBUG->print("RS_Modification::bevel: trim entities to intersection 02"); bool start2 = false; is = sol.getClosest(coord1); RS2::Ending ending2 = trimmed2->getTrimPoint(coord2, is); switch (ending2) { case RS2::EndingStart: trimmed2->trimStartpoint(is); start2 = true; break; case RS2::EndingEnd: trimmed2->trimEndpoint(is); start2 = false; break; default: break; } //} result->trimStart2 = start2; // find definitive bevel points RS_DEBUG->print("RS_Modification::bevel: find definitive bevel points"); RS_Vector bp1 = trimmed1->getNearestDist(data.length1, start1); RS_Vector bp2 = trimmed2->getNearestDist(data.length2, start2); // final trim: RS_DEBUG->print("RS_Modification::bevel: final trim"); if (data.trim){ switch (ending1) { case RS2::EndingStart: trimmed1->trimStartpoint(bp1); break; case RS2::EndingEnd: trimmed1->trimEndpoint(bp1); break; default: break; } switch (ending2) { case RS2::EndingStart: trimmed2->trimStartpoint(bp2); break; case RS2::EndingEnd: trimmed2->trimEndpoint(bp2); break; default: break; } // add new trimmed entities: if (!isPolyline && !previewOnly){ container->addEntity(trimmed1); container->addEntity(trimmed2); } } // add bevel line: RS_DEBUG->print("RS_Modification::bevel: add bevel line"); RS_Line *bevel = nullptr; if (previewOnly){ bevel = new RS_Line(nullptr, bp1, bp2); } else{ bevel = new RS_Line(baseContainer, bp1, bp2); } result->bevel = bevel; if (!isPolyline){ if (!previewOnly){ baseContainer->addEntity(bevel); } } else { int idx1 = baseContainer->findEntity(trimmed1); int idx2 = baseContainer->findEntity(trimmed2); int idx = idx1; //Verify correct order segment in polylines if (idx1 > idx2){ //inverted, reorder it (swap). idx1 = idx2; idx2 = idx; RS_AtomicEntity *trimmedTmp = trimmed1; trimmed1 = trimmed2; trimmed2 = trimmedTmp; } idx = idx1; if (!previewOnly){ bevel->setSelected(baseContainer->isSelected()); bevel->setLayer(baseContainer->getLayer()); bevel->setPen(baseContainer->getPen(false)); } // insert bevel at the right position: if (trimmed1 == baseContainer->first() && trimmed2 == baseContainer->last() && baseContainer->count() > 2){ //bevel are from last and first segments, add at the end if (trimmed2->getEndpoint().distanceTo(bevel->getStartpoint()) > 1.0e-4){ bevel->reverse(); } idx = idx2; } else { //consecutive segments if (trimmed1->getEndpoint().distanceTo(bevel->getStartpoint()) > 1.0e-4){ bevel->reverse(); } } baseContainer->insertEntity(idx + 1, bevel); } result->trimmed1 = trimmed1; result->trimmed2 = trimmed2; if (isPolyline){ auto* polyline = static_cast(baseContainer); polyline->updateEndpoints(); result->polyline = polyline; } RS_DEBUG->print("RS_Modification::bevel: handling undo"); if (handleUndo && !previewOnly){ if (!isPolyline && data.trim){ undo.addUndoable(trimmed1); entity1->setUndoState(true); undo.addUndoable(entity1); undo.addUndoable(trimmed2); entity2->setUndoState(true); undo.addUndoable(entity2); } if (!isPolyline){ undo.addUndoable(bevel); } } //Do not delete trimmed* if are part of a polyline if (!(data.trim || isPolyline)){ RS_DEBUG->print("RS_Modification::bevel: delete trimmed elements"); delete trimmed1; delete trimmed2; RS_DEBUG->print("RS_Modification::bevel: delete trimmed elements: ok"); } viewport->notifyChanged(); return result; } /** * Rounds a corner. * * @param coord Mouse coordinate to specify the rounding. * @param entity1 First entity of the corner. * @param entity2 Second entity of the corner. * @param data Radius and trim flag. */ std::unique_ptr RS_Modification::round(const RS_Vector& coord, const RS_Vector& coord1, RS_AtomicEntity* entity1, const RS_Vector& coord2, RS_AtomicEntity* entity2, RS_RoundData& data){ if (!(entity1 && entity2)){ RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::round: At least one entity is nullptr"); return nullptr; } if (entity1->isLocked() || !entity1->isVisible()) { return nullptr; } if (entity2->isLocked() || !entity2->isVisible()) { return nullptr; } auto result = std::make_unique(); RS_EntityContainer *baseContainer = container; bool isPolyline = false; // bool isClosedPolyline = false; LC_UndoSection undo(document, viewport,handleUndo); auto parent1 = entity1->getParent(); auto parent2 = entity2->getParent(); // find out whether we're rounding within a polyline: if (parent1 != nullptr && parent1->rtti() == RS2::EntityPolyline){ if (parent1 != parent2){ RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::round: entities not in " "the same polyline"); result->error = LC_RoundResult::ERR_NOT_THE_SAME_POLYLINE; return result; } // clone polyline for undo auto cl = dynamic_cast(parent1->clone()); baseContainer = cl; if (handleUndo){ container->addEntity(cl); undo.addUndoable(cl); undo.addUndoable(parent1); parent1->setUndoState(true); } int index1 = parent1->findEntity(entity1); entity1 = static_cast(baseContainer->entityAt(index1)); int index2 = parent2->findEntity(entity2); entity2 = static_cast(baseContainer->entityAt(index2)); isPolyline = true; result->polyline = true; // isClosedPolyline = ((RS_Polyline*)entity1->getParent())->isClosed(); } // create 2 tmp parallels RS_Creation creation(nullptr, nullptr); std::unique_ptr par1 { creation.createParallel(coord, data.radius, 1, entity1)}; std::unique_ptr par2 { creation.createParallel(coord, data.radius, 1, entity2)}; if ((par1 == nullptr) || (par2 == nullptr)) { result->error = LC_RoundResult::NO_PARALLELS; return result; } RS_VectorSolutions sol2 = RS_Information::getIntersection(entity1, entity2, false); RS_VectorSolutions sol = RS_Information::getIntersection(par1.get(), par2.get(), false); if (sol.getNumber() == 0){ result->error = LC_RoundResult::ERR_NO_INTERSECTION; return result; } // there might be two intersections: choose the closest: RS_Vector is = sol.getClosest(coord); RS_Vector p1 = entity1->getNearestPointOnEntity(is, false); RS_Vector p2 = entity2->getNearestPointOnEntity(is, false); double ang1 = is.angleTo(p1); double ang2 = is.angleTo(p2); bool reversed = (RS_Math::getAngleDifference(ang1, ang2) > M_PI); bool isTrimming = data.radius <= RS_TOLERANCE; auto arc = std::make_unique(baseContainer, RS_ArcData(is, data.radius, ang1, ang2, reversed)); result->round = isTrimming ? nullptr : arc.get(); RS_AtomicEntity *trimmed1 = nullptr; RS_AtomicEntity *trimmed2 = nullptr; if (data.trim || isPolyline){ if (isPolyline){ trimmed1 = entity1; trimmed2 = entity2; } else { trimmed1 = static_cast(entity1->clone()); trimmed2 = static_cast(entity2->clone()); } /* // remove trim entity: if (graphicView){ if (isPolyline){ graphicView->deleteEntity(baseContainer); } else { graphicView->deleteEntity(entity1); graphicView->deleteEntity(entity2); } }*/ // trim entities to intersection RS_Vector is2 = sol2.getClosest(coord2); RS2::Ending ending1 = trimmed1->getTrimPoint(coord1, is2); switch (ending1) { case RS2::EndingStart: trimmed1->trimStartpoint(p1); result->trim1Mode = LC_RoundResult::TRIM_START; break; case RS2::EndingEnd: trimmed1->trimEndpoint(p1); result->trim1Mode = LC_RoundResult::TRIM_END; break; default: trimmed1 = trimCircleForRound(trimmed1, *arc); result->trim1Mode = LC_RoundResult::TRIM_CIRCLE; break; } is2 = sol2.getClosest(coord1); RS2::Ending ending2 = trimmed2->getTrimPoint(coord2, is2); switch (ending2) { case RS2::EndingStart: trimmed2->trimStartpoint(p2); result -> trim2Mode = LC_RoundResult::TRIM_START; break; case RS2::EndingEnd: trimmed2->trimEndpoint(p2); result->trim2Mode = LC_RoundResult::TRIM_END; break; default: trimmed2 = trimCircleForRound(trimmed2, *arc); result->trim2Mode = LC_RoundResult::TRIM_CIRCLE; break; } // add new trimmed entities: if (!isPolyline){ container->addEntity(trimmed1); container->addEntity(trimmed2); } } // add rounding: if (!isPolyline){ if (!isTrimming) baseContainer->addEntity(arc.get()); } else { // find out which base entity is before the rounding: int idx1 = baseContainer->findEntity(trimmed1); int idx2 = baseContainer->findEntity(trimmed2); arc->setSelected(baseContainer->isSelected()); arc->setLayer(baseContainer->getLayer()); arc->setPen(baseContainer->getPen(false)); RS_DEBUG->print("RS_Modification::round: idx1print("RS_Modification::round: idx1!=0: %d", (int) (idx1 != 0)); RS_DEBUG->print("RS_Modification::round: idx2==0: %d", (int) (idx2 == 0)); RS_DEBUG->print("RS_Modification::round: idx1==(int)baseContainer->count()-1: %d", (int) (idx1 == (int) baseContainer->count() - 1)); bool insertAfter1 = ((idx1 < idx2 && idx1 != 0) || (idx1 == 0 && idx2 == 1) || (idx2 == 0 && idx1 == (int) baseContainer->count() - 1)); // insert rounding at the right position: //if ((idx1count()-1)) { //if (idx1getEndpoint().distanceTo(arc->getStartpoint()) > 1.0e-4){ arc->reverse(); } if (!isTrimming) baseContainer->insertEntity(idx1 + 1, arc.get()); } else { if (trimmed2->getEndpoint().distanceTo(arc->getStartpoint()) > 1.0e-4){ arc->reverse(); } if (!isTrimming) baseContainer->insertEntity(idx2 + 1, arc.get()); } } result->trimmed1 = trimmed1; result->trimmed2 = trimmed2; if (isPolyline){ static_cast(baseContainer)->updateEndpoints(); } if (handleUndo){ if (!isPolyline && data.trim){ undo.addUndoable(trimmed1); entity1->setUndoState(true); undo.addUndoable(entity1); undo.addUndoable(trimmed2); entity2->setUndoState(true); undo.addUndoable(entity2); } if (!isPolyline){ undo.addUndoable(arc.release()); } } if (!isTrimming) arc.release(); viewport->notifyChanged(); return result; } /** * Repetitive recursive block of code for the explode() method. */ static void updateExplodedChildrenRecursively( RS_EntityContainer* ec, RS_Entity* e, RS_Entity* clone, RS2::ResolveLevel rl, bool resolveLayer, bool resolvePen) { if (ec == nullptr || e == nullptr || clone == nullptr) { return; } if (resolveLayer) { clone->setLayer(ec->getLayer()); } else { clone->setLayer(e->getLayer()); } if (resolvePen) { //clone->setPen(ec->getPen(true)); clone->setPen(ec->getPen(false)); } else { clone->setPen(e->getPen(false)); } clone->update(); if (clone->isContainer()) { // Note: reassigning ec and e here, so keep // that in mind when writing code below this block. ec = static_cast(clone); for(RS_Entity* en: lc::LC_ContainerTraverser{*ec, rl}.entities()) { if (en != nullptr) { // Run the same code for every children recursively updateExplodedChildrenRecursively(ec, clone, en, rl, resolveLayer, resolvePen); } } } } /** * Removes the selected entity containers and adds the entities in them as * new single entities. * @param forceUndoableOperation - flag that indicates that explode should be in undo section regardless of handleUndo flag (for paste transform) */ bool RS_Modification::explode(const bool remove /*= true*/, const bool forceUndoableOperation) { if (container == nullptr) { RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::explode: no valid container for addinge entities"); return false; } std::vector selectedEntities; RS_Layer* activeLayer = graphic->getActiveLayer(); // Issue #2271 : copy/paste across documents is through the current active layer // collectSelected() may fail, if the active layer is frozen const bool isFrozen = activeLayer->isFrozen(); activeLayer->freeze(false); container->collectSelected(selectedEntities, false); activeLayer->freeze(isFrozen); return explode(selectedEntities, remove, forceUndoableOperation); } // fixme - sand - decide how to treat keepSelected flag. So far one is ignored. bool RS_Modification::explode(const std::vector &entitiesList, const bool remove, const bool forceUndoableOperation, [[maybe_unused]]const bool keepSelected) { if (container->isLocked() || ! container->isVisible()) { return false; } std::vector clonesList; // Issue #2296, only collect exploded containers to delete std::vector toDelete; for(auto e: entitiesList){ if (e->isContainer()) { // add entities from container: auto* ec = static_cast(e); //ec->setSelected(false); // iterate and explode container: //for (unsigned i2=0; i2count(); ++i2) { // RS_Entity* e2 = ec->entityAt(i2); RS2::ResolveLevel rl; bool resolvePen; bool resolveLayer; auto containerType = ec->rtti(); switch (containerType) { case RS2::EntityMText: case RS2::EntityText: case RS2::EntityHatch: case RS2::EntityPolyline: rl = RS2::ResolveAll; resolveLayer = true; resolvePen = true; break; case RS2::EntityInsert: resolvePen = false; resolveLayer = false; rl = RS2::ResolveNone; break; default: if (RS2::isDimensionalEntity(containerType)) { rl = RS2::ResolveNone; resolveLayer = true; resolvePen = true; } else { rl = RS2::ResolveAll; resolveLayer = true; resolvePen = false; } break; } auto entities = lc::LC_ContainerTraverser{*ec, rl}.entities(); for(RS_Entity* e2: entities) { if (e2 != nullptr) { RS_Entity* clone = e2->clone(); clone->setSelected(false); clone->reparent(container); clonesList.push_back(clone); // In order to fix bug #819 and escape similar issues, // we have to update all children of exploded entity, // even those (below the tree) which are not direct // subjects to the current explode() call. updateExplodedChildrenRecursively(ec, e2, clone, rl, resolveLayer, resolvePen); /* if (resolveLayer) { clone->setLayer(ec->getLayer()); } else { clone->setLayer(e2->getLayer()); } // clone->setPen(ec->getPen(resolvePen)); if (resolvePen) { clone->setPen(ec->getPen(true)); } else { clone->setPen(e2->getPen(false)); } addList.push_back(clone); clone->update(); */ } } toDelete.push_back(e); } else { e->setSelected(false); } } deleteOriginalAndAddNewEntities(clonesList, toDelete, false, remove, forceUndoableOperation); clonesList.clear(); container->updateInserts(); return true; } bool RS_Modification::explodeTextIntoLetters(bool keepSelected) { if (container == nullptr) { RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::explodeTextIntoLetters: no valid container for adding entities"); return false; } std::vector selectedEntities; container->collectSelected(selectedEntities, false); return explodeTextIntoLetters(selectedEntities, keepSelected); } bool RS_Modification::explodeTextIntoLetters(const std::vector &entitiesList, [[maybe_unused]]bool keepSelected) { if(container->isLocked() || ! container->isVisible()) { return false; } std::vector clonesList; for(auto e: entitiesList){ if (e->rtti()==RS2::EntityMText) { // add letters of text: auto text = dynamic_cast(e); explodeTextIntoLetters(text, clonesList); } else if (e->rtti()==RS2::EntityText) { // add letters of text: auto text = dynamic_cast(e); explodeTextIntoLetters(text, clonesList); } else { e->setSelected(false); } } deleteOriginalAndAddNewEntities(clonesList, entitiesList, false, true); clonesList.clear(); return true; } bool RS_Modification::explodeTextIntoLetters(RS_MText* text, std::vector& addList) { if (text == nullptr) { return false; } if(text->isLocked() || ! text->isVisible()) { return false; } // iterate though lines: for(auto e2: *text){ if (e2 == nullptr) { break; } // text lines: if (e2->rtti()==RS2::EntityContainer) { auto line = dynamic_cast(e2); // iterate though letters: for(auto e3: *line){ if (e3 == nullptr) { break; } if (e3->rtti()==RS2::EntityMText) { // super / sub texts: auto e3MText = dynamic_cast(e3); explodeTextIntoLetters(e3MText, addList); } else if (e3->rtti()==RS2::EntityInsert) { // normal letters: auto letter = dynamic_cast(e3); auto tl = new RS_MText( container, RS_MTextData(letter->getInsertionPoint(), text->getHeight(), 100.0, RS_MTextData::VABottom, RS_MTextData::HALeft, RS_MTextData::LeftToRight, RS_MTextData::Exact, 1.0, letter->getName(), text->getStyle(), letter->getAngle(), RS2::Update)); tl->setLayer(text->getLayer()); tl->setPen(text->getPen(false)); addList.push_back(tl); tl->update(); } } } } return true; } bool RS_Modification::explodeTextIntoLetters(RS_Text* text, std::vector& addList) { if (text == nullptr) { return false; } if(text->isLocked() || ! text->isVisible()) return false; // iterate though letters: for(auto e2: *text){ if (e2 == nullptr) { break; } if (e2->rtti()==RS2::EntityInsert) { auto letter = dynamic_cast(e2); auto tl = new RS_Text( container, RS_TextData(letter->getInsertionPoint(), letter->getInsertionPoint(), text->getHeight(), text->getWidthRel(), RS_TextData::VABaseline, RS_TextData::HALeft, RS_TextData::None, /*text->getTextGeneration(),*/ letter->getName(), text->getStyle(), letter->getAngle(), RS2::Update)); tl->setLayer(text->getLayer()); tl->setPen(text->getPen(false)); addList.push_back(tl); tl->update(); } } return true; } /** * Moves all reference points of selected entities with the given data. */ bool RS_Modification::moveRef(RS_MoveRefData& data) { if (container == nullptr) { RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Modification::moveRef: no valid container"); return false; } if(container->isLocked() || ! container->isVisible()) return false; std::vector addList; // Create new entities for(auto e: *container){ // fixme - iterating all entities for selection if (e != nullptr && e->isSelected()) { RS_Entity* ec = e->clone(); ec->moveRef(data.ref, data.offset); // since 2.0.4.0: keep it selected ec->setSelected(true); addList.push_back(ec); } } LC_UndoSection undo( document,viewport, handleUndo); // bundle remove/add entities in one undoCycle deselectOriginals(true); addNewEntities(addList); return true; }