Sketcher: Intersection externals

This commit is contained in:
PaddleStroke
2024-11-08 10:57:18 +01:00
committed by WandererFan
parent c0f64a9850
commit 8c6b437314
12 changed files with 1309 additions and 168 deletions

View File

@@ -141,6 +141,11 @@ SketchObject::SketchObject()
"Sketch",
(App::PropertyType)(App::Prop_None | App::Prop_ReadOnly),
"Sketch external geometry");
ADD_PROPERTY_TYPE(ExternalTypes,
({}),
"Sketch",
(App::PropertyType)(App::Prop_None | App::Prop_Hidden),
"Sketch external geometry type: 0 = projection, 1 = intersection, 2 = both.");
ADD_PROPERTY_TYPE(FullyConstrained,
(false),
"Sketch",
@@ -7721,8 +7726,12 @@ int SketchObject::addExternal(App::DocumentObject *Obj, const char* SubName, boo
}
// get the actual lists of the externals
std::vector<long> Types = ExternalTypes.getValues();
std::vector<DocumentObject*> Objects = ExternalGeometry.getValues();
std::vector<std::string> SubElements = ExternalGeometry.getSubValues();
if (Types.size() != Objects.size()) {
Types.resize(Objects.size(), 0);
}
const std::vector<DocumentObject*> originalObjects = Objects;
const std::vector<std::string> originalSubElements = SubElements;
@@ -7734,21 +7743,35 @@ int SketchObject::addExternal(App::DocumentObject *Obj, const char* SubName, boo
return -1;
}
bool add = true;
for (size_t i = 0; i < Objects.size(); ++i) {
if (Objects[i] == Obj && std::string(SubName) == SubElements[i]) {
Base::Console().Error("Link to %s already exists in this sketch.\n", SubName);
return -1;
if (Types[i] == (int)ExtType::Both
|| (Types[i] == (int)ExtType::Projection && !intersection)
|| (Types[i] == (int)ExtType::Intersection && intersection)) {
Base::Console().Error("Link to %s already exists in this sketch.\n", SubName);
return -1;
}
// Case where projections are already there when adding intersections.
add = false;
Types[i] = (int)ExtType::Both;
}
}
if (add) {
// add the new ones
Objects.push_back(Obj);
SubElements.emplace_back(SubName);
Types.push_back((int)(intersection ? ExtType::Intersection : ExtType::Projection));
if (intersection) {}
// add the new ones
Objects.push_back(Obj);
SubElements.emplace_back(SubName);
// set the Link list.
ExternalGeometry.setValues(Objects, SubElements);
}
ExternalTypes.setValues(Types);
// set the Link list.
ExternalGeometry.setValues(Objects, SubElements);
try {
rebuildExternalGeometry(defining, intersection);
ExternalToAdd ext{ Obj, std::string(SubName), defining, intersection };
rebuildExternalGeometry(ext);
}
catch (const Base::Exception& e) {
Base::Console().Error("%s\n", e.what());
@@ -8731,6 +8754,8 @@ void processEdge(const TopoDS_Edge& edge,
}
else if (curve.GetType() == GeomAbs_Ellipse) {
gp_Pnt P1 = curve.Value(curve.FirstParameter());
gp_Pnt P2 = curve.Value(curve.LastParameter());
gp_Elips elipsOrig = curve.Ellipse();
gp_Elips elipsDest;
gp_Pnt origCenter = elipsOrig.Location();
@@ -8792,11 +8817,22 @@ void processEdge(const TopoDS_Edge& edge,
// projection is a circle
if ((RDest - rDest) < (double)Precision::Confusion()) {
Handle(Geom_Circle) curve = new Geom_Circle(destCurveAx2, 0.5 * (rDest + RDest));
auto* circle = new Part::GeomCircle();
circle->setHandle(curve);
GeometryFacade::setConstruction(circle, true);
geos.emplace_back(circle);
Handle(Geom_Circle) curve2 = new Geom_Circle(destCurveAx2, 0.5 * (rDest + RDest));
if (P1.SquareDistance(P2) < Precision::Confusion()) {
auto* circle = new Part::GeomCircle();
circle->setHandle(curve2);
GeometryFacade::setConstruction(circle, true);
geos.emplace_back(circle);
}
else {
auto* arc = new Part::GeomArcOfCircle();
Handle(Geom_TrimmedCurve) tCurve = new Geom_TrimmedCurve(curve2,
curve.FirstParameter(),
curve.LastParameter());
arc->setHandle(tCurve);
GeometryFacade::setConstruction(arc, true);
geos.emplace_back(arc);
}
}
else {
if (sketchPlane.Position().Direction().IsNormal(
@@ -8818,11 +8854,23 @@ void processEdge(const TopoDS_Edge& edge,
elipsDest.SetMinorRadius(destAxisMinor.Magnitude());
Handle(Geom_Ellipse) curve = new Geom_Ellipse(elipsDest);
auto* ellipse = new Part::GeomEllipse();
ellipse->setHandle(curve);
GeometryFacade::setConstruction(ellipse, true);
geos.emplace_back(ellipse);
if (P1.SquareDistance(P2) < Precision::Confusion()) {
Handle(Geom_Ellipse) curve = new Geom_Ellipse(elipsDest);
auto* ellipse = new Part::GeomEllipse();
ellipse->setHandle(curve);
GeometryFacade::setConstruction(ellipse, true);
geos.emplace_back(ellipse);
}
else {
auto* aoe = new Part::GeomArcOfEllipse();
Handle(Geom_Curve) curve2 = new Geom_Ellipse(elipsDest);
Handle(Geom_TrimmedCurve) tCurve = new Geom_TrimmedCurve(curve2,
curve.FirstParameter(),
curve.LastParameter());
aoe->setHandle(tCurve);
GeometryFacade::setConstruction(aoe, true);
geos.emplace_back(aoe);
}
}
}
}
@@ -8929,15 +8977,19 @@ std::vector<TopoDS_Shape> projectShape(const TopoDS_Shape& inShape, const gp_Ax3
}
}
void SketchObject::rebuildExternalGeometry(bool defining, bool addIntersection)
void SketchObject::rebuildExternalGeometry(std::optional<ExternalToAdd> extToAdd)
{
Base::StateLocker lock(managedoperation, true); // no need to check input data validity as this is an sketchobject managed operation.
// get the actual lists of the externals
auto Types = ExternalTypes.getValues();
auto Objects = ExternalGeometry.getValues();
auto SubElements = ExternalGeometry.getSubValues();
assert(externalGeoRef.size() == Objects.size());
auto keys = externalGeoRef;
if (Types.size() != Objects.size()) {
Types.resize(Objects.size(), 0);
}
// re-check for any missing geometry element. The code here has a side
// effect that the linked external geometry will continue to work even if
@@ -9007,169 +9059,211 @@ void SketchObject::rebuildExternalGeometry(bool defining, bool addIntersection)
const std::string &SubElement=SubElements[i];
const std::string &key = keys[i];
bool beingCreated = false;
if (extToAdd) {
beingCreated = extToAdd->obj == Obj && extToAdd->subname == SubElement;
}
bool projection = Types[i] == (int)ExtType::Projection || Types[i] == (int)ExtType::Both;
bool intersection = Types[i] == (int)ExtType::Intersection || Types[i] == (int)ExtType::Both;
// Skip frozen geometries
bool frozen = false;
bool sync = false;
bool intersection = addIntersection && (i+1 == (int)Objects.size());
for(auto id : externalGeoRefMap[key]) {
auto it = externalGeoMap.find(id);
if(it != externalGeoMap.end()) {
auto egf = ExternalGeometryFacade::getFacade(ExternalGeo[it->second]);
if(egf->testFlag(ExternalGeometryExtension::Frozen))
if(egf->testFlag(ExternalGeometryExtension::Frozen)) {
frozen = true;
if(egf->testFlag(ExternalGeometryExtension::Sync))
}
if (egf->testFlag(ExternalGeometryExtension::Sync)) {
sync = true;
}
}
}
if(frozen && !sync) {
refSet.insert(std::move(key));
continue;
}
if(!Obj || !Obj->getNameInDocument())
if (!Obj || !Obj->getNameInDocument()) {
continue;
}
std::vector<std::unique_ptr<Part::Geometry> > geos;
try {
TopoDS_Shape refSubShape;
auto importVertex = [&](const TopoDS_Shape& refSubShape) {
gp_Pnt P = BRep_Tool::Pnt(TopoDS::Vertex(refSubShape));
GeomAPI_ProjectPointOnSurf proj(P, gPlane);
P = proj.NearestPoint();
Base::Vector3d p(P.X(), P.Y(), P.Z());
invPlm.multVec(p, p);
if (Obj->isDerivedFrom<Part::Datum>()) {
auto* datum = static_cast<const Part::Datum*>(Obj);
refSubShape = datum->getShape();
}
else if (Obj->isDerivedFrom<Part::Feature>()) {
try {
Part::GeomPoint* point = new Part::GeomPoint(p);
GeometryFacade::setConstruction(point, true);
geos.emplace_back(point);
};
try {
TopoDS_Shape refSubShape;
if (Obj->isDerivedFrom<Part::Datum>()) {
auto* datum = static_cast<const Part::Datum*>(Obj);
refSubShape = datum->getShape();
}
else if (Obj->isDerivedFrom<Part::Feature>()) {
auto* refObj = static_cast<const Part::Feature*>(Obj);
const Part::TopoShape& refShape = refObj->Shape.getShape();
refSubShape = refShape.getSubShape(SubElement.c_str());
}
catch (Standard_Failure& e) {
throw Base::CADKernelError(e.GetMessageString());
else if (Obj->isDerivedFrom<App::Plane>()) {
auto* pl = static_cast<const App::Plane*>(Obj);
Base::Placement plm = pl->Placement.getValue();
Base::Vector3d base = plm.getPosition();
Base::Rotation rot = plm.getRotation();
Base::Vector3d normal(0, 0, 1);
rot.multVec(normal, normal);
gp_Pln plane(gp_Pnt(base.x, base.y, base.z), gp_Dir(normal.x, normal.y, normal.z));
BRepBuilderAPI_MakeFace fBuilder(plane);
if (!fBuilder.IsDone())
throw Base::RuntimeError(
"Sketcher: addExternal(): Failed to build face from App::Plane");
TopoDS_Face f = TopoDS::Face(fBuilder.Shape());
refSubShape = f;
}
else {
throw Base::TypeError(
"Datum feature type is not yet supported as external geometry for a sketch");
}
}
else if (Obj->isDerivedFrom<App::Plane>()) {
auto* pl = static_cast<const App::Plane*>(Obj);
Base::Placement plm = pl->Placement.getValue();
Base::Vector3d base = plm.getPosition();
Base::Rotation rot = plm.getRotation();
Base::Vector3d normal(0, 0, 1);
rot.multVec(normal, normal);
gp_Pln plane(gp_Pnt(base.x, base.y, base.z), gp_Dir(normal.x, normal.y, normal.z));
BRepBuilderAPI_MakeFace fBuilder(plane);
if (!fBuilder.IsDone())
throw Base::RuntimeError(
"Sketcher: addExternal(): Failed to build face from App::Plane");
TopoDS_Face f = TopoDS::Face(fBuilder.Shape());
refSubShape = f;
}
else {
throw Base::TypeError(
"Datum feature type is not yet supported as external geometry for a sketch");
}
if (projection) {
switch (refSubShape.ShapeType()) {
case TopAbs_FACE: {
const TopoDS_Face& face = TopoDS::Face(refSubShape);
BRepAdaptor_Surface surface(face);
if (surface.GetType() == GeomAbs_Plane) {
// Check that the plane is perpendicular to the sketch plane
Geom_Plane plane = surface.Plane();
gp_Dir dnormal = plane.Axis().Direction();
gp_Dir snormal = sketchPlane.Axis().Direction();
switch (refSubShape.ShapeType()) {
case TopAbs_FACE: {
const TopoDS_Face& face = TopoDS::Face(refSubShape);
BRepAdaptor_Surface surface(face);
if (surface.GetType() == GeomAbs_Plane) {
// Check that the plane is perpendicular to the sketch plane
Geom_Plane plane = surface.Plane();
gp_Dir dnormal = plane.Axis().Direction();
gp_Dir snormal = sketchPlane.Axis().Direction();
// Extract all edges from the face
TopExp_Explorer edgeExp;
for (edgeExp.Init(face, TopAbs_EDGE); edgeExp.More(); edgeExp.Next()) {
TopoDS_Edge edge = TopoDS::Edge(edgeExp.Current());
// Process each edge
processEdge(edge, geos, gPlane, invPlm, mov, sketchPlane, invRot, sketchAx3, aProjFace);
}
// Extract all edges from the face
TopExp_Explorer edgeExp;
for (edgeExp.Init(face, TopAbs_EDGE); edgeExp.More(); edgeExp.Next()) {
TopoDS_Edge edge = TopoDS::Edge(edgeExp.Current());
// Process each edge
processEdge(edge, geos, gPlane, invPlm, mov, sketchPlane, invRot, sketchAx3, aProjFace);
}
if (fabs(dnormal.Angle(snormal) - M_PI_2) < Precision::Confusion()) {
// The face is normal to the sketch plane
// We don't want to keep the projection of all the edges of the face.
// We need a single line that goes from min to max of all the projections.
bool initialized = false;
Base::Vector3d start, end;
// Lambda to determine if a point should replace start or end
auto updateExtremes = [&](const Base::Vector3d& point) {
if ((point - start).Length() < (point - end).Length()) {
// `point` is closer to `start` than `end`, check if it's further out than `start`
if ((point - end).Length() > (end - start).Length()) {
start = point;
if (fabs(dnormal.Angle(snormal) - M_PI_2) < Precision::Confusion()) {
// The face is normal to the sketch plane
// We don't want to keep the projection of all the edges of the face.
// We need a single line that goes from min to max of all the projections.
bool initialized = false;
Base::Vector3d start, end;
// Lambda to determine if a point should replace start or end
auto updateExtremes = [&](const Base::Vector3d& point) {
if ((point - start).Length() < (point - end).Length()) {
// `point` is closer to `start` than `end`, check if it's further out than `start`
if ((point - end).Length() > (end - start).Length()) {
start = point;
}
}
else {
// `point` is closer to `end`, check if it's further out than `end`
if ((point - start).Length() > (end - start).Length()) {
end = point;
}
}
};
for (auto& geo : geos) {
auto* line = dynamic_cast<Part::GeomLineSegment*>(geo.get());
if (!line) {
// The face being normal to the sketch, we should have
// only lines. This is just a fail-safe in case there's a
// straight bspline or something like this.
continue;
}
if (!initialized) {
start = line->getStartPoint();
end = line->getEndPoint();
initialized = true;
continue;
}
updateExtremes(line->getStartPoint());
updateExtremes(line->getEndPoint());
}
if (initialized) {
auto* unifiedLine = new Part::GeomLineSegment();
unifiedLine->setPoints(start, end);
geos.clear(); // Clear other segments
geos.emplace_back(unifiedLine);
}
else {
// `point` is closer to `end`, check if it's further out than `end`
if ((point - start).Length() > (end - start).Length()) {
end = point;
// In case we have not initialized, perhaps the projections were
// only straight bsplines.
// Then we use the old method that will give a line with 20000 length:
// Get vector that is normal to both sketch plane normal and plane normal.
// This is the line's direction
gp_Dir lnormal = dnormal.Crossed(snormal);
BRepBuilderAPI_MakeEdge builder(gp_Lin(plane.Location(), lnormal));
builder.Build();
if (builder.IsDone()) {
const TopoDS_Edge& edge = TopoDS::Edge(builder.Shape());
BRepAdaptor_Curve curve(edge);
if (curve.GetType() == GeomAbs_Line) {
geos.emplace_back(projectLine(curve, gPlane, invPlm));
}
}
}
};
for (auto& geo : geos) {
auto* line = dynamic_cast<Part::GeomLineSegment*>(geo.get());
if (!line) {
continue; // we should have only lines
}
if (!initialized) {
start = line->getStartPoint();
end = line->getEndPoint();
initialized = true;
continue;
}
updateExtremes(line->getStartPoint());
updateExtremes(line->getEndPoint());
}
auto* unifiedLine = new Part::GeomLineSegment();
unifiedLine->setPoints(start, end);
geos.clear(); // Clear other segments
geos.emplace_back(unifiedLine);
}
}
else {
std::vector<TopoDS_Shape> res = projectShape(face, sketchAx3);
for (auto& resShape : res) {
TopExp_Explorer explorer(resShape, TopAbs_EDGE);
while (explorer.More()) {
TopoDS_Edge projEdge = TopoDS::Edge(explorer.Current());
processEdge2(projEdge, geos);
explorer.Next();
}
}
else {
std::vector<TopoDS_Shape> res = projectShape(face, sketchAx3);
for (auto& resShape : res) {
TopExp_Explorer explorer(resShape, TopAbs_EDGE);
while (explorer.More()) {
TopoDS_Edge projEdge = TopoDS::Edge(explorer.Current());
processEdge2(projEdge, geos);
explorer.Next();
}
}
}
} break;
case TopAbs_EDGE: {
const TopoDS_Edge& edge = TopoDS::Edge(refSubShape);
processEdge(edge, geos, gPlane, invPlm, mov, sketchPlane, invRot, sketchAx3, aProjFace);
} break;
case TopAbs_VERTEX: {
importVertex(refSubShape);
} break;
default:
throw Base::TypeError("Unknown type of geometry");
break;
}
if (beingCreated && !extToAdd->intersection) {
// We are adding the projections, so we need to initialize those
for (auto& geo : geos) {
auto egf = ExternalGeometryFacade::getFacade(geo.get());
egf->setFlag(ExternalGeometryExtension::Defining, extToAdd->defining);
}
}
} break;
case TopAbs_EDGE: {
const TopoDS_Edge& edge = TopoDS::Edge(refSubShape);
processEdge(edge, geos, gPlane, invPlm, mov, sketchPlane, invRot, sketchAx3, aProjFace);
} break;
case TopAbs_VERTEX: {
gp_Pnt P = BRep_Tool::Pnt(TopoDS::Vertex(refSubShape));
GeomAPI_ProjectPointOnSurf proj(P, gPlane);
P = proj.NearestPoint();
Base::Vector3d p(P.X(), P.Y(), P.Z());
invPlm.multVec(p, p);
auto* point = new Part::GeomPoint(p);
GeometryFacade::setConstruction(point, true);
geos.emplace_back(point);
} break;
default:
throw Base::TypeError("Unknown type of geometry");
break;
}
int projSize = geos.size();
if (intersection && (refSubShape.ShapeType() == TopAbs_EDGE
|| refSubShape.ShapeType() == TopAbs_FACE))
{
if (intersection) {
FCBRepAlgoAPI_Section maker(refSubShape, sketchPlane);
maker.Approximation(Standard_True);
if (!maker.IsDone())
FC_THROWM(Base::CADKernelError,"Failed to get intersection");
FC_THROWM(Base::CADKernelError, "Failed to get intersection");
Part::TopoShape intersectionShape(maker.Shape());
auto edges = intersectionShape.getSubTopoShapes(TopAbs_EDGE);
for (const auto& s : edges) {
TopoDS_Edge edge = TopoDS::Edge(s.getShape());
processEdge(edge, geos, gPlane, invPlm, mov, sketchPlane, invRot, sketchAx3, aProjFace);
}
// Section of some face (e.g. sphere) produce more than one arcs
// from the same circle. So we try to fit the arcs with a single
// circle/arc.
@@ -9190,6 +9284,17 @@ void SketchObject::rebuildExternalGeometry(bool defining, bool addIntersection)
}
}
}
for (const auto& s : intersectionShape.getSubShapes(TopAbs_VERTEX, TopAbs_EDGE)) {
importVertex(s);
}
if (beingCreated && extToAdd->intersection) {
// We are adding the projections, so we need to initialize those
for (size_t i = projSize; i < geos.size(); ++i) {
auto egf = ExternalGeometryFacade::getFacade(geos[i].get());
egf->setFlag(ExternalGeometryExtension::Defining, extToAdd->defining);
}
}
}
} catch (Base::Exception &e) {
@@ -9209,25 +9314,18 @@ void SketchObject::rebuildExternalGeometry(bool defining, bool addIntersection)
<< getFullName() << ": " << key << std::endl << "Unknown exception");
continue;
}
if(geos.empty())
if (geos.empty()) {
continue;
}
if(!refSet.emplace(key).second) {
FC_WARN("Duplicated external reference in " << getFullName() << ": " << key);
continue;
}
if (intersection) {
for(auto &geo : geos) {
auto egf = ExternalGeometryFacade::getFacade(geo.get());
egf->setFlag(ExternalGeometryExtension::Defining, defining);
}
} else if (defining && i+1==(int)Objects.size()) {
for(auto &geo : geos)
ExternalGeometryFacade::getFacade(geo.get())->setFlag(
ExternalGeometryExtension::Defining);
}
for(auto &geo : geos)
for (auto& geo : geos) {
ExternalGeometryFacade::getFacade(geo.get())->setRef(key);
}
newGeos.push_back(std::move(geos));
}
@@ -9308,8 +9406,9 @@ void SketchObject::rebuildExternalGeometry(bool defining, bool addIntersection)
solverNeedsUpdate=true;
Constraints.acceptGeometry(getCompleteGeometry());
if(hasError && this->isRecomputing())
if (hasError && this->isRecomputing()) {
throw Base::RuntimeError("Missing external geometry reference");
}
}
void SketchObject::fixExternalGeometry(const std::vector<int> &geoIds) {