1
/***************************************************************************
2
* Copyright (c) 2022 WandererFan <wandererfan@gmail.com> *
4
* This file is part of the FreeCAD CAx development system. *
6
* This library is free software; you can redistribute it and/or *
7
* modify it under the terms of the GNU Library General Public *
8
* License as published by the Free Software Foundation; either *
9
* version 2 of the License, or (at your option) any later version. *
11
* This library is distributed in the hope that it will be useful, *
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14
* GNU Library General Public License for more details. *
16
* You should have received a copy of the GNU Library General Public *
17
* License along with this library; see the file COPYING.LIB. If not, *
18
* write to the Free Software Foundation, Inc., 59 Temple Place, *
19
* Suite 330, Boston, MA 02111-1307, USA *
21
***************************************************************************/
23
#include "PreCompiled.h"
25
#include <BRepAdaptor_Curve.hxx>
26
#include <BRep_Tool.hxx>
28
#endif//#ifndef _PreComp_
30
#include <App/DocumentObject.h>
31
#include <Base/Console.h>
32
#include <Gui/Selection.h>
33
#include <Mod/TechDraw/App/DrawViewPart.h>
34
#include <Mod/TechDraw/App/ShapeExtractor.h>
36
#include "DimensionValidators.h"
39
using namespace TechDraw;
42
TechDraw::DrawViewPart* TechDraw::getReferencesFromSelection(ReferenceVector& references2d,
43
ReferenceVector& references3d)
45
TechDraw::DrawViewPart* dvp(nullptr);
46
TechDraw::DrawViewDimension* dim(nullptr);
47
std::vector<Gui::SelectionObject> selectionAll = Gui::Selection().getSelectionEx();
48
for (auto& selItem : selectionAll) {
49
if (selItem.getObject()->isDerivedFrom(TechDraw::DrawViewDimension::getClassTypeId())) {
50
//we are probably repairing a dimension, but we will check later
51
dim = static_cast<TechDraw::DrawViewDimension*>(selItem.getObject());
52
} else if (selItem.getObject()->isDerivedFrom(TechDraw::DrawViewPart::getClassTypeId())) {
53
//this could be a 2d geometry selection or just a DrawViewPart for context in
55
dvp = static_cast<TechDraw::DrawViewPart*>(selItem.getObject());
56
if (selItem.getSubNames().empty()) {
57
//there are no subNames, so we think this is a 3d case,
58
//and we only need to select the view. We set the reference
59
//subName to a null string to avoid later misunderstandings.
60
ReferenceEntry ref(dvp, std::string());
61
references2d.push_back(ref);
63
for (auto& sub : selItem.getSubNames()) {
64
ReferenceEntry ref(dvp, sub);
65
references2d.push_back(ref);
67
} else if (!selItem.getObject()->isDerivedFrom(TechDraw::DrawView::getClassTypeId())) {
68
//this is not a TechDraw object, so we check to see if it has 3d geometry
69
std::vector<App::DocumentObject*> links;
70
links.push_back(selItem.getObject());
71
if (!ShapeExtractor::getShapes(links).IsNull()) {
72
//this item has 3d geometry so we are interested
73
App::DocumentObject* obj3d = selItem.getObject();
74
if (selItem.getSubNames().empty()) {
75
if (ShapeExtractor::isPointType(obj3d)) {
76
//a point object may not have a subName when selected,
77
//so we need to perform some special handling.
78
ReferenceEntry ref(obj3d, "Vertex1");
79
references3d.push_back(ref);
82
//this is a whole object reference, probably for an extent dimension
83
ReferenceEntry ref(obj3d, std::string());
84
references3d.push_back(ref);
88
//this is a regular reference in form obj+subelement
89
for (auto& sub3d : selItem.getSubNames()) {
90
ReferenceEntry ref(obj3d, sub3d);
91
references3d.push_back(ref);
94
Base::Console().Message("DV::getRefsFromSel - %s has no shape!\n",
95
selItem.getObject()->getNameInDocument());
101
ReferenceEntry ref(dim->getViewPart(), std::string());
102
references2d.push_back(ref);
103
return dim->getViewPart();
109
//! verify that the proposed references contains valid geometries from a 2d DrawViewPart.
110
DimensionGeometryType TechDraw::validateDimSelection(
111
ReferenceVector references, //[(dvp*, std::string),...,(dvp*, std::string)]
112
StringVector acceptableGeometry,//"Edge", "Vertex", etc
113
std::vector<int> minimumCounts, //how many of each geometry are needed for a good dimension
114
std::vector<DimensionGeometryType> acceptableDimensionGeometrys)//isVertical, isHorizontal, ...
116
StringVector subNames;
117
TechDraw::DrawViewPart* dvpSave(nullptr);
118
for (auto& ref : references) {
119
TechDraw::DrawViewPart* dvp = dynamic_cast<TechDraw::DrawViewPart*>(ref.getObject());
122
if (!ref.getSubName().empty()) {
123
subNames.push_back(ref.getSubName());
128
//must have 1 DVP in selection
132
if (subNames.empty()) {
133
//no geometry referenced. can not make a dim from this mess. We are being called to validate
134
//a selection for a 3d reference
135
return isViewReference;
138
if (subNames.front().empty()) {
139
//can this still happen?
140
return isViewReference;
143
//check for invalid geometry descriptors in the subNames
144
std::unordered_set<std::string> acceptableGeometrySet(acceptableGeometry.begin(),
145
acceptableGeometry.end());
146
if (!TechDraw::validateSubnameList(subNames, acceptableGeometrySet)) {
147
//can not make a dimension from this
151
//check for wrong number of geometry
152
GeomCountVector foundCounts;
153
GeomCountMap minimumCountMap = loadRequiredCounts(acceptableGeometry, minimumCounts);
154
if (!checkGeometryOccurences(subNames, minimumCountMap)) {
155
//too many or too few geometry descriptors.
159
//we have a (potentially valid collection of 2d geometry
160
ReferenceVector valid2dReferences;
161
for (auto& sub : subNames) {
162
ReferenceEntry validEntry(dvpSave, sub);
163
valid2dReferences.push_back(validEntry);
166
DimensionGeometryType foundGeometry = getGeometryConfiguration(valid2dReferences);
167
if (acceptableDimensionGeometrys.empty()) {
168
//if the list is empty, we are accepting anything
169
return foundGeometry;
171
for (auto& acceptable : acceptableDimensionGeometrys) {
172
if (foundGeometry == acceptable) {
173
return foundGeometry;
180
//! verify that the proposed references contains valid geometries from non-TechDraw objects.
181
DimensionGeometryType TechDraw::validateDimSelection3d(
182
TechDraw::DrawViewPart* dvp,
183
ReferenceVector references, //[(dvp*, std::string),...,(dvp*, std::string)]
184
StringVector acceptableGeometry,//"Edge", "Vertex", etc
185
std::vector<int> minimumCounts, //how many of each geometry are needed for a good dimension
186
std::vector<DimensionGeometryType> acceptableDimensionGeometrys)//isVertical, isHorizontal, ...
188
StringVector subNames;
189
for (auto& ref : references) {
190
if (!ref.getSubName().empty()) {
191
subNames.push_back(ref.getSubName());
196
//check for invalid geometry descriptors in the subNames
197
std::unordered_set<std::string> acceptableGeometrySet(acceptableGeometry.begin(),
198
acceptableGeometry.end());
199
if (!TechDraw::validateSubnameList(subNames, acceptableGeometrySet)) {
200
//can not make a dimension from this
204
//check for wrong number of geometry
205
GeomCountMap minimumCountMap = loadRequiredCounts(acceptableGeometry, minimumCounts);
206
if (!checkGeometryOccurences(subNames, minimumCountMap)) {
207
//too many or too few geometry descriptors.
211
//we have a (potentially valid collection of 3d geometry
212
DimensionGeometryType foundGeometry = getGeometryConfiguration3d(dvp, references);
213
if (acceptableDimensionGeometrys.empty()) {
214
//if the list is empty, we are accepting anything
215
return foundGeometry;
217
for (auto& acceptable : acceptableDimensionGeometrys) {
218
if (foundGeometry == acceptable) {
219
return foundGeometry;
225
bool TechDraw::validateSubnameList(StringVector subNames, GeometrySet acceptableGeometrySet)
227
for (auto& sub : subNames) {
228
std::string geometryType = DrawUtil::getGeomTypeFromName(sub);
229
if (acceptableGeometrySet.count(geometryType) == 0) {
230
//this geometry type is not allowed
237
//count how many of each "Edge", "Vertex, etc and compare totals to required minimum
238
bool TechDraw::checkGeometryOccurences(StringVector subNames, GeomCountMap keyedMinimumCounts)
240
//how many of each geometry descriptor are input
241
GeomCountMap foundCounts;
242
for (auto& sub : subNames) {
243
std::string geometryType = DrawUtil::getGeomTypeFromName(sub);
244
std::map<std::string, int>::iterator it0(foundCounts.find(geometryType));
245
if (it0 == foundCounts.end()) {
246
//first occurrence of this geometryType
247
foundCounts[geometryType] = 1;
249
//already have this geometryType
254
//hybrid dims (vertex-edge) can skip this check
255
if (foundCounts.size() > 1) {
256
//this is a hybrid dimension
260
//check found geometry counts against required counts
261
for (auto& foundItem : foundCounts) {
262
std::string currentKey = foundItem.first;
263
int foundCount = foundItem.second;
264
auto itAccept = keyedMinimumCounts.find(currentKey);
265
if (itAccept == keyedMinimumCounts.end()) {
266
//not supposed to happen by this point
267
throw Base::IndexError("Dimension validation counts and geometry do not match");
269
if (foundCount < keyedMinimumCounts[currentKey]) {
270
//not enough of this type of geom to make a good dimension - ex 1 Vertex
274
//we have no complaints about the input
278
//return the first valid configuration contained in the already validated references
279
DimensionGeometryType TechDraw::getGeometryConfiguration(ReferenceVector valid2dReferences)
281
DimensionGeometryType config = isValidHybrid(valid2dReferences);
282
if (config > isInvalid) {
286
config = isValidMultiEdge(valid2dReferences);
287
if (config > isInvalid) {
290
config = isValidVertexes(valid2dReferences);
291
if (config > isInvalid) {
294
config = isValidSingleEdge(valid2dReferences.front());
295
if (config > isInvalid) {
299
// no valid configuration found
303
//return the first valid configuration contained in the already validated references
304
DimensionGeometryType TechDraw::getGeometryConfiguration3d(DrawViewPart* dvp,
305
ReferenceVector valid3dReferences)
307
//first we check for whole object references
308
ReferenceVector wholeObjectRefs;
309
ReferenceVector subElementRefs;
310
for (auto& ref : valid3dReferences) {
311
if (ref.isWholeObject()) {
312
wholeObjectRefs.push_back(ref);
314
subElementRefs.push_back(ref);
317
if (subElementRefs.empty()) {
318
//only whole object references
321
if (!wholeObjectRefs.empty()) {
322
//mix of whole object and subelement refs
323
return isMultiEdge;//??? correct ???
326
//only have subelement refs
327
DimensionGeometryType config = isValidMultiEdge3d(dvp, valid3dReferences);
328
if (config > isInvalid) {
331
config = isValidVertexes3d(dvp, valid3dReferences);
332
if (config > isInvalid) {
335
config = isValidSingleEdge3d(dvp, valid3dReferences.front());
336
if (config > isInvalid) {
339
config = isValidHybrid3d(dvp, valid3dReferences);
340
if (config > isInvalid) {
344
// no valid configuration found
348
//fill the GeomCountMap with pairs made from corresponding items in acceptableGeometry
350
GeomCountMap TechDraw::loadRequiredCounts(StringVector& acceptableGeometry,
351
std::vector<int>& minimumCounts)
353
if (acceptableGeometry.size() != minimumCounts.size()) {
354
throw Base::IndexError("acceptableGeometry and minimum counts have different sizes.");
359
for (auto& acceptableItem : acceptableGeometry) {
360
result[acceptableItem] = minimumCounts.at(iCount);
366
//! verify that Selection contains a valid Geometry for a single Edge Dimension
367
DimensionGeometryType TechDraw::isValidSingleEdge(ReferenceEntry ref)
369
auto objFeat(dynamic_cast<TechDraw::DrawViewPart*>(ref.getObject()));
374
//the Name starts with "Edge"
375
std::string geomName = DrawUtil::getGeomTypeFromName(ref.getSubName());
376
if (geomName != "Edge") {
380
//the geometry exists (redundant?)
381
int GeoId(TechDraw::DrawUtil::getIndexFromName(ref.getSubName()));
382
TechDraw::BaseGeomPtr geom = objFeat->getGeomByIndex(GeoId);
387
if (geom->getGeomType() == TechDraw::GENERIC) {
388
TechDraw::GenericPtr gen1 = std::static_pointer_cast<TechDraw::Generic>(geom);
389
if (gen1->points.size() < 2) {
392
Base::Vector3d line = gen1->points.at(1) - gen1->points.at(0);
393
if (fabs(line.y) < FLT_EPSILON) {
394
return TechDraw::isHorizontal;
395
} else if (fabs(line.x) < FLT_EPSILON) {
396
return TechDraw::isVertical;
398
return TechDraw::isDiagonal;
400
} else if (geom->getGeomType() == TechDraw::CIRCLE || geom->getGeomType() == TechDraw::ARCOFCIRCLE) {
402
} else if (geom->getGeomType() == TechDraw::ELLIPSE || geom->getGeomType() == TechDraw::ARCOFELLIPSE) {
404
} else if (geom->getGeomType() == TechDraw::BSPLINE) {
405
TechDraw::BSplinePtr spline = std::static_pointer_cast<TechDraw::BSpline>(geom);
406
if (spline->isCircle()) {
407
return isBSplineCircle;
415
//! verify that Selection contains a valid Geometry for a single Edge Dimension
416
DimensionGeometryType TechDraw::isValidSingleEdge3d(DrawViewPart* dvp, ReferenceEntry ref)
419
//the Name starts with "Edge"
420
std::string geomName = DrawUtil::getGeomTypeFromName(ref.getSubName());
421
if (geomName != "Edge") {
425
TopoDS_Shape refShape = ref.getGeometry();
426
if (refShape.IsNull() || refShape.ShapeType() != TopAbs_EDGE) {
427
throw Base::RuntimeError("Geometry for reference is not an edge.");
430
TopoDS_Edge occEdge = TopoDS::Edge(refShape);
431
BRepAdaptor_Curve adapt(occEdge);
432
if (adapt.GetType() == GeomAbs_Line) {
433
Base::Vector3d point0 = DU::toVector3d(BRep_Tool::Pnt(TopExp::FirstVertex(occEdge)));
434
point0 = dvp->projectPoint(point0);
435
Base::Vector3d point1 = DU::toVector3d(BRep_Tool::Pnt(TopExp::LastVertex(occEdge)));
436
point1 = dvp->projectPoint(point1);
437
Base::Vector3d line = point1 - point0;
438
if (fabs(line.y) < FLT_EPSILON) {
439
return TechDraw::isHorizontal;
440
} else if (fabs(line.x) < FLT_EPSILON) {
441
return TechDraw::isVertical;
443
// else if (fabs(line.z) < FLT_EPSILON) {
444
// return TechDraw::isZLimited;
447
return TechDraw::isDiagonal;
449
} else if (adapt.GetType() == GeomAbs_Circle) {
451
} else if (adapt.GetType() == GeomAbs_Ellipse) {
453
} else if (adapt.GetType() == GeomAbs_BSplineCurve) {
454
if (GeometryUtils::isCircle(occEdge)) {
455
return isBSplineCircle;
464
//! verify that the edge references can make a dimension. Currently only extent
465
//! dimensions support more than 2 edges
466
DimensionGeometryType TechDraw::isValidMultiEdge(ReferenceVector refs)
468
//there has to be at least 2
469
if (refs.size() < 2) {
473
//they all must start with "Edge"
474
const std::string matchToken{"Edge"};
475
if (!refsMatchToken(refs, matchToken)) {
479
auto objFeat0(dynamic_cast<TechDraw::DrawViewPart*>(refs.at(0).getObject()));
482
throw Base::RuntimeError("Logic error in isValidMultiEdge");
485
if (refs.size() > 2) {
486
//many edges, must be an extent?
490
//exactly 2 edges. could be angle, could be distance
491
int GeoId0(TechDraw::DrawUtil::getIndexFromName(refs.at(0).getSubName()));
492
int GeoId1(TechDraw::DrawUtil::getIndexFromName(refs.at(1).getSubName()));
493
TechDraw::BaseGeomPtr geom0 = objFeat0->getGeomByIndex(GeoId0);
494
TechDraw::BaseGeomPtr geom1 = objFeat0->getGeomByIndex(GeoId1);
496
if (geom0->getGeomType() == TechDraw::GENERIC && geom1->getGeomType() == TechDraw::GENERIC) {
497
TechDraw::GenericPtr gen0 = std::static_pointer_cast<TechDraw::Generic>(geom0);
498
TechDraw::GenericPtr gen1 = std::static_pointer_cast<TechDraw::Generic>(geom1);
499
if (gen0->points.size() > 2 || gen1->points.size() > 2) {//the edge is a polyline
500
return isInvalid; //not supported yet
502
Base::Vector3d line0 = gen0->points.at(1) - gen0->points.at(0);
503
Base::Vector3d line1 = gen1->points.at(1) - gen1->points.at(0);
504
double xprod = fabs(line0.x * line1.y - line0.y * line1.x);
505
if (xprod > FLT_EPSILON) {//edges are not parallel
506
return isAngle; //angle or distance
508
return isDiagonal;//distance || line
511
return isDiagonal;//two edges, not both straight lines
517
//! verify that the edge references can make a dimension. Currently only extent
518
//! dimensions support more than 2 edges
519
DimensionGeometryType TechDraw::isValidMultiEdge3d(DrawViewPart* dvp, ReferenceVector refs)
522
//there has to be at least 2
523
if (refs.size() < 2) {
527
//they all must start with "Edge"
528
const std::string matchToken{"Edge"};
529
if (!refsMatchToken(refs, matchToken)) {
533
std::vector<TopoDS_Edge> edges;
534
for (auto& ref : refs) {
535
std::vector<TopoDS_Shape> shapesAll = ShapeExtractor::getShapesFromObject(ref.getObject());
536
if (shapesAll.empty()) {
537
//reference has no geometry
541
std::vector<TopoDS_Edge> edgesAll;
542
std::vector<int> typeAll;
543
for (auto& ref : refs) {
544
TopoDS_Shape geometry = ref.getGeometry();
545
if (geometry.ShapeType() != TopAbs_EDGE) {
548
TopoDS_Edge edge = TopoDS::Edge(geometry);
549
BRepAdaptor_Curve adapt(edge);
550
if (adapt.GetType() != GeomAbs_Line) {
551
//not a line, so this must be an extent dim?
554
edgesAll.push_back(edge);
556
if (edgesAll.size() > 2) {
557
//must be an extent dimension of lines?
559
} else if (edgesAll.size() == 2) {
560
Base::Vector3d first0 = DU::toVector3d(BRep_Tool::Pnt(TopExp::FirstVertex(edgesAll.at(0))));
561
Base::Vector3d last0 = DU::toVector3d(BRep_Tool::Pnt(TopExp::LastVertex(edgesAll.at(1))));
562
Base::Vector3d line0 = last0 - first0;
563
Base::Vector3d first1 = DU::toVector3d(BRep_Tool::Pnt(TopExp::FirstVertex(edgesAll.at(0))));
564
Base::Vector3d last1 = DU::toVector3d(BRep_Tool::Pnt(TopExp::LastVertex(edgesAll.at(1))));
565
Base::Vector3d line1 = last1 - first1;
566
if (DU::fpCompare(fabs(line0.Dot(line1)), 1)) {
567
//lines are parallel, must be distance dim
570
//lines are skew, could be angle, could be distance?
578
//! verify that the vertex references can make a dimension
579
DimensionGeometryType TechDraw::isValidVertexes(ReferenceVector refs)
581
TechDraw::DrawViewPart* dvp(dynamic_cast<TechDraw::DrawViewPart*>(refs.front().getObject()));
584
throw Base::RuntimeError("Logic error in isValidMultiEdge");
587
const std::string matchToken{"Vertex"};
588
if (!refsMatchToken(refs, matchToken)) {
592
if (refs.size() == 2) {
593
//2 vertices can only make a distance dimension
594
TechDraw::VertexPtr v0 = dvp->getVertex(refs.at(0).getSubName());
595
TechDraw::VertexPtr v1 = dvp->getVertex(refs.at(1).getSubName());
596
Base::Vector3d line = v1->point() - v0->point();
597
if (fabs(line.y) < FLT_EPSILON) {
599
} else if (fabs(line.x) < FLT_EPSILON) {
604
} else if (refs.size() == 3) {
605
//three vertices make an angle dimension
609
// did not find a valid configuration
613
//! verify that the vertex references can make a dimension
614
DimensionGeometryType TechDraw::isValidVertexes3d(DrawViewPart* dvp, ReferenceVector refs)
617
const std::string matchToken{"Vertex"};
618
if (!refsMatchToken(refs, matchToken)) {
622
if (refs.size() == 2) {
623
//2 vertices can only make a distance dimension
624
TopoDS_Shape geometry0 = refs.at(0).getGeometry();
625
TopoDS_Shape geometry1 = refs.at(1).getGeometry();
626
if (geometry0.IsNull() || geometry1.IsNull() || geometry0.ShapeType() != TopAbs_VERTEX
627
|| geometry1.ShapeType() != TopAbs_VERTEX) {
630
Base::Vector3d point0 = DU::toVector3d(BRep_Tool::Pnt(TopoDS::Vertex(geometry0)));
631
point0 = dvp->projectPoint(point0);
632
Base::Vector3d point1 = DU::toVector3d(BRep_Tool::Pnt(TopoDS::Vertex(geometry1)));
633
point1 = dvp->projectPoint(point1);
634
Base::Vector3d line = point1 - point0;
635
if (fabs(line.y) < FLT_EPSILON) {
637
} else if (fabs(line.x) < FLT_EPSILON) {
639
// } else if(fabs(line.z) < FLT_EPSILON) {
640
// return isZLimited;
644
} else if (refs.size() == 3) {
645
//three vertices make an angle dimension
646
//we could check here that all the geometries are Vertex
650
// did not find a valid configuration
654
//! verify that the mixed bag (ex Vertex-Edge) of references can make a dimension
655
DimensionGeometryType TechDraw::isValidHybrid(ReferenceVector refs)
663
for (auto& ref : refs) {
664
if (DU::getGeomTypeFromName(ref.getSubName()) == "Vertex") {
667
if (DU::getGeomTypeFromName(ref.getSubName()) == "Edge") {
671
if (vertexCount > 0 && edgeCount > 0) {
672
//must be a diagonal dim? could it be isHorizontal or isVertical?
679
//! verify that the mixed bag (ex Vertex-Edge) of references can make a dimension
680
DimensionGeometryType TechDraw::isValidHybrid3d(DrawViewPart* dvp, ReferenceVector refs)
683
//we can reuse the 2d check here.
684
return isValidHybrid(refs);
687
//handle situations where revised geometry type is valid but not suitable for existing dimType
688
long int TechDraw::mapGeometryTypeToDimType(long int dimType, DimensionGeometryType geometry2d,
689
DimensionGeometryType geometry3d)
691
if (geometry2d == isInvalid && geometry3d == isInvalid) {
692
//probably an error, but we can't do anything with this
696
if (geometry2d == isViewReference && geometry3d != isInvalid) {
697
switch (geometry3d) {
699
return DrawViewDimension::Distance;
701
return DrawViewDimension::DistanceX;
703
return DrawViewDimension::DistanceY;
705
return DrawViewDimension::Angle;
707
return DrawViewDimension::Angle3Pt;
709
} else if (geometry2d != isViewReference) {
710
switch (geometry2d) {
712
return DrawViewDimension::Distance;
714
return DrawViewDimension::DistanceX;
716
return DrawViewDimension::DistanceY;
718
return DrawViewDimension::Angle;
720
return DrawViewDimension::Angle3Pt;
726
//! true if all the input references have subelements that match the geometry
728
bool TechDraw::refsMatchToken(const ReferenceVector& refs, const std::string& matchToken)
730
for (auto& entry : refs) {
731
std::string entryToken = DU::getGeomTypeFromName(entry.getSubName(false));
732
if (entryToken != matchToken) {