diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index 426ed4361072..d1819cb5514b 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -4783,12 +4783,10 @@ TopoShape& TopoShape::makeElementRefine(const TopoShape& shape, const char* op, return {}; std::vector res; - int type = shape.shapeType(); - for(;;) { - if(--type < 0) - break; - const char *shapetype = shapeName((TopAbs_ShapeEnum)type).c_str(); - for(int idx : findAncestors(shape.getShape(), (TopAbs_ShapeEnum)type)) + + for (int type = shape.shapeType() - 1; type >= 0; type--) { + const char* shapetype = shapeName((TopAbs_ShapeEnum)type).c_str(); + for (int idx : findAncestors(shape.getShape(), (TopAbs_ShapeEnum)type)) res.emplace_back(shapetype, idx); } return res; diff --git a/src/Mod/Sketcher/App/PreCompiled.h b/src/Mod/Sketcher/App/PreCompiled.h index 36c1a184c080..bdb7f8929b7c 100644 --- a/src/Mod/Sketcher/App/PreCompiled.h +++ b/src/Mod/Sketcher/App/PreCompiled.h @@ -43,23 +43,38 @@ #include // Boost +#include +#include #include #include +#include +#include +#include +#include #include +#include // OpenCasCade #include #include +#include #include #include +#include #include +#include #include +#include #include #include +#include +#include +#include #include #include #include #include +#include #include #include #include @@ -74,6 +89,7 @@ #include #include #include +#include #include #include #include diff --git a/src/Mod/Sketcher/App/SketchObject.cpp b/src/Mod/Sketcher/App/SketchObject.cpp index 103828376153..8aeaa2a2447f 100644 --- a/src/Mod/Sketcher/App/SketchObject.cpp +++ b/src/Mod/Sketcher/App/SketchObject.cpp @@ -27,13 +27,26 @@ #include #include +#include #include #include +#include +#include +#include #include +#include #include +#include +#include +#include +#include #include #include +#include +#include #include +#include +#include #include #include #include @@ -46,6 +59,7 @@ #include #include #include +#include #include #include #include @@ -56,11 +70,16 @@ #include #include #include -#endif +#include #include +#include #include #include +#include +#include + +#endif #include #include @@ -79,18 +98,19 @@ #include #include #include +#include #include #include #include #include +#include + #include "SketchObject.h" #include "SketchObjectPy.h" #include "SolverGeometryExtension.h" -#include -#include -#include +#include "ExternalGeometryFacade.h" #undef DEBUG @@ -125,6 +145,20 @@ SketchObject::SketchObject() "Sketch", (App::PropertyType)(App::Prop_Output | App::Prop_ReadOnly | App::Prop_Hidden), "Sketch is fully constrained"); + ADD_PROPERTY_TYPE(Exports, + (nullptr), + "Sketch", + (App::PropertyType)(App::Prop_Hidden),"Sketch export geometry"); + ADD_PROPERTY_TYPE(ExternalGeo, + (nullptr), + "Sketch", + (App::PropertyType)(App::Prop_Hidden),"Sketch external geometry"); + ADD_PROPERTY_TYPE(ArcFitTolerance, + (0.0), + "Sketch", + (App::PropertyType)(App::Prop_None), + "Tolerance for fitting arcs of projected external geometry"); + geoLastId = 0; ADD_PROPERTY(InternalShape, (Part::TopoShape())); @@ -139,21 +173,8 @@ SketchObject::SketchObject() allowOtherBody = true; allowUnaligned = true; - for (std::vector::iterator it = ExternalGeo.begin(); it != ExternalGeo.end(); - ++it) - if (*it) - delete *it; - ExternalGeo.clear(); - auto HLine = GeometryTypedFacade::getTypedFacade(); - auto VLine = GeometryTypedFacade::getTypedFacade(); - HLine->getTypedGeometry()->setPoints(Base::Vector3d(0, 0, 0), Base::Vector3d(1, 0, 0)); - VLine->getTypedGeometry()->setPoints(Base::Vector3d(0, 0, 0), Base::Vector3d(0, 1, 0)); - HLine->setConstruction(true); - VLine->setConstruction(true); - ExternalGeo.push_back(HLine->getGeometry()); - ExternalGeo.push_back(VLine->getGeometry()); - HLine->setOwner(false);// we have transferred the ownership to ExternalGeo - VLine->setOwner(false);// we have transferred the ownership to ExternalGeo + initExternalGeo(); + rebuildVertexIndex(); lastDoF = 0; @@ -186,14 +207,7 @@ SketchObject::SketchObject() registerElementCache(internalPrefix(), &InternalShape); } -SketchObject::~SketchObject() -{ - for (std::vector::iterator it = ExternalGeo.begin(); it != ExternalGeo.end(); - ++it) - if (*it) - delete *it; - ExternalGeo.clear(); - +SketchObject::~SketchObject() { delete analyser; } @@ -201,10 +215,28 @@ void SketchObject::setupObject() { ParameterGrp::handle hGrpp = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Mod/Sketcher"); + ArcFitTolerance.setValue(hGrpp->GetFloat("ArcFitTolerance", Precision::Confusion()*10.0)); MakeInternals.setValue(hGrpp->GetBool("MakeInternals", false)); inherited::setupObject(); } +void SketchObject::initExternalGeo() { + std::vector geos; + auto HLine = GeometryTypedFacade::getTypedFacade(); + auto VLine = GeometryTypedFacade::getTypedFacade(); + HLine->getTypedGeometry()->setPoints(Base::Vector3d(0,0,0),Base::Vector3d(1,0,0)); + VLine->getTypedGeometry()->setPoints(Base::Vector3d(0,0,0),Base::Vector3d(0,1,0)); + HLine->setConstruction(true); + HLine->setId(-1); + VLine->setConstruction(true); + VLine->setId(-2); + geos.push_back(HLine->getGeometry()); + geos.push_back(VLine->getGeometry()); + HLine->setOwner(false); // we have transferred the ownership to ExternalGeo + VLine->setOwner(false); // we have transferred the ownership to ExternalGeo + ExternalGeo.setValues(std::move(geos)); +} + short SketchObject::mustExecute() const { if (Geometry.isTouched()) @@ -213,6 +245,8 @@ short SketchObject::mustExecute() const return 1; if (ExternalGeometry.isTouched()) return 1; + if (ExternalGeo.isTouched()) + return 1; return Part2DObject::mustExecute(); } @@ -274,8 +308,15 @@ App::DocumentObjectExecReturn* SketchObject::execute() return App::DocumentObject::StdReturn; } -void SketchObject::buildShape() -{ +static bool inline checkSmallEdge(const Part::TopoShape &s) { + if (s.shapeType() != TopAbs_EDGE) + return false; + BRepAdaptor_Curve adapt(TopoDS::Edge(s.getShape())); + return GCPnts_AbscissaPoint::Length(adapt, Precision::Confusion()) <= Precision::Confusion(); +} + +void SketchObject::buildShape() { + // Shape.setValue(solvedSketch.toShape()); // We use the following instead to map element names @@ -298,13 +339,15 @@ void SketchObject::buildShape() vertex.resetElementMap(std::make_shared()); } vertex.setElementName(Data::IndexedName::fromConst("Vertex", 1), - Data::MappedName::fromRawData(name.c_str()), 0L); + Data::MappedName::fromRawData(name.c_str()),0L); vertices.push_back(vertex); vertices.back().copyElementMap(vertex, Part::OpCodes::Sketch); - } - else { + } else { auto indexedName = Data::IndexedName::fromConst("Edge", geoId); shapes.push_back(getEdge(geo,convertSubName(indexedName, false).c_str())); + if (checkSmallEdge(shapes.back())) { + FC_WARN("Edge too small: " << indexedName); + } } #else @@ -319,14 +362,16 @@ void SketchObject::buildShape() #endif } - // FIXME: Commented since ExternalGeometryFacade is not added - for(size_t i=2;itestFlag(ExternalGeometryExtension::Defining)) continue; - shapes.push_back(getEdge(geo, convertSubName( - Data::IndexedName::fromConst("ExternalEdge", i-1), false).c_str())); + auto indexedName = Data::IndexedName::fromConst("ExternalEdge", i-1); + shapes.push_back(getEdge(geo, convertSubName(indexedName, false).c_str())); + if (checkSmallEdge(shapes.back())) { + FC_WARN("Edge too small: " << indexedName); + } } internalElementMap.clear(); @@ -547,21 +592,229 @@ int SketchObject::solve(bool updateGeoAfterSolving /*=true*/) if (err == 0 && updateGeoAfterSolving) { // set the newly solved geometry std::vector geomlist = solvedSketch.extractGeometry(); - Geometry.setValues(geomlist); - for (std::vector::iterator it = geomlist.begin(); it != geomlist.end(); - ++it) - if (*it) - delete *it; + Part::PropertyGeometryList tmp; + tmp.setValues(std::move(geomlist)); + // Only set values if there is actual changes + if (!Geometry.isSame(tmp)) + Geometry.moveValues(std::move(tmp)); } else if (err < 0) { // if solver failed, invalid constraints were likely added before solving // (see solve in addConstraint), so solver information is definitely invalid. - this->Constraints.touch(); + // + // Update: ViewProviderSketch shall now rely on the signalSolverUpdate below for update + // this->Constraints.touch(); } + signalSolverUpdate(); + return err; } +namespace bg = boost::geometry; +namespace bgi = boost::geometry::index; + +// NOLINTNEXTLINE +BOOST_GEOMETRY_REGISTER_POINT_3D( + Base::Vector3d,double,bg::cs::cartesian,x,y,z) + +class SketchObject::GeoHistory { +private: + static constexpr int bgiMaxElements = 16; +public: + using Parameters = bgi::linear; + + using IdSet = std::set; + using IdSets = std::pair; + using AdjList = std::list; + + //associate a geo with connected ones on both points + using AdjMap = std::map; + + // maps start/end points to all existing geo to query and update adjacencies + using Value = std::pair; + + AdjList adjlist; + AdjMap adjmap; + bgi::rtree rtree; + + AdjList::iterator find(const Base::Vector3d &pt,bool strict=true){ + std::vector ret; + rtree.query(bgi::nearest(pt,1),std::back_inserter(ret)); + if(!ret.empty()) { + // NOTE: we are using square distance here, the 1e-6 threshold is + // very forgiving. We should have used Precision::SquareConfisuion(), + // which is 1e-14. However, there is a problem with current + // commandGeoCreate. They create new geometry with initial point of + // the exact mouse position, instead of the pre-selected point + // position, and rely on auto constraint to snap in the new + // geometry. So, we cannot use a very strict threshold here. + double tol = strict?Precision::SquareConfusion()*10:1e-6; + double d = Base::DistanceP2(ret[0].first,pt); + if(dinsert(id); + } + + void finishUpdate(const std::map &geomap) { + IdSet oldset; + for(auto &idset : adjlist) { + oldset.clear(); + for(long _id : idset) { + long id = abs(_id); + auto& v = adjmap[id]; + auto& adj = _id > 0 ? v.first : v.second; + for (auto it = adj.begin(); it != adj.end(); /* don't advance here */) { + long other = *it; + auto removeId = it++; // grab ID we might erase, and advance + if (geomap.find(other) == geomap.end()) { + // remember those deleted IDs to swap in below + oldset.insert(other); + } + else if (idset.find(other) == idset.end()) { + // delete any existing IDs that are no longer in the adj list + adj.erase(removeId); + } + } + // now merge the current ones + for(long _id2 : idset) { + long id2 = abs(_id2); + if(id!=id2) { + adj.insert(id2); + } + } + } + // now reset the adjacency list with only those deleted id's, + // because the whole purpose of this history is to try to reuse + // deleted id. + idset.swap(oldset); + } + } + + AdjList::iterator end() { + return adjlist.end(); + } + + size_t size() { + return rtree.size(); + } +}; + +void SketchObject::updateGeoHistory() { + if(!geoHistoryLevel) return; + + if(!geoHistory) + geoHistory = std::make_unique(); + + FC_TIME_INIT(t); + const auto &geos = getInternalGeometry(); + geoHistory->clear(); + for(auto geo : geos) { + auto pstart = getPoint(geo,PointPos::start); + auto pend = getPoint(geo,PointPos::end); + int id = GeometryFacade::getId(geo); + geoHistory->update(pstart,id); + if(pstart!=pend) + geoHistory->update(pend,-id); + } + geoHistory->finishUpdate(geoMap); + FC_TIME_LOG(t,"update geometry history (" << geoHistory->size() << ", " << geoMap.size()<<')'); +} + +void SketchObject::generateId(Part::Geometry *geo) { + if(!geoHistoryLevel) { + GeometryFacade::setId(geo, ++geoLastId); + geoMap[GeometryFacade::getId(geo)] = (long)Geometry.getSize(); + return; + } + + if(!geoHistory) + updateGeoHistory(); + + // Search geo history to see if the start point and end point belongs to + // some deleted geometries. Prefer matching both start and end point. If + // can't then try start and then end. Generate new id if none is found. + auto pstart = getPoint(geo,PointPos::start); + auto it = geoHistory->find(pstart,false); + auto pend = getPoint(geo,PointPos::end); + auto it2 = it; + if(pstart!=pend) { + it2 = geoHistory->find(pend,false); + if(it2 == geoHistory->end()) + it2 = it; + } + long newId = -1; + std::vector found; + + if(geoHistoryLevel<=1 && (it==geoHistory->end() || it2==it)) { + // level<=1 means we only reuse id if both start and end matches + newId = ++geoLastId; + goto END; + } + + if(it!=geoHistory->end()) { + for(long id : *it) { + if(geoMap.find(id)==geoMap.end()) { + if(it2 == it) { + newId = id; + goto END; + } + found.push_back(id); + }else + FC_TRACE("ignore " << id); + } + } + if(it2!=it) { + if(found.empty()) { + // no candidate exists + for(long id : *it2) { + if(geoMap.find(id)==geoMap.end()) { + newId = id; + goto END; + } + FC_TRACE("ignore " << id); + } + }else{ + // already some candidate exists, search for matching of both + // points + for(long id : found) { + if(it2->find(id)!=it2->end()) { + newId = id; + goto END; + } + FC_TRACE("ignore " << id); + } + } + } + if(found.size()) { + FC_TRACE("found " << found.front()); + newId = found.front(); + }else + newId = ++geoLastId; +END: + GeometryFacade::setId(geo, newId); + geoMap[newId] = (long)Geometry.getSize(); +} + int SketchObject::setDatum(int ConstrId, double Datum) { // no need to check input data validity as this is an sketchobject managed operation. @@ -973,6 +1226,10 @@ int SketchObject::setVirtualSpace(int ConstrId, bool isinvirtualspace) this->Constraints.setValues(std::move(newVals)); + // Solver didn't actually update, but we need this to inform view provider + // to redraw + signalSolverUpdate(); + return 0; } @@ -1004,6 +1261,10 @@ int SketchObject::setVirtualSpace(std::vector constrIds, bool isinvirtualsp this->Constraints.setValues(std::move(newVals)); + // Solver didn't actually update, but we need this to inform view provider + // to redraw + signalSolverUpdate(); + return 0; } @@ -1039,6 +1300,10 @@ int SketchObject::toggleVirtualSpace(int ConstrId) this->Constraints.setValues(std::move(newVals)); + // Solver didn't actually update, but we need this to inform view provider + // to redraw + signalSolverUpdate(); + return 0; } @@ -1241,6 +1506,7 @@ void SketchObject::acceptGeometry() { Constraints.acceptGeometry(getCompleteGeometry()); rebuildVertexIndex(); + signalElementsChanged(); } bool SketchObject::isSupportedGeometry(const Part::Geometry* geo) const @@ -1295,6 +1561,7 @@ int SketchObject::addGeometry(const std::vector& geoList, newVals.reserve(newVals.size() + geoList.size()); for (auto& v : geoList) { Part::Geometry* copy = v->copy(); + generateId(copy); if (copy->is()) { // creation mode for points is always construction not to @@ -1333,6 +1600,7 @@ int SketchObject::addGeometry(std::unique_ptr newgeo, bool const std::vector newVals(vals); auto* geoNew = newgeo.release(); + generateId(geoNew); if (geoNew->is()) { // creation mode for points is always construction not to @@ -1354,6 +1622,12 @@ int SketchObject::addGeometry(std::unique_ptr newgeo, bool const int SketchObject::delGeometry(int GeoId, bool deleteinternalgeo) { + if (GeoId < 0) { + if(GeoId > GeoEnum::RefExt) + return -1; + return delExternal(-GeoId-1); + } + // no need to check input data validity as this is an sketchobject managed operation. Base::StateLocker lock(managedoperation, true); @@ -1606,26 +1880,111 @@ int SketchObject::setConstruction(int GeoId, bool on) // no need to check input data validity as this is an sketchobject managed operation. Base::StateLocker lock(managedoperation, true); +#ifdef FC_USE_TNP_FIX + Part::PropertyGeometryList *prop; + int idx; + if (GeoId >= 0) { + prop = &Geometry; + if (GeoId < Geometry.getSize()) + idx = GeoId; + else + return -1; + }else if (GeoId <= GeoEnum::RefExt && -GeoId-1 < ExternalGeo.getSize()) { + prop = &ExternalGeo; + idx = -GeoId-1; + }else + return -1; +#else const std::vector& vals = getInternalGeometry(); if (GeoId < 0 || GeoId >= int(vals.size())) return -1; if (getGeometryFacade(GeoId)->isInternalAligned()) return -1; +#endif // While it may seem that there is not a need to trigger an update at this time, because the // solver has its own copy of the geometry, and updateColors of the viewprovider may be // triggered by the clearselection of the UI command, this won't update the elements widget, in // the accumulative of actions it is judged that it is worth to trigger an update here. +#ifdef FC_USE_TNP_FIX + std::unique_ptr geo(prop->getValues()[idx]->clone()); + if(prop == &Geometry) + GeometryFacade::setConstruction(geo.get(), on); + else { + auto egf = ExternalGeometryFacade::getFacade(geo.get()); + egf->setFlag(ExternalGeometryExtension::Defining, on); + } + + prop->set1Value(idx,std::move(geo)); + +#else std::unique_ptr geo(vals[GeoId]->clone()); GeometryFacade::setConstruction(geo.get(), on); this->Geometry.set1Value(GeoId, std::move(geo)); - +#endif solverNeedsUpdate = true; return 0; } +int SketchObject::toggleExternalGeometryFlag(const std::vector &geoIds, + const std::vector &flags) +{ + if (flags.empty()) + return 0; + auto flag = flags.front(); + + Base::StateLocker lock(managedoperation, true); // no need to check input data validity as this is an sketchobject managed operation. + + bool update = false; + bool touched = false; + auto geos = ExternalGeo.getValues(); + std::set idSet(geoIds.begin(),geoIds.end()); + for(auto geoId : geoIds) { + if(geoId > GeoEnum::RefExt || -geoId-1>=ExternalGeo.getSize()) + continue; + if(!idSet.count(geoId)) + continue; + idSet.erase(geoId); + const int idx = -geoId - 1; + auto& geo = geos[idx]; + const auto egf = ExternalGeometryFacade::getFacade(geo); + const bool value = !egf->testFlag(flag); + if (!egf->getRef().empty()) { + for (auto relatedGeoId : getRelatedGeometry(geoId)) { + if (relatedGeoId == geoId) { + continue; + } + int relatedIndex = -relatedGeoId - 1; + auto& relatedGeometry = geos[relatedIndex]; + relatedGeometry = relatedGeometry->clone(); + auto relatedFacade = ExternalGeometryFacade::getFacade(relatedGeometry); + relatedFacade->setFlag(flag, value); + for (size_t i = 1; i < flags.size(); ++i) { + relatedFacade->setFlag(flags[i], value); + } + idSet.erase(relatedGeoId); + } + } + geo = geo->clone(); + egf->setGeometry(geo); + egf->setFlag(flag, value); + for (size_t i=1; isetFlag(flags[i], value); + if (value || flag != ExternalGeometryExtension::Frozen) + update = true; + touched = true; + } + + if(!touched) + return -1; + ExternalGeo.setValues(geos); + if (update) + rebuildExternalGeometry(); + return 0; +} + void SketchObject::addGeometryState(const Constraint* cstr) const { const std::vector& vals = getInternalGeometry(); @@ -1723,6 +2082,9 @@ int SketchObject::addCopyOfConstraints(const SketchObject& orig) } } + if (noRecomputes) // if we do not have a recompute, the sketch must be solved to update the DoF of the solver + solve(); + return this->Constraints.getSize() - 1; } @@ -2635,6 +2997,17 @@ int SketchObject::fillet(int GeoId1, int GeoId2, const Base::Vector3d& refPnt1, oc2pf.x, oc2pf.y, oc2pf.z); + // To enable detailed Log of ten intermediate points along the curves uncomment this + /*auto printoffsetcurve = [](Part::GeomOffsetCurve *c) { + + for(double param = c->getFirstParameter(); param < c->getLastParameter(); param = param + + (c->getLastParameter()-c->getFirstParameter())/10) Base::Console().Log("\n%f: + (%f,%f,0)\n", param, c->pointAtParameter(param).x,c->pointAtParameter(param).y); + + }; + + printoffsetcurve(ocurve1); + printoffsetcurve(ocurve2);*/ #endif // Next we calculate the intersection of offset curves to get the center of the fillet @@ -3423,6 +3796,7 @@ int SketchObject::trim(int GeoId, const Base::Vector3d& point) auto newVals(vals); newVals[GeoId] = newVals[GeoId]->clone(); newVals.push_back(newVals[GeoId]->clone()); + generateId(newVals.back()); int newGeoId = newVals.size() - 1; chooseCurveForPointOnObject(GeoId, firstParam, point1Param, newGeoId, point2Param, lastParam, isBSpline); @@ -3481,10 +3855,8 @@ int SketchObject::trim(int GeoId, const Base::Vector3d& point) Sketcher::PointPos::mid); } - if (isNonPeriodicBSpline) { + if (isNonPeriodicBSpline) exposeInternalGeometry(GeoId); - exposeInternalGeometry(newGeoId); - } // if we do not have a recompute, the sketch must be solved to update the DoF of the // solver @@ -3616,6 +3988,7 @@ int SketchObject::trim(int GeoId, const Base::Vector3d& point) geoNew = std::move(bspline); } + GeometryFacade::setId(geoNew.get(), GeometryFacade::getId(geo)); this->Geometry.set1Value(GeoId, std::move(geoNew)); //****** Step B.2 (4) => Constraint end points ******// @@ -4417,9 +4790,17 @@ int SketchObject::addSymmetric(const std::vector& geoIdList, int refGeoId, // no need to check input data validity as this is an sketchobject managed operation. Base::StateLocker lock(managedoperation, true); + const std::vector& geovals = getInternalGeometry(); + std::vector newgeoVals(geovals); + const std::vector& constrvals = this->Constraints.getValues(); std::vector newconstrVals(constrvals); + newgeoVals.reserve(geovals.size() + geoIdList.size()); + + std::map geoIdMap; + std::map isStartEndInverted; + // Find out if reference is aligned with V or H axis, // if so we can keep Vertical and Horizontal constraints in the mirrored geometry. bool refIsLine = refPosId == Sketcher::PointPos::none; @@ -4437,9 +4818,6 @@ int SketchObject::addSymmetric(const std::vector& geoIdList, int refGeoId, } // add the geometry - std::map geoIdMap; - std::map isStartEndInverted; - std::vector newgeoVals(getInternalGeometry()); std::vector symmetricVals = getSymmetric(geoIdList, geoIdMap, isStartEndInverted, refGeoId, refPosId); newgeoVals.insert(newgeoVals.end(), symmetricVals.begin(), symmetricVals.end()); @@ -5153,9 +5531,10 @@ int SketchObject::addCopy(const std::vector& geoIdList, const Base::Vector3 // We have already cloned all geometry and constraints, we only need a copy if not // moving - if (!moveonly) + if (!moveonly) { geocopy = geo->copy(); - else + generateId(geocopy); + } else geocopy = newgeoVals[*it]; // Handle Geometry @@ -5395,6 +5774,7 @@ int SketchObject::addCopy(const std::vector& geoIdList, const Base::Vector3 constrline->setPoints(sp, ep); GeometryFacade::setConstruction(constrline, true); + generateId(constrline); newgeoVals.push_back(constrline); Constraint* constNew; @@ -6531,7 +6911,7 @@ bool SketchObject::convertToNURBS(int GeoId) Base::StateLocker lock(managedoperation, true); if (GeoId > getHighestCurveIndex() - || (GeoId < 0 && -GeoId > static_cast(ExternalGeo.size())) || GeoId == -1 + || (GeoId < 0 && -GeoId > static_cast(ExternalGeo.getSize())) || GeoId == -1 || GeoId == -2) return false; @@ -6570,10 +6950,12 @@ bool SketchObject::convertToNURBS(int GeoId) if (GeoId < 0) {// external geometry newVals.push_back(bspline); + generateId(bspline); } else {// normal geometry newVals[GeoId] = bspline; + GeometryFacade::copyId(geo, bspline); const std::vector& cvals = Constraints.getValues(); @@ -6645,6 +7027,7 @@ bool SketchObject::increaseBSplineDegree(int GeoId, int degreeincrement /*= 1*/) std::vector newVals(vals); + GeometryFacade::copyId(geo, bspline.get()); newVals[GeoId] = bspline.release(); // AcceptGeometry called from onChanged @@ -6865,6 +7248,7 @@ bool SketchObject::modifyBSplineKnotMultiplicity(int GeoId, int knotIndex, int m std::vector newVals(vals); + GeometryFacade::copyId(geo, bspline.get()); newVals[GeoId] = bspline.release(); // Block acceptGeometry in OnChanged to avoid unnecessary checks and updates @@ -6920,7 +7304,7 @@ bool SketchObject::insertBSplineKnot(int GeoId, double param, int multiplicity) THROWMT(Base::ValueError, QT_TRANSLATE_NOOP( "Exceptions", - "Knot multiplicity cannot be higher than the degree of the BSpline.")); + "Knot multiplicity cannot be higher than the degree of the B-spline.")); if (param > lastParam || param < firstParam) THROWMT(Base::ValueError, @@ -7094,6 +7478,43 @@ int SketchObject::carbonCopy(App::DocumentObject* pObj, bool construction) newVals.reserve(vals.size() + svals.size()); newcVals.reserve(cvals.size() + scvals.size()); + std::map extMap; + if (psObj->ExternalGeo.getSize() > 1) { + int i = -1; + auto geos = this->ExternalGeo.getValues(); + std::string myName(this->getNameInDocument()); + myName += "."; + for (const auto &geo : psObj->ExternalGeo.getValues()) { + if (++i < 2) // skip h/v axes + continue; + else { + auto egf = ExternalGeometryFacade::getFacade(geo); + const auto &ref = egf->getRef(); + if (boost::starts_with(ref, myName)) { + int geoId; + PointPos posId; + if (this->geoIdFromShapeType(ref.c_str()+myName.size(), geoId, posId)) { + extMap[-i-1] = geoId; + continue; + } + } + } + auto copy = geo->copy(); + auto egf = ExternalGeometryFacade::getFacade(copy); + egf->setId(++geoLastId); + if (!egf->getRef().empty()) { + auto &refs = this->externalGeoRefMap[egf->getRef()]; + refs.push_back(geoLastId); + } + this->externalGeoMap[geoLastId] = (int)geos.size(); + geos.push_back(copy); + extMap[-i-1] = -(int)geos.size(); + } + Base::ObjectStatusLocker + guard(App::Property::User3, &this->ExternalGeo); + this->ExternalGeo.setValues(std::move(geos)); + } + if (psObj->ExternalGeometry.getSize() > 0) { std::vector Objects = ExternalGeometry.getValues(); std::vector SubElements = ExternalGeometry.getSubValues(); @@ -7148,6 +7569,7 @@ int SketchObject::carbonCopy(App::DocumentObject* pObj, bool construction) for (std::vector::const_iterator it = svals.begin(); it != svals.end(); ++it) { Part::Geometry* geoNew = (*it)->copy(); + generateId(geoNew); if (construction && geoNew->getTypeId() != Part::GeomPoint::getClassTypeId()) { GeometryFacade::setConstruction(geoNew, true); } @@ -7221,10 +7643,22 @@ int SketchObject::carbonCopy(App::DocumentObject* pObj, bool construction) } } + // We shall solve in all cases, because recompute may fail, and leave the + // sketch in an inconsistent state. A concrete example. If the copied sketch + // has broken external geometry, its recomputation will fail. And because we + // use expression for copied constraint to add dependency to the copied + // sketch, this sketch will not be recomputed (because its dependency fails + // to recompute). +#if 0 + if (noRecomputes) // if we do not have a recompute, the sketch must be solved to update the DoF of the solver +#endif + solve(); + + return svals.size(); } -int SketchObject::addExternal(App::DocumentObject* Obj, const char* SubName) +int SketchObject::addExternal(App::DocumentObject *Obj, const char* SubName, bool defining, bool intersection) { // no need to check input data validity as this is an sketchobject managed operation. Base::StateLocker lock(managedoperation, true); @@ -7233,6 +7667,58 @@ int SketchObject::addExternal(App::DocumentObject* Obj, const char* SubName) if (!isExternalAllowed(Obj->getDocument(), Obj)) return -1; + auto wholeShape = Part::Feature::getTopoShape(Obj); + auto shape = wholeShape.getSubTopoShape(SubName, /*silent*/true); + TopAbs_ShapeEnum shapeType = TopAbs_SHAPE; + if (shape.shapeType(/*silent*/true) != TopAbs_FACE) { + if (shape.hasSubShape(TopAbs_FACE)) + shapeType = TopAbs_FACE; + else if (shape.shapeType(/*silent*/true) != TopAbs_EDGE + && shape.hasSubShape(TopAbs_EDGE)) + shapeType = TopAbs_EDGE; + } + + if (shapeType != TopAbs_SHAPE) { + std::string element = Part::TopoShape::shapeName(shapeType); + std::size_t elementNameSize = element.size(); + int geometryCount = ExternalGeometry.getSize(); + + gp_Pln sketchPlane; + if (intersection) { + Base::Placement Plm = Placement.getValue(); + Base::Vector3d Pos = Plm.getPosition(); + Base::Rotation Rot = Plm.getRotation(); + Base::Vector3d dN(0,0,1); + Rot.multVec(dN,dN); + Base::Vector3d dX(1,0,0); + Rot.multVec(dX,dX); + gp_Ax3 sketchAx3(gp_Pnt(Pos.x,Pos.y,Pos.z), + gp_Dir(dN.x,dN.y,dN.z), + gp_Dir(dX.x,dX.y,dX.z)); + sketchPlane.SetPosition(sketchAx3); + } + for (const auto &subShape : shape.getSubShapes(shapeType)) { + int idx = wholeShape.findShape(subShape); + if (idx == 0) + continue; + if (intersection) { + try { + BRepAlgoAPI_Section maker(subShape, sketchPlane); + if (!maker.IsDone() || maker.Shape().IsNull()) + continue; + } catch (Standard_Failure &) { + continue; + } + } + element += std::to_string(idx); + addExternal(Obj, element.c_str(), defining, intersection); + element.resize(elementNameSize); + } + if (ExternalGeometry.getSize() == geometryCount) + return -1; + return geometryCount; + } + // get the actual lists of the externals std::vector Objects = ExternalGeometry.getValues(); std::vector SubElements = ExternalGeometry.getSubValues(); @@ -7261,7 +7747,7 @@ int SketchObject::addExternal(App::DocumentObject* Obj, const char* SubName) // set the Link list. ExternalGeometry.setValues(Objects, SubElements); try { - rebuildExternalGeometry(); + rebuildExternalGeometry(defining, intersection); } catch (const Base::Exception& e) { Base::Console().Error("%s\n", e.what()); @@ -7342,8 +7828,125 @@ int SketchObject::delExternal(int ExtGeoId) return 0; } +void SketchObject::delExternalPrivate(const std::set &ids, bool removeRef) { + + Base::StateLocker lock(managedoperation, true); // no need to check input data validity as this is an sketchobject managed operation. + + std::set refs; + // Must sort in reverse order so as to delete geo from back to front to + // avoid index change + std::set> geoIds; + + for(auto id : ids) { + auto it = externalGeoMap.find(id); + if(it == externalGeoMap.end()) + continue; + + auto egf = ExternalGeometryFacade::getFacade(ExternalGeo[it->second]); + if(removeRef && egf->getRef().size()) + refs.insert(egf->getRef()); + geoIds.insert(-it->second-1); + } + + if(geoIds.empty()) + return; + + std::vector< Constraint * > newConstraints; + for(auto cstr : Constraints.getValues()) { + if(!geoIds.count(cstr->First) && + (cstr->Second==GeoEnum::GeoUndef || !geoIds.count(cstr->Second)) && + (cstr->Third==GeoEnum::GeoUndef || !geoIds.count(cstr->Third))) + { + bool cloned = false; + int offset = 0; + for(auto GeoId : geoIds) { + GeoId += offset++; + bool done = true; + if (cstr->First < GeoId && cstr->First != GeoEnum::GeoUndef) { + if (!cloned) { + cloned = true; + cstr = cstr->clone(); + } + cstr->First += 1; + done = false; + } + if (cstr->Second < GeoId && cstr->Second != GeoEnum::GeoUndef) { + if (!cloned) { + cloned = true; + cstr = cstr->clone(); + } + cstr->Second += 1; + done = false; + } + if (cstr->Third < GeoId && cstr->Third != GeoEnum::GeoUndef) { + if (!cloned) { + cloned = true; + cstr = cstr->clone(); + } + cstr->Third += 1; + done = false; + } + if(done) break; + } + newConstraints.push_back(cstr); + } + } + + auto geos = ExternalGeo.getValues(); + int offset = 0; + for(auto geoId : geoIds) { + int idx = -geoId-1; + geos.erase(geos.begin()+idx-offset); + ++offset; + } + + if(refs.size()) { + std::vector newSubs; + std::vector newObjs; + const auto &subs = ExternalGeometry.getSubValues(); + auto itSub = subs.begin(); + const auto &objs = ExternalGeometry.getValues(); + auto itObj = objs.begin(); + bool touched = false; + assert(externalGeoRef.size() == objs.size()); + assert(externalGeoRef.size() == subs.size()); + for(auto it=externalGeoRef.begin();it!=externalGeoRef.end();++it,++itObj,++itSub) { + if(refs.count(*it)) { + if(!touched) { + touched = true; + if(newObjs.empty()) { + newObjs.insert(newObjs.end(),objs.begin(),itObj); + newSubs.insert(newSubs.end(),subs.begin(),itSub); + } + } + }else if(touched) { + newObjs.push_back(*itObj); + newSubs.push_back(*itSub); + } + } + if(touched) + ExternalGeometry.setValues(newObjs,newSubs); + } + + ExternalGeo.setValues(std::move(geos)); + + solverNeedsUpdate = true; + Constraints.setValues(std::move(newConstraints)); + acceptGeometry(); // This may need to be refactored into OnChanged for ExternalGeometry. +} + int SketchObject::delAllExternal() { + int count = 0; // the remaining count of the detached external geometry + std::map indexMap; // the index map of the remain external geometry + std::vector geos; // the remaining external geometry + for(int i=0;igetRef().empty()) + indexMap[i] = count++; + geos.push_back(geo); + } // no need to check input data validity as this is an sketchobject managed operation. Base::StateLocker lock(managedoperation, true); @@ -7385,6 +7988,8 @@ int SketchObject::delAllExternal() return -1; } + ExternalGeometry.setValue(0); + ExternalGeo.setValues(std::move(geos)); solverNeedsUpdate = true; Constraints.setValues(std::move(newConstraints)); acceptGeometry();// This may need to be refactored into OnChanged for ExternalGeometry @@ -7417,13 +8022,137 @@ int SketchObject::delConstraintsToExternal() return 0; } +int SketchObject::attachExternal( + const std::vector &geoIds, App::DocumentObject *Obj, const char* SubName) +{ + if (!isExternalAllowed(Obj->getDocument(), Obj)) + return -1; + + std::set detached; + std::set idSet; + for (int geoId : geoIds) { + if (geoId > GeoEnum::RefExt || -geoId - 1 >= ExternalGeo.getSize()) + continue; + auto geo = getGeometry(geoId); + if(!geo) + continue; + auto egf = ExternalGeometryFacade::getFacade(geo); + if(egf->getRef().size()) + detached.insert(egf->getRef()); + for(int id : getRelatedGeometry(geoId)) + idSet.insert(id); + } + + auto geos = ExternalGeo.getValues(); + + std::vector Objects = ExternalGeometry.getValues(); + auto itObj = Objects.begin(); + std::vector SubElements = ExternalGeometry.getSubValues(); + auto itSub = SubElements.begin(); + + assert(Objects.size()==SubElements.size()); + assert(externalGeoRef.size() == Objects.size()); + + for(auto &key : externalGeoRef) { + if (*itObj == Obj && *itSub == SubName){ + FC_ERR("Duplicate external element reference in " << getFullName() << ": " << key); + return -1; + } + // detach old reference + if(detached.count(key)) { + itObj = Objects.erase(itObj); + itSub = SubElements.erase(itSub); + }else{ + ++itObj; + ++itSub; + } + } + + // add the new ones + Objects.push_back(Obj); + SubElements.push_back(std::string(SubName)); + + ExternalGeometry.setValues(Objects,SubElements); + if(externalGeoRef.size()!=Objects.size()) + return -1; + + std::string ref = externalGeoRef.back(); + for(auto geoId : idSet) { + auto &geo = geos[-geoId-1]; + geo = geo->clone(); + ExternalGeometryFacade::getFacade(geo)->setRef(ref); + } + + ExternalGeo.setValues(std::move(geos)); + rebuildExternalGeometry(); + return ExternalGeometry.getSize()-1; +} + +std::vector SketchObject::getRelatedGeometry(int GeoId) const { + std::vector res; + if(GeoId>GeoEnum::RefExt || -GeoId-1>=ExternalGeo.getSize()) + return res; + auto geo = getGeometry(GeoId); + if(!geo) + return res; + const std::string &ref = ExternalGeometryFacade::getFacade(geo)->getRef(); + if(!ref.size()) + return {GeoId}; + auto iter = externalGeoRefMap.find(ref); + if(iter == externalGeoRefMap.end()) + return {GeoId}; + for(auto id : iter->second) { + auto it = externalGeoMap.find(id); + if(it!=externalGeoMap.end()) + res.push_back(-it->second-1); + } + return res; +} + +int SketchObject::syncGeometry(const std::vector &geoIds) { + bool touched = false; + auto geos = ExternalGeo.getValues(); + std::set idSet; + for(int geoId : geoIds) { + auto geo = getGeometry(geoId); + if(!geo || !ExternalGeometryFacade::getFacade(geo)->testFlag(ExternalGeometryExtension::Frozen)) + continue; + for(int gid : getRelatedGeometry(geoId)) + idSet.insert(gid); + } + for(int geoId : idSet) { + if(geoId <= GeoEnum::RefExt && -geoId-1 < ExternalGeo.getSize()) { + auto &geo = geos[-geoId-1]; + geo = geo->clone(); + ExternalGeometryFacade::getFacade(geo)->setFlag(ExternalGeometryExtension::Sync); + touched = true; + } + } + if(touched) + ExternalGeo.setValues(std::move(geos)); + return 0; +} + +const Part::Geometry* SketchObject::_getGeometry(int GeoId) const +{ + if (GeoId >= 0) { + const std::vector &geomlist = getInternalGeometry(); + if (GeoId < int(geomlist.size())) + return geomlist[GeoId]; + } + else if (GeoId < 0 && -GeoId-1 < ExternalGeo.getSize()) + return ExternalGeo[-GeoId-1]; + + return nullptr; +} + int SketchObject::getCompleteGeometryIndex(int GeoId) const { if (GeoId >= 0) { if (GeoId < int(Geometry.getSize())) return GeoId; } - else if (-GeoId <= int(ExternalGeo.size())) + else if (-GeoId <= int(ExternalGeo.getSize())) return -GeoId - 1; return GeoEnum::GeoUndef; @@ -7431,7 +8160,7 @@ int SketchObject::getCompleteGeometryIndex(int GeoId) const int SketchObject::getGeoIdFromCompleteGeometryIndex(int completeGeometryIndex) const { - int completeGeometryCount = int(Geometry.getSize() + ExternalGeo.size()); + int completeGeometryCount = int(Geometry.getSize() + ExternalGeo.getSize()); if (completeGeometryIndex < 0 || completeGeometryIndex >= completeGeometryCount) return GeoEnum::GeoUndef; @@ -7447,6 +8176,19 @@ std::unique_ptr SketchObject::getGeometryFacade(int GeoId) return GeometryFacade::getFacade(getGeometry(GeoId)); } +int SketchObject::setGeometry(int GeoId, const Part::Geometry *geo) { + std::unique_ptr g(geo->clone()); + if(GeoId>=0 && GeoId > &arcs, + const gp_Pnt &P1, + const gp_Pnt &P2, + double tol) +{ + double radius = 0.0; + double m; + Base::Vector3d center; + for (auto &geo : arcs) { + if (auto arc = Base::freecad_dynamic_cast(geo.get())) { + if (radius == 0.0) { + radius = arc->getRadius(); + center = arc->getCenter(); + double f = arc->getFirstParameter(); + double l = arc->getLastParameter(); + m = (l-f)*0.5 + f; // middle parameter + } else if (std::abs(radius - arc->getRadius()) > tol) + return nullptr; + } else + return nullptr; + } + if (radius == 0.0) + return nullptr; + if (P1.SquareDistance(P2) < Precision::Confusion()) { + Part::GeomCircle* circle = new Part::GeomCircle(); + circle->setCenter(center); + circle->setRadius(radius); + return circle; + } + else if (arcs.size() == 1) { + auto res = arcs.front().release(); + arcs.clear(); + return res; + } + else { + GeomLProp_CLProps prop(Handle(Geom_Curve)::DownCast(arcs.front()->handle()),m,0,Precision::Confusion()); + gp_Pnt midPoint = prop.Value(); + GC_MakeArcOfCircle arc(P1, midPoint, P2); + auto geo = new Part::GeomArcOfCircle(); + geo->setHandle(arc.Value()); + return geo; + } +} + void SketchObject::validateExternalLinks() { // no need to check input data validity as this is an sketchobject managed operation. @@ -7614,11 +8411,41 @@ void SketchObject::validateExternalLinks() } } -void SketchObject::rebuildExternalGeometry() +void SketchObject::rebuildExternalGeometry(bool defining, bool addIntersection) { + Base::StateLocker lock(managedoperation, true); // no need to check input data validity as this is an sketchobject managed operation. + // get the actual lists of the externals - std::vector Objects = ExternalGeometry.getValues(); - std::vector SubElements = ExternalGeometry.getSubValues(); + auto Objects = ExternalGeometry.getValues(); + auto SubElements = ExternalGeometry.getSubValues(); + assert(externalGeoRef.size() == Objects.size()); + auto keys = externalGeoRef; + + // re-check for any missing geometry element. The code here has a side + // effect that the linked external geometry will continue to work even if + // ExternalGeometry is wiped out. + for(auto &geo : ExternalGeo.getValues()) { + auto egf = ExternalGeometryFacade::getFacade(geo); + if(egf->getRef().size() && egf->testFlag(ExternalGeometryExtension::Missing)) { + const std::string &ref = egf->getRef(); + auto pos = ref.find('.'); + if(pos == std::string::npos) + continue; + std::string objName = ref.substr(0,pos); + auto obj = getDocument()->getObject(objName.c_str()); + if(!obj) + continue; + App::ElementNamePair elementName; + App::GeoFeature::resolveElement(obj,ref.c_str()+pos+1,elementName); + if(elementName.oldName.size() + && !App::GeoFeature::hasMissingElement(elementName.oldName.c_str())) + { + Objects.push_back(obj); + SubElements.push_back(elementName.oldName); + keys.push_back(ref); + } + } + } Base::Placement Plm = Placement.getValue(); Base::Vector3d Pos = Plm.getPosition(); @@ -7653,6 +8480,35 @@ void SketchObject::rebuildExternalGeometry() BRepBuilderAPI_MakeFace mkFace(sketchPlane); TopoDS_Shape aProjFace = mkFace.Shape(); + std::set refSet; + // We use a vector here to keep the order (roughly) the same as ExternalGeometry + std::vector > > newGeos; + newGeos.reserve(Objects.size()); +#ifdef FC_USE_TNP_FIX + for (int i=0; i < int(Objects.size()); i++) { + const App::DocumentObject *Obj=Objects[i]; + const std::string &SubElement=SubElements[i]; + const std::string &key = keys[i]; + + // Skip frozen geometries + bool frozen = false; + bool sync = false; + bool intersection = addIntersection && (i+1 == (int)Objects.size()); + for(auto id : externalGeoRefMap[key]) { + auto it = externalGeoMap.find(id); + if(it != externalGeoMap.end()) { + auto egf = ExternalGeometryFacade::getFacade(ExternalGeo[it->second]); + if(egf->testFlag(ExternalGeometryExtension::Frozen)) + frozen = true; + if(egf->testFlag(ExternalGeometryExtension::Sync)) + sync = true; + } + } + if(frozen && !sync) { + refSet.insert(std::move(key)); + continue; + } +#else for (std::vector::iterator it = ExternalGeo.begin(); it != ExternalGeo.end(); ++it) if (*it) @@ -7669,7 +8525,13 @@ void SketchObject::rebuildExternalGeometry() for (int i = 0; i < int(Objects.size()); i++) { const App::DocumentObject* Obj = Objects[i]; const std::string SubElement = SubElements[i]; +#endif + if(!Obj || !Obj->getNameInDocument()) + continue; + std::vector > geos; + + try { TopoDS_Shape refSubShape; if (Obj->isDerivedFrom()) { @@ -7726,7 +8588,7 @@ void SketchObject::rebuildExternalGeometry() const TopoDS_Edge& edge = TopoDS::Edge(builder.Shape()); BRepAdaptor_Curve curve(edge); if (curve.GetType() == GeomAbs_Line) { - ExternalGeo.push_back(projectLine(curve, gPlane, invPlm)); + geos.emplace_back(projectLine(curve, gPlane, invPlm)); } } } @@ -7744,51 +8606,16 @@ void SketchObject::rebuildExternalGeometry() const TopoDS_Edge& edge = TopoDS::Edge(refSubShape); BRepAdaptor_Curve curve(edge); if (curve.GetType() == GeomAbs_Line) { - ExternalGeo.push_back(projectLine(curve, gPlane, invPlm)); - break; - } - - gp_Dir vec1 = sketchPlane.Axis().Direction(); - gp_Dir vec2; - if (curve.GetType() == GeomAbs_Circle) { - vec2 = curve.Circle().Axis().Direction(); + geos.emplace_back(projectLine(curve, gPlane, invPlm)); } - else if (curve.GetType() == GeomAbs_Ellipse) { - vec2 = curve.Ellipse().Axis().Direction(); - } - - gp_Pnt beg, end; - if ((curve.GetType() == GeomAbs_Circle || curve.GetType() == GeomAbs_Ellipse) - && !curve.IsClosed()) { - beg = ProjPointOnPlane_XYZ(curve.Value(curve.FirstParameter()), sketchPlane); - end = ProjPointOnPlane_XYZ(curve.Value(curve.LastParameter()), sketchPlane); - Base::Vector3d vBeg(beg.X(), beg.Y(), beg.Z()); - Base::Vector3d vEnd(end.X(), end.Y(), end.Z()); - invPlm.multVec(vBeg, vBeg); - invPlm.multVec(vEnd, vEnd); - beg = gp_Pnt(vBeg[0], vBeg[1], vBeg[2]); - end = gp_Pnt(vEnd[0], vEnd[1], vEnd[2]); - - if (vec1.IsNormal(vec2, Precision::Angular())) { - if (beg.SquareDistance(end) < Precision::SquareConfusion()) { - Base::Vector3d p = (vBeg + vEnd) / 2; - Part::GeomPoint* point = new Part::GeomPoint(p); - ExternalGeo.push_back(point); - } - else { - Part::GeomLineSegment* line = new Part::GeomLineSegment(); - line->setPoints(vBeg, vEnd); - GeometryFacade::setConstruction(line, true); - ExternalGeo.push_back(line); - } - break; - } - } - - if (curve.GetType() == GeomAbs_Circle) { + else if (curve.GetType() == GeomAbs_Circle) { + gp_Dir vec1 = sketchPlane.Axis().Direction(); + gp_Dir vec2 = curve.Circle().Axis().Direction(); if (vec1.IsParallel(vec2, Precision::Confusion())) { gp_Circ circle = curve.Circle(); gp_Pnt cnt = circle.Location(); + gp_Pnt beg = curve.Value(curve.FirstParameter()); + gp_Pnt end = curve.Value(curve.LastParameter()); GeomAPI_ProjectPointOnSurf proj(cnt, gPlane); cnt = proj.NearestPoint(); @@ -7796,12 +8623,13 @@ void SketchObject::rebuildExternalGeometry() cnt.Transform(mov); circle.Transform(mov); - if (curve.IsClosed()) { + if (beg.SquareDistance(end) < Precision::Confusion()) { Part::GeomCircle* gCircle = new Part::GeomCircle(); gCircle->setRadius(circle.Radius()); gCircle->setCenter(Base::Vector3d(cnt.X(), cnt.Y(), cnt.Z())); + GeometryFacade::setConstruction(gCircle, true); - ExternalGeo.push_back(gCircle); + geos.emplace_back(gCircle); } else { Part::GeomArcOfCircle* gArc = new Part::GeomArcOfCircle(); @@ -7810,19 +8638,24 @@ void SketchObject::rebuildExternalGeometry() hCircle, curve.FirstParameter(), curve.LastParameter()); gArc->setHandle(tCurve); GeometryFacade::setConstruction(gArc, true); - ExternalGeo.push_back(gArc); + geos.emplace_back(gArc); } } else { // creates an ellipse or a segment + gp_Dir vec1 = sketchPlane.Axis().Direction(); + gp_Dir vec2 = curve.Circle().Axis().Direction(); gp_Circ origCircle = curve.Circle(); - gp_Pnt cnt = origCircle.Location(); - GeomAPI_ProjectPointOnSurf proj(cnt, gPlane); - cnt = proj.NearestPoint(); - if (vec1.IsNormal(vec2, Precision::Angular())) { - // projection is a segment + if (vec1.IsNormal( + vec2, Precision::Angular())) {// circle's normal vector in plane: + // projection is a line + // define center by projection + gp_Pnt cnt = origCircle.Location(); + GeomAPI_ProjectPointOnSurf proj(cnt, gPlane); + cnt = proj.NearestPoint(); + gp_Dir dirOrientation = gp_Dir(vec1 ^ vec2); gp_Dir dirLine(dirOrientation); @@ -7833,6 +8666,90 @@ void SketchObject::rebuildExternalGeometry() ligne.D0(-origCircle.Radius(), P1); ligne.D0(origCircle.Radius(), P2); + if (!curve.IsClosed()) {// arc of circle + + // start point of arc of circle + gp_Pnt pntF = curve.Value(curve.FirstParameter()); + // end point of arc of circle + gp_Pnt pntL = curve.Value(curve.LastParameter()); + + double alpha = + dirOrientation.AngleWithRef(curve.Circle().XAxis().Direction(), + curve.Circle().Axis().Direction()); + + double baseAngle = curve.FirstParameter(); + + int tours = 0; + double startAngle = baseAngle + alpha; + // bring startAngle back in [-pi/2 , 3pi/2[ + while (startAngle < -M_PI / 2.0 && tours < 10) { + startAngle = baseAngle + ++tours * 2.0 * M_PI + alpha; + } + while (startAngle >= 3.0 * M_PI / 2.0 && tours > -10) { + startAngle = baseAngle + --tours * 2.0 * M_PI + alpha; + } + + // apply same offset to end angle + double endAngle = curve.LastParameter() + startAngle - baseAngle; + + if (startAngle <= 0.0) { + if (endAngle <= 0.0) { + P1 = ProjPointOnPlane_XYZ(pntF, sketchPlane); + P2 = ProjPointOnPlane_XYZ(pntL, sketchPlane); + } + else { + if (endAngle <= fabs(startAngle)) { + // P2 = P2 already defined + P1 = ProjPointOnPlane_XYZ(pntF, sketchPlane); + } + else if (endAngle < M_PI) { + // P2 = P2, already defined + P1 = ProjPointOnPlane_XYZ(pntL, sketchPlane); + } + else { + // P1 = P1, already defined + // P2 = P2, already defined + } + } + } + else if (startAngle < M_PI) { + if (endAngle < M_PI) { + P1 = ProjPointOnPlane_XYZ(pntF, sketchPlane); + P2 = ProjPointOnPlane_XYZ(pntL, sketchPlane); + } + else if (endAngle < 2.0 * M_PI - startAngle) { + P2 = ProjPointOnPlane_XYZ(pntF, sketchPlane); + // P1 = P1, already defined + } + else if (endAngle < 2.0 * M_PI) { + P2 = ProjPointOnPlane_XYZ(pntL, sketchPlane); + // P1 = P1, already defined + } + else { + // P1 = P1, already defined + // P2 = P2, already defined + } + } + else { + if (endAngle < 2 * M_PI) { + P1 = ProjPointOnPlane_XYZ(pntF, sketchPlane); + P2 = ProjPointOnPlane_XYZ(pntL, sketchPlane); + } + else if (endAngle < 4 * M_PI - startAngle) { + P1 = ProjPointOnPlane_XYZ(pntF, sketchPlane); + // P2 = P2, already defined + } + else if (endAngle < 3 * M_PI) { + // P1 = P1, already defined + P2 = ProjPointOnPlane_XYZ(pntL, sketchPlane); + } + else { + // P1 = P1, already defined + // P2 = P2, already defined + } + } + } + Base::Vector3d p1(P1.X(), P1.Y(), P1.Z());// ends of segment FCAD style Base::Vector3d p2(P2.X(), P2.Y(), P2.Z()); invPlm.multVec(p1, p1); @@ -7840,14 +8757,19 @@ void SketchObject::rebuildExternalGeometry() projectedSegment->setPoints(p1, p2); GeometryFacade::setConstruction(projectedSegment, true); - ExternalGeo.push_back(projectedSegment); + geos.emplace_back(projectedSegment); } - else { // projection is an ellipse + else {// general case, full circle + gp_Pnt cnt = origCircle.Location(); + GeomAPI_ProjectPointOnSurf proj(cnt, gPlane); + // projection of circle center on sketch plane, 3D space + cnt = proj.NearestPoint(); // converting to FCAD style vector Base::Vector3d p(cnt.X(), cnt.Y(), cnt.Z()); // transforming towards sketch's (x,y) coordinates invPlm.multVec(p, p); + gp_Vec vecMajorAxis = vec1 ^ vec2;// major axis in 3D space double minorRadius;// TODO use data type of vectors around... @@ -7869,32 +8791,19 @@ void SketchObject::rebuildExternalGeometry() // NB: force normal of ellipse to be normal of sketch's plane. gp_Ax2 refFrameEllipse( gp_Pnt(gp_XYZ(p[0], p[1], p[2])), gp_Vec(0, 0, 1), vecMajorAxis); - Handle(Geom_Ellipse) hEllipse = + Handle(Geom_Ellipse) curve = new Geom_Ellipse(refFrameEllipse, origCircle.Radius(), minorRadius); - if (curve.IsClosed()) { - Part::GeomEllipse* ellipse = new Part::GeomEllipse(); - ellipse->setHandle(hEllipse); - GeometryFacade::setConstruction(ellipse, true); - ExternalGeo.push_back(ellipse); - } - else { - Part::GeomArcOfEllipse* aoe = new Part::GeomArcOfEllipse(); - GeomAPI_ProjectPointOnCurve pFirst(beg, hEllipse); - GeomAPI_ProjectPointOnCurve pLast(end, hEllipse); - Handle(Geom_TrimmedCurve) tCurve = - new Geom_TrimmedCurve(hEllipse, - pFirst.LowerDistanceParameter(), - pLast.LowerDistanceParameter()); - aoe->setHandle(tCurve); - GeometryFacade::setConstruction(aoe, true); - ExternalGeo.push_back(aoe); - } + Part::GeomEllipse* ellipse = new Part::GeomEllipse(); + ellipse->setHandle(curve); + GeometryFacade::setConstruction(ellipse, true); + geos.emplace_back(ellipse); } } } else if (curve.GetType() == GeomAbs_Ellipse) { gp_Elips elipsOrig = curve.Ellipse(); + gp_Elips elipsDest; gp_Pnt origCenter = elipsOrig.Location(); gp_Pnt destCenter = ProjPointOnPlane_UVN(origCenter, sketchPlane).XYZ(); @@ -7952,62 +8861,40 @@ void SketchObject::rebuildExternalGeometry() gp_Ax2 destCurveAx2( destCenter, gp_Dir(0, 0, sens > 0.0 ? 1.0 : -1.0), gp_Dir(destAxisMajor)); + // projection is a circle if ((RDest - rDest) < (double)Precision::Confusion()) { - // projection is a circle - Handle(Geom_Circle) hCircle = new Geom_Circle( - destCurveAx2, 0.5 * (rDest + RDest)); - - if (curve.IsClosed()) { - Part::GeomCircle* circle = new Part::GeomCircle(hCircle); - GeometryFacade::setConstruction(circle, true); - ExternalGeo.push_back(circle); + Handle(Geom_Circle) curve = + new Geom_Circle(destCurveAx2, 0.5 * (rDest + RDest)); + Part::GeomCircle* circle = new Part::GeomCircle(); + circle->setHandle(curve); + GeometryFacade::setConstruction(circle, true); + geos.emplace_back(circle); + } + else { + if (sketchPlane.Position().Direction().IsNormal( + elipsOrig.Position().Direction(), Precision::Angular())) { + gp_Vec start = gp_Vec(destCenter.XYZ()) + destAxisMajor; + gp_Vec end = gp_Vec(destCenter.XYZ()) - destAxisMajor; + + Part::GeomLineSegment* projectedSegment = new Part::GeomLineSegment(); + projectedSegment->setPoints( + Base::Vector3d(start.X(), start.Y(), start.Z()), + Base::Vector3d(end.X(), end.Y(), end.Z())); + GeometryFacade::setConstruction(projectedSegment, true); + geos.emplace_back(projectedSegment); } else { - Part::GeomArcOfCircle* arc = new Part::GeomArcOfCircle(); - GeomAPI_ProjectPointOnCurve pFirst(beg, hCircle); - GeomAPI_ProjectPointOnCurve pLast(end, hCircle); - Handle(Geom_TrimmedCurve) tCurve = - new Geom_TrimmedCurve(hCircle, - pFirst.LowerDistanceParameter(), - pLast.LowerDistanceParameter()); - arc->setHandle(tCurve); - GeometryFacade::setConstruction(arc, true); - ExternalGeo.push_back(arc); - } - } - else if (vec1.IsNormal(vec2, Precision::Angular())) { - // projection is a segment - gp_Vec start = gp_Vec(destCenter.XYZ()) + destAxisMajor; - gp_Vec end = gp_Vec(destCenter.XYZ()) - destAxisMajor; - - Part::GeomLineSegment* projectedSegment = new Part::GeomLineSegment(); - projectedSegment->setPoints( - Base::Vector3d(start.X(), start.Y(), start.Z()), - Base::Vector3d(end.X(), end.Y(), end.Z())); - GeometryFacade::setConstruction(projectedSegment, true); - ExternalGeo.push_back(projectedSegment); - } - else { // projection is an ellipse - Handle(Geom_Ellipse) hEllipse = new Geom_Ellipse( - destCurveAx2, destAxisMajor.Magnitude(), destAxisMinor.Magnitude()); - if (curve.IsClosed()) { + elipsDest.SetPosition(destCurveAx2); + elipsDest.SetMajorRadius(destAxisMajor.Magnitude()); + elipsDest.SetMinorRadius(destAxisMinor.Magnitude()); + + + Handle(Geom_Ellipse) curve = new Geom_Ellipse(elipsDest); Part::GeomEllipse* ellipse = new Part::GeomEllipse(); - ellipse->setHandle(hEllipse); + ellipse->setHandle(curve); GeometryFacade::setConstruction(ellipse, true); - ExternalGeo.push_back(ellipse); - } - else { - Part::GeomArcOfEllipse* aoe = new Part::GeomArcOfEllipse(); - GeomAPI_ProjectPointOnCurve pFirst(beg, hEllipse); - GeomAPI_ProjectPointOnCurve pLast(end, hEllipse); - Handle(Geom_TrimmedCurve) tCurve = - new Geom_TrimmedCurve(hEllipse, - pFirst.LowerDistanceParameter(), - pLast.LowerDistanceParameter()); - aoe->setHandle(tCurve); - GeometryFacade::setConstruction(aoe, true); - ExternalGeo.push_back(aoe); + geos.emplace_back(ellipse); } } } @@ -8030,17 +8917,17 @@ void SketchObject::rebuildExternalGeometry() Base::Vector3d p1(P1.X(), P1.Y(), P1.Z()); Base::Vector3d p2(P2.X(), P2.Y(), P2.Z()); - if (P1.SquareDistance(P2) < Precision::SquareConfusion()) { + if (Base::Distance(p1, p2) < Precision::Confusion()) { Base::Vector3d p = (p1 + p2) / 2; Part::GeomPoint* point = new Part::GeomPoint(p); GeometryFacade::setConstruction(point, true); - ExternalGeo.push_back(point); + geos.emplace_back(point); } else { Part::GeomLineSegment* line = new Part::GeomLineSegment(); line->setPoints(p1, p2); GeometryFacade::setConstruction(line, true); - ExternalGeo.push_back(line); + geos.emplace_back(line); } } else if (projCurve.GetType() == GeomAbs_Circle) { @@ -8049,13 +8936,13 @@ void SketchObject::rebuildExternalGeometry() gp_Pnt P1 = projCurve.Value(projCurve.FirstParameter()); gp_Pnt P2 = projCurve.Value(projCurve.LastParameter()); - if (P1.SquareDistance(P2) < Precision::SquareConfusion()) { + if (P1.SquareDistance(P2) < Precision::Confusion()) { Part::GeomCircle* circle = new Part::GeomCircle(); circle->setRadius(c.Radius()); circle->setCenter(Base::Vector3d(p.X(), p.Y(), p.Z())); GeometryFacade::setConstruction(circle, true); - ExternalGeo.push_back(circle); + geos.emplace_back(circle); } else { Part::GeomArcOfCircle* arc = new Part::GeomArcOfCircle(); @@ -8066,14 +8953,40 @@ void SketchObject::rebuildExternalGeometry() projCurve.LastParameter()); arc->setHandle(tCurve); GeometryFacade::setConstruction(arc, true); - ExternalGeo.push_back(arc); + geos.emplace_back(arc); } } else if (projCurve.GetType() == GeomAbs_BSplineCurve) { - Part::GeomBSplineCurve* bspline = - new Part::GeomBSplineCurve(projCurve.BSpline()); - GeometryFacade::setConstruction(bspline, true); - ExternalGeo.push_back(bspline); + // Unfortunately, a normal projection of a circle can also give + // a Bspline Split the spline into arcs + GeomConvert_BSplineCurveKnotSplitting bSplineSplitter( + projCurve.BSpline(), 2); + // int s = bSplineSplitter.NbSplits(); + if ((curve.GetType() == GeomAbs_Circle) + && (bSplineSplitter.NbSplits() == 2)) { + // Result of projection is actually a circle... + TColStd_Array1OfInteger splits(1, 2); + bSplineSplitter.Splitting(splits); + gp_Pnt p1 = projCurve.Value(splits(1)); + gp_Pnt p2 = projCurve.Value(splits(2)); + gp_Pnt p3 = projCurve.Value(0.5 * (splits(1) + splits(2))); + GC_MakeCircle circleMaker(p1, p2, p3); + Handle(Geom_Circle) circ = circleMaker.Value(); + Part::GeomCircle* circle = new Part::GeomCircle(); + circle->setRadius(circ->Radius()); + gp_Pnt center = circ->Axis().Location(); + circle->setCenter( + Base::Vector3d(center.X(), center.Y(), center.Z())); + + GeometryFacade::setConstruction(circle, true); + geos.emplace_back(circle); + } + else { + Part::GeomBSplineCurve* bspline = + new Part::GeomBSplineCurve(projCurve.BSpline()); + GeometryFacade::setConstruction(bspline, true); + geos.emplace_back(bspline); + } } else if (projCurve.GetType() == GeomAbs_Hyperbola) { gp_Hypr e = projCurve.Hyperbola(); @@ -8085,7 +8998,7 @@ void SketchObject::rebuildExternalGeometry() gp_Dir xdir = e.XAxis().Direction(); gp_Ax2 xdirref(p, normal); - if (P1.SquareDistance(P2) < Precision::SquareConfusion()) { + if (P1.SquareDistance(P2) < Precision::Confusion()) { Part::GeomHyperbola* hyperbola = new Part::GeomHyperbola(); hyperbola->setMajorRadius(e.MajorRadius()); hyperbola->setMinorRadius(e.MinorRadius()); @@ -8093,7 +9006,7 @@ void SketchObject::rebuildExternalGeometry() hyperbola->setAngleXU( -xdir.AngleWithRef(xdirref.XDirection(), normal)); GeometryFacade::setConstruction(hyperbola, true); - ExternalGeo.push_back(hyperbola); + geos.emplace_back(hyperbola); } else { Part::GeomArcOfHyperbola* aoh = @@ -8105,7 +9018,7 @@ void SketchObject::rebuildExternalGeometry() projCurve.LastParameter()); aoh->setHandle(tCurve); GeometryFacade::setConstruction(aoh, true); - ExternalGeo.push_back(aoh); + geos.emplace_back(aoh); } } else if (projCurve.GetType() == GeomAbs_Parabola) { @@ -8118,14 +9031,14 @@ void SketchObject::rebuildExternalGeometry() gp_Dir xdir = e.XAxis().Direction(); gp_Ax2 xdirref(p, normal); - if (P1.SquareDistance(P2) < Precision::SquareConfusion()) { + if (P1.SquareDistance(P2) < Precision::Confusion()) { Part::GeomParabola* parabola = new Part::GeomParabola(); parabola->setFocal(e.Focal()); parabola->setCenter(Base::Vector3d(p.X(), p.Y(), p.Z())); parabola->setAngleXU( -xdir.AngleWithRef(xdirref.XDirection(), normal)); GeometryFacade::setConstruction(parabola, true); - ExternalGeo.push_back(parabola); + geos.emplace_back(parabola); } else { Part::GeomArcOfParabola* aop = @@ -8137,7 +9050,7 @@ void SketchObject::rebuildExternalGeometry() projCurve.LastParameter()); aop->setHandle(tCurve); GeometryFacade::setConstruction(aop, true); - ExternalGeo.push_back(aop); + geos.emplace_back(aop); } } else if (projCurve.GetType() == GeomAbs_Ellipse) { @@ -8146,15 +9059,16 @@ void SketchObject::rebuildExternalGeometry() gp_Pnt P1 = projCurve.Value(projCurve.FirstParameter()); gp_Pnt P2 = projCurve.Value(projCurve.LastParameter()); + // gp_Dir normal = e.Axis().Direction(); gp_Dir normal = gp_Dir(0, 0, 1); gp_Ax2 xdirref(p, normal); - if (P1.SquareDistance(P2) < Precision::SquareConfusion()) { + if (P1.SquareDistance(P2) < Precision::Confusion()) { Part::GeomEllipse* ellipse = new Part::GeomEllipse(); Handle(Geom_Ellipse) curve = new Geom_Ellipse(e); ellipse->setHandle(curve); GeometryFacade::setConstruction(ellipse, true); - ExternalGeo.push_back(ellipse); + geos.emplace_back(ellipse); } else { Part::GeomArcOfEllipse* aoe = new Part::GeomArcOfEllipse(); @@ -8165,7 +9079,7 @@ void SketchObject::rebuildExternalGeometry() projCurve.LastParameter()); aoe->setHandle(tCurve); GeometryFacade::setConstruction(aoe, true); - ExternalGeo.push_back(aoe); + geos.emplace_back(aoe); } } else { @@ -8189,33 +9103,234 @@ void SketchObject::rebuildExternalGeometry() Part::GeomPoint* point = new Part::GeomPoint(p); GeometryFacade::setConstruction(point, true); - ExternalGeo.push_back(point); + geos.emplace_back(point); } break; default: throw Base::TypeError("Unknown type of geometry"); break; + } + + if (intersection && (refSubShape.ShapeType() == TopAbs_EDGE + || refSubShape.ShapeType() == TopAbs_FACE)) + { + BRepAlgoAPI_Section maker(refSubShape, sketchPlane); + maker.Approximation(Standard_True); + if (!maker.IsDone()) + FC_THROWM(Base::CADKernelError,"Failed to get intersection"); + Part::TopoShape intersectionShape(maker.Shape()); + auto edges = intersectionShape.getSubTopoShapes(TopAbs_EDGE); + // Section of some face (e.g. sphere) produce more than one arcs + // from the same circle. So we try to fit the arcs with a single + // circle/arc. + if (refSubShape.ShapeType() == TopAbs_FACE && geos.size() > 1) { + auto wires = Part::TopoShape().makeElementWires(edges); + if (wires.countSubShapes(TopAbs_WIRE) == 1) { + TopoDS_Vertex firstVertex, lastVertex; + BRepTools_WireExplorer exp(TopoDS::Wire(wires.getSubShape(TopAbs_WIRE, 1))); + firstVertex = exp.CurrentVertex(); + while (!exp.More()) + exp.Next(); + lastVertex = exp.CurrentVertex(); + gp_Pnt P1 = BRep_Tool::Pnt(firstVertex); + gp_Pnt P2 = BRep_Tool::Pnt(lastVertex); + if (auto geo = fitArcs(geos, P1, P2, ArcFitTolerance.getValue())) { + geos.clear(); + geos.emplace_back(geo); + } + } + } + } + + } catch (Base::Exception &e) { + FC_ERR("Failed to project external geometry in " + << getFullName() << ": " << key << std::endl << e.what()); + continue; + } catch (Standard_Failure &e) { + FC_ERR("Failed to project external geometry in " + << getFullName() << ": " << key << std::endl << e.GetMessageString()); + continue; + } catch (std::exception &e) { + FC_ERR("Failed to project external geometry in " + << getFullName() << ": " << key << std::endl << e.what()); + continue; + } catch (...) { + FC_ERR("Failed to project external geometry in " + << getFullName() << ": " << key << std::endl << "Unknown exception"); + continue; + } + if(geos.empty()) + continue; + + if(!refSet.emplace(key).second) { + FC_WARN("Duplicated external reference in " << getFullName() << ": " << key); + continue; + } + if (intersection) { + for(auto &geo : geos) { + auto egf = ExternalGeometryFacade::getFacade(geo.get()); + egf->setFlag(ExternalGeometryExtension::Defining, defining); + } + } else if (defining && i+1==(int)Objects.size()) { + for(auto &geo : geos) + ExternalGeometryFacade::getFacade(geo.get())->setFlag( + ExternalGeometryExtension::Defining); + } + for(auto &geo : geos) + ExternalGeometryFacade::getFacade(geo.get())->setRef(key); + newGeos.push_back(std::move(geos)); + } + + // allocate unique geometry id + for(auto &geos : newGeos) { + auto egf = ExternalGeometryFacade::getFacade(geos.front().get()); + auto &refs = externalGeoRefMap[egf->getRef()]; + while(refs.size() < geos.size()) + refs.push_back(++geoLastId); + + // In case a projection reduces output geometries, delete them + std::set geoIds; + geoIds.insert(refs.begin()+geos.size(),refs.end()); + + // Sync id and ref of the new geometries + int i = 0; + for(auto &geo : geos) + GeometryFacade::setId(geo.get(), refs[i++]); + + delExternalPrivate(geoIds,false); + } + + auto geoms = ExternalGeo.getValues(); + + // now update the geometries + for(auto &geos : newGeos) { + for(auto &geo : geos) { + auto it = externalGeoMap.find(GeometryFacade::getId(geo.get())); + if(it == externalGeoMap.end()) { + // This is a new geometries. + geoms.push_back(geo.release()); + continue; + } + // This is an existing geometry. Update it while keeping the old flags + ExternalGeometryFacade::copyFlags(geoms[it->second], geo.get()); + geoms[it->second] = geo.release(); + } + } + + // Check for any missing references + bool hasError = false; + for(auto geo : geoms) { + auto egf = ExternalGeometryFacade::getFacade(geo); + egf->setFlag(ExternalGeometryExtension::Sync,false); + if(egf->getRef().empty()) + continue; + if(!refSet.count(egf->getRef())) { + FC_ERR( "External geometry " << getFullName() << ".e" << egf->getId() + << " missing reference: " << egf->getRef()); + hasError = true; + egf->setFlag(ExternalGeometryExtension::Missing,true); + } else { + egf->setFlag(ExternalGeometryExtension::Missing,false); } } + ExternalGeo.setValues(std::move(geoms)); rebuildVertexIndex(); + + // clean up geometry reference + if(refSet.size() != (size_t)ExternalGeometry.getSize()) { + if(refSet.size() < keys.size()) { + auto itObj = Objects.begin(); + auto itSub = SubElements.begin(); + for(auto &ref : keys) { + if(!refSet.count(ref)) { + itObj = Objects.erase(itObj); + itSub = SubElements.erase(itSub); + }else { + ++itObj; + ++itSub; + } + } + } + ExternalGeometry.setValues(Objects,SubElements); + } + + solverNeedsUpdate=true; + Constraints.acceptGeometry(getCompleteGeometry()); + + if(hasError && this->isRecomputing()) + throw Base::RuntimeError("Missing external geometry reference"); +} + +void SketchObject::fixExternalGeometry(const std::vector &geoIds) { + std::set idSet(geoIds.begin(),geoIds.end()); + auto geos = ExternalGeo.getValues(); + auto objs = ExternalGeometry.getValues(); + auto subs = ExternalGeometry.getSubValues(); + bool touched = false; + for(int i=2;i<(int)geos.size();++i) { + auto &geo = geos[i]; + auto egf = ExternalGeometryFacade::getFacade(geo); + int GeoId = -i-1; + if(egf->getRef().empty() + || !egf->testFlag(ExternalGeometryExtension::Missing) + || (idSet.size() && !idSet.count(GeoId))) + continue; + std::string ref = egf->getRef(); + auto pos = ref.find('.'); + if(pos == std::string::npos) { + FC_ERR("Invalid geometry reference " << ref); + continue; + } + std::string objName = ref.substr(0,pos); + auto obj = getDocument()->getObject(objName.c_str()); + if(!obj) { + FC_ERR("Cannot find object in reference " << ref); + continue; + } + + auto elements = Part::Feature::getRelatedElements(obj,ref.c_str()+pos+1); + if(!elements.size()) { + FC_ERR("No related reference found for " << ref); + continue; + } + + geo = geo->clone(); + egf->setGeometry(geo); + egf->setFlag(ExternalGeometryExtension::Missing,false); + ref = objName + "." + Data::ComplexGeoData::elementMapPrefix(); + elements.front().name.appendToBuffer(ref); + egf->setRef(ref); + objs.push_back(obj); + subs.emplace_back(); + elements.front().index.appendToStringBuffer(subs.back()); + touched = true; + } + + if(touched) { + ExternalGeo.setValues(geos); + ExternalGeometry.setValues(objs,subs); + rebuildExternalGeometry(); + } } std::vector SketchObject::getCompleteGeometry() const { std::vector vals = getInternalGeometry(); - vals.insert(vals.end(), ExternalGeo.rbegin(), ExternalGeo.rend());// in reverse order + const auto &geos = getExternalGeometry(); + vals.insert(vals.end(), geos.rbegin(), geos.rend()); // in reverse order return vals; } GeoListFacade SketchObject::getGeoListFacade() const { std::vector facade; - facade.reserve(Geometry.getSize() + ExternalGeo.size()); + facade.reserve(Geometry.getSize() + ExternalGeo.getSize()); for (auto geo : Geometry.getValues()) facade.push_back(GeometryFacade::getFacade(geo)); - for (auto rit = ExternalGeo.rbegin(); rit != ExternalGeo.rend(); rit++) + const auto &externalGeos = ExternalGeo.getValues(); + for(auto rit = externalGeos.rbegin(); rit != externalGeos.rend(); rit++) facade.push_back(GeometryFacade::getFacade(*rit)); return GeoListFacade::getGeoListModel(std::move(facade), Geometry.getSize()); @@ -8767,6 +9882,21 @@ std::string SketchObject::validateExpression(const App::ObjectIdentifier& path, "expression."; } } + geoMap.clear(); + const auto &vals = getInternalGeometry(); + for(long i=0;i<(long)vals.size();++i) { + auto geo = vals[i]; + auto gf = GeometryFacade::getFacade(geo); + if(!gf->getId()) + gf->setId(++geoLastId); + else if(gf->getId() > geoLastId) + geoLastId = gf->getId(); + while(!geoMap.insert(std::make_pair(gf->getId(),i)).second) { + FC_WARN("duplicate geometry id " << gf->getId() << " -> " << geoLastId+1); + gf->setId(++geoLastId); + } + } + updateGeoHistory(); } return ""; } @@ -8896,6 +10026,38 @@ unsigned int SketchObject::getMemSize() const void SketchObject::Save(Writer& writer) const { + int index = -1; + auto &geos = const_cast(ExternalGeo).getValues(); + for(auto geo : geos) + ExternalGeometryFacade::getFacade(geo)->setRefIndex(-1); + + if(isExporting()) { + // We cannot export shape with the new topological naming, because it + // uses hasher indices that are unique only within its owner document. + // Therefore, we cannot rely on Geometry::Ref as key to map geometry to + // external object reference. So, before exporting, we pre-calculate + // the mapping and store them in Geometry::RefIndex. When importing, + // inside updateGeometryRefs() (called by onDocumentRestore()), we shall + // regenerate Geometry::Ref based on RefIndex. + // + // Note that the regenerated Ref will not be using the new topological + // naming either, because we didn't export them. This is exactly the + // same as if we are opening a legacy file without new names. + // updateGeometryRefs() will know how to handle the name change thanks + // to a flag setup in onUpdateElementReference(). + for(auto &key : externalGeoRef) { + ++index; + auto iter = externalGeoRefMap.find(key); + if(iter == externalGeoRefMap.end()) + continue; + for(auto id : iter->second) { + auto it = externalGeoMap.find(id); + if(it != externalGeoMap.end()) + ExternalGeometryFacade::getFacade(geos[it->second])->setRefIndex(index); + } + } + } + // save the father classes Part::Part2DObject::Save(writer); } @@ -8906,22 +10068,71 @@ void SketchObject::Restore(XMLReader& reader) Part::Part2DObject::Restore(reader); } +void SketchObject::handleChangedPropertyType(Base::XMLReader &reader, + const char *TypeName, App::Property *prop) +{ + if (prop == &Exports) { + if(strcmp(TypeName, "App::PropertyLinkList") == 0) + Exports.Restore(reader); + } +} + +static inline bool checkMigration(Part::PropertyGeometryList &prop) +{ + for (auto g : prop.getValues()) { + if(g->hasExtension(Part::GeometryMigrationExtension::getClassTypeId()) + || !g->hasExtension(SketchGeometryExtension::getClassTypeId())) + return true; + } + return false; +} + void SketchObject::onChanged(const App::Property* prop) { - if (isRestoring() && prop == &Geometry) { - std::vector geom = Geometry.getValues(); - std::vector supportedGeom = supportedGeometry(geom); - // To keep upward compatibility ignore unsupported geometry types - if (supportedGeom.size() != geom.size()) { - Geometry.setValues(supportedGeom); - return; + if (prop == &Geometry) { + if (isRestoring() && checkMigration(Geometry)) { + // Construction migration to extension + for( auto geometryValue : Geometry.getValues()) { + if(geometryValue->hasExtension(Part::GeometryMigrationExtension::getClassTypeId())) { + auto ext = std::static_pointer_cast( + geometryValue->getExtension(Part::GeometryMigrationExtension::getClassTypeId()).lock()); + + auto gf = GeometryFacade::getFacade(geometryValue); // at this point IA geometry is already migrated + + if(ext->testMigrationType(Part::GeometryMigrationExtension::Construction)) { + bool oldconstr = ext->getConstruction(); + if( geometryValue->getTypeId() == Part::GeomPoint::getClassTypeId() && !gf->isInternalAligned()){ + oldconstr = true; + } + gf->setConstruction(oldconstr); + } + if(ext->testMigrationType(Part::GeometryMigrationExtension::GeometryId)) { + gf->setId(ext->getId()); + } + } + } + } + geoMap.clear(); + const auto &vals = getInternalGeometry(); + for(long i=0;i<(long)vals.size();++i) { + auto geo = vals[i]; + auto gf = GeometryFacade::getFacade(geo); + if(gf->getId() == 0) { + gf->setId(++geoLastId); + } else if(gf->getId() > geoLastId) { + geoLastId = gf->getId(); + } + while(!geoMap.insert(std::make_pair(gf->getId(),i)).second) { + FC_WARN("duplicate geometry id " << gf->getId() << " -> " << geoLastId+1); // NOLINT + gf->setId(++geoLastId); + } } + updateGeoHistory(); } - if (prop == &Geometry || prop == &Constraints) { - - auto doc = getDocument(); + auto doc = getDocument(); + if (prop == &Geometry || prop == &Constraints) { if (doc && doc->isPerformingTransaction()) {// undo/redo setStatus(App::PendingTransactionUpdate, true); } @@ -8951,6 +10162,8 @@ void SketchObject::onChanged(const App::Property* prop) "SketchObject::onChanged(): Unmanaged change of Geometry Property " "results in invalid constraint indices\n"); } + Base::StateLocker lock(internaltransaction, true); + setUpSketch(); } } else {// Change is in Constraints @@ -8979,25 +10192,276 @@ void SketchObject::onChanged(const App::Property* prop) "SketchObject::onChanged(): Unmanaged change of Constraint " "Property results in invalid constraint indices\n"); } + Base::StateLocker lock(internaltransaction, true); + setUpSketch(); + } + } + } + } + } else if ( prop == &ExternalGeo && !prop->testStatus(App::Property::User3) ) { + if(doc && doc->isPerformingTransaction()) { + setStatus(App::PendingTransactionUpdate, true); +} + + if (isRestoring() && checkMigration(ExternalGeo)) { + for( auto geometryValue : ExternalGeo.getValues()) { + if(geometryValue->hasExtension(Part::GeometryMigrationExtension::getClassTypeId())) { + auto ext = std::static_pointer_cast( + geometryValue->getExtension(Part::GeometryMigrationExtension::getClassTypeId()).lock()); + std::unique_ptr egf; + if(ext->testMigrationType(Part::GeometryMigrationExtension::GeometryId)) { + egf = ExternalGeometryFacade::getFacade(geometryValue); + egf->setId(ext->getId()); + } + + if(ext->testMigrationType(Part::GeometryMigrationExtension::ExternalReference)) { + if (!egf) { + egf = ExternalGeometryFacade::getFacade(geometryValue); + } + egf->setRef(ext->getRef()); + egf->setRefIndex(ext->getRefIndex()); + egf->setFlags(ext->getFlags()); + } + } + } + } + externalGeoRefMap.clear(); + externalGeoMap.clear(); + std::set detached; + for(int i=0;itestFlag(ExternalGeometryExtension::Detached)) { + if(!egf->getRef().empty()) { + detached.insert(egf->getRef()); + egf->setRef(std::string()); + } + egf->setFlag(ExternalGeometryExtension::Detached,false); + egf->setFlag(ExternalGeometryExtension::Missing,false); + } + if(egf->getId() > geoLastId) { + geoLastId = egf->getId(); + } + if(!externalGeoMap.emplace(egf->getId(),i).second) { + FC_WARN("duplicate geometry id " << egf->getId() << " -> " << geoLastId+1); // NOLINT + egf->setId(++geoLastId); + externalGeoMap[egf->getId()] = i; + } + if(!egf->getRef().empty()) { + externalGeoRefMap[egf->getRef()].push_back(egf->getId()); + } + } + if(!detached.empty()) { + auto objs = ExternalGeometry.getValues(); + assert(externalGeoRef.size() == objs.size()); + auto itObj = objs.begin(); + auto subs = ExternalGeometry.getSubValues(); + auto itSub = subs.begin(); + for(const auto & i : externalGeoRef) { + if(detached.count(i) != 0U) { + itObj = objs.erase(itObj); + itSub = subs.erase(itSub); + auto &refs = externalGeoRefMap[i]; + for(long id : refs) { + auto it = externalGeoMap.find(id); + if(it!=externalGeoMap.end()) { + auto geo = ExternalGeo[it->second]; + ExternalGeometryFacade::getFacade(geo)->setRef(std::string()); + } } + refs.clear(); + } else { + ++itObj; + ++itSub; } } + ExternalGeometry.setValues(objs,subs); + } + else { + signalElementsChanged(); } } else if (prop == &ExternalGeometry) { +#ifdef FC_USE_TNP_FIX + if(doc && doc->isPerformingTransaction()) { + setStatus(App::PendingTransactionUpdate, true); + } + + if(!isRestoring()) { + // must wait till onDocumentRestored() when shadow references are + // fully restored + updateGeometryRefs(); + signalElementsChanged(); + } + } else if (prop == &Placement) { + if (ExternalGeometry.getSize() > 0) { + touch(); + } + } else if (prop == &ExpressionEngine) { + if(!isRestoring() + && doc && !doc->isPerformingTransaction() + && noRecomputes + && !managedoperation) + { + // if we do not have a recompute, the sketch must be solved to + // update the DoF of the solver, constraints and UI + try { + auto res = ExpressionEngine.execute(); + if(res) { + FC_ERR("Failed to recompute " << ExpressionEngine.getFullName() << ": " << res->Why); // NOLINT + delete res; + } + } catch (Base::Exception &e) { + e.ReportException(); + FC_ERR("Failed to recompute " << ExpressionEngine.getFullName() << ": " << e.what()); // NOLINT + } + solve(); + } +#else // make sure not to change anything while restoring this object if (!isRestoring()) { // external geometry was cleared if (ExternalGeometry.getSize() == 0) { delConstraintsToExternal(); } + } +#endif + } +#if 0 + // For now do not delete anything (#0001791). When changing the support + // face it might be better to check which external geometries can be kept. + else if (prop == &AttachmentSupport) { + // make sure not to change anything while restoring this object + if (!isRestoring()) { + // if support face has changed then clear the external geometry + delConstraintsToExternal(); + for (int i=0; i < getExternalGeometryCount(); i++) { + delExternal(0); + } rebuildExternalGeometry(); } } - +#endif Part::Part2DObject::onChanged(prop); } +void SketchObject::onUpdateElementReference(const App::Property *prop) { + if(prop == &ExternalGeometry) { + updateGeoRef = true; + // Must call updateGeometryRefs() now to avoid the case of recursive + // property change (e.g. temporary object removal in SubShapeBinder) + // afterwards causing assertion failure, although this may mean extra + // call of updateGeometryRefs() later in onChange(). + updateGeometryRefs(); + signalElementsChanged(); + } +} + +void SketchObject::updateGeometryRefs() { + const auto &objs = ExternalGeometry.getValues(); + const auto &subs = ExternalGeometry.getSubValues(); + const auto &shadows = ExternalGeometry.getShadowSubs(); + assert(subs.size() == shadows.size()); + std::vector originalRefs; + std::map refMap; + if(updateGeoRef) { + assert(externalGeoRef.size() == objs.size()); + updateGeoRef = false; + originalRefs = std::move(externalGeoRef); + } + externalGeoRef.clear(); + std::unordered_map legacyMap; + for(int i=0;i<(int)objs.size();++i) { + auto obj = objs[i]; + const std::string &sub = shadows[i].newName.empty() ? subs[i] : shadows[i].newName; + externalGeoRef.emplace_back(obj->getNameInDocument()); + auto &key = externalGeoRef.back(); + key += '.'; + + legacyMap[key + Data::oldElementName(sub.c_str())] = i; + + if (!obj->getTypeId().isDerivedFrom(Part::Datum::getClassTypeId())) { + key += Data::newElementName(sub.c_str()); + } + if (!originalRefs.empty() && originalRefs[i] != key) { + refMap[originalRefs[i]] = key; + } + } + bool touched = false; + auto geos = ExternalGeo.getValues(); + if(refMap.empty()) { + for(auto geo : geos) { + auto egf = ExternalGeometryFacade::getFacade(geo); + if(egf->getRefIndex()<0) { + if (egf->getId() < 0 && !egf->getRef().empty()) { + // NOLINTNEXTLINE + FC_ERR("External geometry reference corrupted in " << getFullName() + << " Please check."); + // This could happen if someone saved the sketch containing + // external geometries using some rouge releases during the + // migration period. As a remedy, We re-initiate the + // external geometry here to trigger rebuild later, with + // call to rebuildExternalGeometry() + initExternalGeo(); + return; + } + auto it = legacyMap.find(egf->getRef()); + if (it != legacyMap.end() && egf->getRef() != externalGeoRef[it->second]) { + if(getDocument() && !getDocument()->isPerformingTransaction()) { + // FIXME: this is a bug. Find out when and why does this happen + // + // Amendment: maybe the original bug is because of not + // handling external geometry changes during undo/redo, + // which should be considered as normal. So warning only + // if not undo/redo. + // + // NOLINTNEXTLINE + FC_WARN("Update legacy external reference " << egf->getRef() << " -> " + << externalGeoRef[it->second] << " in " << getFullName()); + } else { + // NOLINTNEXTLINE + FC_LOG("Update undo/redo external reference " << egf->getRef() << " -> " + << externalGeoRef[it->second] << " in " << getFullName()); + } + touched = true; + egf->setRef(externalGeoRef[it->second]); + } + continue; + } + if(egf->getRefIndex() < (int)externalGeoRef.size() + && egf->getRef() != externalGeoRef[egf->getRefIndex()]) + { + touched = true; + egf->setRef(externalGeoRef[egf->getRefIndex()]); + } + egf->setRefIndex(-1); + } + }else{ + for(auto &v : refMap) { + auto it = externalGeoRefMap.find(v.first); + if (it == externalGeoRefMap.end()) { + continue; + } + for(long id : it->second) { + auto iter = externalGeoMap.find(id); + if(iter!=externalGeoMap.end()) { + auto &geo = geos[iter->second]; + geo = geo->clone(); + auto egf = ExternalGeometryFacade::getFacade(geo); + // NOLINTNEXTLINE + FC_LOG(getFullName() << " ref change on ExternalEdge" + << iter->second-1 << ' ' << egf->getRef() << " -> " << v.second); + egf->setRef(v.second); + touched = true; + } + } + } + } + if(touched) { + ExternalGeo.setValues(std::move(geos)); + } +} + void SketchObject::onUndoRedoFinished() { // upon undo/redo, PropertyConstraintList does not have updated valid geometry keys, which @@ -9108,6 +10572,14 @@ void SketchObject::onDocumentRestored() try { restoreFinished(); Part::Part2DObject::onDocumentRestored(); + + if (getDocument()->testStatus(App::Document::Importing)) { + App::GeoFeatureGroupExtension *grp = nullptr; + auto grpObj = App::GeoFeatureGroupExtension::getGroupOfObject(this); + if (grpObj) + grp = grpObj->getExtensionByType(true); + } + } catch (...) { } @@ -9118,9 +10590,28 @@ void SketchObject::restoreFinished() try { migrateSketch(); +#ifdef FC_USE_TNP_FIX + updateGeometryRefs(); + if(ExternalGeo.getSize()<=2) { + if (ExternalGeo.getSize() < 2) + initExternalGeo(); + for(auto &key : externalGeoRef) { + long id = getDocument()->getStringHasher()->getID(key.c_str()).value(); + if(geoLastId < id) + geoLastId = id; + externalGeoRefMap[key].push_back(id); + } + rebuildExternalGeometry(); + if(ExternalGeometry.getSize()+2!=ExternalGeo.getSize()) + FC_WARN("Failed to restore some external geometry in " << getFullName()); + }else + acceptGeometry(); + +#else validateExternalLinks(); rebuildExternalGeometry(); Constraints.acceptGeometry(getCompleteGeometry()); +#endif synchroniseGeometryState(); // this may happen when saving a sketch directly in edit mode // but never performed a recompute before @@ -9128,8 +10619,32 @@ void SketchObject::restoreFinished() if (this->solve(true) == 0) Shape.setValue(solvedSketch.toShape()); } - } - catch (...) { + + // Sanity check on constraints with expression. It is added because the + // way SketchObject syncs expression and constraints heavily relies on + // proper setup of undo/redo transactions. The missing transaction in + // EditDatumDialog may cause stray or worse wrongly bound expressions. + for (auto &v : ExpressionEngine.getExpressions()) { + if (v.first.getProperty() != &Constraints) + continue; + const Constraint * cstr = nullptr; + try { + cstr = Constraints.getConstraint(v.first); + } catch (Base::Exception &) { + } + if (!cstr || !cstr->isDimensional()) { + FC_WARN((cstr ? "Invalid" : "Orphan") + << " constraint expression in " + << getFullName() << "." + << v.first.toString() + << ": " << v.second->toString()); + ExpressionEngine.setValue(v.first, nullptr); + } + } + } catch (Base::Exception &e) { + e.ReportException(); + FC_ERR("Error while restoring " << getFullName()); + } catch (...) { } } @@ -9431,6 +10946,7 @@ int SketchObject::port_reversedExternalArcs(bool justAnalyze) if (geoId <= GeoEnum::RefExt && (posId == Sketcher::PointPos::start || posId == Sketcher::PointPos::end)) { // we are dealing with a link to an endpoint of external geom +// Part::Geometry* g = this->ExternalGeo[-geoId - 1]; Part::Geometry* g = this->ExternalGeo[-geoId - 1]; if (g->is()) { const Part::GeomArcOfCircle* segm = @@ -9596,6 +11112,17 @@ App::DocumentObject *SketchObject::getSubObject( const char *mapped = Data::isMappedElement(subname); if(!subname || !subname[0]) return Part2DObject::getSubObject(subname,pyObj,pmat,transform,depth); + const char *element = Data::findElementName(subname); + if(element != subname) { + const char *dot = strchr(subname,'.'); + if(!dot) + return 0; + std::string name(subname,dot-subname); + auto child = Exports.find(name.c_str()); + if(!child) + return 0; + return child->getSubObject(dot+1,pyObj,pmat,true,depth+1); + } Data::IndexedName indexedName = checkSubName(subname); int index = indexedName.getIndex(); @@ -9685,8 +11212,14 @@ App::DocumentObject *SketchObject::getSubObject( if(pmat) point = (*pmat)*point; shape = BRepBuilderAPI_MakeVertex(gp_Pnt(point.x,point.y,point.z)).Vertex(); + // Originally in ComplexGeoData::setElementName + // LinkStable/src/App/ComplexGeoData.cpp#L1631 + // No longer possible after map separated in ElementMap.cpp + if ( !shape.hasElementMap() ) { + shape.resetElementMap(std::make_shared()); + } shape.setElementName(Data::IndexedName::fromConst("Vertex", 1), - Data::MappedName::fromRawData(name.c_str()), 0); + Data::MappedName::fromRawData(name.c_str()),0); } shape.Tag = getID(); *pyObj = Py::new_reference_to(Part::shape2pyshape(shape)); @@ -9796,7 +11329,7 @@ App::ElementNamePair SketchObject::getElementName( // Todo: Toponaming Project March 2024: This method override breaks the sketcher - selection and deletion // of constraints ceases to work. See #13169. We need to prove that this works before // enabling it. - return Part2DObject::getElementName(name,type); +// return Part2DObject::getElementName(name,type); #ifndef FC_USE_TNP_FIX return Part2DObject::getElementName(name,type); #endif @@ -9949,6 +11482,36 @@ Data::IndexedName SketchObject::checkSubName(const char *subname) const{ return Data::IndexedName(); } +Data::IndexedName SketchObject::shapeTypeFromGeoId(int geoId, PointPos posId) const { + if(geoId == GeoEnum::HAxis) { + if(posId == PointPos::start) { + return Data::IndexedName::fromConst("RootPoint", 0); + } + return Data::IndexedName::fromConst("H_Axis", 0); + } + if(geoId == GeoEnum::VAxis) { + return Data::IndexedName::fromConst("V_Axis", 0); + } + + if (posId == PointPos::none) { + auto geo = getGeometry(geoId); + if (geo && geo->isDerivedFrom(Part::GeomPoint::getClassTypeId())) { + posId = PointPos::start; + } + } + if(posId != PointPos::none) { + int idx = getVertexIndexGeoPos(geoId, posId); + if(idx < 0){ + return Data::IndexedName(); + } + return Data::IndexedName::fromConst("Vertex", idx+1); + } + if (geoId >= 0) { + return Data::IndexedName::fromConst("Edge", geoId+1); + } + return Data::IndexedName::fromConst("ExternalEdge", -geoId-2); +} + bool SketchObject::geoIdFromShapeType(const Data::IndexedName & indexedName, int &geoId, PointPos &posId) const @@ -10041,6 +11604,40 @@ std::string SketchObject::convertSubName(const Data::IndexedName &indexedName, b return ss.str(); } +std::string SketchObject::getGeometryReference(int GeoId) const { + auto geo = getGeometry(GeoId); + if (!geo) { + return {}; + } + auto egf = ExternalGeometryFacade::getFacade(geo); + if (egf->getRef().empty()) { + return {}; + } + + const std::string &ref = egf->getRef(); + + if(egf->testFlag(ExternalGeometryExtension::Missing)) { + return std::string("? ") + ref; + } + + auto pos = ref.find('.'); + if(pos == std::string::npos) { + return ref; + } + std::string objName = ref.substr(0,pos); + auto obj = getDocument()->getObject(objName.c_str()); + if(!obj) { + return ref; + } + + App::ElementNamePair elementName; + App::GeoFeature::resolveElement(obj,ref.c_str()+pos+1,elementName); + if (!elementName.oldName.empty()) { + return objName + "." + elementName.oldName; + } + return ref; +} + int SketchObject::autoConstraint(double precision, double angleprecision, bool includeconstruction) { return analyser->autoconstraint(precision, angleprecision, includeconstruction); @@ -10269,4 +11866,5 @@ PyObject* Sketcher::SketchObjectPython::getPyObject() // explicit template instantiation template class SketcherExport FeaturePythonT; }// namespace App + // clang-format on diff --git a/src/Mod/Sketcher/App/SketchObject.h b/src/Mod/Sketcher/App/SketchObject.h index 54921dfdc8c8..dd32a9a9364e 100644 --- a/src/Mod/Sketcher/App/SketchObject.h +++ b/src/Mod/Sketcher/App/SketchObject.h @@ -38,6 +38,8 @@ #include "GeometryFacade.h" #include "Sketch.h" +#include "SketchGeometryExtension.h" +#include "ExternalGeometryExtension.h" namespace Sketcher { @@ -66,8 +68,12 @@ class SketcherExport SketchObject: public Part::Part2DObject Part ::PropertyGeometryList Geometry; Sketcher::PropertyConstraintList Constraints; App ::PropertyLinkSubList ExternalGeometry; + App ::PropertyLinkListHidden Exports; + Part ::PropertyGeometryList ExternalGeo; App ::PropertyBool FullyConstrained; + App ::PropertyPrecision ArcFitTolerance; Part ::PropertyPartShape InternalShape; + App ::PropertyPrecision InternalTolerance; App ::PropertyBool MakeInternals; /** @name methods override Feature */ //@{ @@ -172,16 +178,31 @@ class SketcherExport SketchObject: public Part::Part2DObject /// Carbon copy another sketch geometry and constraints int carbonCopy(App::DocumentObject* pObj, bool construction = true); /// add an external geometry reference - int addExternal(App::DocumentObject* Obj, const char* SubName); + int addExternal(App::DocumentObject* Obj, + const char* SubName, + bool defining = false, + bool intersection = false); /** delete external * ExtGeoId >= 0 with 0 corresponding to the first user defined * external geometry */ int delExternal(int ExtGeoId); + int delExternal(const std::vector& ExtGeoIds); + /// attach a link reference to an external geometry + int + attachExternal(const std::vector& geoIds, App::DocumentObject* Obj, const char* SubName); + int detachExternal(const std::vector& geoIds); /** deletes all external geometry */ int delAllExternal(); + const Part::Geometry* _getGeometry(int GeoId) const; + int setGeometry(int GeoId, const Part::Geometry*); + /// returns GeoId of all geometries projected from the same external geometry reference + std::vector getRelatedGeometry(int GeoId) const; + /// Sync frozen external geometries + int syncGeometry(const std::vector& geoIds); + /** returns a pointer to a given Geometry index, possible indexes are: * id>=0 for user defined geometries, * id==-1 for the horizontal sketch axis, @@ -192,7 +213,10 @@ class SketcherExport SketchObject: public Part::Part2DObject typename GeometryT = Part::Geometry, typename = typename std::enable_if< std::is_base_of::type>::value>::type> - const GeometryT* getGeometry(int GeoId) const; + const GeometryT* getGeometry(int GeoId) const + { + return static_cast(_getGeometry(GeoId)); + } std::unique_ptr getGeometryFacade(int GeoId) const; @@ -204,15 +228,17 @@ class SketcherExport SketchObject: public Part::Part2DObject /// returns a list of projected external geometries const std::vector& getExternalGeometry() const { - return ExternalGeo; + return ExternalGeo.getValues(); } /// rebuilds external geometry (projection onto the sketch plane) - void rebuildExternalGeometry(); + void rebuildExternalGeometry(bool defining = false, bool intersection = false); /// returns the number of external Geometry entities int getExternalGeometryCount() const { - return ExternalGeo.size(); + return ExternalGeo.getSize(); } + /// auto fix external geometry references + void fixExternalGeometry(const std::vector& geoIds = {}); /// retrieves a vector containing both normal and external Geometry (including the sketch axes) std::vector getCompleteGeometry() const; @@ -516,6 +542,9 @@ class SketcherExport SketchObject: public Part::Part2DObject unsigned int getMemSize() const override; void Save(Base::Writer& /*writer*/) const override; void Restore(Base::XMLReader& /*reader*/) override; + void handleChangedPropertyType(Base::XMLReader& reader, + const char* TypeName, + App::Property* prop) override; /// returns the number of construction lines (to be used as axes) int getAxisCount() const override; @@ -704,6 +733,9 @@ class SketcherExport SketchObject: public Part::Part2DObject return geoIdFromShapeType(shapetype, geoId, posId); } + /// Return a human friendly element reference of an external geometry + std::string getGeometryReference(int GeoId) const; + std::string convertSubName(const char* subname, bool postfix = true) const; std::string convertSubName(const std::string& subname, bool postfix = true) const @@ -716,6 +748,8 @@ class SketcherExport SketchObject: public Part::Part2DObject std::string convertSubName(const Data::IndexedName&, bool postfix = true) const; + Data::IndexedName shapeTypeFromGeoId(int GeoId, PointPos pos = Sketcher::PointPos::none) const; + App::ElementNamePair getElementName(const char* name, ElementNameType type) const override; bool isPerformingInternalTransaction() const @@ -789,13 +823,17 @@ class SketcherExport SketchObject: public Part::Part2DObject int getGeometryId(int GeoId, long& id) const; protected: + // Only the first flag is toggled, the rest of the flags is set or cleared following the first + // flag. + int toggleExternalGeometryFlag(const std::vector& geoIds, + const std::vector& flags); + + void buildShape(); /// get called by the container when a property has changed void onChanged(const App::Property* /*prop*/) override; void onDocumentRestored() override; void restoreFinished() override; - void buildShape(); - std::string validateExpression(const App::ObjectIdentifier& path, std::shared_ptr expr); @@ -809,6 +847,8 @@ class SketcherExport SketchObject: public Part::Part2DObject std::vector supportedGeometry(const std::vector& geoList) const; + void updateGeoHistory(); + void generateId(Part::Geometry* geo); /*! \brief Transfer constraints on lines being filleted. @@ -827,6 +867,14 @@ class SketcherExport SketchObject: public Part::Part2DObject // check whether constraint may be changed driving status int testDrivingChange(int ConstrId, bool isdriving); + void initExternalGeo(); + + void onUpdateElementReference(const App::Property*) override; + + void delExternalPrivate(const std::set& ids, bool removeReference); + + void updateGeometryRefs(); + void onUndoRedoFinished() override; // migration functions @@ -879,8 +927,6 @@ class SketcherExport SketchObject: public Part::Part2DObject /// Flag to allow carbon copy from misaligned geometry bool allowUnaligned; - std::vector ExternalGeo; - std::vector VertexId2GeoId; std::vector VertexId2PosId; @@ -932,9 +978,30 @@ class SketcherExport SketchObject: public Part::Part2DObject bool internaltransaction; - // indicates whether changes to properties are the deed of SketchObject or not (for input - // validation) - bool managedoperation; + bool managedoperation; // indicates whether changes to properties are the deed of SketchObject + // or not (for input validation) + + // mapping from ExternalGeometry[*] to ExternalGeo[*].Id + // Some external geometry may generate more than one projection + std::map> externalGeoRefMap; + bool updateGeoRef = false; + + // backup of ExternalGeometry in case of element reference change + std::vector externalGeoRef; + + // mapping from ExternalGeo[*].Id to index of ExternalGeo + std::map externalGeoMap; + + // mapping from Geometry[*].Id to index of Geometry + std::map geoMap; + + int geoHistoryLevel; + std::vector geoIdHistory; + long geoLastId; + + class GeoHistory; + std::unique_ptr geoHistory; + mutable std::map internalElementMap; }; @@ -973,24 +1040,10 @@ inline int SketchObject::moveTemporaryPoint(int geoId, return solvedSketch.movePoint(geoId, pos, toPoint, relative); } -template -const GeometryT* SketchObject::getGeometry(int GeoId) const -{ - if (GeoId >= 0) { - const std::vector& geomlist = getInternalGeometry(); - if (GeoId < int(geomlist.size())) { - return static_cast(geomlist[GeoId]); - } - } - else if (-GeoId <= int(ExternalGeo.size())) { - return static_cast(ExternalGeo[-GeoId - 1]); - } - - return nullptr; -} using SketchObjectPython = App::FeaturePythonT; +// --------------------------------------------------------- } // namespace Sketcher diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp index d5d158388709..a3f2d7170110 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp @@ -2884,41 +2884,38 @@ void ViewProviderSketch::drawEditMarkers(const std::vector& Edit editCoinManager->drawEditMarkers(EditMarkers, augmentationlevel); } -void ViewProviderSketch::updateData(const App::Property* prop) -{ +void ViewProviderSketch::updateData(const App::Property* prop) { ViewProvider2DObject::updateData(prop); - // In the case of an undo/redo transaction, updateData is triggered by - // SketchObject::onUndoRedoFinished() in the solve() In the case of an internal transaction, - // touching the geometry results in a call to updateData. - if (isInEditMode() && !getSketchObject()->getDocument()->isPerformingTransaction() - && !getSketchObject()->isPerformingInternalTransaction() - && (prop == &(getSketchObject()->Geometry) || prop == &(getSketchObject()->Constraints))) { - - // At this point, we do not need to solve the Sketch - // If we are adding geometry an update can be triggered before the sketch is actually - // solved. Because a solve is mandatory to any addition (at least to update the DoF of the - // solver), only when the solver geometry is the same in number than the sketch geometry an - // update should trigger a redraw. This reduces even more the number of redraws per - // insertion of geometry - - // solver information is also updated when no matching geometry, so that if a solving fails - // this failed solving info is presented to the user - UpdateSolverInformation();// just update the solver window with the last SketchObject - // solving information - - if (getSketchObject()->getExternalGeometryCount() - + getSketchObject()->getHighestCurveIndex() + 1 - == getSolvedSketch().getGeometrySize()) { - Gui::MDIView* mdi = Gui::Application::Instance->editDocument()->getActiveView(); - if (mdi->isDerivedFrom(Gui::View3DInventor::getClassTypeId())) - draw(false, true); + if (prop != &getSketchObject()->Constraints) + signalElementsChanged(); +} - signalConstraintsChanged(); - } +void ViewProviderSketch::slotSolverUpdate() +{ + if (!isInEditMode() ) + return; - if (prop != &getSketchObject()->Constraints) - signalElementsChanged(); + // At this point, we do not need to solve the Sketch + // If we are adding geometry an update can be triggered before the sketch is actually + // solved. Because a solve is mandatory to any addition (at least to update the DoF of the + // solver), only when the solver geometry is the same in number than the sketch geometry an + // update should trigger a redraw. This reduces even more the number of redraws per + // insertion of geometry + + // solver information is also updated when no matching geometry, so that if a solving fails + // this failed solving info is presented to the user + UpdateSolverInformation();// just update the solver window with the last SketchObject + // solving information + + if (getSketchObject()->getExternalGeometryCount() + + getSketchObject()->getHighestCurveIndex() + 1 + == getSolvedSketch().getGeometrySize()) { + Gui::MDIView* mdi = Gui::Application::Instance->editDocument()->getActiveView(); + if (mdi->isDerivedFrom(Gui::View3DInventor::getClassTypeId())) + draw(false, true); + + signalConstraintsChanged(); } } @@ -3124,6 +3121,15 @@ bool ViewProviderSketch::setEdit(int ModNum) getSketchObject()->validateExternalLinks(); } + //NOLINTBEGIN + connectUndoDocument = getDocument()->signalUndoDocument.connect( + std::bind(&ViewProviderSketch::slotUndoDocument, this, sp::_1)); + connectRedoDocument = getDocument()->signalRedoDocument.connect( + std::bind(&ViewProviderSketch::slotRedoDocument, this, sp::_1)); + connectSolverUpdate = getSketchObject() + ->signalSolverUpdate.connect(boost::bind(&ViewProviderSketch::slotSolverUpdate, this)); + //NOLINTEND + // There are geometry extensions introduced by the solver and geometry extensions introduced by // the viewprovider. // 1. It is important that the solver has geometry with updated extensions. @@ -3139,13 +3145,6 @@ bool ViewProviderSketch::setEdit(int ModNum) // a draw(true) via ViewProvider::UpdateData. getSketchObject()->solve(true); - //NOLINTBEGIN - connectUndoDocument = getDocument()->signalUndoDocument.connect( - std::bind(&ViewProviderSketch::slotUndoDocument, this, sp::_1)); - connectRedoDocument = getDocument()->signalRedoDocument.connect( - std::bind(&ViewProviderSketch::slotRedoDocument, this, sp::_1)); - //NOLINTEND - // Enable solver initial solution update while dragging. getSketchObject()->setRecalculateInitialSolutionWhileMovingPoint( viewProviderParameters.recalculateInitialSolutionWhileDragging); @@ -3355,6 +3354,7 @@ void ViewProviderSketch::unsetEdit(int ModNum) connectUndoDocument.disconnect(); connectRedoDocument.disconnect(); + connectSolverUpdate.disconnect(); // when pressing ESC make sure to close the dialog Gui::Control().closeDialog(); diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.h b/src/Mod/Sketcher/Gui/ViewProviderSketch.h index 90d8372cd21d..61837ec680ca 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.h +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.h @@ -745,6 +745,7 @@ class SketcherGuiExport ViewProviderSketch: public PartGui::ViewProvider2DObject //@{ void slotUndoDocument(const Gui::Document&); void slotRedoDocument(const Gui::Document&); + void slotSolverUpdate(); void forceUpdateData(); //@} @@ -924,6 +925,7 @@ class SketcherGuiExport ViewProviderSketch: public PartGui::ViewProvider2DObject private: boost::signals2::connection connectUndoDocument; boost::signals2::connection connectRedoDocument; + boost::signals2::connection connectSolverUpdate; // modes while sketching SketchMode Mode; diff --git a/src/Mod/Sketcher/SketcherTests/TestSketcherSolver.py b/src/Mod/Sketcher/SketcherTests/TestSketcherSolver.py index e83e9093e495..b2733ad31c0e 100644 --- a/src/Mod/Sketcher/SketcherTests/TestSketcherSolver.py +++ b/src/Mod/Sketcher/SketcherTests/TestSketcherSolver.py @@ -537,6 +537,113 @@ def testRemovedExternalGeometryReference(self): self.assertEqual(len(hole.Shape.Edges), 32) self.assertEqual(len(sketch2.ExternalGeometry), 1) + def testSaveLoadWithExternalGeometryReference(self): + body = self.Doc.addObject("PartDesign::Body", "Body") + sketch = self.Doc.addObject("Sketcher::SketchObject", "Sketch") + CreateRectangleSketch(sketch, (0, 0), (30, 30)) + pad = self.Doc.addObject("PartDesign::Pad", "Pad") + pad.Profile = sketch + sketch1 = self.Doc.addObject("Sketcher::SketchObject", "Sketch1") + body.addObject(sketch) + body.addObject(pad) + body.addObject(sketch1) + self.Doc.recompute() + sketch1.addExternal("Pad", "Edge12") + self.Doc.recompute() + + # Try changing it before the save + sketch = self.Doc.getObject("Sketch") + g1 = sketch.Constraints[11].First + d1 = sketch.Constraints[11].Value + sketch.delConstraint(11) + sketch.addConstraint(Sketcher.Constraint("Distance", g1, d1 - 1.0)) + self.Doc.recompute() + + filename = self.Doc.Name + ".FCStd" + self.Doc.saveAs(filename) + FreeCAD.closeDocument(self.Doc.Name) + self.Doc = FreeCAD.openDocument(filename) + pad = self.Doc.getObject("Pad") + self.Doc.recompute() + pad = self.Doc.getObject("Pad") + + sketch = self.Doc.getObject("Sketch") + g1 = sketch.Constraints[11].First + d1 = sketch.Constraints[11].Value + sketch.delConstraint(11) + sketch.addConstraint(Sketcher.Constraint("Distance", g1, d1 - 1.0)) + self.Doc.recompute() + + pad = self.Doc.getObject("Pad") + # TODO: Assert some stuff when the bug is fixed + # self.assertEqual(pad.Shape.ElementMapSize,0) + # self.assertNotNull(pad.Shape.ElementReverseMap["Edge12"]) + + def testTNPExternalGeometryStored(self): + # Arrange + import xml.etree.ElementTree as ET + + sketch = self.Doc.addObject("Sketcher::SketchObject", "Sketch") + CreateRectangleSketch(sketch, (0, 0), (30, 30)) + sketch1 = self.Doc.addObject("Sketcher::SketchObject", "Sketch1") + pad = self.Doc.addObject("PartDesign::Pad", "Pad") + pad.Profile = sketch + self.Doc.recompute() + # sketch1.addExternal("Sketch", "Edge3") + sketch1.addExternal("Pad", "Edge12") + self.Doc.recompute() + # Act + root = ET.fromstring("" + sketch1.Content + "") + # Can't use != in an xpath because it wasn't added until python 3.10. + # "*/*[@name='ExternalGeo']//*/[@type='Sketcher::ExternalGeometryExtension']/[@Ref!='']" + extRefs = root.findall( + "*/*[@name='ExternalGeo']//*/[@type='Sketcher::ExternalGeometryExtension']/[@Ref='']" + ) + extRefsAll = root.findall( + "*/*[@name='ExternalGeo']//*/[@type='Sketcher::ExternalGeometryExtension']/[@Ref]" + ) + # Assert + self.assertEqual(len(extRefs), 2) + self.assertEqual(len(extRefsAll), 3) + self.assertEqual(root.tag, "all") + # Act + filename = self.Doc.Name + ".FCStd" + self.Doc.saveAs(filename) + FreeCAD.closeDocument(self.Doc.Name) + self.Doc = FreeCAD.openDocument(filename) + # Assert + root = ET.fromstring("" + self.Doc.getObject("Sketch1").Content + "") + extRefs = root.findall( + "*/*[@name='ExternalGeo']//*/[@type='Sketcher::ExternalGeometryExtension']/[@Ref='']" + ) + extRefsAll = root.findall( + "*/*[@name='ExternalGeo']//*/[@type='Sketcher::ExternalGeometryExtension']/[@Ref]" + ) + self.assertEqual(len(extRefs), 2) + self.assertEqual(len(extRefsAll), 3) + self.assertEqual(root.tag, "all") + # Act + sketch = self.Doc.getObject("Sketch") + g1 = sketch.Constraints[11].First + d1 = sketch.Constraints[11].Value + sketch.delConstraint(11) + sketch.addConstraint(Sketcher.Constraint("Distance", g1, d1 - 1.0)) + self.Doc.recompute() + # Assert + root = ET.fromstring("" + self.Doc.getObject("Sketch1").Content + "") + extRefs = root.findall( + "*/*[@name='ExternalGeo']//*/[@type='Sketcher::ExternalGeometryExtension']/[@Ref='']" + ) + extRefsAll = root.findall( + "*/*[@name='ExternalGeo']//*/[@type='Sketcher::ExternalGeometryExtension']/[@Ref]" + ) + self.assertEqual(len(extRefs), 2) + self.assertEqual(len(extRefsAll), 3) + self.assertEqual(root.tag, "all") + + # TODO other tests: + # getHigherElement + def assertSuccessfulSolve(self, sketch, msg=None): status = sketch.solve() # TODO: can we get the solver's messages somehow to improve the message? diff --git a/tests/src/Mod/Sketcher/App/SketchObject.cpp b/tests/src/Mod/Sketcher/App/SketchObject.cpp index 761ebf97ff55..4311649badce 100644 --- a/tests/src/Mod/Sketcher/App/SketchObject.cpp +++ b/tests/src/Mod/Sketcher/App/SketchObject.cpp @@ -260,35 +260,45 @@ TEST_F(SketchObjectTest, testGetElementName) Base::Vector3d p1(0.0, 0.0, 0.0), p2(1.0, 0.0, 0.0); std::unique_ptr geoline(new Part::GeomLineSegment()); static_cast(geoline.get())->setPoints(p1, p2); - getObject()->addGeometry(geoline.get()); + auto id = getObject()->addGeometry(geoline.get()); + long tag; + getObject()->getGeometryId(id, tag); // We need to look up the tag that got assigned + std::ostringstream oss; + oss << "g" << tag; + auto tagName = oss.str(); getObject()->recomputeFeature(); // or ->execute() // Act // unless it's Export, we are really just testing the superclass App::GeoFeature::getElementName // call. auto forward_normal_name = - getObject()->getElementName("g39;SKT", App::GeoFeature::ElementNameType::Normal); + getObject()->getElementName((tagName + ";SKT").c_str(), + App::GeoFeature::ElementNameType::Normal); auto reverse_normal_name = getObject()->getElementName("Vertex2", App::GeoFeature::ElementNameType::Normal); auto reverse_export_name = getObject()->getElementName("Vertex1", App::GeoFeature::ElementNameType::Export); auto map = getObject()->Shape.getShape().getElementMap(); ASSERT_EQ(map.size(), 3); - EXPECT_STREQ(map[0].name.toString().c_str(), "g39;SKT"); + EXPECT_STREQ(map[0].name.toString().c_str(), (tagName + ";SKT").c_str()); EXPECT_EQ(map[0].index.toString(), "Edge1"); + EXPECT_STREQ(map[1].name.toString().c_str(), (tagName + "v1;SKT").c_str()); + EXPECT_EQ(map[1].index.toString(), "Vertex1"); + EXPECT_STREQ(map[2].name.toString().c_str(), (tagName + "v2;SKT").c_str()); + EXPECT_EQ(map[2].index.toString(), "Vertex2"); // Assert #ifndef FC_USE_TNP_FIX EXPECT_STREQ(forward_normal_name.newName.c_str(), ""); - EXPECT_STREQ(forward_normal_name.oldName.c_str(), "g39;SKT"); + EXPECT_STREQ(forward_normal_name.oldName.c_str(), "g1;SKT"); EXPECT_STREQ(reverse_normal_name.newName.c_str(), ""); EXPECT_STREQ(reverse_normal_name.oldName.c_str(), "Vertex2"); - EXPECT_STREQ(reverse_export_name.newName.c_str(), ";g39v1;SKT.Vertex1"); + EXPECT_STREQ(reverse_export_name.newName.c_str(), ";g1v1;SKT.Vertex1"); EXPECT_STREQ(reverse_export_name.oldName.c_str(), "Vertex1"); #else - EXPECT_STREQ(forward_normal_name.newName.c_str(), ";g39;SKT.Edge1"); + EXPECT_STREQ(forward_normal_name.newName.c_str(), (";" + tagName + ";SKT.Edge1").c_str()); EXPECT_STREQ(forward_normal_name.oldName.c_str(), "Edge1"); - EXPECT_STREQ(reverse_normal_name.newName.c_str(), ";g39v2;SKT.Vertex2"); + EXPECT_STREQ(reverse_normal_name.newName.c_str(), (";" + tagName + "v2;SKT.Vertex2").c_str()); EXPECT_STREQ(reverse_normal_name.oldName.c_str(), "Vertex2"); - EXPECT_STREQ(reverse_export_name.newName.c_str(), ";g39v1;SKT.Vertex1"); + EXPECT_STREQ(reverse_export_name.newName.c_str(), (";" + tagName + "v1;SKT.Vertex1").c_str()); EXPECT_STREQ(reverse_export_name.oldName.c_str(), "Vertex1"); #endif }