// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2010 Jürgen Riegel * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #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 #include #include "Constraint.h" #include "GeometryFacade.h" #include "Sketch.h" #include "SolverGeometryExtension.h" // #define DEBUG_BLOCK_CONSTRAINT #undef DEBUG_BLOCK_CONSTRAINT using namespace Sketcher; using namespace Base; using namespace Part; TYPESYSTEM_SOURCE(Sketcher::Sketch, Base::Persistence) Sketch::Sketch() : SolveTime(0) , RecalculateInitialSolutionWhileMovingPoint(false) , resolveAfterGeometryUpdated(false) , GCSsys() , ConstraintsCounter(0) , isInitMove(false) , isFine(true) , moveStep(0) , defaultSolver(GCS::DogLeg) , defaultSolverRedundant(GCS::DogLeg) , debugMode(GCS::Minimal) {} Sketch::~Sketch() { clear(); } void Sketch::clear() { // clear all internal data sets Points.clear(); Lines.clear(); Arcs.clear(); Circles.clear(); Ellipses.clear(); ArcsOfEllipse.clear(); ArcsOfHyperbola.clear(); ArcsOfParabola.clear(); BSplines.clear(); resolveAfterGeometryUpdated = false; // deleting the doubles allocated with new for (auto param : Parameters) { delete param; } Parameters.clear(); DrivenParameters.clear(); for (auto fixParam : FixParameters) { delete fixParam; } FixParameters.clear(); param2geoelement.clear(); pDependencyGroups.clear(); solverExtensions.clear(); internalAlignmentGeometryMap.clear(); // deleting the geometry copied into this sketch for (auto geom : Geoms) { delete geom.geo; } Geoms.clear(); // deleting the non-Driving constraints copied into this sketch // for (auto* constr : NonDrivingConstraints) { // delete constr; // } Constrs.clear(); GCSsys.clear(); isInitMove = false; ConstraintsCounter = 0; Conflicting.clear(); Redundant.clear(); PartiallyRedundant.clear(); MalformedConstraints.clear(); } bool Sketch::analyseBlockedGeometry( const std::vector& internalGeoList, const std::vector& constraintList, std::vector& onlyblockedGeometry, std::vector& blockedGeoIds ) const { // To understand this function read the documentation in Sketch.h // It is important that "onlyblockedGeometry" ONLY identifies blocked geometry // that is not affected by any other driving constraint bool doesBlockAffectOtherConstraints = false; int geoindex = 0; for (auto g : internalGeoList) { if (GeometryFacade::getBlocked(g)) { // is it only affected by one constraint, the block constraint (and this is driving), or // by any other driving constraint ? bool blockOnly = true; bool blockisDriving = false; for (auto c : constraintList) { // is block driving if (c->Type == Sketcher::Block && c->isActive && c->First == geoindex) { blockisDriving = true; } // We have another driving constraint (which may be InternalAlignment) if (c->Type != Sketcher::Block && c->isDriving && (c->First == geoindex || c->Second == geoindex || c->Third == geoindex)) { blockOnly = false; } } if (blockisDriving) { if (blockOnly) { onlyblockedGeometry[geoindex] = true; // we pre-fix this geometry } else { // we will have to pos-analyse the first diagnose result for these geometries // in order to avoid redundant constraints doesBlockAffectOtherConstraints = true; blockedGeoIds.push_back(geoindex); } } } geoindex++; } return doesBlockAffectOtherConstraints; } int Sketch::setUpSketch( const std::vector& GeoList, const std::vector& ConstraintList, int extGeoCount ) { Base::TimeElapsed start_time; clear(); std::vector intGeoList, extGeoList; std::copy(GeoList.begin(), GeoList.end() - extGeoCount, std::back_inserter(intGeoList)); std::copy(GeoList.end() - extGeoCount, GeoList.end(), std::back_inserter(extGeoList)); // these geometries are blocked, frozen and sent as fixed parameters to the solver std::vector onlyBlockedGeometry(intGeoList.size(), false); // these constraints are unenforceable due to a Blocked constraint std::vector unenforceableConstraints(ConstraintList.size(), false); /* This implements the old block constraint. I have decided not to remove it at this time while * the new is tested, just in case the change needs to be reverted */ /*if(!intGeoList.empty()) getBlockedGeometry(blockedGeometry, unenforceableConstraints, ConstraintList);*/ // Pre-analysis of blocked geometry (new block constraint) to fix geometry only affected by a // block constraint (see comment in Sketch.h) std::vector blockedGeoIds; bool doesBlockAffectOtherConstraints = analyseBlockedGeometry(intGeoList, ConstraintList, onlyBlockedGeometry, blockedGeoIds); #ifdef DEBUG_BLOCK_CONSTRAINT if (doesBlockAffectOtherConstraints) { Base::Console().log("\n Block interferes with other constraints: Post-analysis required"); } Base::Console().log("\nOnlyBlocked GeoIds:"); size_t i = 0; bool found = false; for (; i < onlyBlockedGeometry.size(); i++) { if (onlyBlockedGeometry[i]) { Base::Console().log("\n GeoId=%d", i); found = true; } } if (found) { Base::Console().log("\n None"); } Base::Console().log("\nNotOnlyBlocked GeoIds:"); i = 0; for (; i < blockedGeoIds.size(); i++) { Base::Console().log("\n GeoId=%d", blockedGeoIds[i]); } if (i == 0) { Base::Console().log("\n None"); } Base::Console().log("\n"); #endif // DEBUG_BLOCK_CONSTRAINT buildInternalAlignmentGeometryMap(ConstraintList); addGeometry(intGeoList, onlyBlockedGeometry); int extStart = Geoms.size(); addGeometry(extGeoList, true); int extEnd = Geoms.size() - 1; for (int i = extStart; i <= extEnd; i++) { Geoms[i].external = true; } // The Geoms list might be empty after an undo/redo if (!Geoms.empty()) { addConstraints(ConstraintList, unenforceableConstraints); } clearTemporaryConstraints(); GCSsys.declareUnknowns(Parameters); GCSsys.declareDrivenParams(DrivenParameters); GCSsys.initSolution(defaultSolverRedundant); // Post-analysis // Now that we have all the parameters information, we deal properly with the block constraints // if necessary if (doesBlockAffectOtherConstraints) { std::vector params_to_block; bool unsatisfied_groups = analyseBlockedConstraintDependentParameters(blockedGeoIds, params_to_block); // I am unsure if more than one QR iterations are needed with the current implementation. // // With previous implementations mostly one QR iteration was enough, but if block constraint // is abused, more iterations were needed. int index = 0; while (unsatisfied_groups) { // We tried hard not to arrive to an unsatisfied group, so we try harder // This loop has the advantage that the user will notice increased effort to solve, // so they may understand that they are abusing the block constraint, while guaranteeing // that wrong behaviour of the block constraint is not undetected. // Another QR iteration fixParametersAndDiagnose(params_to_block); unsatisfied_groups = analyseBlockedConstraintDependentParameters(blockedGeoIds, params_to_block); if (debugMode == GCS::IterationLevel) { Base::Console().log("Sketcher::setUpSketch()-BlockConstraint-PostAnalysis:%d\n", index); } index++; } // 2. If something needs blocking, block-it fixParametersAndDiagnose(params_to_block); #ifdef DEBUG_BLOCK_CONSTRAINT if (params_to_block.size() > 0) { std::vector> groups; GCSsys.getDependentParamsGroups(groups); // Debug code block for (size_t i = 0; i < groups.size(); i++) { Base::Console().log("\nDepParams: Group %d:", i); for (size_t j = 0; j < groups[i].size(); j++) { Base::Console().log( "\n Param=%x ,GeoId=%d, GeoPos=%d", param2geoelement.find(*std::next(groups[i].begin(), j))->first, param2geoelement.find(*std::next(groups[i].begin(), j))->second.first, param2geoelement.find(*std::next(groups[i].begin(), j))->second.second ); } } } #endif // DEBUG_BLOCK_CONSTRAINT } // Now we set the Sketch status with the latest solver information GCSsys.getConflicting(Conflicting); GCSsys.getRedundant(Redundant); GCSsys.getPartiallyRedundant(PartiallyRedundant); GCSsys.getDependentParams(pDependentParametersList); calculateDependentParametersElements(); if (debugMode == GCS::Minimal || debugMode == GCS::IterationLevel) { Base::TimeElapsed end_time; Base::Console().log( "Sketcher::setUpSketch()-T:%s\n", Base::TimeElapsed::diffTime(start_time, end_time).c_str() ); } return GCSsys.dofsNumber(); } void Sketch::buildInternalAlignmentGeometryMap(const std::vector& constraintList) { for (auto* c : constraintList) { if (c->Type == InternalAlignment) { internalAlignmentGeometryMap[c->First] = c->Second; } } } void Sketch::fixParametersAndDiagnose(std::vector& params_to_block) { if (!params_to_block.empty()) { // only there are parameters to fix for (auto p : params_to_block) { if (auto findparam = std::ranges::find(Parameters, p); findparam != Parameters.end()) { FixParameters.push_back(*findparam); Parameters.erase(findparam); } } pDependencyGroups.clear(); clearTemporaryConstraints(); GCSsys.invalidatedDiagnosis(); GCSsys.declareUnknowns(Parameters); GCSsys.declareDrivenParams(DrivenParameters); GCSsys.initSolution(defaultSolverRedundant); /*GCSsys.getConflicting(Conflicting); GCSsys.getRedundant(Redundant); GCSsys.getPartlyRedundant(PartiallyRedundant); GCSsys.getDependentParams(pDependentParametersList); calculateDependentParametersElements();*/ } } bool Sketch::analyseBlockedConstraintDependentParameters( std::vector& blockedGeoIds, std::vector& params_to_block ) const { // 1. Retrieve solver information std::vector> groups; GCSsys.getDependentParamsGroups(groups); // 2. Determine blockable parameters for each group (see documentation in header file). struct group { std::vector blockable_params_in_group; double* blocking_param_in_group = nullptr; }; std::vector prop_groups(groups.size()); #ifdef DEBUG_BLOCK_CONSTRAINT for (size_t i = 0; i < groups.size(); i++) { Base::Console().log("\nDepParams: Group %d:", i); for (size_t j = 0; j < groups[i].size(); j++) { Base::Console().log( "\n Param=%x ,GeoId=%d, GeoPos=%d", param2geoelement.find(*std::next(groups[i].begin(), j))->first, param2geoelement.find(*std::next(groups[i].begin(), j))->second.first, param2geoelement.find(*std::next(groups[i].begin(), j))->second.second ); } } #endif // DEBUG_BLOCK_CONSTRAINT for (size_t i = 0; i < groups.size(); i++) { for (size_t j = 0; j < groups[i].size(); j++) { double* thisparam = *std::next(groups[i].begin(), j); if (auto element = param2geoelement.find(thisparam); element != param2geoelement.end()) { if (auto blockable = std::ranges::find(blockedGeoIds, std::get<0>(element->second)); blockable != blockedGeoIds.end()) { // This dependent parameter group contains at least one parameter that should be // blocked, so added to the blockable list. prop_groups[i].blockable_params_in_group.push_back(thisparam); } } } } // 3. Apply heuristic - pick the last blockable param available to block the group, starting // from the last group for (size_t i = prop_groups.size(); i-- > 0;) { for (size_t j = prop_groups[i].blockable_params_in_group.size(); j-- > 0;) { // check if parameter is already satisfying one group double* thisparam = prop_groups[i].blockable_params_in_group[j]; auto pos = std::ranges::find(params_to_block, thisparam); if (pos == params_to_block.end()) { // not found, so add params_to_block.push_back(thisparam); prop_groups[i].blocking_param_in_group = thisparam; #ifdef DEBUG_BLOCK_CONSTRAINT Base::Console().log("\nTentatively blocking group %d, with param=%x", i, thisparam); #endif // DEBUG_BLOCK_CONSTRAINT break; } } } // 4. Check if groups are satisfied or are licitly unsatisfiable and thus deemed as satisfied bool unsatisfied_groups = false; for (auto& prop_group : prop_groups) { // 4.1. unsatisfiable group if (prop_group.blockable_params_in_group.empty()) { // this group does not contain any blockable parameter, so it is by definition satisfied // (or impossible to satisfy by block constraints) continue; } // 4.2. satisfiable and not satisfied if (!prop_group.blocking_param_in_group) { unsatisfied_groups = true; } } return unsatisfied_groups; } void Sketch::clearTemporaryConstraints() { GCSsys.clearByTag(GCS::DefaultTemporaryConstraint); } void Sketch::calculateDependentParametersElements() { // initialize solve extensions to a know state solverExtensions.resize(Geoms.size()); int i = 0; for (auto geo : Geoms) { if (!geo.geo->hasExtension(Sketcher::SolverGeometryExtension::getClassTypeId())) { geo.geo->setExtension(std::make_unique()); } auto solvext = std::static_pointer_cast( geo.geo->getExtension(Sketcher::SolverGeometryExtension::getClassTypeId()).lock() ); if (GCSsys.isEmptyDiagnoseMatrix()) { solvext->init(SolverGeometryExtension::Dependent); } else { solvext->init(SolverGeometryExtension::Independent); } solverExtensions[i] = solvext; i++; } for (auto param : pDependentParametersList) { // auto element = param2geoelement.at(param); auto element = param2geoelement.find(param); if (element != param2geoelement.end()) { auto geoid = std::get<0>(element->second); auto geopos = std::get<1>(element->second); auto solvext = std::static_pointer_cast( Geoms[geoid].geo->getExtension(Sketcher::SolverGeometryExtension::getClassTypeId()).lock() ); auto index = std::get<2>(element->second); switch (geopos) { case PointPos::none: solvext->setEdge(index, SolverGeometryExtension::Dependent); break; case PointPos::start: if (index == 0) { solvext->setStartx(SolverGeometryExtension::Dependent); } else { solvext->setStarty(SolverGeometryExtension::Dependent); } break; case PointPos::end: if (index == 0) { solvext->setEndx(SolverGeometryExtension::Dependent); } else { solvext->setEndy(SolverGeometryExtension::Dependent); } break; case PointPos::mid: if (index == 0) { solvext->setMidx(SolverGeometryExtension::Dependent); } else { solvext->setMidy(SolverGeometryExtension::Dependent); } break; } } } std::vector> groups; GCSsys.getDependentParamsGroups(groups); pDependencyGroups.resize(groups.size()); // translate parameters into elements (Geoid, PointPos) for (size_t i = 0; i < groups.size(); i++) { for (size_t j = 0; j < groups[i].size(); j++) { auto element = param2geoelement.find(groups[i][j]); if (element != param2geoelement.end()) { pDependencyGroups[i].insert( std::pair(std::get<0>(element->second), std::get<1>(element->second)) ); } } } // check if groups have a common element, if yes merge the groups auto havecommonelement = [](std::set>::iterator begin1, std::set>::iterator end1, std::set>::iterator begin2, std::set>::iterator end2) { while (begin1 != end1 && begin2 != end2) { if (*begin1 < *begin2) { ++begin1; } else if (*begin2 < *begin1) { ++begin2; } else { return true; } } return false; }; if (pDependencyGroups.size() > 1) { // only if there is more than 1 group size_t endcount = pDependencyGroups.size() - 1; for (size_t i = 0; i < endcount; i++) { if (havecommonelement( pDependencyGroups[i].begin(), pDependencyGroups[i].end(), pDependencyGroups[i + 1].begin(), pDependencyGroups[i + 1].end() )) { pDependencyGroups[i].insert( pDependencyGroups[i + 1].begin(), pDependencyGroups[i + 1].end() ); pDependencyGroups.erase(pDependencyGroups.begin() + i + 1); endcount--; } } } } std::set> Sketch::getDependencyGroup(int geoId, PointPos pos) const { geoId = checkGeoId(geoId); std::set> group; auto key = std::make_pair(geoId, pos); for (auto& set : pDependencyGroups) { if (set.find(key) != set.end()) { group = set; break; } } return group; } std::shared_ptr Sketch::getSolverExtension(int geoId) const { if (geoId >= 0 && geoId < int(solverExtensions.size())) { return solverExtensions[geoId]; } return nullptr; } int Sketch::resetSolver() { clearTemporaryConstraints(); GCSsys.declareUnknowns(Parameters); GCSsys.declareDrivenParams(DrivenParameters); GCSsys.initSolution(defaultSolverRedundant); GCSsys.getConflicting(Conflicting); GCSsys.getRedundant(Redundant); GCSsys.getPartiallyRedundant(PartiallyRedundant); GCSsys.getDependentParams(pDependentParametersList); calculateDependentParametersElements(); return GCSsys.dofsNumber(); } const char* nameByType(Sketch::GeoType type) { switch (type) { case Sketch::Point: return "point"; case Sketch::Line: return "line"; case Sketch::Arc: return "arc"; case Sketch::Circle: return "circle"; case Sketch::Ellipse: return "ellipse"; case Sketch::ArcOfEllipse: return "arcofellipse"; case Sketch::ArcOfHyperbola: return "arcofhyperbola"; case Sketch::ArcOfParabola: return "arcofparabola"; case Sketch::BSpline: return "bspline"; case Sketch::None: default: return "unknown"; } } // Geometry adding ========================================================== int Sketch::addGeometry(const Part::Geometry* geo, bool fixed) { if (geo->is()) { // add a point const GeomPoint* point = static_cast(geo); auto pointf = GeometryFacade::getFacade(point); // create the definition struct for that geom return addPoint(*point, fixed); } else if (geo->is()) { // add a line const GeomLineSegment* lineSeg = static_cast(geo); // create the definition struct for that geom return addLineSegment(*lineSeg, fixed); } else if (geo->is()) { // add a circle const GeomCircle* circle = static_cast(geo); // create the definition struct for that geom return addCircle(*circle, fixed); } else if (geo->is()) { // add a ellipse const GeomEllipse* ellipse = static_cast(geo); // create the definition struct for that geom return addEllipse(*ellipse, fixed); } else if (geo->is()) { // add an arc const GeomArcOfCircle* aoc = static_cast(geo); // create the definition struct for that geom return addArc(*aoc, fixed); } else if (geo->is()) { // add an arc const GeomArcOfEllipse* aoe = static_cast(geo); // create the definition struct for that geom return addArcOfEllipse(*aoe, fixed); } else if (geo->is()) { // add an arc of hyperbola const GeomArcOfHyperbola* aoh = static_cast(geo); // create the definition struct for that geom return addArcOfHyperbola(*aoh, fixed); } else if (geo->is()) { // add an arc of parabola const GeomArcOfParabola* aop = static_cast(geo); // create the definition struct for that geom return addArcOfParabola(*aop, fixed); } else if (geo->is()) { // add a bspline const GeomBSplineCurve* bsp = static_cast(geo); // Current B-Spline implementation relies on OCCT calculations, so a second solve // is necessary to update actual solver implementation to account for changes in B-Spline // geometry resolveAfterGeometryUpdated = true; return addBSpline(*bsp, fixed); } else { throw Base::TypeError("Sketch::addGeometry(): Unknown or unsupported type added to a sketch"); } } int Sketch::addGeometry(const std::vector& geos, bool fixed) { int ret = -1; for (const auto& geo : geos) { ret = addGeometry(geo, fixed); } return ret; } int Sketch::addGeometry(const std::vector& geos, const std::vector& blockedGeometry) { assert(geos.size() == blockedGeometry.size()); int ret = -1; std::vector::const_iterator it; std::vector::const_iterator bit; for (it = geos.begin(), bit = blockedGeometry.begin(); it != geos.end() && bit != blockedGeometry.end(); ++it, ++bit) { ret = addGeometry(*it, *bit); } return ret; } int Sketch::addPoint(const Part::GeomPoint& point, bool fixed) { std::vector& params = fixed ? FixParameters : Parameters; // create our own copy GeomPoint* p = static_cast(point.clone()); // create the definition struct for that geom GeoDef def; def.geo = p; def.type = Point; // set the parameter for the solver params.push_back(new double(p->getPoint().x)); params.push_back(new double(p->getPoint().y)); // set the points for later constraints GCS::Point p1; p1.x = params[params.size() - 2]; p1.y = params[params.size() - 1]; def.startPointId = Points.size(); def.endPointId = Points.size(); def.midPointId = Points.size(); Points.push_back(p1); // store complete set Geoms.push_back(def); if (!fixed) { param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p1.x), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::start, 0) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p1.y), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::start, 1) ); } // return the position of the newly added geometry return Geoms.size() - 1; } int Sketch::addLine(const Part::GeomLineSegment& /*line*/, bool /*fixed*/) { // return the position of the newly added geometry return Geoms.size() - 1; } int Sketch::addLineSegment(const Part::GeomLineSegment& lineSegment, bool fixed) { std::vector& params = fixed ? FixParameters : Parameters; // create our own copy GeomLineSegment* lineSeg = static_cast(lineSegment.clone()); // create the definition struct for that geom GeoDef def; def.geo = lineSeg; def.type = Line; // get the points from the line Base::Vector3d start = lineSeg->getStartPoint(); Base::Vector3d end = lineSeg->getEndPoint(); // the points for later constraints GCS::Point p1, p2; params.push_back(new double(start.x)); params.push_back(new double(start.y)); p1.x = params[params.size() - 2]; p1.y = params[params.size() - 1]; params.push_back(new double(end.x)); params.push_back(new double(end.y)); p2.x = params[params.size() - 2]; p2.y = params[params.size() - 1]; // add the points def.startPointId = Points.size(); def.endPointId = Points.size() + 1; Points.push_back(p1); Points.push_back(p2); // set the line for later constraints GCS::Line l; l.p1 = p1; l.p2 = p2; def.index = Lines.size(); Lines.push_back(l); // store complete set Geoms.push_back(def); if (!fixed) { param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p1.x), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::start, 0) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p1.y), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::start, 1) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p2.x), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::end, 0) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p2.y), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::end, 1) ); } // return the position of the newly added geometry return Geoms.size() - 1; } int Sketch::addArc(const Part::GeomArcOfCircle& circleSegment, bool fixed) { std::vector& params = fixed ? FixParameters : Parameters; // create our own copy GeomArcOfCircle* aoc = static_cast(circleSegment.clone()); // create the definition struct for that geom GeoDef def; def.geo = aoc; def.type = Arc; Base::Vector3d center = aoc->getCenter(); Base::Vector3d startPnt = aoc->getStartPoint(/*emulateCCW=*/true); Base::Vector3d endPnt = aoc->getEndPoint(/*emulateCCW=*/true); double radius = aoc->getRadius(); double startAngle, endAngle; aoc->getRange(startAngle, endAngle, /*emulateCCW=*/true); GCS::Point p1, p2, p3; params.push_back(new double(startPnt.x)); params.push_back(new double(startPnt.y)); p1.x = params[params.size() - 2]; p1.y = params[params.size() - 1]; params.push_back(new double(endPnt.x)); params.push_back(new double(endPnt.y)); p2.x = params[params.size() - 2]; p2.y = params[params.size() - 1]; params.push_back(new double(center.x)); params.push_back(new double(center.y)); p3.x = params[params.size() - 2]; p3.y = params[params.size() - 1]; def.startPointId = Points.size(); Points.push_back(p1); def.endPointId = Points.size(); Points.push_back(p2); def.midPointId = Points.size(); Points.push_back(p3); params.push_back(new double(radius)); double* r = params[params.size() - 1]; params.push_back(new double(startAngle)); double* a1 = params[params.size() - 1]; params.push_back(new double(endAngle)); double* a2 = params[params.size() - 1]; // set the arc for later constraints GCS::Arc a; a.start = p1; a.end = p2; a.center = p3; a.rad = r; a.startAngle = a1; a.endAngle = a2; def.index = Arcs.size(); Arcs.push_back(a); // store complete set Geoms.push_back(def); // arcs require an ArcRules constraint for the end points if (!fixed) { GCSsys.addConstraintArcRules(a); } if (!fixed) { param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p1.x), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::start, 0) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p1.y), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::start, 1) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p2.x), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::end, 0) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p2.y), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::end, 1) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p3.x), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::mid, 0) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p3.y), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::mid, 1) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(r), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::none, 0) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(a1), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::none, 1) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(a2), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::none, 2) ); } // return the position of the newly added geometry return Geoms.size() - 1; } int Sketch::addArcOfEllipse(const Part::GeomArcOfEllipse& ellipseSegment, bool fixed) { std::vector& params = fixed ? FixParameters : Parameters; // create our own copy GeomArcOfEllipse* aoe = static_cast(ellipseSegment.clone()); // create the definition struct for that geom GeoDef def; def.geo = aoe; def.type = ArcOfEllipse; Base::Vector3d center = aoe->getCenter(); Base::Vector3d startPnt = aoe->getStartPoint(/*emulateCCW=*/true); Base::Vector3d endPnt = aoe->getEndPoint(/*emulateCCW=*/true); double radmaj = aoe->getMajorRadius(); double radmin = aoe->getMinorRadius(); Base::Vector3d radmajdir = aoe->getMajorAxisDir(); double dist_C_F = sqrt(radmaj * radmaj - radmin * radmin); // solver parameters Base::Vector3d focus1 = center + dist_C_F * radmajdir; double startAngle, endAngle; aoe->getRange(startAngle, endAngle, /*emulateCCW=*/true); GCS::Point p1, p2, p3; params.push_back(new double(startPnt.x)); params.push_back(new double(startPnt.y)); p1.x = params[params.size() - 2]; p1.y = params[params.size() - 1]; params.push_back(new double(endPnt.x)); params.push_back(new double(endPnt.y)); p2.x = params[params.size() - 2]; p2.y = params[params.size() - 1]; params.push_back(new double(center.x)); params.push_back(new double(center.y)); p3.x = params[params.size() - 2]; p3.y = params[params.size() - 1]; params.push_back(new double(focus1.x)); params.push_back(new double(focus1.y)); double* f1X = params[params.size() - 2]; double* f1Y = params[params.size() - 1]; def.startPointId = Points.size(); Points.push_back(p1); def.endPointId = Points.size(); Points.push_back(p2); def.midPointId = Points.size(); Points.push_back(p3); // Points.push_back(f1); // add the radius parameters params.push_back(new double(radmin)); double* rmin = params[params.size() - 1]; params.push_back(new double(startAngle)); double* a1 = params[params.size() - 1]; params.push_back(new double(endAngle)); double* a2 = params[params.size() - 1]; // set the arc for later constraints GCS::ArcOfEllipse a; a.start = p1; a.end = p2; a.center = p3; a.focus1.x = f1X; a.focus1.y = f1Y; a.radmin = rmin; a.startAngle = a1; a.endAngle = a2; def.index = ArcsOfEllipse.size(); ArcsOfEllipse.push_back(a); // store complete set Geoms.push_back(def); // arcs require an ArcRules constraint for the end points if (!fixed) { GCSsys.addConstraintArcOfEllipseRules(a); } if (!fixed) { param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p1.x), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::start, 0) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p1.y), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::start, 1) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p2.x), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::end, 0) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p2.y), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::end, 1) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p3.x), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::mid, 0) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p3.y), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::mid, 1) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(f1X), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::none, 0) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(f1Y), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::none, 1) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(rmin), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::none, 2) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(a1), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::none, 3) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(a2), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::none, 4) ); } // return the position of the newly added geometry return Geoms.size() - 1; } int Sketch::addArcOfHyperbola(const Part::GeomArcOfHyperbola& hyperbolaSegment, bool fixed) { std::vector& params = fixed ? FixParameters : Parameters; // create our own copy GeomArcOfHyperbola* aoh = static_cast(hyperbolaSegment.clone()); // create the definition struct for that geom GeoDef def; def.geo = aoh; def.type = ArcOfHyperbola; Base::Vector3d center = aoh->getCenter(); Base::Vector3d startPnt = aoh->getStartPoint(); Base::Vector3d endPnt = aoh->getEndPoint(); double radmaj = aoh->getMajorRadius(); double radmin = aoh->getMinorRadius(); Base::Vector3d radmajdir = aoh->getMajorAxisDir(); double dist_C_F = sqrt(radmaj * radmaj + radmin * radmin); // solver parameters Base::Vector3d focus1 = center + dist_C_F * radmajdir; //+x double startAngle, endAngle; aoh->getRange(startAngle, endAngle, /*emulateCCW=*/true); GCS::Point p1, p2, p3; params.push_back(new double(startPnt.x)); params.push_back(new double(startPnt.y)); p1.x = params[params.size() - 2]; p1.y = params[params.size() - 1]; params.push_back(new double(endPnt.x)); params.push_back(new double(endPnt.y)); p2.x = params[params.size() - 2]; p2.y = params[params.size() - 1]; params.push_back(new double(center.x)); params.push_back(new double(center.y)); p3.x = params[params.size() - 2]; p3.y = params[params.size() - 1]; params.push_back(new double(focus1.x)); params.push_back(new double(focus1.y)); double* f1X = params[params.size() - 2]; double* f1Y = params[params.size() - 1]; def.startPointId = Points.size(); Points.push_back(p1); def.endPointId = Points.size(); Points.push_back(p2); def.midPointId = Points.size(); Points.push_back(p3); // add the radius parameters params.push_back(new double(radmin)); double* rmin = params[params.size() - 1]; params.push_back(new double(startAngle)); double* a1 = params[params.size() - 1]; params.push_back(new double(endAngle)); double* a2 = params[params.size() - 1]; // set the arc for later constraints GCS::ArcOfHyperbola a; a.start = p1; a.end = p2; a.center = p3; a.focus1.x = f1X; a.focus1.y = f1Y; a.radmin = rmin; a.startAngle = a1; a.endAngle = a2; def.index = ArcsOfHyperbola.size(); ArcsOfHyperbola.push_back(a); // store complete set Geoms.push_back(def); // arcs require an ArcRules constraint for the end points if (!fixed) { GCSsys.addConstraintArcOfHyperbolaRules(a); } if (!fixed) { param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p1.x), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::start, 0) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p1.y), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::start, 1) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p2.x), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::end, 0) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p2.y), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::end, 1) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p3.x), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::mid, 0) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p3.y), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::mid, 1) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(f1X), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::none, 0) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(f1Y), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::none, 1) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(rmin), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::none, 2) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(a1), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::none, 3) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(a2), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::none, 4) ); } // return the position of the newly added geometry return Geoms.size() - 1; } int Sketch::addArcOfParabola(const Part::GeomArcOfParabola& parabolaSegment, bool fixed) { std::vector& params = fixed ? FixParameters : Parameters; // create our own copy GeomArcOfParabola* aop = static_cast(parabolaSegment.clone()); // create the definition struct for that geom GeoDef def; def.geo = aop; def.type = ArcOfParabola; Base::Vector3d vertex = aop->getCenter(); Base::Vector3d startPnt = aop->getStartPoint(); Base::Vector3d endPnt = aop->getEndPoint(); Base::Vector3d focus = aop->getFocus(); double startAngle, endAngle; aop->getRange(startAngle, endAngle, /*emulateCCW=*/true); GCS::Point p1, p2, p3, p4; params.push_back(new double(startPnt.x)); params.push_back(new double(startPnt.y)); p1.x = params[params.size() - 2]; p1.y = params[params.size() - 1]; params.push_back(new double(endPnt.x)); params.push_back(new double(endPnt.y)); p2.x = params[params.size() - 2]; p2.y = params[params.size() - 1]; params.push_back(new double(vertex.x)); params.push_back(new double(vertex.y)); p3.x = params[params.size() - 2]; p3.y = params[params.size() - 1]; params.push_back(new double(focus.x)); params.push_back(new double(focus.y)); p4.x = params[params.size() - 2]; p4.y = params[params.size() - 1]; def.startPointId = Points.size(); Points.push_back(p1); def.endPointId = Points.size(); Points.push_back(p2); def.midPointId = Points.size(); Points.push_back(p3); // add the radius parameters params.push_back(new double(startAngle)); double* a1 = params[params.size() - 1]; params.push_back(new double(endAngle)); double* a2 = params[params.size() - 1]; // set the arc for later constraints GCS::ArcOfParabola a; a.start = p1; a.end = p2; a.vertex = p3; a.focus1 = p4; a.startAngle = a1; a.endAngle = a2; def.index = ArcsOfParabola.size(); ArcsOfParabola.push_back(a); // store complete set Geoms.push_back(def); // arcs require an ArcRules constraint for the end points if (!fixed) { GCSsys.addConstraintArcOfParabolaRules(a); } if (!fixed) { param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p1.x), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::start, 0) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p1.y), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::start, 1) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p2.x), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::end, 0) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p2.y), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::end, 1) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p3.x), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::mid, 0) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p3.y), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::mid, 1) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p4.x), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::none, 0) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p4.y), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::none, 1) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(a1), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::none, 2) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(a2), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::none, 3) ); } // return the position of the newly added geometry return Geoms.size() - 1; } int Sketch::addBSpline(const Part::GeomBSplineCurve& bspline, bool fixed) { std::vector& params = fixed ? FixParameters : Parameters; // create our own copy GeomBSplineCurve* bsp = static_cast(bspline.clone()); // create the definition struct for that geom GeoDef def; def.geo = bsp; def.type = BSpline; std::vector poles = bsp->getPoles(); std::vector weights = bsp->getWeights(); std::vector knots = bsp->getKnots(); std::vector mult = bsp->getMultiplicities(); int degree = bsp->getDegree(); bool periodic = bsp->isPeriodic(); // OCC hack // c means there is a constraint on that weight, nc no constraint // OCC provides normalized weights when polynomic [1 1 1] [c c c] and unnormalized weights when // rational [5 1 5] [c nc c] then when changing from polynomic to rational, after the first // solve any not-constrained pole circle gets normalized to 1. This only happens when changing // from polynomic to rational, any subsequent change remains unnormalized [5 1 5] [c nc nc] This // creates a visual problem that one of the poles shrinks to 1 mm when deleting an equality // constraint. int lastoneindex = -1; int countones = 0; double lastnotone = 1.0; for (size_t i = 0; i < weights.size(); i++) { if (weights[i] != 1.0) { lastnotone = weights[i]; } else { // is 1.0 lastoneindex = i; countones++; } } if (countones == 1) { weights[lastoneindex] = (lastnotone * 0.99); } // end hack Base::Vector3d startPnt = bsp->getStartPoint(); Base::Vector3d endPnt = bsp->getEndPoint(); std::vector spoles; int i = 0; for (const auto& pole : poles) { params.push_back(new double(pole.x)); params.push_back(new double(pole.y)); GCS::Point p; p.x = params[params.size() - 2]; p.y = params[params.size() - 1]; spoles.push_back(p); if (!fixed) { param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p.x), std::forward_as_tuple(Geoms.size(), Sketcher::PointPos::none, i++) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p.y), std::forward_as_tuple(Geoms.size(), Sketcher::PointPos::none, i++) ); } } std::vector sweights; for (const auto& weight : weights) { auto r = new double(weight); params.push_back(r); sweights.push_back(params[params.size() - 1]); if (!fixed) { param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(r), std::forward_as_tuple(Geoms.size(), Sketcher::PointPos::none, i++) ); } } std::vector sknots; for (const auto& knot : knots) { double* _knot = new double(knot); // params.push_back(knot); sknots.push_back(_knot); } GCS::Point p1, p2; double* p1x = new double(startPnt.x); double* p1y = new double(startPnt.y); // If periodic, startpoint and endpoint do not play a role in the solver, this can remove // unnecessary DoF of determining where in the curve the start and the stop should be. However, // since start and end points are placed above knots, removing them leads to that knot being // unusable. params.push_back(p1x); params.push_back(p1y); p1.x = p1x; p1.y = p1y; double* p2x = new double(endPnt.x); double* p2y = new double(endPnt.y); // If periodic, startpoint and endpoint do not play a role in the solver, this can remove // unnecessary DoF of determining where in the curve the start and the stop should be. However, // since start and end points are placed above knots, removing them leads to that knot being // unusable. params.push_back(p2x); params.push_back(p2y); p2.x = p2x; p2.y = p2y; def.startPointId = Points.size(); Points.push_back(p1); def.endPointId = Points.size(); Points.push_back(p2); GCS::BSpline bs; bs.start = p1; bs.end = p2; bs.poles = spoles; bs.weights = sweights; bs.knots = sknots; bs.mult = mult; bs.degree = degree; bs.periodic = periodic; def.index = BSplines.size(); // non-solver related, just to enable initialization of knotspoints which is not a parameter of // the solver bs.knotpointGeoids.resize(knots.size()); for (auto& kpGeoId : bs.knotpointGeoids) { kpGeoId = GeoEnum::GeoUndef; } BSplines.push_back(bs); // store complete set Geoms.push_back(def); // WARNING: This is only valid where the multiplicity of the endpoints conforms with a BSpline // only then the startpoint is the first control point and the endpoint is the last control // point accordingly, it is never the case for a periodic BSpline. NOTE: For an external // B-spline (i.e. fixed=true) we must not set the coincident constraints as the points are not // movable anyway. See #issue 0003176: Sketcher: always over-constrained when referencing // external B-Spline if (!fixed && !bs.periodic) { if (bs.mult[0] > bs.degree) { GCSsys.addConstraintP2PCoincident(*(bs.poles.begin()), bs.start); } if (bs.mult[mult.size() - 1] > bs.degree) { GCSsys.addConstraintP2PCoincident(*(bs.poles.end() - 1), bs.end); } } if (!fixed) { // Note: Poles and weight parameters are emplaced above param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p1.x), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::start, 0) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p1.y), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::start, 1) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p2.x), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::end, 0) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p2.y), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::end, 1) ); } // return the position of the newly added geometry return Geoms.size() - 1; } int Sketch::addCircle(const Part::GeomCircle& cir, bool fixed) { std::vector& params = fixed ? FixParameters : Parameters; // create our own copy GeomCircle* circ = static_cast(cir.clone()); // create the definition struct for that geom GeoDef def; def.geo = circ; def.type = Circle; Base::Vector3d center = circ->getCenter(); double radius = circ->getRadius(); GCS::Point p1; params.push_back(new double(center.x)); params.push_back(new double(center.y)); p1.x = params[params.size() - 2]; p1.y = params[params.size() - 1]; params.push_back(new double(radius)); def.midPointId = Points.size(); Points.push_back(p1); // add the radius parameter double* r = params[params.size() - 1]; // set the circle for later constraints GCS::Circle c; c.center = p1; c.rad = r; def.index = Circles.size(); Circles.push_back(c); // store complete set Geoms.push_back(def); if (!fixed) { param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p1.x), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::mid, 0) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(p1.y), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::mid, 1) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(r), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::none, 0) ); } // return the position of the newly added geometry return Geoms.size() - 1; } int Sketch::addEllipse(const Part::GeomEllipse& elip, bool fixed) { std::vector& params = fixed ? FixParameters : Parameters; // create our own copy GeomEllipse* elips = static_cast(elip.clone()); // create the definition struct for that geom GeoDef def; def.geo = elips; def.type = Ellipse; Base::Vector3d center = elips->getCenter(); double radmaj = elips->getMajorRadius(); double radmin = elips->getMinorRadius(); Base::Vector3d radmajdir = elips->getMajorAxisDir(); double dist_C_F = sqrt(radmaj * radmaj - radmin * radmin); // solver parameters Base::Vector3d focus1 = center + dist_C_F * radmajdir; //+x // double *radmin; GCS::Point c; params.push_back(new double(center.x)); params.push_back(new double(center.y)); c.x = params[params.size() - 2]; c.y = params[params.size() - 1]; def.midPointId = Points.size(); // this takes midPointId+1 Points.push_back(c); params.push_back(new double(focus1.x)); params.push_back(new double(focus1.y)); double* f1X = params[params.size() - 2]; double* f1Y = params[params.size() - 1]; // add the radius parameters params.push_back(new double(radmin)); double* rmin = params[params.size() - 1]; // set the ellipse for later constraints GCS::Ellipse e; e.focus1.x = f1X; e.focus1.y = f1Y; e.center = c; e.radmin = rmin; def.index = Ellipses.size(); Ellipses.push_back(e); // store complete set Geoms.push_back(def); if (!fixed) { param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(c.x), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::mid, 0) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(c.y), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::mid, 1) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(f1X), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::none, 0) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(f1Y), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::none, 1) ); param2geoelement.emplace( std::piecewise_construct, std::forward_as_tuple(rmin), std::forward_as_tuple(Geoms.size() - 1, Sketcher::PointPos::none, 2) ); } // return the position of the newly added geometry return Geoms.size() - 1; } std::vector Sketch::extractGeometry( bool withConstructionElements, bool withExternalElements ) const { std::vector temp; temp.reserve(Geoms.size()); for (const auto& geom : Geoms) { auto gf = GeometryFacade::getFacade(geom.geo); if ((!geom.external || withExternalElements) && (!gf->getConstruction() || withConstructionElements)) { temp.push_back(geom.geo->clone()); } } return temp; } GeoListFacade Sketch::extractGeoListFacade() const { std::vector temp; temp.reserve(Geoms.size()); int internalGeometryCount = 0; for (const auto& geom : Geoms) { // GeometryFacade is the owner of this allocation auto gf = GeometryFacade::getFacade(geom.geo->clone(), true); if (!geom.external) { internalGeometryCount++; } temp.push_back(std::move(gf)); } return GeoListFacade::getGeoListModel(std::move(temp), internalGeometryCount); } void Sketch::updateExtension(int geoId, std::unique_ptr&& ext) { geoId = checkGeoId(geoId); Geoms[geoId].geo->setExtension(std::move(ext)); } Py::Tuple Sketch::getPyGeometry() const { Py::Tuple tuple(Geoms.size()); int i = 0; for (auto it = Geoms.begin(); it != Geoms.end(); ++it, ++i) { switch (it->type) { case Point: { Base::Vector3d temp(*(Points[it->startPointId].x), *(Points[it->startPointId].y), 0); tuple[i] = Py::asObject(new VectorPy(temp)); break; } case Line: { auto* lineSeg = static_cast(it->geo->clone()); tuple[i] = Py::asObject(new LineSegmentPy(lineSeg)); break; } case Arc: { auto* aoc = static_cast(it->geo->clone()); tuple[i] = Py::asObject(new ArcOfCirclePy(aoc)); break; } case Circle: { auto* circle = static_cast(it->geo->clone()); tuple[i] = Py::asObject(new CirclePy(circle)); break; } case Ellipse: { auto* ellipse = static_cast(it->geo->clone()); tuple[i] = Py::asObject(new EllipsePy(ellipse)); break; } case ArcOfEllipse: { auto* ellipse = static_cast(it->geo->clone()); tuple[i] = Py::asObject(new ArcOfEllipsePy(ellipse)); break; } case ArcOfHyperbola: { auto* aoh = static_cast(it->geo->clone()); tuple[i] = Py::asObject(new ArcOfHyperbolaPy(aoh)); break; } case ArcOfParabola: { auto* aop = static_cast(it->geo->clone()); tuple[i] = Py::asObject(new ArcOfParabolaPy(aop)); break; } case BSpline: { auto* bsp = static_cast(it->geo->clone()); tuple[i] = Py::asObject(new BSplineCurvePy(bsp)); break; } default: // not implemented type in the sketch! break; } } return tuple; } int Sketch::checkGeoId(int geoId) const { if (geoId < 0) { geoId += Geoms.size(); // convert negative external-geometry index to index into Geoms } if (!(geoId >= 0 && geoId < int(Geoms.size()))) { throw Base::IndexError("Sketch::checkGeoId. GeoId index out range."); } return geoId; } GCS::Curve* Sketch::getGCSCurveByGeoId(int geoId) { geoId = checkGeoId(geoId); switch (Geoms[geoId].type) { case Line: return &Lines[Geoms[geoId].index]; break; case Circle: return &Circles[Geoms[geoId].index]; break; case Arc: return &Arcs[Geoms[geoId].index]; break; case Ellipse: return &Ellipses[Geoms[geoId].index]; break; case ArcOfEllipse: return &ArcsOfEllipse[Geoms[geoId].index]; break; case ArcOfHyperbola: return &ArcsOfHyperbola[Geoms[geoId].index]; break; case ArcOfParabola: return &ArcsOfParabola[Geoms[geoId].index]; break; case BSpline: return &BSplines[Geoms[geoId].index]; break; default: return nullptr; }; } const GCS::Curve* Sketch::getGCSCurveByGeoId(int geoId) const { // I hereby guarantee that if I modify the non-const version, I will still // never modify (this). I return const copy to enforce on my users. return const_cast(this)->getGCSCurveByGeoId(geoId); } // constraint adding ========================================================== int Sketch::addConstraint(const Constraint* constraint) { if (Geoms.empty()) { throw Base::ValueError( "Sketch::addConstraint. Can't add constraint to a sketch with no geometry!" ); } int rtn = -1; ConstrDef c; c.constr = const_cast(constraint); c.driving = constraint->isDriving; switch (constraint->Type) { case DistanceX: if (constraint->FirstPos == PointPos::none) { // horizontal length of a line c.value = new double(constraint->getValue()); if (c.driving) { FixParameters.push_back(c.value); } else { Parameters.push_back(c.value); DrivenParameters.push_back(c.value); } rtn = addDistanceXConstraint(constraint->First, c.value, c.driving); } else if (constraint->Second == GeoEnum::GeoUndef) { // point on fixed x-coordinate c.value = new double(constraint->getValue()); if (c.driving) { FixParameters.push_back(c.value); } else { Parameters.push_back(c.value); DrivenParameters.push_back(c.value); } rtn = addCoordinateXConstraint(constraint->First, constraint->FirstPos, c.value, c.driving); } else if (constraint->SecondPos != PointPos::none) { // point to point horizontal distance c.value = new double(constraint->getValue()); if (c.driving) { FixParameters.push_back(c.value); } else { Parameters.push_back(c.value); DrivenParameters.push_back(c.value); } rtn = addDistanceXConstraint( constraint->First, constraint->FirstPos, constraint->Second, constraint->SecondPos, c.value, c.driving ); } break; case DistanceY: if (constraint->FirstPos == PointPos::none) { // vertical length of a line c.value = new double(constraint->getValue()); if (c.driving) { FixParameters.push_back(c.value); } else { Parameters.push_back(c.value); DrivenParameters.push_back(c.value); } rtn = addDistanceYConstraint(constraint->First, c.value, c.driving); } else if (constraint->Second == GeoEnum::GeoUndef) { // point on fixed y-coordinate c.value = new double(constraint->getValue()); if (c.driving) { FixParameters.push_back(c.value); } else { Parameters.push_back(c.value); DrivenParameters.push_back(c.value); } rtn = addCoordinateYConstraint(constraint->First, constraint->FirstPos, c.value, c.driving); } else if (constraint->SecondPos != PointPos::none) { // point to point vertical distance c.value = new double(constraint->getValue()); if (c.driving) { FixParameters.push_back(c.value); } else { Parameters.push_back(c.value); DrivenParameters.push_back(c.value); } rtn = addDistanceYConstraint( constraint->First, constraint->FirstPos, constraint->Second, constraint->SecondPos, c.value, c.driving ); } break; case Horizontal: if (constraint->Second == GeoEnum::GeoUndef) { // horizontal line rtn = addHorizontalConstraint(constraint->First); } else { // two points on the same horizontal line rtn = addHorizontalConstraint( constraint->First, constraint->FirstPos, constraint->Second, constraint->SecondPos ); } break; case Vertical: if (constraint->Second == GeoEnum::GeoUndef) { // vertical line rtn = addVerticalConstraint(constraint->First); } else { // two points on the same vertical line rtn = addVerticalConstraint( constraint->First, constraint->FirstPos, constraint->Second, constraint->SecondPos ); } break; case Coincident: rtn = addPointCoincidentConstraint( constraint->First, constraint->FirstPos, constraint->Second, constraint->SecondPos ); break; case PointOnObject: if (Geoms[checkGeoId(constraint->Second)].type == BSpline) { c.value = new double(constraint->getValue()); // Driving doesn't make sense here Parameters.push_back(c.value); rtn = addPointOnObjectConstraint( constraint->First, constraint->FirstPos, constraint->Second, c.value ); } else { rtn = addPointOnObjectConstraint( constraint->First, constraint->FirstPos, constraint->Second ); } break; case Parallel: rtn = addParallelConstraint(constraint->First, constraint->Second); break; case Perpendicular: if (constraint->FirstPos == PointPos::none && constraint->SecondPos == PointPos::none && constraint->Third == GeoEnum::GeoUndef) { // simple perpendicularity rtn = addPerpendicularConstraint(constraint->First, constraint->Second); } else { // any other point-wise perpendicularity c.value = new double(constraint->getValue()); if (c.driving) { FixParameters.push_back(c.value); } else { Parameters.push_back(c.value); DrivenParameters.push_back(c.value); } rtn = addAngleAtPointConstraint( constraint->First, constraint->FirstPos, constraint->Second, constraint->SecondPos, constraint->Third, constraint->ThirdPos, c.value, constraint->Type, c.driving ); } break; case Tangent: { bool isSpecialCase = false; if (constraint->FirstPos == PointPos::none && constraint->SecondPos == PointPos::none && constraint->Third == GeoEnum::GeoUndef) { // simple tangency rtn = addTangentConstraint(constraint->First, constraint->Second); isSpecialCase = true; } else if (constraint->FirstPos == PointPos::start && constraint->Third == GeoEnum::GeoUndef) { // check for B-Spline Knot to curve tangency auto knotgeoId = checkGeoId(constraint->First); if (Geoms[knotgeoId].type == Point) { auto* point = static_cast(Geoms[knotgeoId].geo); if (GeometryFacade::isInternalType(point, InternalType::BSplineKnotPoint)) { auto bsplinegeoid = internalAlignmentGeometryMap.at(constraint->First); bsplinegeoid = checkGeoId(bsplinegeoid); auto linegeoid = checkGeoId(constraint->Second); if (Geoms[linegeoid].type == Line) { if (constraint->SecondPos == PointPos::none) { rtn = addTangentLineAtBSplineKnotConstraint( linegeoid, bsplinegeoid, knotgeoId ); isSpecialCase = true; } else if (constraint->SecondPos == PointPos::start || constraint->SecondPos == PointPos::end) { rtn = addTangentLineEndpointAtBSplineKnotConstraint( linegeoid, constraint->SecondPos, bsplinegeoid, knotgeoId ); isSpecialCase = true; } } } } } if (!isSpecialCase) { // any other point-wise tangency (endpoint-to-curve, endpoint-to-endpoint, // tangent-via-point) c.value = new double(constraint->getValue()); if (c.driving) { FixParameters.push_back(c.value); } else { Parameters.push_back(c.value); DrivenParameters.push_back(c.value); } rtn = addAngleAtPointConstraint( constraint->First, constraint->FirstPos, constraint->Second, constraint->SecondPos, constraint->Third, constraint->ThirdPos, c.value, constraint->Type, c.driving ); } break; } case Distance: if (constraint->SecondPos != PointPos::none) { // point to point distance c.value = new double(constraint->getValue()); if (c.driving) { FixParameters.push_back(c.value); } else { Parameters.push_back(c.value); DrivenParameters.push_back(c.value); } rtn = addDistanceConstraint( constraint->First, constraint->FirstPos, constraint->Second, constraint->SecondPos, c.value, c.driving ); } else if (constraint->FirstPos == PointPos::none && constraint->SecondPos == PointPos::none && constraint->Second != GeoEnum::GeoUndef && constraint->Third == GeoEnum::GeoUndef) { // circle to circle, circle to // arc, etc. c.value = new double(constraint->getValue()); if (c.driving) { FixParameters.push_back(c.value); } else { Parameters.push_back(c.value); DrivenParameters.push_back(c.value); } rtn = addDistanceConstraint(constraint->First, constraint->Second, c.value, c.driving); } else if (constraint->Second != GeoEnum::GeoUndef) { if (constraint->FirstPos != PointPos::none) { // point to line distance c.value = new double(constraint->getValue()); if (c.driving) { FixParameters.push_back(c.value); } else { Parameters.push_back(c.value); DrivenParameters.push_back(c.value); } rtn = addDistanceConstraint( constraint->First, constraint->FirstPos, constraint->Second, c.value, c.driving ); } } else { // line length, arc length c.value = new double(constraint->getValue()); if (c.driving) { FixParameters.push_back(c.value); } else { Parameters.push_back(c.value); DrivenParameters.push_back(c.value); } rtn = addDistanceConstraint(constraint->First, c.value, c.driving); } break; case Angle: if (constraint->Third != GeoEnum::GeoUndef) { c.value = new double(constraint->getValue()); if (c.driving) { FixParameters.push_back(c.value); } else { Parameters.push_back(c.value); DrivenParameters.push_back(c.value); } rtn = addAngleAtPointConstraint( constraint->First, constraint->FirstPos, constraint->Second, constraint->SecondPos, constraint->Third, constraint->ThirdPos, c.value, constraint->Type, c.driving ); } // angle between two lines (with explicit start points) else if (constraint->SecondPos != PointPos::none) { c.value = new double(constraint->getValue()); if (c.driving) { FixParameters.push_back(c.value); } else { Parameters.push_back(c.value); DrivenParameters.push_back(c.value); } rtn = addAngleConstraint( constraint->First, constraint->FirstPos, constraint->Second, constraint->SecondPos, c.value, c.driving ); } else if (constraint->Second != GeoEnum::GeoUndef) { // angle between two lines c.value = new double(constraint->getValue()); if (c.driving) { FixParameters.push_back(c.value); } else { Parameters.push_back(c.value); DrivenParameters.push_back(c.value); } rtn = addAngleConstraint(constraint->First, constraint->Second, c.value, c.driving); } else if (constraint->First != GeoEnum::GeoUndef) { // orientation angle of a line c.value = new double(constraint->getValue()); if (c.driving) { FixParameters.push_back(c.value); } else { Parameters.push_back(c.value); DrivenParameters.push_back(c.value); } rtn = addAngleConstraint(constraint->First, c.value, c.driving); } break; case Radius: { c.value = new double(constraint->getValue()); if (c.driving) { FixParameters.push_back(c.value); } else { Parameters.push_back(c.value); DrivenParameters.push_back(c.value); } rtn = addRadiusConstraint(constraint->First, c.value, c.driving); break; } case Diameter: { c.value = new double(constraint->getValue()); if (c.driving) { FixParameters.push_back(c.value); } else { Parameters.push_back(c.value); DrivenParameters.push_back(c.value); } rtn = addDiameterConstraint(constraint->First, c.value, c.driving); break; } case Weight: { c.value = new double(constraint->getValue()); if (c.driving) { FixParameters.push_back(c.value); } else { Parameters.push_back(c.value); DrivenParameters.push_back(c.value); } rtn = addRadiusConstraint(constraint->First, c.value, c.driving); break; } case Equal: rtn = addEqualConstraint(constraint->First, constraint->Second); break; case Symmetric: if (constraint->ThirdPos != PointPos::none) { rtn = addSymmetricConstraint( constraint->First, constraint->FirstPos, constraint->Second, constraint->SecondPos, constraint->Third, constraint->ThirdPos ); } else { rtn = addSymmetricConstraint( constraint->First, constraint->FirstPos, constraint->Second, constraint->SecondPos, constraint->Third ); } break; case InternalAlignment: switch (constraint->AlignmentType) { case EllipseMajorDiameter: rtn = addInternalAlignmentEllipseMajorDiameter(constraint->First, constraint->Second); break; case EllipseMinorDiameter: rtn = addInternalAlignmentEllipseMinorDiameter(constraint->First, constraint->Second); break; case EllipseFocus1: rtn = addInternalAlignmentEllipseFocus1(constraint->First, constraint->Second); break; case EllipseFocus2: rtn = addInternalAlignmentEllipseFocus2(constraint->First, constraint->Second); break; case HyperbolaMajor: rtn = addInternalAlignmentHyperbolaMajorDiameter( constraint->First, constraint->Second ); break; case HyperbolaMinor: rtn = addInternalAlignmentHyperbolaMinorDiameter( constraint->First, constraint->Second ); break; case HyperbolaFocus: rtn = addInternalAlignmentHyperbolaFocus(constraint->First, constraint->Second); break; case ParabolaFocus: rtn = addInternalAlignmentParabolaFocus(constraint->First, constraint->Second); break; case BSplineControlPoint: rtn = addInternalAlignmentBSplineControlPoint( constraint->First, constraint->Second, constraint->InternalAlignmentIndex ); break; case BSplineKnotPoint: rtn = addInternalAlignmentKnotPoint( constraint->First, constraint->Second, constraint->InternalAlignmentIndex ); break; case ParabolaFocalAxis: rtn = addInternalAlignmentParabolaFocalDistance( constraint->First, constraint->Second ); break; default: break; } break; case SnellsLaw: { c.value = new double(constraint->getValue()); c.secondvalue = new double(constraint->getValue()); if (c.driving) { FixParameters.push_back(c.value); FixParameters.push_back(c.secondvalue); } else { Parameters.push_back(c.value); Parameters.push_back(c.secondvalue); DrivenParameters.push_back(c.value); DrivenParameters.push_back(c.secondvalue); } // assert(constraint->ThirdPos==none); //will work anyway... rtn = addSnellsLawConstraint( constraint->First, constraint->FirstPos, constraint->Second, constraint->SecondPos, constraint->Third, c.value, c.secondvalue, c.driving ); } break; case Sketcher::None: // ambiguous enum value case Sketcher::Block: // handled separately while adding geometry case NumConstraintTypes: break; } Constrs.push_back(c); return rtn; } int Sketch::addConstraints(const std::vector& ConstraintList) { int rtn = -1; int cid = 0; for (auto it = ConstraintList.cbegin(); it != ConstraintList.cend(); ++it, ++cid) { rtn = addConstraint(*it); if (rtn == -1) { int humanConstraintId = cid + 1; Base::Console().error("Sketcher constraint number %d is malformed!\n", humanConstraintId); MalformedConstraints.push_back(humanConstraintId); } } return rtn; } int Sketch::addConstraints( const std::vector& ConstraintList, const std::vector& unenforceableConstraints ) { int rtn = -1; int cid = 0; for (auto it = ConstraintList.cbegin(); it != ConstraintList.cend(); ++it, ++cid) { if (!unenforceableConstraints[cid] && (*it)->Type != Block && (*it)->isActive) { rtn = addConstraint(*it); if (rtn == -1) { int humanConstraintId = cid + 1; Base::Console().error("Sketcher constraint number %d is malformed!\n", humanConstraintId); MalformedConstraints.push_back(humanConstraintId); } } else { ++ConstraintsCounter; // For correct solver redundant reporting } } return rtn; } void Sketch::getBlockedGeometry( std::vector& blockedGeometry, std::vector& unenforceableConstraints, const std::vector& ConstraintList ) const { std::vector internalAlignmentConstraintIndex; std::vector internalAlignmentgeo; std::vector geo2blockingconstraintindex(blockedGeometry.size(), -1); // Detect Blocked and internal constraints int i = 0; for (auto it = ConstraintList.cbegin(); it != ConstraintList.cend(); ++it, ++i) { switch ((*it)->Type) { case Block: { int geoid = (*it)->First; if (geoid >= 0 && geoid < int(blockedGeometry.size())) { blockedGeometry[geoid] = true; geo2blockingconstraintindex[geoid] = i; } } break; case InternalAlignment: internalAlignmentConstraintIndex.push_back(i); break; default: break; } } // if a GeoId is blocked and it is linked to Internal Alignment, then GeoIds linked via Internal // Alignment are also to be blocked for (auto idx : internalAlignmentConstraintIndex) { if (blockedGeometry[ConstraintList[idx]->Second]) { blockedGeometry[ConstraintList[idx]->First] = true; // associated geometry gets the same blocking constraint index as the blocked element geo2blockingconstraintindex[ConstraintList[idx]->First] = geo2blockingconstraintindex[ConstraintList[idx]->Second]; internalAlignmentgeo.push_back(ConstraintList[idx]->First); unenforceableConstraints[idx] = true; } } i = 0; for (auto it = ConstraintList.begin(); it != ConstraintList.end(); ++it, ++i) { if ((*it)->isDriving) { // additionally any further constraint on auxiliary elements linked via Internal // Alignment are also unenforceable. for (auto& iag : internalAlignmentgeo) { if ((*it)->First == iag || (*it)->Second == iag || (*it)->Third == iag) { unenforceableConstraints[i] = true; } } // IMPORTANT NOTE: // The rest of the ignoring of redundant/conflicting applies to constraints introduced // before the blocking constraint only Constraints introduced after the block will not // be ignored and will lead to redundancy/conflicting status as per normal solver // behaviour // further, any constraint taking only one element, which is blocked is also // unenforceable if ((*it)->Second == GeoEnum::GeoUndef && (*it)->Third == GeoEnum::GeoUndef && (*it)->First >= 0) { if (blockedGeometry[(*it)->First] && i < geo2blockingconstraintindex[(*it)->First]) { unenforceableConstraints[i] = true; } } // further any constraint on only two elements where both elements are blocked or one is // blocked and the other is an axis or external provided that the constraints precede // the last block constraint. else if ((*it)->Third == GeoEnum::GeoUndef) { if (((*it)->First >= 0 && (*it)->Second >= 0 && blockedGeometry[(*it)->First] && blockedGeometry[(*it)->Second] && (i < geo2blockingconstraintindex[(*it)->First] || i < geo2blockingconstraintindex[(*it)->Second])) || ((*it)->First < 0 && (*it)->Second >= 0 && blockedGeometry[(*it)->Second] && i < geo2blockingconstraintindex[(*it)->Second]) || ((*it)->First >= 0 && (*it)->Second < 0 && blockedGeometry[(*it)->First] && i < geo2blockingconstraintindex[(*it)->First])) { unenforceableConstraints[i] = true; } } // further any constraint on three elements where the three of them are blocked, or two // are blocked and the other is an axis or external geo or any constraint on three // elements where one is blocked and the other two are axis or external geo, provided // that the constraints precede the last block constraint. else { if (((*it)->First >= 0 && (*it)->Second >= 0 && (*it)->Third >= 0 && blockedGeometry[(*it)->First] && blockedGeometry[(*it)->Second] && blockedGeometry[(*it)->Third] && (i < geo2blockingconstraintindex[(*it)->First] || i < geo2blockingconstraintindex[(*it)->Second] || i < geo2blockingconstraintindex[(*it)->Third])) || ((*it)->First < 0 && (*it)->Second >= 0 && (*it)->Third >= 0 && blockedGeometry[(*it)->Second] && blockedGeometry[(*it)->Third] && (i < geo2blockingconstraintindex[(*it)->Second] || i < geo2blockingconstraintindex[(*it)->Third])) || ((*it)->First >= 0 && (*it)->Second < 0 && (*it)->Third >= 0 && blockedGeometry[(*it)->First] && blockedGeometry[(*it)->Third] && (i < geo2blockingconstraintindex[(*it)->First] || i < geo2blockingconstraintindex[(*it)->Third])) || ((*it)->First >= 0 && (*it)->Second >= 0 && (*it)->Third < 0 && blockedGeometry[(*it)->First] && blockedGeometry[(*it)->Second] && (i < geo2blockingconstraintindex[(*it)->First] || i < geo2blockingconstraintindex[(*it)->Second])) || ((*it)->First >= 0 && (*it)->Second < 0 && (*it)->Third < 0 && blockedGeometry[(*it)->First] && i < geo2blockingconstraintindex[(*it)->First]) || ((*it)->First < 0 && (*it)->Second >= 0 && (*it)->Third < 0 && blockedGeometry[(*it)->Second] && i < geo2blockingconstraintindex[(*it)->Second]) || ((*it)->First < 0 && (*it)->Second < 0 && (*it)->Third >= 0 && blockedGeometry[(*it)->Third] && i < geo2blockingconstraintindex[(*it)->Third])) { unenforceableConstraints[i] = true; } } } } } int Sketch::addCoordinateXConstraint(int geoId, PointPos pos, double* value, bool driving) { geoId = checkGeoId(geoId); int pointId = getPointId(geoId, pos); if (pointId >= 0 && pointId < int(Points.size())) { GCS::Point& p = Points[pointId]; int tag = ++ConstraintsCounter; GCSsys.addConstraintCoordinateX(p, value, tag, driving); return ConstraintsCounter; } return -1; } int Sketch::addCoordinateYConstraint(int geoId, PointPos pos, double* value, bool driving) { geoId = checkGeoId(geoId); int pointId = getPointId(geoId, pos); if (pointId >= 0 && pointId < int(Points.size())) { GCS::Point& p = Points[pointId]; int tag = ++ConstraintsCounter; GCSsys.addConstraintCoordinateY(p, value, tag, driving); return ConstraintsCounter; } return -1; } int Sketch::addDistanceXConstraint(int geoId, double* value, bool driving) { geoId = checkGeoId(geoId); if (Geoms[geoId].type != Line) { return -1; } GCS::Line& l = Lines[Geoms[geoId].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintDifference(l.p1.x, l.p2.x, value, tag, driving); return ConstraintsCounter; } int Sketch::addDistanceYConstraint(int geoId, double* value, bool driving) { geoId = checkGeoId(geoId); if (Geoms[geoId].type != Line) { return -1; } GCS::Line& l = Lines[Geoms[geoId].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintDifference(l.p1.y, l.p2.y, value, tag, driving); return ConstraintsCounter; } int Sketch::addDistanceXConstraint( int geoId1, PointPos pos1, int geoId2, PointPos pos2, double* value, bool driving ) { geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); int pointId1 = getPointId(geoId1, pos1); int pointId2 = getPointId(geoId2, pos2); if (pointId1 >= 0 && pointId1 < int(Points.size()) && pointId2 >= 0 && pointId2 < int(Points.size())) { GCS::Point& p1 = Points[pointId1]; GCS::Point& p2 = Points[pointId2]; int tag = ++ConstraintsCounter; GCSsys.addConstraintDifference(p1.x, p2.x, value, tag, driving); return ConstraintsCounter; } return -1; } int Sketch::addDistanceYConstraint( int geoId1, PointPos pos1, int geoId2, PointPos pos2, double* value, bool driving ) { geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); int pointId1 = getPointId(geoId1, pos1); int pointId2 = getPointId(geoId2, pos2); if (pointId1 >= 0 && pointId1 < int(Points.size()) && pointId2 >= 0 && pointId2 < int(Points.size())) { GCS::Point& p1 = Points[pointId1]; GCS::Point& p2 = Points[pointId2]; int tag = ++ConstraintsCounter; GCSsys.addConstraintDifference(p1.y, p2.y, value, tag, driving); return ConstraintsCounter; } return -1; } // horizontal line constraint int Sketch::addHorizontalConstraint(int geoId) { geoId = checkGeoId(geoId); if (Geoms[geoId].type != Line) { return -1; } GCS::Line& l = Lines[Geoms[geoId].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintHorizontal(l, tag); return ConstraintsCounter; } // two points on a horizontal line constraint int Sketch::addHorizontalConstraint(int geoId1, PointPos pos1, int geoId2, PointPos pos2) { geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); int pointId1 = getPointId(geoId1, pos1); int pointId2 = getPointId(geoId2, pos2); if (pointId1 >= 0 && pointId1 < int(Points.size()) && pointId2 >= 0 && pointId2 < int(Points.size())) { GCS::Point& p1 = Points[pointId1]; GCS::Point& p2 = Points[pointId2]; int tag = ++ConstraintsCounter; GCSsys.addConstraintHorizontal(p1, p2, tag); return ConstraintsCounter; } return -1; } // vertical line constraint int Sketch::addVerticalConstraint(int geoId) { geoId = checkGeoId(geoId); if (Geoms[geoId].type != Line) { return -1; } GCS::Line& l = Lines[Geoms[geoId].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintVertical(l, tag); return ConstraintsCounter; } // two points on a vertical line constraint int Sketch::addVerticalConstraint(int geoId1, PointPos pos1, int geoId2, PointPos pos2) { geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); int pointId1 = getPointId(geoId1, pos1); int pointId2 = getPointId(geoId2, pos2); if (pointId1 >= 0 && pointId1 < int(Points.size()) && pointId2 >= 0 && pointId2 < int(Points.size())) { GCS::Point& p1 = Points[pointId1]; GCS::Point& p2 = Points[pointId2]; int tag = ++ConstraintsCounter; GCSsys.addConstraintVertical(p1, p2, tag); return ConstraintsCounter; } return -1; } int Sketch::addPointCoincidentConstraint(int geoId1, PointPos pos1, int geoId2, PointPos pos2) { geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); int pointId1 = getPointId(geoId1, pos1); int pointId2 = getPointId(geoId2, pos2); if (pointId1 >= 0 && pointId1 < int(Points.size()) && pointId2 >= 0 && pointId2 < int(Points.size())) { GCS::Point& p1 = Points[pointId1]; GCS::Point& p2 = Points[pointId2]; int tag = ++ConstraintsCounter; GCSsys.addConstraintP2PCoincident(p1, p2, tag); return ConstraintsCounter; } return -1; } int Sketch::addParallelConstraint(int geoId1, int geoId2) { geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); if (Geoms[geoId1].type != Line || Geoms[geoId2].type != Line) { return -1; } GCS::Line& l1 = Lines[Geoms[geoId1].index]; GCS::Line& l2 = Lines[Geoms[geoId2].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintParallel(l1, l2, tag); return ConstraintsCounter; } // simple perpendicularity constraint int Sketch::addPerpendicularConstraint(int geoId1, int geoId2) { // accepts the following combinations: // 1) Line1, Line2/Circle2/Arc2 // 2) Circle1, Line2 (converted to case #1) // 3) Arc1, Line2 (converted to case #1) geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); if (Geoms[geoId2].type == Line) { if (Geoms[geoId1].type == Line) { GCS::Line& l1 = Lines[Geoms[geoId1].index]; GCS::Line& l2 = Lines[Geoms[geoId2].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintPerpendicular(l1, l2, tag); return ConstraintsCounter; } else { std::swap(geoId1, geoId2); } } if (Geoms[geoId1].type == Line) { GCS::Line& l1 = Lines[Geoms[geoId1].index]; if (Geoms[geoId2].type == Arc || Geoms[geoId2].type == Circle) { GCS::Point& p2 = Points[Geoms[geoId2].midPointId]; int tag = ++ConstraintsCounter; GCSsys.addConstraintPointOnLine(p2, l1, tag); return ConstraintsCounter; } } Base::Console().warning( "Perpendicular constraints between %s and %s are not supported.\n", nameByType(Geoms[geoId1].type), nameByType(Geoms[geoId2].type) ); return -1; } // simple tangency constraint int Sketch::addTangentConstraint(int geoId1, int geoId2) { // accepts the following combinations: // 1) Line1, Line2/Circle2/Arc2 // 2) Circle1, Line2 (converted to case #1) // Circle1, Circle2/Arc2 // 3) Arc1, Line2 (converted to case #1) // Arc1, Circle2/Arc2 geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); if (Geoms[geoId2].type == Line) { if (Geoms[geoId1].type == Line) { GCS::Line& l1 = Lines[Geoms[geoId1].index]; GCS::Point& l2p1 = Points[Geoms[geoId2].startPointId]; GCS::Point& l2p2 = Points[Geoms[geoId2].endPointId]; int tag = ++ConstraintsCounter; GCSsys.addConstraintPointOnLine(l2p1, l1, tag); GCSsys.addConstraintPointOnLine(l2p2, l1, tag); return ConstraintsCounter; } else { std::swap(geoId1, geoId2); } } if (Geoms[geoId1].type == Line) { GCS::Line& l = Lines[Geoms[geoId1].index]; if (Geoms[geoId2].type == Arc) { GCS::Arc& a = Arcs[Geoms[geoId2].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintTangent(l, a, tag); return ConstraintsCounter; } else if (Geoms[geoId2].type == Circle) { GCS::Circle& c = Circles[Geoms[geoId2].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintTangent(l, c, tag); return ConstraintsCounter; } else if (Geoms[geoId2].type == Ellipse) { GCS::Ellipse& e = Ellipses[Geoms[geoId2].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintTangent(l, e, tag); return ConstraintsCounter; } else if (Geoms[geoId2].type == ArcOfEllipse) { GCS::ArcOfEllipse& a = ArcsOfEllipse[Geoms[geoId2].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintTangent(l, a, tag); return ConstraintsCounter; } else if (Geoms[geoId2].type == BSpline) { Base::Console().error( "Direct tangency constraint between line and B-spline is not " "supported. Use tangent-via-point instead." ); return -1; } } else if (Geoms[geoId1].type == Circle) { GCS::Circle& c = Circles[Geoms[geoId1].index]; if (Geoms[geoId2].type == Circle) { GCS::Circle& c2 = Circles[Geoms[geoId2].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintTangent(c, c2, tag); return ConstraintsCounter; } else if (Geoms[geoId2].type == Ellipse) { Base::Console().error( "Direct tangency constraint between circle and ellipse is not " "supported. Use tangent-via-point instead." ); return -1; } else if (Geoms[geoId2].type == Arc) { GCS::Arc& a = Arcs[Geoms[geoId2].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintTangent(c, a, tag); return ConstraintsCounter; } else if (Geoms[geoId2].type == BSpline) { Base::Console().error( "Direct tangency constraint between circle and B-spline is not " "supported. Use tangent-via-point instead." ); return -1; } } else if (Geoms[geoId1].type == Ellipse) { if (Geoms[geoId2].type == Circle) { Base::Console().error( "Direct tangency constraint between circle and ellipse is not " "supported. Use tangent-via-point instead." ); return -1; } else if (Geoms[geoId2].type == Arc) { Base::Console().error( "Direct tangency constraint between arc and ellipse is not " "supported. Use tangent-via-point instead." ); return -1; } else if (Geoms[geoId2].type == BSpline) { Base::Console().error( "Direct tangency constraint between ellipse and B-spline is not " "supported. Use tangent-via-point instead." ); return -1; } } else if (Geoms[geoId1].type == Arc) { GCS::Arc& a = Arcs[Geoms[geoId1].index]; if (Geoms[geoId2].type == Circle) { GCS::Circle& c = Circles[Geoms[geoId2].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintTangent(c, a, tag); return ConstraintsCounter; } else if (Geoms[geoId2].type == Ellipse) { Base::Console().error( "Direct tangency constraint between arc and ellipse is not " "supported. Use tangent-via-point instead." ); return -1; } else if (Geoms[geoId2].type == Arc) { GCS::Arc& a2 = Arcs[Geoms[geoId2].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintTangent(a, a2, tag); return ConstraintsCounter; } else if (Geoms[geoId2].type == BSpline) { Base::Console().error( "Direct tangency constraint between arc and B-spline is not " "supported. Use tangent-via-point instead." ); return -1; } } else if (Geoms[geoId1].type == BSpline) { Base::Console().error( "Direct tangency constraint including B-splines is not " "supported. Use tangent-via-point instead." ); return -1; } return -1; } int Sketch::addTangentLineAtBSplineKnotConstraint( int checkedlinegeoId, int checkedbsplinegeoId, int checkedknotgeoid ) { GCS::BSpline& b = BSplines[Geoms[checkedbsplinegeoId].index]; GCS::Line& l = Lines[Geoms[checkedlinegeoId].index]; size_t knotindex = b.knots.size(); const auto knotIt = std::ranges::find(b.knotpointGeoids, checkedknotgeoid); knotindex = std::distance(b.knotpointGeoids.begin(), knotIt); if (knotindex >= b.knots.size()) { Base::Console().error("addConstraint: Knot index out-of-range!\n"); return -1; } if (b.mult[knotindex] >= b.degree) { if (b.periodic || (knotindex > 0 && knotindex < (b.knots.size() - 1))) { Base::Console().error( "addTangentLineAtBSplineKnotConstraint: cannot set constraint " "when B-spline slope is discontinuous at knot!\n" ); return -1; } else { // TODO: Let angle-at-point do the work. Requires a `double * value` // return addAngleAtPointConstraint( // linegeoid, PointPos::none, // bsplinegeoid, PointPos::none, // knotgeoId, PointPos::start, // nullptr, Tangent, true); // For now we just throw an error. Base::Console().error( "addTangentLineAtBSplineKnotConstraint: This method cannot set tangent constraints " "at end knots of a B-spline. Constrain the start/end points instead.\n" ); return -1; } } else { // increases ConstraintsCounter int tag = Sketch::addPointOnObjectConstraint(checkedknotgeoid, PointPos::start, checkedlinegeoId); GCSsys.addConstraintTangentAtBSplineKnot(b, l, knotindex, tag); return ConstraintsCounter; } } int Sketch::addTangentLineEndpointAtBSplineKnotConstraint( int checkedlinegeoId, PointPos endpointPos, int checkedbsplinegeoId, int checkedknotgeoid ) { GCS::BSpline& b = BSplines[Geoms[checkedbsplinegeoId].index]; GCS::Line& l = Lines[Geoms[checkedlinegeoId].index]; auto pointId = getPointId(checkedlinegeoId, endpointPos); auto pointIdKnot = getPointId(checkedknotgeoid, PointPos::start); GCS::Point& p = Points[pointId]; GCS::Point& pk = Points[pointIdKnot]; size_t knotindex = b.knots.size(); auto knotIt = std::ranges::find(b.knotpointGeoids, checkedknotgeoid); knotindex = std::distance(b.knotpointGeoids.begin(), knotIt); if (knotindex >= b.knots.size()) { Base::Console().error("addConstraint: Knot index out-of-range!\n"); return -1; } if (b.mult[knotindex] >= b.degree) { if (b.periodic || (knotindex > 0 && knotindex < (b.knots.size() - 1))) { Base::Console().error( "addTangentLineEndpointAtBSplineKnotConstraint: cannot set " "constraint when B-spline slope is discontinuous at knot!\n" ); return -1; } else { // TODO: Let angle-at-point do the work. Requires a `double * value` // return addAngleAtPointConstraint( // linegeoid, endpointPos, // bsplinegeoid, PointPos::none, // knotgeoId, PointPos::start, // nullptr, Tangent, true); // For now we just throw an error. Base::Console().error( "addTangentLineEndpointAtBSplineKnotConstraint: This method " "cannot set tangent constraint at end knots of a B-spline. " "Constrain the start/end points instead.\n" ); return -1; } } else { int tag = ++ConstraintsCounter; GCSsys.addConstraintP2PCoincident(p, pk, tag); GCSsys.addConstraintTangentAtBSplineKnot(b, l, knotindex, tag); return ConstraintsCounter; } } // This function handles any type of tangent, perpendicular and angle // constraint that involves a point. // i.e. endpoint-to-curve, endpoint-to-endpoint and tangent-via-point // geoid1, geoid2 and geoid3 as in the constraint object. // For perp-ty and tangency, angle is used to lock the direction. // angle==0 - autodetect direction. +pi/2, -pi/2 - specific direction. int Sketch::addAngleAtPointConstraint( int geoId1, PointPos pos1, int geoId2, PointPos pos2, int geoId3, PointPos pos3, double* value, ConstraintType cTyp, bool driving ) { using std::numbers::pi; if (!(cTyp == Angle || cTyp == Tangent || cTyp == Perpendicular)) { // assert(0);//none of the three types. Why are we here?? return -1; } bool avp = geoId3 != GeoEnum::GeoUndef; // is angle-via-point? bool e2c = pos2 == PointPos::none && pos1 != PointPos::none; // is endpoint-to-curve? bool e2e = pos2 != PointPos::none && pos1 != PointPos::none; // is endpoint-to-endpoint? if (!(avp || e2c || e2e)) { // assert(0);//none of the three types. Why are we here?? return -1; } geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); if (avp) { geoId3 = checkGeoId(geoId3); } if (Geoms[geoId1].type == Point || Geoms[geoId2].type == Point) { Base::Console().error("addAngleAtPointConstraint: one of the curves is a point!\n"); return -1; } GCS::Curve* crv1 = getGCSCurveByGeoId(geoId1); GCS::Curve* crv2 = getGCSCurveByGeoId(geoId2); if (!crv1 || !crv2) { Base::Console().error("addAngleAtPointConstraint: getGCSCurveByGeoId returned NULL!\n"); return -1; } int pointId = -1; if (avp) { pointId = getPointId(geoId3, pos3); } else if (e2e || e2c) { pointId = getPointId(geoId1, pos1); } if (pointId < 0 || pointId >= int(Points.size())) { Base::Console().error("addAngleAtPointConstraint: point index out of range.\n"); return -1; } GCS::Point& p = Points[pointId]; GCS::Point* p2 = nullptr; if (e2e) { // we need second point int pointId = getPointId(geoId2, pos2); if (pointId < 0 || pointId >= int(Points.size())) { Base::Console().error("addAngleAtPointConstraint: point index out of range.\n"); return -1; } p2 = &(Points[pointId]); } double* angle = value; // For tangency/perpendicularity, we don't just copy the angle. // The angle stored for tangency/perpendicularity is offset, so that the options // are -Pi/2 and Pi/2. If value is 0 - this is an indicator of an old sketch. // Use autodetect then. // The same functionality is implemented in SketchObject.cpp, where // it is used to permanently lock down the autodecision. if (cTyp != Angle) { // The same functionality is implemented in SketchObject.cpp, where // it is used to permanently lock down the autodecision. // the difference between the datum value and the actual angle to apply. // (datum=angle+offset) double angleOffset = 0.0; // the desired angle value (and we are to decide if 180* should be added to it) double angleDesire = 0.0; if (cTyp == Tangent) { angleOffset = -pi / 2; angleDesire = 0.0; } if (cTyp == Perpendicular) { angleOffset = 0; angleDesire = pi / 2; } if (*value == 0.0) { // autodetect tangency internal/external (and same for perpendicularity) double angleErr = GCSsys.calculateAngleViaPoint(*crv1, *crv2, p) - angleDesire; // bring angleErr to -pi..pi if (angleErr > pi) { angleErr -= pi * 2; } if (angleErr < -pi) { angleErr += pi * 2; } // the autodetector if (fabs(angleErr) > pi / 2) { angleDesire += pi; } *angle = angleDesire; } else { *angle = *value - angleOffset; } } int tag = -1; // FIXME: Perform construction of any parameters where this method is called instead of here if (e2c) { if (Geoms[geoId2].type == BSpline) { GCS::Point& p1 = Points[getPointId(geoId1, pos1)]; auto* partBsp = static_cast(Geoms[geoId2].geo); double uNear; partBsp->closestParameter(Base::Vector3d(*p1.x, *p1.y, 0.0), uNear); double* pointparam = new double(uNear); Parameters.push_back(pointparam); --ConstraintsCounter; // Do this just before point-on-object because ConstraintsCounter // is increased again before being used tag = addPointOnObjectConstraint( geoId1, pos1, geoId2, pointparam, driving ); // increases ConstraintsCounter GCSsys.addConstraintAngleViaPointAndParam(*crv2, *crv1, p, pointparam, angle, tag, driving); } else { // increases ConstraintsCounter tag = Sketch::addPointOnObjectConstraint( geoId1, pos1, geoId2, driving ); // increases ConstraintsCounter GCSsys.addConstraintAngleViaPoint(*crv1, *crv2, p, angle, tag, driving); } } if (e2e) { tag = ++ConstraintsCounter; GCSsys.addConstraintP2PCoincident(p, *p2, tag, driving); if (Geoms[geoId1].type == BSpline && Geoms[geoId2].type == BSpline) { GCSsys.addConstraintAngleViaTwoPoints(*crv1, *crv2, p, *p2, angle, tag, driving); } else { GCSsys.addConstraintAngleViaPoint(*crv1, *crv2, p, angle, tag, driving); } } if (avp) { tag = ++ConstraintsCounter; if (Geoms[geoId1].type == BSpline || Geoms[geoId2].type == BSpline) { if (Geoms[geoId1].type == BSpline && Geoms[geoId2].type == BSpline) { GCS::Point& p3 = Points[getPointId(geoId3, pos3)]; auto* partBsp = static_cast(Geoms[geoId1].geo); double uNear; partBsp->closestParameter(Base::Vector3d(*p3.x, *p3.y, 0.0), uNear); double* pointparam1 = new double(uNear); Parameters.push_back(pointparam1); --ConstraintsCounter; // Do this just before point-on-object because // ConstraintsCounter is increased again before being used addPointOnObjectConstraint( geoId3, pos3, geoId1, pointparam1, driving ); // increases ConstraintsCounter partBsp = static_cast(Geoms[geoId2].geo); partBsp->closestParameter(Base::Vector3d(*p3.x, *p3.y, 0.0), uNear); double* pointparam2 = new double(uNear); --ConstraintsCounter; // Do this just before point-on-object because // ConstraintsCounter is increased again before being used addPointOnObjectConstraint( geoId3, pos3, geoId2, pointparam2, driving ); // increases ConstraintsCounter Parameters.push_back(pointparam2); GCSsys.addConstraintAngleViaPointAndTwoParams( *crv1, *crv2, p, pointparam1, pointparam2, angle, tag, driving ); } else { if (Geoms[geoId1].type != BSpline) { std::swap(geoId1, geoId2); std::swap(crv1, crv2); std::swap(pos1, pos2); // FIXME: Confirm whether or not this is needed // *angle = -*angle; } GCS::Point& p3 = Points[getPointId(geoId3, pos3)]; auto* partBsp = static_cast(Geoms[geoId1].geo); double uNear; partBsp->closestParameter(Base::Vector3d(*p3.x, *p3.y, 0.0), uNear); double* pointparam = new double(uNear); Parameters.push_back(pointparam); --ConstraintsCounter; // Do this just before point-on-object because // ConstraintsCounter is increased again before being used addPointOnObjectConstraint( geoId3, pos3, geoId1, pointparam, driving ); // increases ConstraintsCounter GCSsys.addConstraintAngleViaPointAndParam(*crv1, *crv2, p, pointparam, angle, tag, driving); } } else { GCSsys.addConstraintAngleViaPoint(*crv1, *crv2, p, angle, tag, driving); } } return ConstraintsCounter; } // line length and arc length constraint int Sketch::addDistanceConstraint(int geoId, double* value, bool driving) { geoId = checkGeoId(geoId); int tag = ++ConstraintsCounter; if (Geoms[geoId].type == Line) { GCS::Line& l = Lines[Geoms[geoId].index]; GCSsys.addConstraintP2PDistance(l.p1, l.p2, value, tag, driving); } else if (Geoms[geoId].type == Arc) { GCS::Arc& a = Arcs[Geoms[geoId].index]; GCSsys.addConstraintArcLength(a, value, tag, driving); } else { return -1; } return ConstraintsCounter; } // point to line or circular distance constraint int Sketch::addDistanceConstraint(int geoId1, PointPos pos1, int geoId2, double* value, bool driving) { geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); int pointId1 = getPointId(geoId1, pos1); if (pointId1 < 0 && pointId1 >= int(Points.size())) { return -1; } GCS::Point& p1 = Points[pointId1]; if (Geoms[geoId2].type == Line) { GCS::Line& l2 = Lines[Geoms[geoId2].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintP2LDistance(p1, l2, value, tag, driving); return ConstraintsCounter; } else { GCS::Circle* c2; if (Geoms[geoId2].type == Circle) { c2 = &Circles[Geoms[geoId2].index]; } else if (Geoms[geoId2].type == Arc) { c2 = &Arcs[Geoms[geoId2].index]; } else { return -1; } int tag = ++ConstraintsCounter; GCSsys.addConstraintP2CDistance(p1, *c2, value, tag, driving); return ConstraintsCounter; } } // point to point distance constraint int Sketch::addDistanceConstraint( int geoId1, PointPos pos1, int geoId2, PointPos pos2, double* value, bool driving ) { geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); int pointId1 = getPointId(geoId1, pos1); int pointId2 = getPointId(geoId2, pos2); if (pointId1 >= 0 && pointId1 < int(Points.size()) && pointId2 >= 0 && pointId2 < int(Points.size())) { GCS::Point& p1 = Points[pointId1]; GCS::Point& p2 = Points[pointId2]; int tag = ++ConstraintsCounter; GCSsys.addConstraintP2PDistance(p1, p2, value, tag, driving); return ConstraintsCounter; } return -1; } // circular-(circular or line) distance constraint int Sketch::addDistanceConstraint(int geoId1, int geoId2, double* value, bool driving) { geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); if (Geoms[geoId2].type == Line) { GCS::Circle* c1; if (Geoms[geoId1].type == Circle) { c1 = &Circles[Geoms[geoId1].index]; } else if (Geoms[geoId1].type == Arc) { c1 = &Arcs[Geoms[geoId1].index]; } else { return -1; } GCS::Line* l = &Lines[Geoms[geoId2].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintC2LDistance(*c1, *l, value, tag, driving); return ConstraintsCounter; } else { GCS::Circle *c1 {}, *c2 {}; if (Geoms[geoId1].type == Circle) { c1 = &Circles[Geoms[geoId1].index]; } else if (Geoms[geoId1].type == Arc) { c1 = &Arcs[Geoms[geoId1].index]; } if (Geoms[geoId2].type == Circle) { c2 = &Circles[Geoms[geoId2].index]; } else if (Geoms[geoId2].type == Arc) { c2 = &Arcs[Geoms[geoId2].index]; } if (c1 == nullptr || c2 == nullptr) { return -1; } int tag = ++ConstraintsCounter; GCSsys.addConstraintC2CDistance(*c1, *c2, value, tag, driving); return ConstraintsCounter; } } int Sketch::addRadiusConstraint(int geoId, double* value, bool driving) { geoId = checkGeoId(geoId); if (Geoms[geoId].type == Circle) { GCS::Circle& c = Circles[Geoms[geoId].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintCircleRadius(c, value, tag, driving); return ConstraintsCounter; } else if (Geoms[geoId].type == Arc) { GCS::Arc& a = Arcs[Geoms[geoId].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintArcRadius(a, value, tag, driving); return ConstraintsCounter; } return -1; } int Sketch::addDiameterConstraint(int geoId, double* value, bool driving) { geoId = checkGeoId(geoId); if (Geoms[geoId].type == Circle) { GCS::Circle& c = Circles[Geoms[geoId].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintCircleDiameter(c, value, tag, driving); return ConstraintsCounter; } else if (Geoms[geoId].type == Arc) { GCS::Arc& a = Arcs[Geoms[geoId].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintArcDiameter(a, value, tag, driving); return ConstraintsCounter; } return -1; } // line orientation angle constraint int Sketch::addAngleConstraint(int geoId, double* value, bool driving) { geoId = checkGeoId(geoId); if (Geoms[geoId].type == Line) { GCS::Line& l = Lines[Geoms[geoId].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintP2PAngle(l.p1, l.p2, value, tag, driving); return ConstraintsCounter; } else if (Geoms[geoId].type == Arc) { GCS::Arc& a = Arcs[Geoms[geoId].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintL2LAngle(a.center, a.start, a.center, a.end, value, tag, driving); return ConstraintsCounter; } return -1; } // line to line angle constraint int Sketch::addAngleConstraint(int geoId1, int geoId2, double* value, bool driving) { geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); if (Geoms[geoId1].type != Line || Geoms[geoId2].type != Line) { return -1; } GCS::Line& l1 = Lines[Geoms[geoId1].index]; GCS::Line& l2 = Lines[Geoms[geoId2].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintL2LAngle(l1, l2, value, tag, driving); return ConstraintsCounter; } // line to line angle constraint (with explicitly given start points) int Sketch::addAngleConstraint(int geoId1, PointPos pos1, int geoId2, PointPos pos2, double* value, bool driving) { geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); if (Geoms[geoId1].type != Line || Geoms[geoId2].type != Line) { return -1; } GCS::Point *l1p1 = nullptr, *l1p2 = nullptr; if (pos1 == PointPos::start) { l1p1 = &Points[Geoms[geoId1].startPointId]; l1p2 = &Points[Geoms[geoId1].endPointId]; } else if (pos1 == PointPos::end) { l1p1 = &Points[Geoms[geoId1].endPointId]; l1p2 = &Points[Geoms[geoId1].startPointId]; } GCS::Point *l2p1 = nullptr, *l2p2 = nullptr; if (pos2 == PointPos::start) { l2p1 = &Points[Geoms[geoId2].startPointId]; l2p2 = &Points[Geoms[geoId2].endPointId]; } else if (pos2 == PointPos::end) { l2p1 = &Points[Geoms[geoId2].endPointId]; l2p2 = &Points[Geoms[geoId2].startPointId]; } if (!l1p1 || !l2p1) { return -1; } int tag = ++ConstraintsCounter; GCSsys.addConstraintL2LAngle(*l1p1, *l1p2, *l2p1, *l2p2, value, tag, driving); return ConstraintsCounter; } int Sketch::addEqualConstraint(int geoId1, int geoId2) { geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); if (Geoms[geoId1].type == Line && Geoms[geoId2].type == Line) { GCS::Line& l1 = Lines[Geoms[geoId1].index]; GCS::Line& l2 = Lines[Geoms[geoId2].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintEqualLength(l1, l2, tag); return ConstraintsCounter; } if (Geoms[geoId2].type == Circle) { if (Geoms[geoId1].type == Circle) { GCS::Circle& c1 = Circles[Geoms[geoId1].index]; GCS::Circle& c2 = Circles[Geoms[geoId2].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintEqualRadius(c1, c2, tag); return ConstraintsCounter; } else { std::swap(geoId1, geoId2); } } if (Geoms[geoId2].type == Ellipse) { if (Geoms[geoId1].type == Ellipse) { GCS::Ellipse& e1 = Ellipses[Geoms[geoId1].index]; GCS::Ellipse& e2 = Ellipses[Geoms[geoId2].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintEqualRadii(e1, e2, tag); return ConstraintsCounter; } else { std::swap(geoId1, geoId2); } } if (Geoms[geoId1].type == Circle) { GCS::Circle& c1 = Circles[Geoms[geoId1].index]; if (Geoms[geoId2].type == Arc) { GCS::Arc& a2 = Arcs[Geoms[geoId2].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintEqualRadius(c1, a2, tag); return ConstraintsCounter; } } if (Geoms[geoId1].type == Arc && Geoms[geoId2].type == Arc) { GCS::Arc& a1 = Arcs[Geoms[geoId1].index]; GCS::Arc& a2 = Arcs[Geoms[geoId2].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintEqualRadius(a1, a2, tag); return ConstraintsCounter; } if (Geoms[geoId2].type == ArcOfEllipse) { if (Geoms[geoId1].type == ArcOfEllipse) { GCS::ArcOfEllipse& a1 = ArcsOfEllipse[Geoms[geoId1].index]; GCS::ArcOfEllipse& a2 = ArcsOfEllipse[Geoms[geoId2].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintEqualRadii(a1, a2, tag); return ConstraintsCounter; } } if (Geoms[geoId2].type == ArcOfHyperbola) { if (Geoms[geoId1].type == ArcOfHyperbola) { GCS::ArcOfHyperbola& a1 = ArcsOfHyperbola[Geoms[geoId1].index]; GCS::ArcOfHyperbola& a2 = ArcsOfHyperbola[Geoms[geoId2].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintEqualRadii(a1, a2, tag); return ConstraintsCounter; } } if (Geoms[geoId2].type == ArcOfParabola) { if (Geoms[geoId1].type == ArcOfParabola) { GCS::ArcOfParabola& a1 = ArcsOfParabola[Geoms[geoId1].index]; GCS::ArcOfParabola& a2 = ArcsOfParabola[Geoms[geoId2].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintEqualFocus(a1, a2, tag); return ConstraintsCounter; } } if (Geoms[geoId1].type == Ellipse) { GCS::Ellipse& e1 = Ellipses[Geoms[geoId1].index]; if (Geoms[geoId2].type == ArcOfEllipse) { GCS::ArcOfEllipse& a2 = ArcsOfEllipse[Geoms[geoId2].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintEqualRadii(a2, e1, tag); return ConstraintsCounter; } } Base::Console().warning( "Equality constraints between %s and %s are not supported.\n", nameByType(Geoms[geoId1].type), nameByType(Geoms[geoId2].type) ); return -1; } // point on object constraint int Sketch::addPointOnObjectConstraint(int geoId1, PointPos pos1, int geoId2, bool driving) { geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); int pointId1 = getPointId(geoId1, pos1); if (pointId1 >= 0 && pointId1 < int(Points.size())) { GCS::Point& p1 = Points[pointId1]; if (Geoms[geoId2].type == Line) { GCS::Line& l2 = Lines[Geoms[geoId2].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintPointOnLine(p1, l2, tag, driving); return ConstraintsCounter; } else if (Geoms[geoId2].type == Arc) { GCS::Arc& a = Arcs[Geoms[geoId2].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintPointOnArc(p1, a, tag, driving); return ConstraintsCounter; } else if (Geoms[geoId2].type == Circle) { GCS::Circle& c = Circles[Geoms[geoId2].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintPointOnCircle(p1, c, tag, driving); return ConstraintsCounter; } else if (Geoms[geoId2].type == Ellipse) { GCS::Ellipse& e = Ellipses[Geoms[geoId2].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintPointOnEllipse(p1, e, tag, driving); return ConstraintsCounter; } else if (Geoms[geoId2].type == ArcOfEllipse) { GCS::ArcOfEllipse& a = ArcsOfEllipse[Geoms[geoId2].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintPointOnEllipse(p1, a, tag, driving); return ConstraintsCounter; } else if (Geoms[geoId2].type == ArcOfHyperbola) { GCS::ArcOfHyperbola& a = ArcsOfHyperbola[Geoms[geoId2].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintPointOnHyperbolicArc(p1, a, tag, driving); return ConstraintsCounter; } else if (Geoms[geoId2].type == ArcOfParabola) { GCS::ArcOfParabola& a = ArcsOfParabola[Geoms[geoId2].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintPointOnParabolicArc(p1, a, tag, driving); return ConstraintsCounter; } } return -1; } int Sketch::addPointOnObjectConstraint(int geoId1, PointPos pos1, int geoId2, double* pointparam, bool driving) { geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); int pointId1 = getPointId(geoId1, pos1); if (pointId1 >= 0 && pointId1 < int(Points.size())) { GCS::Point& p1 = Points[pointId1]; if (Geoms[geoId2].type == BSpline) { GCS::BSpline& b = BSplines[Geoms[geoId2].index]; int tag = ++ConstraintsCounter; auto partBsp = static_cast(Geoms[geoId2].geo); double uNear; partBsp->closestParameter(Base::Vector3d(*p1.x, *p1.y, 0.0), uNear); *pointparam = uNear; GCSsys.addConstraintPointOnBSpline(p1, b, pointparam, tag, driving); return ConstraintsCounter; } } return -1; } // symmetric points constraint int Sketch::addSymmetricConstraint(int geoId1, PointPos pos1, int geoId2, PointPos pos2, int geoId3) { geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); geoId3 = checkGeoId(geoId3); if (Geoms[geoId3].type != Line) { return -1; } // Special handling for arc endpoint symmetry when the arc's center is on the symmetry axis. // In this specific geometric configuration, the perpendicularity part of the symmetry // constraint is inherently redundant. Applying it would lead to solver errors. // We detect this case and add only the midpoint-on-line part of the constraint, // which is sufficient to enforce symmetry without causing redundancy. // Step 1: Check if the two points (p1, p2) are endpoints of the same arc. // We iterate through all geometries to find an arc whose start/end points match our input // points. int arcGeoId = -1; for (int i = 0; i < (int)Geoms.size(); ++i) { if (Geoms[i].type == Arc) { int arcStartPointId = Geoms[i].startPointId; int arcEndPointId = Geoms[i].endPointId; int p1_Id = getPointId(geoId1, pos1); int p2_Id = getPointId(geoId2, pos2); if ((p1_Id == arcStartPointId && p2_Id == arcEndPointId) || (p1_Id == arcEndPointId && p2_Id == arcStartPointId)) { arcGeoId = i; break; } } } if (arcGeoId != -1) { // Step 2: We found the arc. Now check if its center lies on the symmetry line. int centerPointId = Geoms[arcGeoId].midPointId; GCS::Point& center = Points[centerPointId]; GCS::Line& l = Lines[Geoms[geoId3].index]; // The symmetry line double dx = *l.p2.x - *l.p1.x; double dy = *l.p2.y - *l.p1.y; double line_len_sq = dx * dx + dy * dy; if (line_len_sq > Precision::SquareConfusion()) { double area = (*center.x - *l.p1.x) * dy - (*center.y - *l.p1.y) * dx; if (std::abs(area) / sqrt(line_len_sq) < Precision::Confusion()) { // The center IS on the symmetry line. This is the degenerate case. // Weaken the constraint by only adding the midpoint part. int pointId1 = getPointId(geoId1, pos1); int pointId2 = getPointId(geoId2, pos2); GCS::Point& p1 = Points[pointId1]; GCS::Point& p2 = Points[pointId2]; int tag = ++ConstraintsCounter; // addConstraintMidpointOnLine is a GCS::System method, but we can call it from // here. GCSsys.addConstraintMidpointOnLine(p1, p2, l.p1, l.p2, tag); return ConstraintsCounter; } } } int pointId1 = getPointId(geoId1, pos1); int pointId2 = getPointId(geoId2, pos2); if (pointId1 >= 0 && pointId1 < int(Points.size()) && pointId2 >= 0 && pointId2 < int(Points.size())) { GCS::Point& p1 = Points[pointId1]; GCS::Point& p2 = Points[pointId2]; GCS::Line& l = Lines[Geoms[geoId3].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintP2PSymmetric(p1, p2, l, tag); return ConstraintsCounter; } return -1; } int Sketch::addSymmetricConstraint( int geoId1, PointPos pos1, int geoId2, PointPos pos2, int geoId3, PointPos pos3 ) { geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); geoId3 = checkGeoId(geoId3); int pointId1 = getPointId(geoId1, pos1); int pointId2 = getPointId(geoId2, pos2); int pointId3 = getPointId(geoId3, pos3); if (pointId1 >= 0 && pointId1 < int(Points.size()) && pointId2 >= 0 && pointId2 < int(Points.size()) && pointId3 >= 0 && pointId3 < int(Points.size())) { GCS::Point& p1 = Points[pointId1]; GCS::Point& p2 = Points[pointId2]; GCS::Point& p = Points[pointId3]; int tag = ++ConstraintsCounter; GCSsys.addConstraintP2PSymmetric(p1, p2, p, tag); return ConstraintsCounter; } return -1; } int Sketch::addSnellsLawConstraint( int geoIdRay1, PointPos posRay1, int geoIdRay2, PointPos posRay2, int geoIdBnd, double* value, double* secondvalue, bool driving ) { geoIdRay1 = checkGeoId(geoIdRay1); geoIdRay2 = checkGeoId(geoIdRay2); geoIdBnd = checkGeoId(geoIdBnd); if (Geoms[geoIdRay1].type == Point || Geoms[geoIdRay2].type == Point) { Base::Console().error("addSnellsLawConstraint: point is not a curve. Not applicable!\n"); return -1; } GCS::Curve* ray1 = getGCSCurveByGeoId(geoIdRay1); GCS::Curve* ray2 = getGCSCurveByGeoId(geoIdRay2); GCS::Curve* boundary = getGCSCurveByGeoId(geoIdBnd); if (!ray1 || !ray2 || !boundary) { Base::Console().error("addSnellsLawConstraint: getGCSCurveByGeoId returned NULL!\n"); return -1; } int pointId1 = getPointId(geoIdRay1, posRay1); int pointId2 = getPointId(geoIdRay2, posRay2); if (pointId1 < 0 || pointId1 >= int(Points.size()) || pointId2 < 0 || pointId2 >= int(Points.size())) { Base::Console().error("addSnellsLawConstraint: point index out of range.\n"); return -1; } GCS::Point& p1 = Points[pointId1]; // add the parameters (refractive indexes) // n1 uses the place hold by n2divn1, so that is retrievable in updateNonDrivingConstraints double* n1 = value; double* n2 = secondvalue; double n2divn1 = *value; if (fabs(n2divn1) >= 1.0) { *n2 = n2divn1; *n1 = 1.0; } else { *n2 = 1.0; *n1 = 1 / n2divn1; } int tag = -1; // increases ConstraintsCounter // tag = Sketch::addPointOnObjectConstraint(geoIdRay1, posRay1, geoIdBnd); tag = ++ConstraintsCounter; // GCSsys.addConstraintP2PCoincident(p1, p2, tag); GCSsys.addConstraintSnellsLaw( *ray1, *ray2, *boundary, p1, n1, n2, posRay1 == PointPos::start, posRay2 == PointPos::end, tag, driving ); return ConstraintsCounter; } int Sketch::addInternalAlignmentEllipseMajorDiameter(int geoId1, int geoId2) { std::swap(geoId1, geoId2); geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); if (Geoms[geoId1].type != Ellipse && Geoms[geoId1].type != ArcOfEllipse) { return -1; } if (Geoms[geoId2].type != Line) { return -1; } int pointId1 = getPointId(geoId2, PointPos::start); int pointId2 = getPointId(geoId2, PointPos::end); if (pointId1 >= 0 && pointId1 < int(Points.size()) && pointId2 >= 0 && pointId2 < int(Points.size())) { GCS::Point& p1 = Points[pointId1]; GCS::Point& p2 = Points[pointId2]; if (Geoms[geoId1].type == Ellipse) { GCS::Ellipse& e1 = Ellipses[Geoms[geoId1].index]; // constraints // 1. start point with ellipse -a // 2. end point with ellipse +a int tag = ++ConstraintsCounter; GCSsys.addConstraintInternalAlignmentEllipseMajorDiameter(e1, p1, p2, tag); return ConstraintsCounter; } else { GCS::ArcOfEllipse& a1 = ArcsOfEllipse[Geoms[geoId1].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintInternalAlignmentEllipseMajorDiameter(a1, p1, p2, tag); return ConstraintsCounter; } } return -1; } int Sketch::addInternalAlignmentEllipseMinorDiameter(int geoId1, int geoId2) { std::swap(geoId1, geoId2); geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); if (Geoms[geoId1].type != Ellipse && Geoms[geoId1].type != ArcOfEllipse) { return -1; } if (Geoms[geoId2].type != Line) { return -1; } int pointId1 = getPointId(geoId2, PointPos::start); int pointId2 = getPointId(geoId2, PointPos::end); if (pointId1 >= 0 && pointId1 < int(Points.size()) && pointId2 >= 0 && pointId2 < int(Points.size())) { GCS::Point& p1 = Points[pointId1]; GCS::Point& p2 = Points[pointId2]; if (Geoms[geoId1].type == Ellipse) { GCS::Ellipse& e1 = Ellipses[Geoms[geoId1].index]; // constraints // 1. start point with ellipse -a // 2. end point with ellipse +a int tag = ++ConstraintsCounter; GCSsys.addConstraintInternalAlignmentEllipseMinorDiameter(e1, p1, p2, tag); return ConstraintsCounter; } else { GCS::ArcOfEllipse& a1 = ArcsOfEllipse[Geoms[geoId1].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintInternalAlignmentEllipseMinorDiameter(a1, p1, p2, tag); return ConstraintsCounter; } } return -1; } int Sketch::addInternalAlignmentEllipseFocus1(int geoId1, int geoId2) { std::swap(geoId1, geoId2); geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); if (Geoms[geoId1].type != Ellipse && Geoms[geoId1].type != ArcOfEllipse) { return -1; } if (Geoms[geoId2].type != Point) { return -1; } int pointId1 = getPointId(geoId2, PointPos::start); if (pointId1 >= 0 && pointId1 < int(Points.size())) { GCS::Point& p1 = Points[pointId1]; if (Geoms[geoId1].type == Ellipse) { GCS::Ellipse& e1 = Ellipses[Geoms[geoId1].index]; // constraints // 1. start point with ellipse -a // 2. end point with ellipse +a int tag = ++ConstraintsCounter; GCSsys.addConstraintInternalAlignmentEllipseFocus1(e1, p1, tag); return ConstraintsCounter; } else { GCS::ArcOfEllipse& a1 = ArcsOfEllipse[Geoms[geoId1].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintInternalAlignmentEllipseFocus1(a1, p1, tag); return ConstraintsCounter; } } return -1; } int Sketch::addInternalAlignmentEllipseFocus2(int geoId1, int geoId2) { std::swap(geoId1, geoId2); geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); if (Geoms[geoId1].type != Ellipse && Geoms[geoId1].type != ArcOfEllipse) { return -1; } if (Geoms[geoId2].type != Point) { return -1; } int pointId1 = getPointId(geoId2, PointPos::start); if (pointId1 >= 0 && pointId1 < int(Points.size())) { GCS::Point& p1 = Points[pointId1]; if (Geoms[geoId1].type == Ellipse) { GCS::Ellipse& e1 = Ellipses[Geoms[geoId1].index]; // constraints // 1. start point with ellipse -a // 2. end point with ellipse +a int tag = ++ConstraintsCounter; GCSsys.addConstraintInternalAlignmentEllipseFocus2(e1, p1, tag); return ConstraintsCounter; } else { GCS::ArcOfEllipse& a1 = ArcsOfEllipse[Geoms[geoId1].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintInternalAlignmentEllipseFocus2(a1, p1, tag); return ConstraintsCounter; } } return -1; } int Sketch::addInternalAlignmentHyperbolaMajorDiameter(int geoId1, int geoId2) { std::swap(geoId1, geoId2); geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); if (Geoms[geoId1].type != ArcOfHyperbola) { return -1; } if (Geoms[geoId2].type != Line) { return -1; } int pointId1 = getPointId(geoId2, PointPos::start); int pointId2 = getPointId(geoId2, PointPos::end); if (pointId1 >= 0 && pointId1 < int(Points.size()) && pointId2 >= 0 && pointId2 < int(Points.size())) { GCS::Point& p1 = Points[pointId1]; GCS::Point& p2 = Points[pointId2]; GCS::ArcOfHyperbola& a1 = ArcsOfHyperbola[Geoms[geoId1].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintInternalAlignmentHyperbolaMajorDiameter(a1, p1, p2, tag); return ConstraintsCounter; } return -1; } int Sketch::addInternalAlignmentHyperbolaMinorDiameter(int geoId1, int geoId2) { std::swap(geoId1, geoId2); geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); if (Geoms[geoId1].type != ArcOfHyperbola) { return -1; } if (Geoms[geoId2].type != Line) { return -1; } int pointId1 = getPointId(geoId2, PointPos::start); int pointId2 = getPointId(geoId2, PointPos::end); if (pointId1 >= 0 && pointId1 < int(Points.size()) && pointId2 >= 0 && pointId2 < int(Points.size())) { GCS::Point& p1 = Points[pointId1]; GCS::Point& p2 = Points[pointId2]; GCS::ArcOfHyperbola& a1 = ArcsOfHyperbola[Geoms[geoId1].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintInternalAlignmentHyperbolaMinorDiameter(a1, p1, p2, tag); return ConstraintsCounter; } return -1; } int Sketch::addInternalAlignmentHyperbolaFocus(int geoId1, int geoId2) { std::swap(geoId1, geoId2); geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); if (Geoms[geoId1].type != ArcOfHyperbola) { return -1; } if (Geoms[geoId2].type != Point) { return -1; } int pointId1 = getPointId(geoId2, PointPos::start); if (pointId1 >= 0 && pointId1 < int(Points.size())) { GCS::Point& p1 = Points[pointId1]; GCS::ArcOfHyperbola& a1 = ArcsOfHyperbola[Geoms[geoId1].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintInternalAlignmentHyperbolaFocus(a1, p1, tag); return ConstraintsCounter; } return -1; } int Sketch::addInternalAlignmentParabolaFocus(int geoId1, int geoId2) { std::swap(geoId1, geoId2); geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); if (Geoms[geoId1].type != ArcOfParabola) { return -1; } if (Geoms[geoId2].type != Point) { return -1; } int pointId1 = getPointId(geoId2, PointPos::start); if (pointId1 >= 0 && pointId1 < int(Points.size())) { GCS::Point& p1 = Points[pointId1]; GCS::ArcOfParabola& a1 = ArcsOfParabola[Geoms[geoId1].index]; int tag = ++ConstraintsCounter; GCSsys.addConstraintInternalAlignmentParabolaFocus(a1, p1, tag); return ConstraintsCounter; } return -1; } int Sketch::addInternalAlignmentParabolaFocalDistance(int geoId1, int geoId2) { std::swap(geoId1, geoId2); geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); if (Geoms[geoId1].type != ArcOfParabola) { return -1; } if (Geoms[geoId2].type != Line) { return -1; } int pointId1 = getPointId(geoId2, PointPos::start); int pointId2 = getPointId(geoId2, PointPos::end); if (pointId1 >= 0 && pointId1 < int(Points.size()) && pointId2 >= 0 && pointId2 < int(Points.size())) { GCS::Point& p1 = Points[pointId1]; GCS::Point& p2 = Points[pointId2]; GCS::ArcOfParabola& a1 = ArcsOfParabola[Geoms[geoId1].index]; auto& vertexpoint = a1.vertex; auto& focuspoint = a1.focus1; int tag = ++ConstraintsCounter; GCSsys.addConstraintP2PCoincident(p1, vertexpoint, tag); GCSsys.addConstraintP2PCoincident(p2, focuspoint, tag); return ConstraintsCounter; } return -1; } int Sketch::addInternalAlignmentBSplineControlPoint(int geoId1, int geoId2, int poleindex) { std::swap(geoId1, geoId2); geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); if (Geoms[geoId1].type != BSpline) { return -1; } if (Geoms[geoId2].type != Circle) { return -1; } int pointId1 = getPointId(geoId2, PointPos::mid); if (pointId1 >= 0 && pointId1 < int(Points.size())) { GCS::Circle& c = Circles[Geoms[geoId2].index]; GCS::BSpline& b = BSplines[Geoms[geoId1].index]; assert(poleindex < static_cast(b.poles.size()) && poleindex >= 0); int tag = ++ConstraintsCounter; GCSsys.addConstraintInternalAlignmentBSplineControlPoint(b, c, poleindex, tag); return ConstraintsCounter; } return -1; } int Sketch::addInternalAlignmentKnotPoint(int geoId1, int geoId2, int knotindex) { std::swap(geoId1, geoId2); geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); if (Geoms[geoId1].type != BSpline) { return -1; } if (Geoms[geoId2].type != Point) { return -1; } int pointId1 = getPointId(geoId2, PointPos::start); if (pointId1 >= 0 && pointId1 < int(Points.size())) { GCS::Point& p = Points[pointId1]; GCS::BSpline& b = BSplines[Geoms[geoId1].index]; assert(knotindex < static_cast(b.knots.size()) && knotindex >= 0); b.knotpointGeoids[knotindex] = geoId2; int tag = ++ConstraintsCounter; GCSsys.addConstraintInternalAlignmentKnotPoint(b, p, knotindex, tag); return ConstraintsCounter; } return -1; } double Sketch::calculateAngleViaPoint(int geoId1, int geoId2, double px, double py) { geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); GCS::Point p; p.x = &px; p.y = &py; // check pointers GCS::Curve* crv1 = getGCSCurveByGeoId(geoId1); GCS::Curve* crv2 = getGCSCurveByGeoId(geoId2); if (!crv1 || !crv2) { throw Base::ValueError("calculateAngleViaPoint: getGCSCurveByGeoId returned NULL!"); } return GCSsys.calculateAngleViaPoint(*crv1, *crv2, p); } double Sketch::calculateAngleViaParams(int geoId1, int geoId2, double param1, double param2) { geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); // check pointers GCS::Curve* crv1 = getGCSCurveByGeoId(geoId1); GCS::Curve* crv2 = getGCSCurveByGeoId(geoId2); if (!crv1 || !crv2) { throw Base::ValueError("calculateAngleViaPoint: getGCSCurveByGeoId returned NULL!"); } // FIXME: This should probably not be needed auto* crv1AsBSpline = dynamic_cast(crv1); if (crv1AsBSpline && crv1AsBSpline->flattenedknots.empty()) { crv1AsBSpline->setupFlattenedKnots(); } auto* crv2AsBSpline = dynamic_cast(crv2); if (crv2AsBSpline && crv2AsBSpline->flattenedknots.empty()) { crv2AsBSpline->setupFlattenedKnots(); } return GCSsys.calculateAngleViaParams(*crv1, *crv2, ¶m1, ¶m2); } Base::Vector3d Sketch::calculateNormalAtPoint(int geoIdCurve, double px, double py) const { geoIdCurve = checkGeoId(geoIdCurve); GCS::Point p; p.x = &px; p.y = &py; // check pointers const GCS::Curve* crv = getGCSCurveByGeoId(geoIdCurve); if (!crv) { throw Base::ValueError("calculateNormalAtPoint: getGCSCurveByGeoId returned NULL!\n"); } double tx = 0.0, ty = 0.0; GCSsys.calculateNormalAtPoint(*crv, p, tx, ty); return Base::Vector3d(tx, ty, 0.0); } bool Sketch::updateGeometry() { int i = 0; for (const GeoDef& it : Geoms) { try { updateGeometry(it); ++i; } catch (Base::Exception& e) { Base::Console().error("Updating geometry: Error build geometry(%d): %s\n", i, e.what()); return false; } } return true; } void Sketch::tryUpdateGeometry() { for (const GeoDef& it : Geoms) { updateGeometry(it); } } void Sketch::updateGeometry(const GeoDef& it) { if (it.type == Point) { updatePoint(it); } else if (it.type == Line) { updateLineSegment(it); } else if (it.type == Arc) { updateArcOfCircle(it); } else if (it.type == ArcOfEllipse) { updateArcOfEllipse(it); } else if (it.type == Circle) { updateCircle(it); } else if (it.type == Ellipse) { updateEllipse(it); } else if (it.type == ArcOfHyperbola) { updateArcOfHyperbola(it); } else if (it.type == ArcOfParabola) { updateArcOfParabola(it); } else if (it.type == BSpline) { updateBSpline(it); } } void Sketch::updatePoint(const GeoDef& def) { GeomPoint* point = static_cast(def.geo); auto pointf = GeometryFacade::getFacade(point); point->setPoint(Vector3d(*Points[def.startPointId].x, *Points[def.startPointId].y, 0.0)); } void Sketch::updateLineSegment(const GeoDef& def) { GeomLineSegment* lineSeg = static_cast(def.geo); lineSeg->setPoints( Vector3d(*Lines[def.index].p1.x, *Lines[def.index].p1.y, 0.0), Vector3d(*Lines[def.index].p2.x, *Lines[def.index].p2.y, 0.0) ); } void Sketch::updateArcOfCircle(const GeoDef& def) { GCS::Arc& myArc = Arcs[def.index]; // the following 4 lines are redundant since these equations are already included in // the arc constraints *myArc.start.x = *myArc.center.x + *myArc.rad * // cos(*myArc.startAngle); *myArc.start.y = *myArc.center.y + *myArc.rad * // sin(*myArc.startAngle); *myArc.end.x = *myArc.center.x + *myArc.rad * // cos(*myArc.endAngle); *myArc.end.y = *myArc.center.y + *myArc.rad * // sin(*myArc.endAngle); GeomArcOfCircle* aoc = static_cast(def.geo); aoc->setCenter(Vector3d(*Points[def.midPointId].x, *Points[def.midPointId].y, 0.0)); aoc->setRadius(*myArc.rad); aoc->setRange(*myArc.startAngle, *myArc.endAngle, /*emulateCCWXY=*/true); } void Sketch::updateArcOfEllipse(const GeoDef& def) { GCS::ArcOfEllipse& myArc = ArcsOfEllipse[def.index]; GeomArcOfEllipse* aoe = static_cast(def.geo); Base::Vector3d center = Vector3d(*Points[def.midPointId].x, *Points[def.midPointId].y, 0.0); Base::Vector3d f1 = Vector3d(*myArc.focus1.x, *myArc.focus1.y, 0.0); double radmin = *myArc.radmin; Base::Vector3d fd = f1 - center; double radmaj = sqrt(fd * fd + radmin * radmin); aoe->setCenter(center); // ensure that ellipse's major radius is always larger than minor radius... may // still cause problems with degenerates. if (radmaj >= aoe->getMinorRadius()) { aoe->setMajorRadius(radmaj); aoe->setMinorRadius(radmin); } else { aoe->setMinorRadius(radmin); aoe->setMajorRadius(radmaj); } aoe->setMajorAxisDir(fd); aoe->setRange(*myArc.startAngle, *myArc.endAngle, /*emulateCCWXY=*/true); } void Sketch::updateArcOfHyperbola(const GeoDef& def) { GCS::ArcOfHyperbola& myArc = ArcsOfHyperbola[def.index]; GeomArcOfHyperbola* aoh = static_cast(def.geo); Base::Vector3d center = Vector3d(*Points[def.midPointId].x, *Points[def.midPointId].y, 0.0); Base::Vector3d f1 = Vector3d(*myArc.focus1.x, *myArc.focus1.y, 0.0); double radmin = *myArc.radmin; Base::Vector3d fd = f1 - center; double radmaj = sqrt(fd * fd - radmin * radmin); aoh->setCenter(center); if (radmaj >= aoh->getMinorRadius()) { aoh->setMajorRadius(radmaj); aoh->setMinorRadius(radmin); } else { aoh->setMinorRadius(radmin); aoh->setMajorRadius(radmaj); } aoh->setMajorAxisDir(fd); aoh->setRange(*myArc.startAngle, *myArc.endAngle, /*emulateCCWXY=*/true); } void Sketch::updateArcOfParabola(const GeoDef& def) { GCS::ArcOfParabola& myArc = ArcsOfParabola[def.index]; GeomArcOfParabola* aop = static_cast(def.geo); Base::Vector3d vertex = Vector3d(*Points[def.midPointId].x, *Points[def.midPointId].y, 0.0); Base::Vector3d f1 = Vector3d(*myArc.focus1.x, *myArc.focus1.y, 0.0); Base::Vector3d fd = f1 - vertex; aop->setXAxisDir(fd); aop->setCenter(vertex); aop->setFocal(fd.Length()); aop->setRange(*myArc.startAngle, *myArc.endAngle, /*emulateCCWXY=*/true); } void Sketch::updateCircle(const GeoDef& def) { GeomCircle* circ = static_cast(def.geo); circ->setCenter(Vector3d(*Points[def.midPointId].x, *Points[def.midPointId].y, 0.0)); circ->setRadius(*Circles[def.index].rad); } void Sketch::updateEllipse(const GeoDef& def) { GeomEllipse* ellipse = static_cast(def.geo); Base::Vector3d center = Vector3d(*Points[def.midPointId].x, *Points[def.midPointId].y, 0.0); Base::Vector3d f1 = Vector3d(*Ellipses[def.index].focus1.x, *Ellipses[def.index].focus1.y, 0.0); double radmin = *Ellipses[def.index].radmin; Base::Vector3d fd = f1 - center; double radmaj = sqrt(fd * fd + radmin * radmin); ellipse->setCenter(center); // ensure that ellipse's major radius is always larger than minor radius... may // still cause problems with degenerates. if (radmaj >= ellipse->getMinorRadius()) { ellipse->setMajorRadius(radmaj); ellipse->setMinorRadius(radmin); } else { ellipse->setMinorRadius(radmin); ellipse->setMajorRadius(radmaj); } ellipse->setMajorAxisDir(fd); } void Sketch::updateBSpline(const GeoDef& def) { GCS::BSpline& mybsp = BSplines[def.index]; GeomBSplineCurve* bsp = static_cast(def.geo); std::vector poles; std::vector weights; std::vector::const_iterator it1; std::vector::const_iterator it2; for (it1 = mybsp.poles.begin(), it2 = mybsp.weights.begin(); it1 != mybsp.poles.end() && it2 != mybsp.weights.end(); ++it1, ++it2) { poles.emplace_back(*(*it1).x, *(*it1).y, 0.0); weights.push_back(*(*it2)); } bsp->setPoles(poles, weights); std::vector knots; std::vector mult; // This is the code that should be here when/if b-spline gets its full // implementation in the solver. /*std::vector::const_iterator it3; std::vector::const_iterator it4; for( it3 = mybsp.knots.begin(), it4 = mybsp.mult.begin(); it3 != mybsp.knots.end() && it4 != mybsp.mult.end(); ++it3, ++it4) { knots.push_back(*(*it3)); mult.push_back((*it4)); } bsp->setKnots(knots,mult);*/ } bool Sketch::updateNonDrivingConstraints() { for (auto& constrDef : Constrs) { if (constrDef.driving) { continue; } if (constrDef.constr->Type == SnellsLaw) { double n1 = *(constrDef.value); double n2 = *(constrDef.secondvalue); constrDef.constr->setValue(n2 / n1); } else if (constrDef.constr->Type == Angle) { constrDef.constr->setValue(std::fmod(*(constrDef.value), 2.0 * std::numbers::pi)); } else if (constrDef.constr->Type == Diameter && constrDef.constr->First >= 0) { // two cases, the geometry parameter is fixed or it is not // NOTE: This is different from being blocked, as new block constraint may fix // the parameter or not depending on whether other driving constraints are present int geoId = constrDef.constr->First; geoId = checkGeoId(geoId); double* rad = nullptr; if (Geoms[geoId].type == Circle) { GCS::Circle& c = Circles[Geoms[geoId].index]; rad = c.rad; } else if (Geoms[geoId].type == Arc) { GCS::Arc& a = Arcs[Geoms[geoId].index]; rad = a.rad; } if (auto pos = std::ranges::find(FixParameters, rad); pos != FixParameters.end()) { constrDef.constr->setValue(*(constrDef.value)); } else { constrDef.constr->setValue(2.0 * *(constrDef.value)); } } else { constrDef.constr->setValue(*(constrDef.value)); } } return true; } // solving ========================================================== int Sketch::solve() { Base::TimeElapsed start_time; std::string solvername; auto result = internalSolve(solvername); Base::TimeElapsed end_time; if (debugMode == GCS::Minimal || debugMode == GCS::IterationLevel) { Base::Console().log( "Sketcher::Solve()-%s-T:%s\n", solvername.c_str(), Base::TimeElapsed::diffTime(start_time, end_time).c_str() ); } SolveTime = Base::TimeElapsed::diffTimeF(start_time, end_time); return result; } int Sketch::internalSolve(std::string& solvername, int level) { if (!isInitMove) { // make sure we are in single subsystem mode clearTemporaryConstraints(); isFine = true; } int ret = -1; bool valid_solution; int defaultsoltype = -1; if (isInitMove) { solvername = "DogLeg"; // DogLeg is used for dragging (same as before) ret = GCSsys.solve(isFine, GCS::DogLeg); } else { switch (defaultSolver) { case 0: solvername = "BFGS"; ret = GCSsys.solve(isFine, GCS::BFGS); defaultsoltype = 2; break; case 1: // solving with the LevenbergMarquardt solver solvername = "LevenbergMarquardt"; ret = GCSsys.solve(isFine, GCS::LevenbergMarquardt); defaultsoltype = 1; break; case 2: // solving with the BFGS solver solvername = "DogLeg"; ret = GCSsys.solve(isFine, GCS::DogLeg); defaultsoltype = 0; break; } } // if successfully solved try to write the parameters back if (ret == GCS::Success) { GCSsys.applySolution(); valid_solution = updateGeometry(); if (!valid_solution) { GCSsys.undoSolution(); updateGeometry(); Base::Console().warning("Invalid solution from %s solver.\n", solvername.c_str()); } else { updateNonDrivingConstraints(); } } else { valid_solution = false; if (debugMode == GCS::Minimal || debugMode == GCS::IterationLevel) { Base::Console().log("Sketcher::Solve()-%s- Failed!! Falling back...\n", solvername.c_str()); } } if (!valid_solution && !isInitMove) { // Fall back to other solvers for (int soltype = 0; soltype < 4; soltype++) { if (soltype == defaultsoltype) { continue; // skip default solver } switch (soltype) { case 0: solvername = "DogLeg"; ret = GCSsys.solve(isFine, GCS::DogLeg); break; case 1: // solving with the LevenbergMarquardt solver solvername = "LevenbergMarquardt"; ret = GCSsys.solve(isFine, GCS::LevenbergMarquardt); break; case 2: // solving with the BFGS solver solvername = "BFGS"; ret = GCSsys.solve(isFine, GCS::BFGS); break; // last resort: augment the system with a second subsystem and use the SQP solver case 3: solvername = "SQP(augmented system)"; InitParameters.resize(Parameters.size()); int i = 0; for (auto it = Parameters.begin(); it != Parameters.end(); ++it, ++i) { InitParameters[i] = **it; GCSsys.addConstraintEqual( *it, &InitParameters[i], GCS::DefaultTemporaryConstraint ); } GCSsys.initSolution(); ret = GCSsys.solve(isFine); break; } // if successfully solved try to write the parameters back if (ret == GCS::Success) { GCSsys.applySolution(); valid_solution = updateGeometry(); if (!valid_solution) { GCSsys.undoSolution(); updateGeometry(); Base::Console().warning("Invalid solution from %s solver.\n", solvername.c_str()); ret = GCS::SuccessfulSolutionInvalid; } else { updateNonDrivingConstraints(); } } else { valid_solution = false; if (debugMode == GCS::Minimal || debugMode == GCS::IterationLevel) { Base::Console().log( "Sketcher::Solve()-%s- Failed!! Falling back...\n", solvername.c_str() ); } } if (soltype == 3) { // cleanup temporary constraints of the augmented system clearTemporaryConstraints(); } if (valid_solution) { if (soltype == 1) { Base::Console().log( "Important: the LevenbergMarquardt solver succeeded where " "the DogLeg solver had failed.\n" ); } else if (soltype == 2) { Base::Console().log( "Important: the BFGS solver succeeded where the DogLeg and " "LevenbergMarquardt solvers have failed.\n" ); } else if (soltype == 3) { Base::Console().log( "Important: the SQP solver succeeded where all single " "subsystem solvers have failed.\n" ); } else if (soltype > 0) { Base::Console().log("All solvers failed.\n"); } break; } } // soltype } // For OCCT reliant geometry that needs an extra solve() for example to update non-driving // constraints. if (resolveAfterGeometryUpdated && ret == GCS::Success && level == 0) { return internalSolve(solvername, 1); } return ret; } int Sketch::initMove(const std::vector& geoEltIds, bool fine) { if (hasConflicts()) { // don't try to move sketches that contain conflicting constraints isInitMove = false; return -1; } isFine = fine; clearTemporaryConstraints(); MoveParameters.clear(); // We need to reserve enough size in the vec or the dynamic resizing // (emplace_back in the for loop below) will trigger reallocation. // Which will corrupt pointers we're storing. size_t reserveSize = 0; for (auto& pair : geoEltIds) { int geoId = checkGeoId(pair.GeoId); Sketcher::PointPos pos = pair.Pos; if (Geoms[geoId].type == BSpline && (pos == PointPos::none || pos == PointPos::mid)) { GCS::BSpline& bsp = BSplines[Geoms[geoId].index]; reserveSize += bsp.poles.size() * 2; } else { reserveSize += 6; // 6 is the max for all other cases. } } MoveParameters.reserve(reserveSize); for (auto& pair : geoEltIds) { int geoId = checkGeoId(pair.GeoId); Sketcher::PointPos pos = pair.Pos; if (Geoms[geoId].type == Point) { if (pos == PointPos::start) { GCS::Point& point = Points[Geoms[geoId].startPointId]; GCS::Point p0; p0.x = &MoveParameters.emplace_back(*point.x); p0.y = &MoveParameters.emplace_back(*point.y); GCSsys.addConstraintP2PCoincident(p0, point, GCS::DefaultTemporaryConstraint); } } else if (Geoms[geoId].type == Line) { if (pos == PointPos::start || pos == PointPos::end) { GCS::Point p0; GCS::Point& p = pos == PointPos::start ? Points[Geoms[geoId].startPointId] : Points[Geoms[geoId].endPointId]; p0.x = &MoveParameters.emplace_back(*p.x); p0.y = &MoveParameters.emplace_back(*p.y); GCSsys.addConstraintP2PCoincident(p0, p, GCS::DefaultTemporaryConstraint); } else if (pos == PointPos::none || pos == PointPos::mid) { GCS::Point p1, p2; GCS::Line& l = Lines[Geoms[geoId].index]; p1.x = &MoveParameters.emplace_back(*l.p1.x); p1.y = &MoveParameters.emplace_back(*l.p1.y); p2.x = &MoveParameters.emplace_back(*l.p2.x); p2.y = &MoveParameters.emplace_back(*l.p2.y); GCSsys.addConstraintP2PCoincident(p1, l.p1, GCS::DefaultTemporaryConstraint); GCSsys.addConstraintP2PCoincident(p2, l.p2, GCS::DefaultTemporaryConstraint); } } else if (Geoms[geoId].type == Circle) { GCS::Point& center = Points[Geoms[geoId].midPointId]; GCS::Point p0, p1; if (pos == PointPos::mid) { p0.x = &MoveParameters.emplace_back(*center.x); p0.y = &MoveParameters.emplace_back(*center.y); GCSsys.addConstraintP2PCoincident(p0, center, GCS::DefaultTemporaryConstraint); } else if (pos == PointPos::none) { // bool pole = GeometryFacade::isInternalType(Geoms[geoId].geo, // InternalType::BSplineControlPoint); GCS::Circle& c = Circles[Geoms[geoId].index]; p0.x = &MoveParameters.emplace_back(*center.x); p0.y = &MoveParameters.emplace_back(*center.y + *c.rad); GCSsys.addConstraintPointOnCircle(p0, c, GCS::DefaultTemporaryConstraint); p1.x = &MoveParameters.emplace_back(*center.x); p1.y = &MoveParameters.emplace_back(*center.y); int i = GCSsys.addConstraintP2PCoincident(p1, center, GCS::DefaultTemporaryConstraint); GCSsys.rescaleConstraint(i - 1, 0.01); GCSsys.rescaleConstraint(i, 0.01); } } else if (Geoms[geoId].type == Ellipse) { if (pos == PointPos::mid || pos == PointPos::none) { GCS::Point& center = Points[Geoms[geoId].midPointId]; GCS::Point p0; p0.x = &MoveParameters.emplace_back(*center.x); p0.y = &MoveParameters.emplace_back(*center.y); GCSsys.addConstraintP2PCoincident(p0, center, GCS::DefaultTemporaryConstraint); } } else if (Geoms[geoId].type == ArcOfEllipse) { GCS::Point& center = Points[Geoms[geoId].midPointId]; GCS::Point p0, p1; if (pos == PointPos::mid || pos == PointPos::none) { p0.x = &MoveParameters.emplace_back(*center.x); p0.y = &MoveParameters.emplace_back(*center.y); GCSsys.addConstraintP2PCoincident(p0, center, GCS::DefaultTemporaryConstraint); } else if (pos == PointPos::start || pos == PointPos::end) { if (pos == PointPos::start || pos == PointPos::end) { GCS::Point& p = (pos == PointPos::start) ? Points[Geoms[geoId].startPointId] : Points[Geoms[geoId].endPointId]; p0.x = &MoveParameters.emplace_back(*p.x); p0.y = &MoveParameters.emplace_back(*p.y); GCSsys.addConstraintP2PCoincident(p0, p, GCS::DefaultTemporaryConstraint); } p1.x = &MoveParameters.emplace_back(*center.x); p1.y = &MoveParameters.emplace_back(*center.y); int i = GCSsys.addConstraintP2PCoincident(p1, center, GCS::DefaultTemporaryConstraint); GCSsys.rescaleConstraint(i - 1, 0.01); GCSsys.rescaleConstraint(i, 0.01); } } else if (Geoms[geoId].type == ArcOfHyperbola) { GCS::Point& center = Points[Geoms[geoId].midPointId]; GCS::Point p0, p1; if (pos == PointPos::mid || pos == PointPos::none) { p0.x = &MoveParameters.emplace_back(*center.x); p0.y = &MoveParameters.emplace_back(*center.y); GCSsys.addConstraintP2PCoincident(p0, center, GCS::DefaultTemporaryConstraint); } else if (pos == PointPos::start || pos == PointPos::end) { GCS::Point& p = (pos == PointPos::start) ? Points[Geoms[geoId].startPointId] : Points[Geoms[geoId].endPointId]; p0.x = &MoveParameters.emplace_back(*p.x); p0.y = &MoveParameters.emplace_back(*p.y); GCSsys.addConstraintP2PCoincident(p0, p, GCS::DefaultTemporaryConstraint); p1.x = &MoveParameters.emplace_back(*center.x); p1.y = &MoveParameters.emplace_back(*center.y); int i = GCSsys.addConstraintP2PCoincident(p1, center, GCS::DefaultTemporaryConstraint); GCSsys.rescaleConstraint(i - 1, 0.01); GCSsys.rescaleConstraint(i, 0.01); } } else if (Geoms[geoId].type == ArcOfParabola) { GCS::Point& center = Points[Geoms[geoId].midPointId]; GCS::Point p0, p1; if (pos == PointPos::mid || pos == PointPos::none) { p0.x = &MoveParameters.emplace_back(*center.x); p0.y = &MoveParameters.emplace_back(*center.y); GCSsys.addConstraintP2PCoincident(p0, center, GCS::DefaultTemporaryConstraint); } else if (pos == PointPos::start || pos == PointPos::end) { GCS::Point& p = (pos == PointPos::start) ? Points[Geoms[geoId].startPointId] : Points[Geoms[geoId].endPointId]; p0.x = &MoveParameters.emplace_back(*p.x); p0.y = &MoveParameters.emplace_back(*p.y); GCSsys.addConstraintP2PCoincident(p0, p, GCS::DefaultTemporaryConstraint); p1.x = &MoveParameters.emplace_back(*center.x); p1.y = &MoveParameters.emplace_back(*center.y); int i = GCSsys.addConstraintP2PCoincident(p1, center, GCS::DefaultTemporaryConstraint); GCSsys.rescaleConstraint(i - 1, 0.01); GCSsys.rescaleConstraint(i, 0.01); } } else if (Geoms[geoId].type == BSpline) { if (pos == PointPos::start || pos == PointPos::end) { GCS::Point p0; GCS::Point& p = pos == PointPos::start ? Points[Geoms[geoId].startPointId] : Points[Geoms[geoId].endPointId]; p0.x = &MoveParameters.emplace_back(*p.x); p0.y = &MoveParameters.emplace_back(*p.y); GCSsys.addConstraintP2PCoincident(p0, p, GCS::DefaultTemporaryConstraint); } else if (pos == PointPos::none || pos == PointPos::mid) { GCS::BSpline& bsp = BSplines[Geoms[geoId].index]; for (auto pole : bsp.poles) { GCS::Point p1; p1.x = &MoveParameters.emplace_back(*pole.x); p1.y = &MoveParameters.emplace_back(*pole.y); GCSsys.addConstraintP2PCoincident(p1, pole, GCS::DefaultTemporaryConstraint); } } } else if (Geoms[geoId].type == Arc) { GCS::Point& center = Points[Geoms[geoId].midPointId]; GCS::Point p0, p1; if (pos == PointPos::mid) { p0.x = &MoveParameters.emplace_back(*center.x); p0.y = &MoveParameters.emplace_back(*center.y); GCSsys.addConstraintP2PCoincident(p0, center, GCS::DefaultTemporaryConstraint); } else if (pos == PointPos::none && geoEltIds.size() > 1) { // When group dragging, arcs should move without modification. GCS::Point p2; GCS::Point& sp = Points[Geoms[geoId].startPointId]; GCS::Point& ep = Points[Geoms[geoId].endPointId]; p0.x = &MoveParameters.emplace_back(*sp.x); p0.y = &MoveParameters.emplace_back(*sp.y); GCSsys.addConstraintP2PCoincident(p0, sp, GCS::DefaultTemporaryConstraint); p2.x = &MoveParameters.emplace_back(*ep.x); p2.y = &MoveParameters.emplace_back(*ep.y); GCSsys.addConstraintP2PCoincident(p2, ep, GCS::DefaultTemporaryConstraint); p1.x = &MoveParameters.emplace_back(*center.x); p1.y = &MoveParameters.emplace_back(*center.y); int i = GCSsys.addConstraintP2PCoincident(p1, center, GCS::DefaultTemporaryConstraint); GCSsys.rescaleConstraint(i - 2, 0.01); GCSsys.rescaleConstraint(i - 1, 0.01); GCSsys.rescaleConstraint(i, 0.01); } else if (pos == PointPos::start || pos == PointPos::end || pos == PointPos::none) { if (pos == PointPos::start || pos == PointPos::end) { GCS::Point& p = (pos == PointPos::start) ? Points[Geoms[geoId].startPointId] : Points[Geoms[geoId].endPointId]; p0.x = &MoveParameters.emplace_back(*p.x); p0.y = &MoveParameters.emplace_back(*p.y); GCSsys.addConstraintP2PCoincident(p0, p, GCS::DefaultTemporaryConstraint); } else if (pos == PointPos::none) { GCS::Arc& a = Arcs[Geoms[geoId].index]; p0.x = &MoveParameters.emplace_back(*center.x); p0.y = &MoveParameters.emplace_back(*center.y + *a.rad); GCSsys.addConstraintPointOnArc(p0, a, GCS::DefaultTemporaryConstraint); } p1.x = &MoveParameters.emplace_back(*center.x); p1.y = &MoveParameters.emplace_back(*center.y); int i = GCSsys.addConstraintP2PCoincident(p1, center, GCS::DefaultTemporaryConstraint); GCSsys.rescaleConstraint(i - 1, 0.01); GCSsys.rescaleConstraint(i, 0.01); } } } InitParameters = MoveParameters; GCSsys.initSolution(); isInitMove = true; return 0; } int Sketch::initMove(int geoId, PointPos pos, bool fine) { std::vector geoEltIds = {GeoElementId(geoId, pos)}; return initMove(geoEltIds, fine); } void Sketch::resetInitMove() { isInitMove = false; } int Sketch::initBSplinePieceMove(int geoId, PointPos pos, const Base::Vector3d& firstPoint, bool fine) { isFine = fine; geoId = checkGeoId(geoId); clearTemporaryConstraints(); // don't try to move sketches that contain conflicting constraints if (hasConflicts()) { isInitMove = false; return -1; } // this is only meant for B-Splines if (Geoms[geoId].type != BSpline || pos == PointPos::start || pos == PointPos::end) { return -1; } GCS::BSpline& bsp = BSplines[Geoms[geoId].index]; // If spline has too few poles, just move all if (bsp.poles.size() <= std::size_t(bsp.degree + 1)) { return initMove(geoId, pos, fine); } // Find the closest knot auto partBsp = static_cast(Geoms[geoId].geo); double uNear; partBsp->closestParameter(firstPoint, uNear); auto& knots = bsp.knots; auto upperknot = std::upper_bound(knots.begin(), knots.end(), uNear, [](double u, double* element) { return u < *element; }); size_t idx = 0; // skipping the first knot for adjustment // TODO: ensure this works for periodic as well for (size_t i = 1; i < bsp.mult.size() && knots[i] != *upperknot; ++i) { idx += bsp.mult[i]; } MoveParameters.resize(2 * (bsp.degree + 1)); // x[idx],y[idx],x[idx+1],y[idx+1],... size_t mvindex = 0; auto lastIt = (idx + bsp.degree + 1) % bsp.poles.size(); for (size_t i = idx; i != lastIt; i = (i + 1) % bsp.poles.size(), ++mvindex) { GCS::Point p1; p1.x = &MoveParameters[mvindex]; ++mvindex; p1.y = &MoveParameters[mvindex]; *p1.x = *bsp.poles[i].x; *p1.y = *bsp.poles[i].y; GCSsys.addConstraintP2PCoincident(p1, bsp.poles[i], GCS::DefaultTemporaryConstraint); } InitParameters = MoveParameters; GCSsys.initSolution(); isInitMove = true; return 0; } int Sketch::moveGeometries(const std::vector& geoEltIds, Base::Vector3d toPoint, bool relative) { if (hasConflicts()) { // don't try to move sketches that contain conflicting constraints return -1; } if (!isInitMove) { initMove(geoEltIds); initToPoint = toPoint; moveStep = 0; } else { if (!relative && RecalculateInitialSolutionWhileMovingPoint) { if (moveStep == 0) { moveStep = (toPoint - initToPoint).Length(); } else { // I am getting too far away from the original solution so reinit the solution if ((toPoint - initToPoint).Length() > 20 * moveStep) { initMove(geoEltIds); initToPoint = toPoint; } } } } if (relative) { for (size_t i = 0; i < MoveParameters.size() - 1; i += 2) { MoveParameters[i] = InitParameters[i] + toPoint.x; MoveParameters[i + 1] = InitParameters[i + 1] + toPoint.y; } } else { size_t i = 0; for (auto& pair : geoEltIds) { if (i >= MoveParameters.size()) { break; } int geoId = checkGeoId(pair.GeoId); Sketcher::PointPos pos = pair.Pos; if (Geoms[geoId].type == Point) { if (pos == PointPos::start) { MoveParameters[i] = toPoint.x; MoveParameters[i + 1] = toPoint.y; i += 2; } } else if (Geoms[geoId].type == Line) { if (pos == PointPos::start || pos == PointPos::end) { MoveParameters[i] = toPoint.x; MoveParameters[i + 1] = toPoint.y; i += 2; } else if (pos == PointPos::none || pos == PointPos::mid) { double dx = (InitParameters[i + 2] - InitParameters[i]) * 0.5; double dy = (InitParameters[i + 3] - InitParameters[i + 1]) * 0.5; MoveParameters[i] = toPoint.x - dx; MoveParameters[i + 1] = toPoint.y - dy; MoveParameters[i + 2] = toPoint.x + dx; MoveParameters[i + 3] = toPoint.y + dy; i += 4; } } else if (Geoms[geoId].type == Circle || Geoms[geoId].type == Ellipse) { if (pos == PointPos::mid || pos == PointPos::none) { MoveParameters[i] = toPoint.x; MoveParameters[i + 1] = toPoint.y; i += 2; } } else if (Geoms[geoId].type == Arc || Geoms[geoId].type == ArcOfEllipse || Geoms[geoId].type == ArcOfHyperbola || Geoms[geoId].type == ArcOfParabola) { MoveParameters[i] = toPoint.x; MoveParameters[i + 1] = toPoint.y; i += 2; } else if (Geoms[geoId].type == BSpline) { if (pos == PointPos::start || pos == PointPos::end) { MoveParameters[i] = toPoint.x; MoveParameters[i + 1] = toPoint.y; i += 2; } else if (pos == PointPos::none || pos == PointPos::mid) { GCS::BSpline& bsp = BSplines[Geoms[geoId].index]; double cx = 0, cy = 0; // geometric center for (size_t j = 0; j < bsp.poles.size() * 2; j += 2) { cx += InitParameters[i + j]; cy += InitParameters[i + j + 1]; j += 2; } cx /= bsp.poles.size(); cy /= bsp.poles.size(); for (size_t j = 0; j < bsp.poles.size() * 2; j += 2) { MoveParameters[i + j] = toPoint.x + InitParameters[i + j] - cx; MoveParameters[i + j + 1] = toPoint.y + InitParameters[i + j + 1] - cy; j += 2; } i += bsp.poles.size() * 2; } } } } return solve(); } int Sketch::moveGeometry(int geoId, PointPos pos, Base::Vector3d toPoint, bool relative) { std::vector geoEltIds = {GeoElementId(geoId, pos)}; return moveGeometries(geoEltIds, toPoint, relative); } int Sketch::setDatum(int /*constrId*/, double /*value*/) { return -1; } int Sketch::getPointId(int geoId, PointPos pos) const { // do a range check first if (geoId < 0 || geoId >= (int)Geoms.size()) { return -1; } switch (pos) { case PointPos::start: return Geoms[geoId].startPointId; case PointPos::end: return Geoms[geoId].endPointId; case PointPos::mid: return Geoms[geoId].midPointId; case PointPos::none: break; } return -1; } Base::Vector3d Sketch::getPoint(int geoId, PointPos pos) const { geoId = checkGeoId(geoId); int pointId = getPointId(geoId, pos); if (pointId != -1) { return Base::Vector3d(*Points[pointId].x, *Points[pointId].y, 0); } return Base::Vector3d(); } TopoShape Sketch::toShape() const { TopoShape result; std::vector::const_iterator it = Geoms.begin(); #if 0 bool first = true; for (; it!=Geoms.end(); ++it) { if (!it->geo->Construction) { TopoDS_Shape sh = it->geo->toShape(); if (first) { first = false; result.setShape(sh); } else { result.setShape(result.fuse(sh)); } } } return result; #else std::list edge_list; std::list vertex_list; std::list wires; // collecting all (non constructive and non external) edges out of the sketch for (; it != Geoms.end(); ++it) { auto gf = GeometryFacade::getFacade(it->geo); if (!it->external && !gf->getConstruction()) { if (it->type != Point) { auto shape = it->geo->toShape(); if (!shape.IsNull()) { edge_list.push_back(TopoDS::Edge(shape)); } } else { vertex_list.push_back(TopoDS::Vertex(it->geo->toShape())); } } } // Hint: Use ShapeAnalysis_FreeBounds::ConnectEdgesToWires() as an alternative // // sort them together to wires while (!edge_list.empty()) { BRepBuilderAPI_MakeWire mkWire; // add and erase first edge mkWire.Add(edge_list.front()); edge_list.pop_front(); TopoDS_Wire new_wire = mkWire.Wire(); // current new wire // try to connect each edge to the wire, the wire is complete if no more edges are // connectible bool found = false; do { found = false; for (auto pE = edge_list.begin(); pE != edge_list.end(); ++pE) { mkWire.Add(*pE); if (mkWire.Error() != BRepBuilderAPI_DisconnectedWire) { // edge added ==> remove it from list found = true; edge_list.erase(pE); new_wire = mkWire.Wire(); break; } } } while (found); // Fix any topological issues of the wire ShapeFix_Wire aFix; aFix.SetPrecision(Precision::Confusion()); aFix.Load(new_wire); aFix.FixReorder(); aFix.FixConnected(); aFix.FixClosed(); wires.push_back(aFix.Wire()); } if (wires.size() == 1 && vertex_list.empty()) { result = *wires.begin(); } else if (wires.size() > 1 || !vertex_list.empty()) { // FIXME: The right way here would be to determine the outer and inner wires and // generate a face with holes (inner wires have to be tagged REVERSE or INNER). // that's the only way to transport a somewhat more complex sketch... // result = *wires.begin(); // I think a compound can be used as container because it is just a collection of // shapes and doesn't need too much information about the topology. // The actual knowledge how to create a prism from several wires should go to the Pad // feature (Werner). BRep_Builder builder; TopoDS_Compound comp; builder.MakeCompound(comp); for (auto& wire : wires) { builder.Add(comp, wire); } for (auto& vertex : vertex_list) { builder.Add(comp, vertex); } result.setShape(comp); } #endif return result; } // Persistence implementer ------------------------------------------------- unsigned int Sketch::getMemSize() const { return 0; } void Sketch::Save(Writer&) const {} void Sketch::Restore(XMLReader&) {}