FreeCAD

Форк
0
/
Attacher.cpp 
2695 строк · 102.3 Кб
1
/***************************************************************************
2
 *   Copyright (c) 2015 Victor Titov (DeepSOIC) <vv.titov@gmail.com>       *
3
 *                                                                         *
4
 *   This file is part of the FreeCAD CAx development system.              *
5
 *                                                                         *
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.      *
10
 *                                                                         *
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.                  *
15
 *                                                                         *
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                                *
20
 *                                                                         *
21
 ***************************************************************************/
22

23
#include "PreCompiled.h"
24
#ifndef _PreComp_
25
# include <BRep_Tool.hxx>
26
# include <BRepAdaptor_Curve.hxx>
27
# include <BRepAdaptor_Surface.hxx>
28
# include <BRepBuilderAPI_MakeEdge.hxx>
29
# include <BRepBuilderAPI_MakeFace.hxx>
30
# include <BRepExtrema_DistShapeShape.hxx>
31
# include <BRepGProp.hxx>
32
# include <BRepIntCurveSurface_Inter.hxx>
33
# include <BRepLProp_SLProps.hxx>
34
# include <Geom_Line.hxx>
35
# include <Geom_Plane.hxx>
36
# include <GeomAdaptor.hxx>
37
# include <GeomAPI.hxx>
38
# include <GeomAPI_ProjectPointOnCurve.hxx>
39
# include <GeomAPI_ProjectPointOnSurf.hxx>
40
# include <GeomAPI_IntSS.hxx>
41
# include <GeomLib_IsPlanarSurface.hxx>
42
# include <gp_Ax1.hxx>
43
# include <gp_Dir.hxx>
44
# include <gp_Elips.hxx>
45
# include <gp_Hypr.hxx>
46
# include <gp_Parab.hxx>
47
# include <gp_Pln.hxx>
48
# include <gp_Pnt.hxx>
49
# include <GProp_GProps.hxx>
50
# include <GProp_PGProps.hxx>
51
# include <GProp_PrincipalProps.hxx>
52
# include <ShapeExtend_Explorer.hxx>
53
# include <TopoDS.hxx>
54
# include <TopoDS_Edge.hxx>
55
# include <TopoDS_Face.hxx>
56
# include <TopoDS_Iterator.hxx>
57
# include <TopoDS_Shape.hxx>
58
# include <TopoDS_Vertex.hxx>
59
# include <TopTools_HSequenceOfShape.hxx>
60
#endif
61

62
#include <App/Application.h>
63
#include <App/Document.h>
64
#include <App/OriginFeature.h>
65
#include <Base/Console.h>
66

67
#include "Attacher.h"
68
#include "AttachExtension.h"
69
#include "Tools.h"
70

71

72
using namespace Part;
73
using namespace Attacher;
74

75
//These strings are for mode list enum property.
76
const char* AttachEngine::eMapModeStrings[]= {
77
    "Deactivated",
78
    "Translate",
79
    "ObjectXY",
80
    "ObjectXZ",
81
    "ObjectYZ",
82
    "FlatFace",
83
    "TangentPlane",
84
    "NormalToEdge",
85
    "FrenetNB",
86
    "FrenetTN",
87
    "FrenetTB",
88
    "Concentric",
89
    "SectionOfRevolution",
90
    "ThreePointsPlane",
91
    "ThreePointsNormal",
92
    "Folding",
93

94
    "ObjectX",
95
    "ObjectY",
96
    "ObjectZ",
97
    "AxisOfCurvature",
98
    "Directrix1",
99
    "Directrix2",
100
    "Asymptote1",
101
    "Asymptote2",
102
    "Tangent",
103
    "Normal",
104
    "Binormal",
105
    "TangentU",
106
    "TangentV",
107
    "TwoPointLine",
108
    "IntersectionLine",
109
    "ProximityLine",
110

111
    "ObjectOrigin",
112
    "Focus1",
113
    "Focus2",
114
    "OnEdge",
115
    "CenterOfCurvature",
116
    "CenterOfMass",
117
    "IntersectionPoint",
118
    "Vertex",
119
    "ProximityPoint1",
120
    "ProximityPoint2",
121

122
    "AxisOfInertia1",
123
    "AxisOfInertia2",
124
    "AxisOfInertia3",
125

126
    "InertialCS",
127

128
    "FaceNormal",
129

130
    "OZX",
131
    "OZY",
132
    "OXY",
133
    "OXZ",
134
    "OYZ",
135
    "OYX",
136

137
    "ParallelPlane",
138

139
    nullptr};
140

141
//this list must be in sync with eRefType enum.
142
//These strings are used only by Py interface of Attacher. Strings for use in Gui are in Mod/Part/Gui/AttacherTexts.cpp
143
const char* AttachEngine::eRefTypeStrings[]= {
144
    "Any",
145
    "Vertex",
146
    "Edge",
147
    "Face",
148

149
    "Line",
150
    "Curve",
151
    "Circle",
152
    "Conic",
153
    "Ellipse",
154
    "Parabola",
155
    "Hyperbola",
156

157
    "Plane",
158
    "Sphere",
159
    "Revolve",
160
    "Cylinder",
161
    "Torus",
162
    "Cone",
163

164
    "Object",
165
    "Solid",
166
    "Wire",
167
    nullptr
168
};
169

170

171

172

173

174
TYPESYSTEM_SOURCE_ABSTRACT(Attacher::AttachEngine, Base::BaseClass)
175

176
AttachEngine::AttachEngine() = default;
177

178
void AttachEngine::setReferences(const App::PropertyLinkSubList& references)
179
{
180
    std::string docname;
181
    std::vector<std::string> names;
182
    for (auto obj : references.getValues()) {
183
        if (!obj->getNameInDocument()) {
184
            throw AttachEngineException("AttachEngine::invalid object");
185
        }
186
        if (docname.empty()) {
187
            docname = obj->getDocument()->getName();
188
        }
189
        else if (docname != obj->getDocument()->getName()) {
190
            throw AttachEngineException("AttachEngine::object from multiple document");
191
        }
192
        names.emplace_back(obj->getNameInDocument());
193
    }
194
    this->docName = docname;
195
    this->objNames = std::move(names);
196
    this->subnames.clear();
197
    this->subnames.reserve(this->objNames.size());
198
    this->shadowSubs.clear();
199
    this->shadowSubs.reserve(this->objNames.size());
200
    for (auto& shadow : references.getShadowSubs()) {
201
        this->shadowSubs.push_back(shadow.newName);
202
        this->subnames.push_back(shadow.oldName);
203
    }
204
    assert(this->objNames.size() == this->subnames.size());
205
}
206

207
void AttachEngine::setReferences(const std::vector<App::SubObjectT>& references)
208
{
209
    std::string docname;
210
    std::vector<std::string> names;
211
    std::vector<std::string> subnames;
212
    std::vector<std::string> shadowSubs;
213
    for (auto& ref : references) {
214
        if (!ref.getSubObject()) {
215
            FC_THROWM(AttachEngineException,
216
                      "AttachEngine::invalid object " << ref.getSubObjectFullName());
217
        }
218
        if (docname.empty()) {
219
            docname = ref.getDocumentName();
220
        }
221
        else if (docname != ref.getDocumentName()) {
222
            throw AttachEngineException("AttachEngine::object from multiple document");
223
        }
224
        names.push_back(ref.getObjectName());
225
        subnames.push_back(ref.getSubNameNoElement() + ref.getOldElementName());
226
        shadowSubs.push_back(ref.getSubNameNoElement() + ref.getNewElementName());
227
    }
228
    this->docName = docname;
229
    this->objNames = std::move(names);
230
    this->subnames = std::move(subnames);
231
    this->shadowSubs = std::move(shadowSubs);
232
}
233

234
void AttachEngine::setUp(const App::PropertyLinkSubList &references,
235
                         eMapMode mapMode, bool mapReverse,
236
                         double attachParameter,
237
                         double surfU, double surfV,
238
                         const Base::Placement &attachmentOffset)
239
{
240
    setReferences(references);
241
    this->mapMode = mapMode;
242
    this->mapReverse = mapReverse;
243
    this->attachParameter = attachParameter;
244
    this->surfU = surfU;
245
    this->surfV = surfV;
246
    this->attachmentOffset = attachmentOffset;
247
}
248

249
void AttachEngine::setUp(const AttachEngine &another)
250
{
251
    this->docName = another.docName;
252
    this->objNames = another.objNames;
253
    this->subnames = another.subnames;
254
    this->shadowSubs = another.shadowSubs;
255
    this->mapMode = another.mapMode;
256
    this->mapReverse = another.mapReverse;
257
    this->attachParameter = another.attachParameter;
258
    this->surfU = another.surfU;
259
    this->surfV = another.surfV;
260
    this->attachmentOffset = another.attachmentOffset;
261
}
262

263
void AttachEngine::setOffset(const Base::Placement &offset)
264
{
265
    this->attachmentOffset = offset;
266
}
267

268
Base::Placement AttachEngine::placementFactory(const gp_Dir &ZAxis,
269
                                        gp_Vec XAxis,
270
                                        gp_Pnt Origin,
271
                                        gp_Pnt refOrg,
272
                                        bool useRefOrg_Line,
273
                                        bool useRefOrg_Plane,
274
                                        bool makeYVertical,
275
                                        bool makeLegacyFlatFaceOrientation,
276
                                        Base::Placement* placeOfRef) const
277
{
278
    if(useRefOrg_Line){
279
        //move Origin to projection of refOrg onto ZAxis
280
        gp_Vec refOrgV = gp_Vec(refOrg.XYZ());
281
        gp_Vec OriginV = gp_Vec(Origin.XYZ());
282
        gp_Vec ZAxisV = gp_Vec(ZAxis);
283
        Origin = gp_Pnt((
284
         OriginV + ZAxisV*ZAxisV.Dot(refOrgV-OriginV)
285
          ).XYZ());
286
    }
287
    if(useRefOrg_Plane){
288
        //move Origin to projection of refOrg onto plane (ZAxis, Origin)
289
        gp_Vec refOrgV = gp_Vec(refOrg.XYZ());
290
        gp_Vec OriginV = gp_Vec(Origin.XYZ());
291
        gp_Vec ZAxisV = gp_Vec(ZAxis);
292
        Origin = gp_Pnt((
293
         refOrgV + ZAxisV*ZAxisV.Dot(OriginV-refOrgV)
294
          ).XYZ());
295
    }
296

297
    if (XAxis.Magnitude() < Precision::Confusion())
298
        makeYVertical = true;
299

300
    gp_Ax3 ax3;//OCC representation of the final placement
301
    if (!makeYVertical) {
302
        ax3 = gp_Ax3(Origin, ZAxis, XAxis);
303
    } else if (!makeLegacyFlatFaceOrientation) {
304
        //align Y along Z, if possible
305
        gp_Vec YAxis(0.0,0.0,1.0);
306
        XAxis = YAxis.Crossed(gp_Vec(ZAxis));
307
        if (XAxis.Magnitude() < Precision::Confusion()){
308
            //ZAxis is along true ZAxis
309
            XAxis = (gp_Vec(1,0,0)*ZAxis.Z()).Normalized();
310
        }
311
        ax3 = gp_Ax3(Origin, ZAxis, XAxis);
312
    } else if (makeLegacyFlatFaceOrientation) {
313
        //find out, to which axis of support Normal is closest to.
314
        //The result will be written into pos variable (0..2 = X..Z)
315
        if (!placeOfRef)
316
            throw AttachEngineException("AttachEngine::placementFactory: for Legacy mode, placement of the reference must be supplied. Got null instead!");
317
        Base::Placement &Place = *placeOfRef;
318
        Base::Vector3d dX,dY,dZ;//internal axes of support object, as they are in global space
319
        Place.getRotation().multVec(Base::Vector3d(1,0,0),dX);
320
        Place.getRotation().multVec(Base::Vector3d(0,1,0),dY);
321
        Place.getRotation().multVec(Base::Vector3d(0,0,1),dZ);
322
        gp_Dir dirX(dX.x, dX.y, dX.z);
323
        gp_Dir dirY(dY.x, dY.y, dY.z);
324
        gp_Dir dirZ(dZ.x, dZ.y, dZ.z);
325
        double cosNX = ZAxis.Dot(dirX);
326
        double cosNY = ZAxis.Dot(dirY);
327
        double cosNZ = ZAxis.Dot(dirZ);
328
        std::vector<double> cosXYZ;
329
        cosXYZ.push_back(fabs(cosNX));
330
        cosXYZ.push_back(fabs(cosNY));
331
        cosXYZ.push_back(fabs(cosNZ));
332

333
        int pos = std::max_element(cosXYZ.begin(), cosXYZ.end()) - cosXYZ.begin();
334

335
        // +X/-X
336
        if (pos == 0) {
337
            if (cosNX > 0)
338
                ax3 = gp_Ax3(Origin, ZAxis, dirY);
339
            else
340
                ax3 = gp_Ax3(Origin, ZAxis, -dirY);
341
        }
342
        // +Y/-Y
343
        else if (pos == 1) {
344
            if (cosNY > 0)
345
                ax3 = gp_Ax3(Origin, ZAxis, -dirX);
346
            else
347
                ax3 = gp_Ax3(Origin, ZAxis, dirX);
348
        }
349
        // +Z/-Z
350
        else {
351
            ax3 = gp_Ax3(Origin, ZAxis, dirX);
352
        }
353
    }
354

355
    if(this->mapReverse){
356
        ax3.ZReverse();
357
        ax3.XReverse();
358
    }
359

360
    //convert ax3 into Base::Placement
361
    gp_Trsf Trf;
362
    Trf.SetTransformation(ax3);
363
    Trf.Invert();
364
    Trf.SetScaleFactor(Standard_Real(1.0));
365

366
    Base::Matrix4D mtrx;
367
    TopoShape::convertToMatrix(Trf,mtrx);
368

369
    return Base::Placement(mtrx);
370

371
}
372

373
void AttachEngine::suggestMapModes(SuggestResult &result) const
374
{
375
    std::vector<eMapMode> &mlist = result.allApplicableModes;
376
    mlist.clear();
377
    mlist.reserve(mmDummy_NumberOfModes);
378

379
    std::set<eRefType> &hints = result.nextRefTypeHint;
380
    hints.clear();
381

382
    std::map<eMapMode,refTypeStringList> &mlist_reachable = result.reachableModes;
383
    mlist_reachable.clear();
384

385
    result.message = SuggestResult::srLinkBroken;
386
    result.bestFitMode = mmDeactivated;
387

388

389
    std::vector<App::GeoFeature*> parts;
390
    std::vector<const TopoDS_Shape*> shapes;
391
    std::vector<TopoDS_Shape> shapeStorage;
392
    std::vector<eRefType> typeStr;
393
    try{
394
        readLinks(getRefObjects(),subnames, parts, shapes, shapeStorage, typeStr);
395
    } catch (Base::Exception &err) {
396
        result.references_Types = typeStr;
397
        result.message = SuggestResult::srLinkBroken;
398
        result.error.Exception::operator = (err);
399
        return;
400
    }
401

402
    result.references_Types = typeStr;
403

404
    //search valid modes.
405
    int bestMatchScore = -1;
406
    result.message = SuggestResult::srNoModesFit;
407
    for (std::size_t iMode = 0; iMode < this->modeRefTypes.size(); ++iMode) {
408
        if (! this->modeEnabled[iMode])
409
            continue;
410
        const refTypeStringList &listStrings = modeRefTypes[iMode];
411
        for (const auto & str : listStrings) {
412
            int score = 1; //-1 = topo incompatible, 0 = topo compatible, geom incompatible; 1+ = compatible (the higher - the more specific is the mode for the support)
413
            for (std::size_t iChr = 0; iChr < str.size() && iChr < typeStr.size(); ++iChr) {
414
                int match = AttachEngine::isShapeOfType(typeStr[iChr], str[iChr]);
415
                switch(match){
416
                    case -1:
417
                        score = -1;
418
                        break;
419
                    case 0:
420
                        score = 0;
421
                        break;
422
                    case 1:
423
                        //keep score
424
                        break;
425
                    default: //2 and above
426
                        if (score > 0)
427
                        score += match;
428
                break;
429
                }
430
            }
431

432
            if (score > 0  &&  str.size() > typeStr.size()){
433
                //mode does not fit, but adding more references will make this mode fit.
434
                hints.insert(str[typeStr.size()]);
435

436
                //build string of references to be added to fit this mode
437
                refTypeString extraRefs;
438
                extraRefs.resize(str.size() - typeStr.size());
439
                for (std::size_t iChr = typeStr.size(); iChr < str.size(); iChr++) {
440
                    extraRefs[iChr - typeStr.size()] = str[iChr];
441
                }
442

443
                //add reachable mode
444
                auto it_r = mlist_reachable.find(eMapMode(iMode));
445
                if (it_r == mlist_reachable.end()){
446
                    it_r = mlist_reachable.insert(std::pair<eMapMode,refTypeStringList>(eMapMode(iMode),refTypeStringList())).first;
447
                }
448
                refTypeStringList &list = it_r->second;
449
                list.push_back(extraRefs);
450
            }
451

452
            //size check is last, because we needed to collect hints
453
            if (str.size() != typeStr.size())
454
                score = -1;
455

456
            if (score > -1){//still output a best match, even if it is not completely compatible
457
                if (score > bestMatchScore){
458
                    bestMatchScore = score;
459
                    result.bestFitMode = eMapMode(iMode);
460
                    result.message = score > 0 ? SuggestResult::srOK : SuggestResult::srIncompatibleGeometry;
461
                }
462
            }
463
            if (score > 0){
464
                if(mlist.empty())
465
                    mlist.push_back(eMapMode(iMode));
466
                else if (mlist.back() != eMapMode(iMode))
467
                    mlist.push_back(eMapMode(iMode));
468
            }
469
        }
470
    }
471

472
}
473

474
void AttachEngine::EnableAllSupportedModes()
475
{
476
    this->modeEnabled.resize(mmDummy_NumberOfModes,false);
477
    assert(modeRefTypes.size() > 0);
478
    for (std::size_t i = 0; i < this->modeEnabled.size(); i++) {
479
        modeEnabled[i] = !modeRefTypes[i].empty();
480
    }
481
}
482

483
eRefType AttachEngine::getShapeType(const TopoDS_Shape& sh)
484
{
485
    if(sh.IsNull())
486
        return rtAnything;
487

488
    switch (sh.ShapeType()){
489
    case TopAbs_SHAPE:
490
        return rtAnything; //note: there's no rtPart detection here - not enough data!
491
    break;
492
    case TopAbs_SOLID:
493
        return rtSolid;
494
    break;
495
    case TopAbs_COMPOUND:{
496
        const TopoDS_Compound &cmpd = TopoDS::Compound(sh);
497
        TopoDS_Iterator it (cmpd, Standard_False, Standard_False);//don't mess with placements, to hopefully increase speed
498
        if (! it.More())//empty compound
499
            return rtAnything;
500
        const TopoDS_Shape &sh1 = it.Value();
501
        it.Next();
502
        if (it.More()){
503
            //more than one object, a true compound
504
            return rtAnything;
505
        } else {
506
            //just one object, let's take a look inside
507
            return getShapeType(sh1);
508
        }
509
    }break;
510
    case TopAbs_COMPSOLID:
511
    case TopAbs_SHELL:
512
        return rtAnything;
513
    break;
514
    case TopAbs_FACE:{
515
        const TopoDS_Face &f = TopoDS::Face(sh);
516
        BRepAdaptor_Surface surf(f, /*restriction=*/Standard_False);
517
        switch(surf.GetType()) {
518
        case GeomAbs_Plane:
519
            return rtFlatFace;
520
        case GeomAbs_Cylinder:
521
            return rtCylindricalFace;
522
        case GeomAbs_Cone:
523
            return rtConicalFace;
524
        case GeomAbs_Sphere:
525
            return rtSphericalFace;
526
        case GeomAbs_Torus:
527
            return rtToroidalFace;
528
        case GeomAbs_BezierSurface:
529
            break;
530
        case GeomAbs_BSplineSurface:
531
            break;
532
        case GeomAbs_SurfaceOfRevolution:
533
            return rtSurfaceRev;
534
        case GeomAbs_SurfaceOfExtrusion:
535
            break;
536
        case GeomAbs_OffsetSurface:
537
            break;
538
        case GeomAbs_OtherSurface:
539
            break;
540
        }
541
        return rtFace;
542
    }break;
543
    case TopAbs_EDGE:{
544
        const TopoDS_Edge &e = TopoDS::Edge(sh);
545
        BRepAdaptor_Curve crv(e);
546
        switch (crv.GetType()){
547
        case GeomAbs_Line:
548
            return rtLine;
549
        case GeomAbs_Circle:
550
            return rtCircle;
551
        case GeomAbs_Ellipse:
552
            return rtEllipse;
553
        case GeomAbs_Hyperbola:
554
            return rtHyperbola;
555
        case GeomAbs_Parabola:
556
            return rtParabola;
557
        case GeomAbs_BezierCurve:
558
        case GeomAbs_BSplineCurve:
559
        case GeomAbs_OtherCurve:
560
        case GeomAbs_OffsetCurve:
561
        return rtCurve;
562
        }
563
    }break;
564
    case TopAbs_WIRE:
565
        return rtWire;
566
    case TopAbs_VERTEX:
567
        return rtVertex;
568
    default:
569
        throw AttachEngineException("AttachEngine::getShapeType: unexpected TopoDS_Shape::ShapeType");
570
    }//switch shapetype
571
    return rtAnything;//shouldn't happen, it's here to shut up compiler warning
572
}
573

574
eRefType AttachEngine::getShapeType(const App::DocumentObject *obj, const std::string &subshape)
575
{
576
    App::PropertyLinkSubList tmpLink;
577
    //const_cast is worth here, to keep obj argument const. We are not going to write anything to obj through this temporary link.
578
    tmpLink.setValue(const_cast<App::DocumentObject*>(obj), subshape.c_str());
579

580
    std::vector<App::GeoFeature*> parts;
581
    std::vector<const TopoDS_Shape*> shapes;
582
    std::vector<TopoDS_Shape> copiedShapeStorage;
583
    std::vector<eRefType> types;
584
    readLinks(tmpLink.getValues(),tmpLink.getSubValues(), parts, shapes, copiedShapeStorage, types);
585

586
    assert(types.size() == 1);
587
    return types[0];
588
}
589

590
eRefType AttachEngine::downgradeType(eRefType type)
591
{
592
    //get rid of hasplacement flags, to simplify the rest
593
    type = eRefType(type & (rtFlagHasPlacement - 1));
594
    //FIXME: reintroduce the flag when returning a value.
595

596
    switch(type){
597
    case rtVertex:
598
    case rtEdge:
599
    case rtFace:
600
        return rtAnything;
601
    case rtAnything:
602
        return rtAnything;
603
    case rtLine:
604
    case rtCurve:
605
        return rtEdge;
606
    case rtConic:
607
    case rtCircle:
608
        return rtCurve;
609
    case rtEllipse:
610
    case rtParabola:
611
    case rtHyperbola:
612
        return rtConic;
613
    case rtFlatFace:
614
    case rtSphericalFace:
615
    case rtSurfaceRev:
616
        return rtFace;
617
    case rtCylindricalFace:
618
    case rtToroidalFace:
619
    case rtConicalFace:
620
        return rtSurfaceRev;
621
    case rtSolid:
622
    case rtWire:
623
        return rtPart;
624
    case rtPart:
625
        return rtAnything;
626
    default:
627
        throw AttachEngineException("AttachEngine::downgradeType: unknown type");
628
    }
629
}
630

631
int AttachEngine::getTypeRank(eRefType type)
632
{
633
    //get rid of hasplacement flags, to simplify the rest
634
    type = eRefType(type & (rtFlagHasPlacement - 1));
635

636
    int rank = 0;
637
    while (type != rtAnything) {
638
        type = downgradeType(type);
639
        rank++;
640
        assert(rank<8);//downgrading never yields rtAnything, something's wrong with downgrader.
641
    }
642
    return rank;
643
}
644

645
int AttachEngine::isShapeOfType(eRefType shapeType, eRefType requirement)
646
{
647
    //first up, check for hasplacement flag
648
    if (requirement & rtFlagHasPlacement) {
649
        if(! (shapeType & rtFlagHasPlacement))
650
            return -1;
651
    }
652

653
    //get rid of hasplacement flags, to simplify the rest
654
    shapeType = eRefType(shapeType & (rtFlagHasPlacement - 1));
655
    requirement = eRefType(requirement & (rtFlagHasPlacement - 1));
656

657
    if (requirement == rtAnything)
658
        return 1;
659

660
    int reqRank = getTypeRank(requirement);
661

662
    //test for valid match
663
    eRefType shDeg = shapeType;
664
    while(shDeg != rtAnything){
665
        if (shDeg == requirement)
666
            return reqRank;
667
        shDeg = downgradeType(shDeg);
668
    }
669

670
    //test for slightly invalid match (e.g. requirement==line, shapeType == curve)
671
    requirement = downgradeType(requirement);
672
    if (requirement != rtAnything) {
673
        eRefType shDeg = shapeType;
674
        while(shDeg != rtAnything){
675
            if (shDeg == requirement)
676
                return 0;
677
            shDeg = downgradeType(shDeg);
678
        }
679
    }
680

681
    //complete mismatch!
682
    return -1;
683
}
684

685
std::string AttachEngine::getModeName(eMapMode mmode)
686
{
687
    if(mmode < 0 || mmode >= mmDummy_NumberOfModes)
688
        throw AttachEngineException("AttachEngine::getModeName: Attachment Mode index is out of range");
689
    return {AttachEngine::eMapModeStrings[mmode]};
690
}
691

692
eMapMode AttachEngine::getModeByName(const std::string &modeName)
693
{
694
    for (int mmode = 0   ;   mmode < mmDummy_NumberOfModes   ;   mmode++){
695
        if (strcmp(eMapModeStrings[mmode],modeName.c_str())==0) {
696
            return eMapMode(mmode);
697
        }
698
    }
699
    std::stringstream errMsg;
700
    errMsg << "AttachEngine::getModeByName: mode with this name doesn't exist: " << modeName;
701
    throw AttachEngineException(errMsg.str());
702
}
703

704
std::string AttachEngine::getRefTypeName(eRefType shapeType)
705
{
706
    eRefType flagless = eRefType(shapeType & 0xFF);
707
    if(flagless < 0 || flagless >= rtDummy_numberOfShapeTypes)
708
        throw AttachEngineException("eRefType value is out of range");
709
    std::string result = std::string(eRefTypeStrings[flagless]);
710
    if (shapeType & rtFlagHasPlacement){
711
        result.append("|Placement");
712
    }
713
    return result;
714
}
715

716
eRefType AttachEngine::getRefTypeByName(const std::string& typeName)
717
{
718
    std::string flagless;
719
    std::string flags;
720
    size_t seppos = typeName.find('|');
721
    flagless = typeName.substr(0, seppos);
722
    if(seppos != std::string::npos ){
723
        flags = typeName.substr(seppos+1);
724
    }
725
    for(int irt = 0   ;   irt < rtDummy_numberOfShapeTypes   ;   irt++){
726
        if(strcmp(flagless.c_str(),eRefTypeStrings[irt]) == 0){
727
            if(strcmp("Placement",flags.c_str()) == 0){
728
                return eRefType(irt | rtFlagHasPlacement);
729
            } else if (flags.length() == 0){
730
                return eRefType(irt);
731
            } else {
732
                std::stringstream errmsg;
733
                errmsg << "RefType flag not recognized: " << flags;
734
                throw AttachEngineException(errmsg.str());
735
            }
736
        }
737
    }
738
    std::stringstream errmsg;
739
    errmsg << "RefType not recognized: " << typeName;
740
    throw AttachEngineException(errmsg.str());
741
}
742

743
GProp_GProps AttachEngine::getInertialPropsOfShape(const std::vector<const TopoDS_Shape*> &shapes)
744
{
745
    //explode compounds
746
    TopTools_HSequenceOfShape totalSeq;
747
    for (const TopoDS_Shape* pSh : shapes) {
748
        ShapeExtend_Explorer xp;
749
        totalSeq.Append( xp.SeqFromCompound(*pSh, /*recursive=*/true));
750
    }
751
    if (totalSeq.Length() == 0)
752
        throw AttachEngineException("AttachEngine::getInertialPropsOfShape: no geometry provided");
753
    const TopoDS_Shape &sh0 = totalSeq.Value(1);
754
    switch (sh0.ShapeType()){
755
    case TopAbs_VERTEX:{
756
        GProp_PGProps gpr;
757
        for (int i = 0   ;   i < totalSeq.Length()   ;   i++){
758
            const TopoDS_Shape &sh = totalSeq.Value(i+1);
759
            if (sh.ShapeType() != TopAbs_VERTEX)
760
                throw AttachEngineException("AttachEngine::getInertialPropsOfShape: provided shapes are incompatible (not only vertices)");
761
            gpr.AddPoint(BRep_Tool::Pnt(TopoDS::Vertex(sh)));
762
        }
763
        return gpr;
764
    } break;
765
    case TopAbs_EDGE:
766
    case TopAbs_WIRE:{
767
        GProp_GProps gpr_acc;
768
        GProp_GProps gpr;
769
        for (int i = 0   ;   i < totalSeq.Length()   ;   i++){
770
            const TopoDS_Shape &sh = totalSeq.Value(i+1);
771
            if (sh.ShapeType() != TopAbs_EDGE && sh.ShapeType() != TopAbs_WIRE)
772
                throw AttachEngineException("AttachEngine::getInertialPropsOfShape: provided shapes are incompatible (not only edges/wires)");
773
            if (sh.Infinite())
774
                throw AttachEngineException("AttachEngine::getInertialPropsOfShape: infinite shape provided");
775
            BRepGProp::LinearProperties(sh,gpr);
776
            gpr_acc.Add(gpr);
777
        }
778
        return gpr_acc;
779
    } break;
780
    case TopAbs_FACE:
781
    case TopAbs_SHELL:{
782
        GProp_GProps gpr_acc;
783
        GProp_GProps gpr;
784
        for (int i = 0   ;   i < totalSeq.Length()   ;   i++){
785
            const TopoDS_Shape &sh = totalSeq.Value(i+1);
786
            if (sh.ShapeType() != TopAbs_FACE && sh.ShapeType() != TopAbs_SHELL)
787
                throw AttachEngineException("AttachEngine::getInertialPropsOfShape: provided shapes are incompatible (not only faces/shells)");
788
            if (sh.Infinite())
789
                throw AttachEngineException("AttachEngine::getInertialPropsOfShape: infinite shape provided");
790
            BRepGProp::SurfaceProperties(sh,gpr);
791
            gpr_acc.Add(gpr);
792
        }
793
        return gpr_acc;
794
    } break;
795
    case TopAbs_SOLID:
796
    case TopAbs_COMPSOLID:{
797
        GProp_GProps gpr_acc;
798
        GProp_GProps gpr;
799
        for (int i = 0   ;   i < totalSeq.Length()   ;   i++){
800
            const TopoDS_Shape &sh = totalSeq.Value(i+1);
801
            if (sh.ShapeType() != TopAbs_SOLID && sh.ShapeType() != TopAbs_COMPSOLID)
802
                throw AttachEngineException("AttachEngine::getInertialPropsOfShape: provided shapes are incompatible (not only solids/compsolids)");
803
            if (sh.Infinite())
804
                throw AttachEngineException("AttachEngine::getInertialPropsOfShape: infinite shape provided");
805
            BRepGProp::VolumeProperties(sh,gpr);
806
            gpr_acc.Add(gpr);
807
        }
808
        return gpr_acc;
809
    } break;
810
    default:
811
        throw AttachEngineException("AttachEngine::getInertialPropsOfShape: unexpected shape type");
812
    }
813
}
814

815
/*!
816
 * \brief AttachEngine3D::readLinks
817
 * \param parts
818
 * \param shapes
819
 * \param storage is a buffer storing what some of the pointers in shapes point to. It is needed, since
820
 * subshapes are copied in the process (but copying a whole shape of an object can potentially be slow).
821
 */
822
void AttachEngine::readLinks(const std::vector<App::DocumentObject*> &objs,
823
                             const std::vector<std::string> &sub,
824
                             std::vector<App::GeoFeature*> &geofs,
825
                             std::vector<const TopoDS_Shape*> &shapes,
826
                             std::vector<TopoDS_Shape> &storage,
827
                             std::vector<eRefType> &types)
828
{
829
    geofs.resize(objs.size());
830
    storage.reserve(objs.size());
831
    shapes.resize(objs.size());
832
    types.resize(objs.size());
833
    for (std::size_t i = 0; i < objs.size(); i++) {
834
        if (!objs[i]->getTypeId().isDerivedFrom(App::GeoFeature::getClassTypeId())) {
835
            FC_THROWM(AttachEngineException,
836
                      "AttachEngine3D: attached to a non App::GeoFeature '"
837
                          << objs[i]->getNameInDocument() << "'");
838
        }
839
        auto* geof = dynamic_cast<App::GeoFeature*>(objs[i]);
840
        geofs[i] = geof;
841
        Part::TopoShape shape;
842
        if (geof->isDerivedFrom(App::Plane::getClassTypeId())) {
843
            // obtain Z axis and origin of placement
844
            Base::Vector3d norm;
845
            geof->Placement.getValue().getRotation().multVec(Base::Vector3d(0.0, 0.0, 1.0), norm);
846
            Base::Vector3d org;
847
            geof->Placement.getValue().multVec(Base::Vector3d(), org);
848
            // make shape - an local-XY plane infinite face
849
            gp_Pln plane = gp_Pln(gp_Pnt(org.x, org.y, org.z), gp_Dir(norm.x, norm.y, norm.z));
850
            TopoDS_Shape myShape = BRepBuilderAPI_MakeFace(plane).Shape();
851
            myShape.Infinite(true);
852
            storage.emplace_back(myShape);
853
            shapes[i] = &(storage[storage.size() - 1]);
854
        }
855
        else if (geof->isDerivedFrom(App::Line::getClassTypeId())) {
856
            // obtain X axis and origin of placement
857
            // note an inconsistency: App::Line is along local X, PartDesign::DatumLine is along
858
            // local Z.
859
            Base::Vector3d dir;
860
            geof->Placement.getValue().getRotation().multVec(Base::Vector3d(1.0, 0.0, 0.0), dir);
861
            Base::Vector3d org;
862
            geof->Placement.getValue().multVec(Base::Vector3d(), org);
863
            // make shape - an infinite line along local X axis
864
            gp_Lin line = gp_Lin(gp_Pnt(org.x, org.y, org.z), gp_Dir(dir.x, dir.y, dir.z));
865
            TopoDS_Shape myShape = BRepBuilderAPI_MakeEdge(line).Shape();
866
            myShape.Infinite(true);
867
            storage.emplace_back(myShape);
868
            shapes[i] = &(storage[storage.size() - 1]);
869
        }
870
        else {
871
            try {
872
                shape = Part::Feature::getTopoShape(geof, sub[i].c_str(), true);
873
                for (;;) {
874
                    if (shape.isNull()) {
875
                        FC_THROWM(AttachEngineException,
876
                                  "AttachEngine3D: subshape not found "
877
                                      << objs[i]->getNameInDocument() << '.' << sub[i]);
878
                    }
879
                    if (shape.shapeType() != TopAbs_COMPOUND
880
                        || shape.countSubShapes(TopAbs_SHAPE) != 1) {
881
                        break;
882
                    }
883
                    // auto extract the single sub-shape from a compound
884
                    shape = shape.getSubTopoShape(TopAbs_SHAPE, 1);
885
                }
886
                storage.emplace_back(shape.getShape());
887
            }
888
            catch (Standard_Failure& e) {
889
                FC_THROWM(AttachEngineException,
890
                          "AttachEngine3D: subshape not found " << objs[i]->getNameInDocument()
891
                                                                << '.' << sub[i] << std::endl
892
                                                                << e.GetMessageString());
893
            }
894
            catch (Base::CADKernelError& e) {
895
                FC_THROWM(AttachEngineException,
896
                          "AttachEngine3D: subshape not found " << objs[i]->getNameInDocument()
897
                                                                << '.' << sub[i] << std::endl
898
                                                                << e.what());
899
            }
900
            if (storage.back().IsNull()) {
901
                FC_THROWM(AttachEngineException,
902
                          "AttachEngine3D: null subshape " << objs[i]->getNameInDocument() << '.'
903
                                                           << sub[i]);
904
            }
905
            shapes[i] = &(storage.back());
906
        }
907

908
        // FIXME: unpack single-child compounds here? Compounds are not used so far, so it should be
909
        // considered later, when the need arises.
910
        types[i] = getShapeType(*(shapes[i]));
911
        if (sub[i].length() == 0) {
912
            types[i] = eRefType(types[i] | rtFlagHasPlacement);
913
        }
914
    }
915

916
}
917

918
void AttachEngine::throwWrongMode(eMapMode mmode)
919
{
920
    std::stringstream errmsg;
921
    if (mmode >= 0 && mmode<mmDummy_NumberOfModes) {
922
        if (AttachEngine::eMapModeStrings[mmode]) {
923
            errmsg << "Attachment mode " << AttachEngine::eMapModeStrings[mmode] << " is not implemented." ;
924
        } else {
925
            errmsg << "Attachment mode " << int(mmode) << " is undefined." ;
926
        }
927
    } else {
928
        errmsg << "Attachment mode index (" << int(mmode) << ") is out of range." ;
929
    }
930
    throw Base::ValueError(errmsg.str().c_str());
931
}
932

933
void AttachEngine::verifyReferencesAreSafe(const App::PropertyLinkSubList &references)
934
{
935
    const std::vector<App::DocumentObject*> links =  references.getValues();
936
    const std::vector<App::Document*> docs = App::GetApplication().getDocuments();
937
    for(App::DocumentObject* lnk : links){
938
        bool found = false;
939
        for(App::Document* doc : docs){
940
            if(doc->isIn(lnk)){
941
                found = true;
942
            }
943
        }
944
        if (!found){
945
            throw AttachEngineException("AttachEngine: verifyReferencesAreSafe: references point to deleted object.");
946
        }
947
    }
948
}
949

950
std::vector<App::DocumentObject*> AttachEngine::getRefObjects() const
951
{
952
    std::vector<App::DocumentObject*> objs;
953
    if (objNames.empty()) {
954
        return objs;
955
    }
956
    auto doc = App::GetApplication().getDocument(docName.c_str());
957
    if (!doc) {
958
        FC_THROWM(AttachEngineException, "AttachEngine: document '" << docName << "' not found");
959
    }
960
    objs.reserve(objNames.size());
961
    for (auto& name : objNames) {
962
        objs.push_back(doc->getObject(name.c_str()));
963
        if (!objs.back()) {
964
            FC_THROWM(AttachEngineException,
965
                      "AttachEngine: object '" << docName << "#" << name << "' not found");
966
        }
967
    }
968
    return objs;
969
}
970

971
Base::Placement AttachEngine::calculateAttachedPlacement(const Base::Placement& origPlacement,
972
                                                         bool* subChanged)
973
{
974
    std::map<int, std::pair<std::string, std::string>> subChanges;
975
    int i = -1;
976
    auto objs = getRefObjects();
977
    for (auto obj : objs) {
978
        ++i;
979
        auto& sub = subnames[i];
980
        auto& shadow = shadowSubs[i];
981
        if (shadow.empty() || !Data::hasMissingElement(sub.c_str())) {
982
            continue;
983
        }
984
        auto related = Part::Feature::getRelatedElements(obj,
985
                                                         shadow.c_str(),
986
                                                         Part::HistoryTraceType::followTypeChange,
987
                                                         false);
988
        if (!related.empty()) {
989
            auto& res = subChanges[i];
990
            res.first = Data::ComplexGeoData::elementMapPrefix();
991
            related.front().name.appendToBuffer(res.first);
992
            res.second.clear();
993
            related.front().index.appendToStringBuffer(res.second);
994
        }
995
        else {
996
            std::string name = Data::oldElementName(shadow.c_str());
997
            if (!name.empty()) {
998
                auto& res = subChanges[i];
999
                res.first.clear();
1000
                res.second = name;
1001
            }
1002
            else {
1003
                subnames[i] = shadow;
1004
            }
1005
        }
1006
    }
1007
    if (!subChanges.empty()) {
1008
        // In case there is topological name changes, we only auto change the
1009
        // subname if the calculated placement stays the same. If not, just
1010
        // proceed as normal, which will throw exception and catch user's
1011
        // attention.
1012
        auto subs = subnames;
1013
        for (auto& change : subChanges) {
1014
            auto [subkey, namechange] =change;
1015
            auto [_oldname, newname] = namechange;
1016
            subs[subkey] = newname;
1017
        }
1018
        auto pla = _calculateAttachedPlacement(objs, subs, origPlacement);
1019
        // check equal placement with some tolerance
1020
        if (pla.getPosition().IsEqual(origPlacement.getPosition(),    Precision::Confusion())
1021
            && pla.getRotation().isSame(origPlacement.getRotation(), Precision::Angular())) {
1022
            // Only make changes if the caller supplies 'subChanged', because
1023
            // otherwise it means the caller just want to do an immutable test.
1024
            // See AttachExtension::isAttacherActive().
1025
            if (subChanged) {
1026
                *subChanged = true;
1027
                subnames = std::move(subs);
1028
                for (auto& v : subChanges) {
1029
                    shadowSubs[v.first] = v.second.first;
1030
                }
1031
            }
1032
            return pla;
1033
        }
1034
    }
1035
    return _calculateAttachedPlacement(objs, subnames, origPlacement);
1036
}
1037

1038

1039
//=================================================================================
1040

1041

1042
TYPESYSTEM_SOURCE(Attacher::AttachEngine3D, Attacher::AttachEngine)
1043

1044
AttachEngine3D::AttachEngine3D()
1045
{
1046
    //fill type lists for modes
1047
    modeRefTypes.resize(mmDummy_NumberOfModes);
1048
    refTypeString s;
1049
    refTypeStringList ss;
1050

1051
    modeRefTypes[mmTranslate].push_back(cat(rtVertex));
1052

1053
    ss.clear();
1054
    ss.push_back(cat(eRefType(rtAnything | rtFlagHasPlacement)));
1055
    ss.push_back(cat(rtConic));
1056
    modeRefTypes[mmObjectXY] = ss;
1057
    modeRefTypes[mmObjectXZ] = ss;
1058
    modeRefTypes[mmObjectYZ] = ss;
1059

1060
    modeRefTypes[mmParallelPlane].push_back(
1061
        cat(eRefType(rtFlatFace | rtFlagHasPlacement), rtVertex));
1062
    modeRefTypes[mmParallelPlane].push_back(
1063
        cat(eRefType(rtAnything | rtFlagHasPlacement), rtVertex));
1064

1065
    modeRefTypes[mmInertialCS].push_back(cat(rtAnything));
1066
    modeRefTypes[mmInertialCS].push_back(cat(rtAnything,rtAnything));
1067
    modeRefTypes[mmInertialCS].push_back(cat(rtAnything,rtAnything,rtAnything));
1068
    modeRefTypes[mmInertialCS].push_back(cat(rtAnything,rtAnything,rtAnything,rtAnything));
1069

1070
    modeRefTypes[mmFlatFace].push_back(cat(rtFlatFace));
1071

1072
    modeRefTypes[mmTangentPlane].push_back(cat(rtFace, rtVertex));
1073
    modeRefTypes[mmTangentPlane].push_back(cat(rtVertex, rtFace));
1074

1075
    //---------Edge-driven
1076

1077
    s=cat(rtEdge);
1078
    modeRefTypes[mmNormalToPath].push_back(s);
1079

1080
    s = cat(rtCurve);
1081
    modeRefTypes[mmFrenetNB].push_back(s);
1082
    modeRefTypes[mmFrenetTN].push_back(s);
1083
    modeRefTypes[mmFrenetTB].push_back(s);
1084
    modeRefTypes[mmRevolutionSection].push_back(s);
1085
    modeRefTypes[mmConcentric].push_back(s);
1086
    s = cat(rtCircle);
1087
    modeRefTypes[mmRevolutionSection].push_back(s);//for this mode to get best score on circles
1088
    modeRefTypes[mmConcentric].push_back(s);
1089

1090
    //-----------Edge-driven at vertex
1091

1092
    s=cat(rtEdge, rtVertex);
1093
    modeRefTypes[mmNormalToPath].push_back(s);
1094
    s=cat(rtVertex, rtEdge);
1095
    modeRefTypes[mmNormalToPath].push_back(s);
1096

1097
    s=cat(rtCurve, rtVertex);
1098
    modeRefTypes[mmFrenetNB].push_back(s);
1099
    modeRefTypes[mmFrenetTN].push_back(s);
1100
    modeRefTypes[mmFrenetTB].push_back(s);
1101
    modeRefTypes[mmRevolutionSection].push_back(s);
1102
    modeRefTypes[mmConcentric].push_back(s);
1103
    s = cat(rtCircle, rtVertex);
1104
    modeRefTypes[mmRevolutionSection].push_back(s);//for this mode to get best score on circles
1105
    modeRefTypes[mmConcentric].push_back(s);
1106

1107
    s=cat(rtVertex, rtCurve);
1108
    modeRefTypes[mmFrenetNB].push_back(s);
1109
    modeRefTypes[mmFrenetTN].push_back(s);
1110
    modeRefTypes[mmFrenetTB].push_back(s);
1111
    modeRefTypes[mmRevolutionSection].push_back(s);
1112
    modeRefTypes[mmConcentric].push_back(s);
1113
    s = cat(rtVertex, rtCircle);
1114
    modeRefTypes[mmRevolutionSection].push_back(s);//for this mode to get best score on circles
1115
    modeRefTypes[mmConcentric].push_back(s);
1116

1117
    //------------ThreePoints
1118

1119
    s = cat(rtVertex, rtVertex, rtVertex);
1120
    modeRefTypes[mmThreePointsPlane].push_back(s);
1121
    modeRefTypes[mmThreePointsNormal].push_back(s);
1122

1123
    s = cat(rtLine, rtVertex);
1124
    modeRefTypes[mmThreePointsPlane].push_back(s);
1125
    modeRefTypes[mmThreePointsNormal].push_back(s);
1126

1127
    s = cat(rtVertex, rtLine);
1128
    modeRefTypes[mmThreePointsPlane].push_back(s);
1129
    modeRefTypes[mmThreePointsNormal].push_back(s);
1130

1131
    s = cat(rtLine, rtLine);
1132
    modeRefTypes[mmThreePointsPlane].push_back(s);
1133
    modeRefTypes[mmThreePointsNormal].push_back(s);
1134

1135
    //------------origin-axis-axis modes
1136
    for (int mmode = mmOZX; mmode <= mmOYX; ++mmode){
1137
        modeRefTypes[mmode].push_back(cat(rtVertex, rtVertex, rtVertex));
1138
        modeRefTypes[mmode].push_back(cat(rtVertex, rtVertex, rtLine));
1139
        modeRefTypes[mmode].push_back(cat(rtVertex, rtLine, rtVertex));
1140
        modeRefTypes[mmode].push_back(cat(rtVertex, rtLine, rtLine));
1141
        modeRefTypes[mmode].push_back(cat(rtVertex, rtVertex));
1142
        modeRefTypes[mmode].push_back(cat(rtVertex, rtLine));
1143
    }
1144

1145

1146
    modeRefTypes[mmFolding].push_back(cat(rtLine, rtLine, rtLine, rtLine));
1147

1148
    this->EnableAllSupportedModes();
1149
}
1150

1151
AttachEngine3D* AttachEngine3D::copy() const
1152
{
1153
    AttachEngine3D* p = new AttachEngine3D;
1154
    p->setUp(*this);
1155
    return p;
1156
}
1157

1158
Base::Placement
1159
AttachEngine3D::_calculateAttachedPlacement(const std::vector<App::DocumentObject*>& objs,
1160
                                            const std::vector<std::string>& subs,
1161
                                            const Base::Placement& origPlacement) const
1162
{
1163
    const eMapMode mmode = this->mapMode;
1164
    if (mmode == mmDeactivated) {
1165
        throw ExceptionCancel();  // to be handled in positionBySupport, to not do anything if
1166
                                  // disabled
1167
    }
1168
    std::vector<App::GeoFeature*> parts;
1169
    std::vector<const TopoDS_Shape*> shapes;
1170
    std::vector<TopoDS_Shape> copiedShapeStorage;
1171
    std::vector<eRefType> types;
1172
    readLinks(objs, subs, parts, shapes, copiedShapeStorage, types);
1173

1174
    if (parts.empty()) {
1175
        throw ExceptionCancel();
1176
    }
1177

1178
    // common stuff for all map modes
1179
    gp_Pnt refOrg(0.0, 0.0, 0.0);  // origin of linked object
1180
    Base::Placement Place = parts[0]->Placement.getValue();
1181
    refOrg = gp_Pnt(Place.getPosition().x, Place.getPosition().y, Place.getPosition().z);
1182

1183
    // variables to derive the actual placement.
1184
    // They are to be set, depending on the mode:
1185
    // to the sketch
1186
    gp_Dir SketchNormal;     // points at the user
1187
    gp_Vec SketchXAxis;      // if left zero, a guess will be made
1188
    gp_Pnt SketchBasePoint;  // where to put the origin of the sketch
1189

1190

1191
    switch (mmode) {
1192
        case mmDeactivated:
1193
            // should have been filtered out already!
1194
            break;
1195
        case mmTranslate: {
1196
            if (shapes.empty()) {
1197
                throw Base::ValueError("AttachEngine3D::calculateAttachedPlacement: no subobjects "
1198
                                       "specified (need one vertex).");
1199
            }
1200
            const TopoDS_Shape& sh = *shapes[0];
1201
            if (sh.IsNull()) {
1202
                throw Base::ValueError(
1203
                    "Null shape in AttachEngine3D::calculateAttachedPlacement()!");
1204
            }
1205
            if (sh.ShapeType() != TopAbs_VERTEX) {
1206
                throw Base::ValueError("AttachEngine3D::calculateAttachedPlacement: no subobjects "
1207
                                       "specified (need one vertex).");
1208
            }
1209
            gp_Pnt p = BRep_Tool::Pnt(TopoDS::Vertex(sh));
1210
            Base::Placement plm = Base::Placement();
1211
            plm.setPosition(Base::Vector3d(p.X(), p.Y(), p.Z()));
1212
            plm.setPosition(plm.getPosition() + this->attachmentOffset.getPosition());
1213
            plm.setRotation(origPlacement.getRotation());
1214
            return plm;
1215
        } break;
1216
        case mmObjectXY:
1217
        case mmObjectXZ:
1218
        case mmObjectYZ:
1219
        case mmParallelPlane: {
1220
            // DeepSOIC: could have been done much more efficiently, but I'm lazy...
1221
            gp_Dir dirX, dirY, dirZ;
1222
            if (types[0] & rtFlagHasPlacement) {
1223
                Base::Vector3d dX, dY,
1224
                    dZ;  // internal axes of support object, as they are in global space
1225
                Place.getRotation().multVec(Base::Vector3d(1, 0, 0), dX);
1226
                Place.getRotation().multVec(Base::Vector3d(0, 1, 0), dY);
1227
                Place.getRotation().multVec(Base::Vector3d(0, 0, 1), dZ);
1228
                dirX = gp_Dir(dX.x, dX.y, dX.z);
1229
                dirY = gp_Dir(dY.x, dY.y, dY.z);
1230
                dirZ = gp_Dir(dZ.x, dZ.y, dZ.z);
1231
                SketchBasePoint =
1232
                    gp_Pnt(Place.getPosition().x, Place.getPosition().y, Place.getPosition().z);
1233
            }
1234
            else if (isShapeOfType(types[0], rtConic) > 0) {
1235
                const TopoDS_Edge& e = TopoDS::Edge(*shapes[0]);
1236
                BRepAdaptor_Curve adapt(e);
1237
                gp_Ax3 pos;
1238
                switch (adapt.GetType()) {
1239
                    case GeomAbs_Ellipse: {
1240
                        gp_Elips cc = adapt.Ellipse();
1241
                        pos = gp_Ax3(cc.Position());
1242
                    } break;
1243
                    case GeomAbs_Hyperbola: {
1244
                        gp_Hypr cc = adapt.Hyperbola();
1245
                        pos = gp_Ax3(cc.Position());
1246
                    } break;
1247
                    case GeomAbs_Parabola: {
1248
                        gp_Parab cc = adapt.Parabola();
1249
                        pos = gp_Ax3(cc.Position());
1250
                    } break;
1251
                    default:
1252
                        assert(0);  // conics should have been filtered out by testing shape type in
1253
                                    // the above if.
1254
                }
1255
                dirX = pos.XDirection();
1256
                dirY = pos.YDirection();
1257
                dirZ = pos.Axis().Direction();
1258
                SketchBasePoint = pos.Location();
1259
            }
1260
            else {
1261
                throw Base::ValueError(
1262
                    "AttachEngine3D::calculateAttachedPlacement: need either a conic section edge, "
1263
                    "or a whole object for ObjectXY-like modes.");
1264
            }
1265

1266
            switch (mmode) {
1267
                case mmObjectXY:
1268
                    SketchNormal = dirZ;
1269
                    SketchXAxis = gp_Vec(dirX);
1270
                    break;
1271
                case mmObjectXZ:
1272
                    SketchNormal = dirY.Reversed();
1273
                    SketchXAxis = gp_Vec(dirX);
1274
                    break;
1275
                case mmObjectYZ:
1276
                    SketchNormal = dirX;
1277
                    SketchXAxis = gp_Vec(dirY);
1278
                    break;
1279
                case mmParallelPlane: {
1280
                    if (shapes.size() < 2) {
1281
                        throw Base::ValueError("AttachEngine3D::calculateAttachedPlacement: not "
1282
                                               "enough subshapes (need one plane and one vertex).");
1283
                    }
1284

1285
                    TopoDS_Vertex vertex;
1286
                    try {
1287
                        vertex = TopoDS::Vertex(*(shapes[1]));
1288
                    }
1289
                    catch (...) {
1290
                    }
1291
                    if (vertex.IsNull()) {
1292
                        throw Base::ValueError(
1293
                            "Null vertex in AttachEngine3D::calculateAttachedPlacement()!");
1294
                    }
1295

1296
                    SketchNormal = dirZ;
1297
                    SketchXAxis = gp_Vec(dirX);
1298

1299
                    // The new origin will be the vertex projected onto the normal.
1300
                    Handle(Geom_Line) hCurve(new Geom_Line(SketchBasePoint, dirZ));
1301
                    gp_Pnt p = BRep_Tool::Pnt(vertex);
1302
                    GeomAPI_ProjectPointOnCurve projector(p, hCurve);
1303
                    SketchBasePoint = projector.NearestPoint();
1304
                } break;
1305
                default:
1306
                    break;
1307
            }
1308

1309
        } break;
1310
        case mmInertialCS: {
1311
            GProp_GProps gpr = AttachEngine::getInertialPropsOfShape(shapes);
1312
            GProp_PrincipalProps pr = gpr.PrincipalProperties();
1313
            if (pr.HasSymmetryPoint()) {
1314
                throw Base::ValueError("AttachEngine3D::calculateAttachedPlacement:InertialCS: "
1315
                                       "inertia tensor is trivial, principal axes are undefined.");
1316
            }
1317
            if (pr.HasSymmetryAxis()) {
1318
                Base::Console().Warning(
1319
                    "AttachEngine3D::calculateAttachedPlacement:InertialCS: inertia tensor has "
1320
                    "axis of symmetry. Second and third axes of inertia are undefined.\n");
1321
                // find defined axis, and use it as Z axis
1322
                // situation: we have two moments that are almost equal, and one
1323
                // that is substantially different. The one that is different
1324
                // corresponds to a defined axis. We'll identify the different one by
1325
                // comparing differences.
1326
                Standard_Real I1, I2, I3;
1327
                pr.Moments(I1, I2, I3);
1328
                Standard_Real d12, d23, d31;
1329
                d12 = fabs(I1 - I2);
1330
                d23 = fabs(I2 - I3);
1331
                d31 = fabs(I3 - I1);
1332
                if (d12 < d23 && d12 < d31) {
1333
                    SketchNormal = pr.ThirdAxisOfInertia();
1334
                }
1335
                else if (d23 < d31 && d23 < d12) {
1336
                    SketchNormal = pr.FirstAxisOfInertia();
1337
                }
1338
                else {
1339
                    SketchNormal = pr.SecondAxisOfInertia();
1340
                }
1341
            }
1342
            else {
1343
                SketchNormal = pr.FirstAxisOfInertia();
1344
                SketchXAxis = pr.SecondAxisOfInertia();
1345
            }
1346
            SketchBasePoint = gpr.CentreOfMass();
1347
        } break;
1348
        case mmFlatFace: {
1349
            if (shapes.empty()) {
1350
                throw Base::ValueError("AttachEngine3D::calculateAttachedPlacement: no subobjects "
1351
                                       "specified (needed one planar face).");
1352
            }
1353

1354
            TopoDS_Face face;
1355
            gp_Pln plane;
1356
            bool Reverse = false;
1357
            try {
1358
                face = TopoDS::Face(*(shapes[0]));
1359
            }
1360
            catch (...) {
1361
            }
1362
            if (face.IsNull()) {
1363
                if (!TopoShape(*shapes[0]).findPlane(plane)) {
1364
                    throw Base::ValueError(
1365
                        "No planar face in AttachEngine3D::calculateAttachedPlacement()!");
1366
                }
1367
            }
1368
            else {
1369
                BRepAdaptor_Surface adapt(face);
1370
                if (adapt.GetType() == GeomAbs_Plane) {
1371
                    plane = adapt.Plane();
1372
                }
1373
                else {
1374
                    TopLoc_Location loc;
1375
                    Handle(Geom_Surface) surf = BRep_Tool::Surface(face, loc);
1376
                    GeomLib_IsPlanarSurface check(surf);
1377
                    if (check.IsPlanar()) {
1378
                        plane = check.Plan();
1379
                    }
1380
                    else {
1381
                        throw Base::ValueError(
1382
                            "No planar face in AttachEngine3D::calculateAttachedPlacement()!");
1383
                    }
1384
                }
1385

1386
                if (face.Orientation() == TopAbs_REVERSED) {
1387
                    Reverse = true;
1388
                }
1389
            }
1390

1391
            Standard_Boolean ok = plane.Direct();
1392
            if (!ok) {
1393
                // toggle if plane has a left-handed coordinate system
1394
                plane.UReverse();
1395
                Reverse = !Reverse;
1396
            }
1397
            gp_Ax1 Normal = plane.Axis();
1398
            if (Reverse) {
1399
                Normal.Reverse();
1400
            }
1401
            SketchNormal = Normal.Direction();
1402

1403
            Handle(Geom_Plane) gPlane = new Geom_Plane(plane);
1404
            GeomAPI_ProjectPointOnSurf projector(refOrg, gPlane);
1405
            SketchBasePoint = projector.NearestPoint();
1406

1407
        } break;
1408
        case mmTangentPlane: {
1409
            if (shapes.size() < 2) {
1410
                throw Base::ValueError("AttachEngine3D::calculateAttachedPlacement: not enough "
1411
                                       "subshapes (need one face and one vertex).");
1412
            }
1413

1414
            bool bThruVertex = false;
1415
            if (shapes[0]->ShapeType() == TopAbs_VERTEX) {
1416
                std::swap(shapes[0], shapes[1]);
1417
                bThruVertex = true;
1418
            }
1419

1420
            TopoDS_Face face;
1421
            try {
1422
                face = TopoDS::Face(*(shapes[0]));
1423
            }
1424
            catch (...) {
1425
            }
1426
            if (face.IsNull()) {
1427
                throw Base::ValueError(
1428
                    "Null face in AttachEngine3D::calculateAttachedPlacement()!");
1429
            }
1430

1431
            TopoDS_Vertex vertex;
1432
            try {
1433
                vertex = TopoDS::Vertex(*(shapes[1]));
1434
            }
1435
            catch (...) {
1436
            }
1437
            if (vertex.IsNull()) {
1438
                throw Base::ValueError(
1439
                    "Null vertex in AttachEngine3D::calculateAttachedPlacement()!");
1440
            }
1441

1442
            Handle(Geom_Surface) hSurf = BRep_Tool::Surface(face);
1443
            gp_Pnt p = BRep_Tool::Pnt(vertex);
1444

1445
            GeomAPI_ProjectPointOnSurf projector(p, hSurf);
1446
            double u, v;
1447
            if (projector.NbPoints() == 0) {
1448
                throw Base::ValueError("AttachEngine3D::calculateAttachedPlacement: projecting "
1449
                                       "point onto surface failed.");
1450
            }
1451

1452
            projector.LowerDistanceParameters(u, v);
1453
            BRepAdaptor_Surface surf(face);
1454
            BRepLProp_SLProps prop(surf, u, v, 1, Precision::Confusion());
1455
            gp_Dir dirX;
1456
            Standard_Boolean done;
1457

1458
            Tools::getNormal(face, u, v, Precision::Confusion(), SketchNormal, done);
1459

1460
            if (!done) {
1461
                throw Base::ValueError("AttachEngine3D::calculateAttachedPlacement: finding normal "
1462
                                       "to surface at projected point failed.");
1463
            }
1464

1465
            // if getNormal succeeds, at least one of the tangent is defined
1466
            if (prop.IsTangentUDefined()) {
1467
                prop.TangentU(dirX);
1468
                if (face.Orientation() == TopAbs_REVERSED) {
1469
                    dirX.Reverse();
1470
                }
1471
            }
1472
            // here the orientation of dirX is managed by SketchNormal orientation
1473
            else {
1474
                gp_Dir dirY;
1475
                prop.TangentV(dirY);
1476
                dirX = dirY.Crossed(SketchNormal);
1477
            }
1478

1479
            SketchXAxis = gp_Vec(dirX).Reversed();  // yields upside-down sketches less often.
1480

1481
            if (bThruVertex) {
1482
                SketchBasePoint = p;
1483
            }
1484
            else {
1485
                SketchBasePoint = projector.NearestPoint();
1486
            }
1487
        } break;
1488
        case mmNormalToPath:
1489
        case mmFrenetNB:
1490
        case mmFrenetTN:
1491
        case mmFrenetTB:
1492
        case mmRevolutionSection:
1493
        case mmConcentric: {  // all alignments to point on curve
1494
            if (shapes.empty()) {
1495
                throw Base::ValueError("AttachEngine3D::calculateAttachedPlacement: no subshapes "
1496
                                       "specified (need one edge, and an optional vertex).");
1497
            }
1498

1499
            bool bThruVertex = false;
1500
            if (shapes[0]->ShapeType() == TopAbs_VERTEX && shapes.size() >= 2) {
1501
                std::swap(shapes[0], shapes[1]);
1502
                bThruVertex = true;
1503
            }
1504

1505
            TopoDS_Edge path;
1506
            try {
1507
                path = TopoDS::Edge(*(shapes[0]));
1508
            }
1509
            catch (...) {
1510
            }
1511
            if (path.IsNull()) {
1512
                throw Base::ValueError(
1513
                    "Null path in AttachEngine3D::calculateAttachedPlacement()!");
1514
            }
1515

1516
            BRepAdaptor_Curve adapt(path);
1517

1518
            double u = 0.0;
1519
            double u1 = adapt.FirstParameter();
1520
            double u2 = adapt.LastParameter();
1521
            if (Precision::IsInfinite(u1) || Precision::IsInfinite(u2)) {
1522
                // prevent attachment to infinities in case of infinite shape.
1523
                // example of an infinite shape is a datum line.
1524
                u1 = 0.0;
1525
                u2 = 1.0;
1526
            }
1527

1528
            // if a point is specified, use the point as a point of mapping, otherwise use parameter
1529
            // value from properties
1530
            gp_Pnt p_in;
1531
            if (shapes.size() >= 2) {
1532
                TopoDS_Vertex vertex;
1533
                try {
1534
                    vertex = TopoDS::Vertex(*(shapes[1]));
1535
                }
1536
                catch (...) {
1537
                }
1538
                if (vertex.IsNull()) {
1539
                    throw Base::ValueError(
1540
                        "Null vertex in AttachEngine3D::calculateAttachedPlacement()!");
1541
                }
1542
                p_in = BRep_Tool::Pnt(vertex);
1543

1544
                Handle(Geom_Curve) hCurve = BRep_Tool::Curve(path, u1, u2);
1545

1546
                GeomAPI_ProjectPointOnCurve projector(p_in, hCurve);
1547
                u = projector.LowerDistanceParameter();
1548
            }
1549
            else {
1550
                u = u1 + this->attachParameter * (u2 - u1);
1551
            }
1552
            gp_Pnt p;
1553
            gp_Vec d;  // point and derivative
1554
            adapt.D1(u, p, d);
1555

1556
            if (d.Magnitude() < Precision::Confusion()) {
1557
                throw Base::ValueError("AttachEngine3D::calculateAttachedPlacement: path curve "
1558
                                       "derivative is below 1e-7, too low, can't align");
1559
            }
1560

1561
            // Set origin. Note that it will be overridden later for mmConcentric and
1562
            // mmRevolutionSection
1563
            if (bThruVertex) {
1564
                SketchBasePoint = p_in;
1565
            }
1566
            else {
1567
                SketchBasePoint = p;
1568
            }
1569

1570
            if (mmode == mmRevolutionSection || mmode == mmConcentric || mmode == mmFrenetNB
1571
                || mmode == mmFrenetTN || mmode == mmFrenetTB) {
1572
                gp_Vec dd;  // second derivative
1573
                try {
1574
                    adapt.D2(u, p, d, dd);
1575
                }
1576
                catch (Standard_Failure& e) {
1577
                    // ignore. This is probably due to insufficient continuity.
1578
                    dd = gp_Vec(0., 0., 0.);
1579
                    Base::Console().Warning("AttachEngine3D::calculateAttachedPlacement: can't "
1580
                                            "calculate second derivative of curve. OCC error: %s\n",
1581
                                            e.GetMessageString());
1582
                }
1583

1584
                gp_Vec T, N, B;  // Frenet?Serret axes: tangent, normal, binormal
1585
                T = d.Normalized();
1586
                N = dd.Subtracted(
1587
                    T.Multiplied(dd.Dot(T)));  // take away the portion of dd that is along tangent
1588
                if (N.Magnitude() > Precision::SquareConfusion()) {
1589
                    N.Normalize();
1590
                    B = T.Crossed(N);
1591
                }
1592
                else {
1593
                    Base::Console().Warning(
1594
                        "AttachEngine3D::calculateAttachedPlacement: path curve second derivative "
1595
                        "is below 1e-14, can't align x axis.\n");
1596
                    N = gp_Vec(0., 0., 0.);
1597
                    B = gp_Vec(0., 0., 0.);  // redundant, just for consistency
1598
                }
1599

1600

1601
                switch (mmode) {
1602
                    case mmFrenetNB:
1603
                    case mmRevolutionSection:
1604
                        SketchNormal = T.Reversed();  // to avoid sketches upside-down for regular
1605
                                                      // curves like circles
1606
                        SketchXAxis = N.Reversed();
1607
                        break;
1608
                    case mmFrenetTN:
1609
                    case mmConcentric:
1610
                        if (N.Magnitude() == 0.0) {
1611
                            throw Base::ValueError(
1612
                                "AttachEngine3D::calculateAttachedPlacement: Frenet-Serret normal "
1613
                                "is undefined. Can't align to TN plane.");
1614
                        }
1615
                        SketchNormal = B;
1616
                        SketchXAxis = T;
1617
                        break;
1618
                    case mmFrenetTB:
1619
                        if (N.Magnitude() == 0.0) {
1620
                            throw Base::ValueError(
1621
                                "AttachEngine3D::calculateAttachedPlacement: Frenet-Serret normal "
1622
                                "is undefined. Can't align to TB plane.");
1623
                        }
1624
                        SketchNormal = N.Reversed();  // it is more convenient to sketch on
1625
                                                      // something looking at it so it is convex.
1626
                        SketchXAxis = T;
1627
                        break;
1628
                    default:
1629
                        assert(0);  // mode forgotten?
1630
                }
1631
                if (mmode == mmRevolutionSection || mmode == mmConcentric) {
1632
                    // make sketch origin be at center of osculating circle
1633
                    if (N.Magnitude() == 0.0) {
1634
                        throw Base::ValueError(
1635
                            "AttachEngine3D::calculateAttachedPlacement: path has infinite radius "
1636
                            "of curvature at the point. Can't align for revolving.");
1637
                    }
1638
                    double curvature = dd.Dot(N) / pow(d.Magnitude(), 2);
1639
                    gp_Vec pv(p.XYZ());
1640
                    pv.Add(N.Multiplied(
1641
                        1 / curvature));  // shift the point along curvature by radius of curvature
1642
                    SketchBasePoint = gp_Pnt(pv.XYZ());
1643
                    // it would have been cool to have the curve attachment point available inside
1644
                    // sketch... Leave for future.
1645
                }
1646
            }
1647
            else if (mmode == mmNormalToPath) {  // mmNormalToPath
1648
                // align sketch origin to the origin of support
1649
                SketchNormal =
1650
                    gp_Dir(d.Reversed());  // sketch normal looks at user. It is natural to have the
1651
                                           // curve directed away from user, so reversed.
1652
            }
1653

1654
        } break;
1655
        case mmThreePointsPlane:
1656
        case mmThreePointsNormal: {
1657

1658
            std::vector<gp_Pnt> points;
1659

1660
            for (const auto & shape : shapes) {
1661
                const TopoDS_Shape &sh = *shape;
1662
                if (sh.IsNull()) {
1663
                    throw Base::ValueError(
1664
                        "Null shape in AttachEngine3D::calculateAttachedPlacement()!");
1665
                }
1666
                if (sh.ShapeType() == TopAbs_VERTEX) {
1667
                    const TopoDS_Vertex& v = TopoDS::Vertex(sh);
1668
                    points.push_back(BRep_Tool::Pnt(v));
1669
                }
1670
                else if (sh.ShapeType() == TopAbs_EDGE) {
1671
                    const TopoDS_Edge& e = TopoDS::Edge(sh);
1672
                    BRepAdaptor_Curve crv(e);
1673
                    double u1 = crv.FirstParameter();
1674
                    double u2 = crv.LastParameter();
1675
                    if (Precision::IsInfinite(u1) || Precision::IsInfinite(u2)) {
1676
                        u1 = 0.0;
1677
                        u2 = 1.0;
1678
                    }
1679
                    points.push_back(crv.Value(u1));
1680
                    points.push_back(crv.Value(u2));
1681
                }
1682
                if (points.size() >= 3) {
1683
                    break;
1684
                }
1685
            }
1686

1687
            if (points.size() < 3) {
1688
                throw Base::ValueError("AttachEngine3D::calculateAttachedPlacement: less than 3 "
1689
                                       "points are specified, cannot derive the plane.");
1690
            }
1691

1692
            gp_Pnt p0 = points[0];
1693
            gp_Pnt p1 = points[1];
1694
            gp_Pnt p2 = points[2];
1695

1696
            gp_Vec vec01(p0, p1);
1697
            gp_Vec vec02(p0, p2);
1698
            if (vec01.Magnitude() < Precision::Confusion()
1699
                || vec02.Magnitude() < Precision::Confusion()) {
1700
                throw Base::ValueError("AttachEngine3D::calculateAttachedPlacement: some of 3 "
1701
                                       "points are coincident. Can't make a plane");
1702
            }
1703
            vec01.Normalize();
1704
            vec02.Normalize();
1705

1706
            gp_Vec norm;
1707
            if (mmode == mmThreePointsPlane) {
1708
                norm = vec01.Crossed(vec02);
1709
                if (norm.Magnitude() < Precision::Confusion()) {
1710
                    throw Base::ValueError("AttachEngine3D::calculateAttachedPlacement: points are "
1711
                                           "collinear. Can't make a plane");
1712
                }
1713
                // SketchBasePoint = (p0+p1+p2)/3.0
1714
                SketchBasePoint = gp_Pnt(
1715
                    gp_Vec(p0.XYZ()).Added(p1.XYZ()).Added(p2.XYZ()).Multiplied(1.0 / 3.0).XYZ());
1716
            }
1717
            else if (mmode == mmThreePointsNormal) {
1718
                norm = vec02.Subtracted(vec01.Multiplied(vec02.Dot(vec01)))
1719
                           .Reversed();  // norm = vec02 forced perpendicular to vec01.
1720
                if (norm.Magnitude() < Precision::Confusion()) {
1721
                    throw Base::ValueError("AttachEngine3D::calculateAttachedPlacement: points are "
1722
                                           "collinear. Can't make a plane");
1723
                }
1724
                // SketchBasePoint = (p0+p1)/2.0
1725

1726
                Handle(Geom_Plane) gPlane = new Geom_Plane(p0, gp_Dir(norm));
1727
                GeomAPI_ProjectPointOnSurf projector(p2, gPlane);
1728
                SketchBasePoint = projector.NearestPoint();
1729
            }
1730

1731
            norm.Normalize();
1732
            SketchNormal = gp_Dir(norm);
1733

1734
        } break;
1735
        case mmFolding: {
1736

1737
            // Expected selection: four edges in order: edgeA, fold axis A,
1738
            // fold axis B, edgeB. The sketch will be placed angled so as to join
1739
            // edgeA to edgeB by folding the sheet along axes. All edges are
1740
            // expected to be in one plane.
1741

1742
            if (shapes.size() < 4) {
1743
                throw Base::ValueError("AttachEngine3D::calculateAttachedPlacement: not enough "
1744
                                       "shapes (need 4 lines: edgeA, axisA, axisB, edgeB).");
1745
            }
1746

1747
            // extract the four lines
1748
            const TopoDS_Edge* edges[4];
1749
            BRepAdaptor_Curve adapts[4];
1750
            gp_Lin lines[4];
1751
            for (int i = 0; i < 4; i++) {
1752
                try {
1753
                    edges[i] = &TopoDS::Edge(*(shapes[i]));
1754
                }
1755
                catch (...) {
1756
                }
1757
                if (edges[i]->IsNull()) {
1758
                    throw Base::ValueError(
1759
                        "Null edge in AttachEngine3D::calculateAttachedPlacement()!");
1760
                }
1761

1762
                adapts[i] = BRepAdaptor_Curve(*(edges[i]));
1763
                if (adapts[i].GetType() != GeomAbs_Line) {
1764
                    throw Base::ValueError(
1765
                        "AttachEngine3D::calculateAttachedPlacement: Folding - non-straight edge.");
1766
                }
1767
                lines[i] = adapts[i].Line();
1768
            }
1769

1770
            // figure out the common starting point (variable p)
1771
            gp_Pnt p, p1, p2, p3, p4;
1772
            double signs[4] = {0, 0, 0, 0};  // flags whether to reverse line directions, for all
1773
                                             // directions to point away from the common vertex
1774
            p1 = adapts[0].Value(adapts[0].FirstParameter());
1775
            p2 = adapts[0].Value(adapts[0].LastParameter());
1776
            p3 = adapts[1].Value(adapts[1].FirstParameter());
1777
            p4 = adapts[1].Value(adapts[1].LastParameter());
1778
            p = p1;
1779
            if (p1.Distance(p3) < Precision::Confusion()) {
1780
                p = p3;
1781
                signs[0] = +1.0;
1782
                signs[1] = +1.0;
1783
            }
1784
            else if (p1.Distance(p4) < Precision::Confusion()) {
1785
                p = p4;
1786
                signs[0] = +1.0;
1787
                signs[1] = -1.0;
1788
            }
1789
            else if (p2.Distance(p3) < Precision::Confusion()) {
1790
                p = p3;
1791
                signs[0] = -1.0;
1792
                signs[1] = +1.0;
1793
            }
1794
            else if (p2.Distance(p4) < Precision::Confusion()) {
1795
                p = p4;
1796
                signs[0] = -1.0;
1797
                signs[1] = -1.0;
1798
            }
1799
            else {
1800
                throw Base::ValueError("AttachEngine3D::calculateAttachedPlacement: Folding - "
1801
                                       "edges to not share a vertex.");
1802
            }
1803
            for (int i = 2; i < 4; i++) {
1804
                p1 = adapts[i].Value(adapts[i].FirstParameter());
1805
                p2 = adapts[i].Value(adapts[i].LastParameter());
1806
                if (p.Distance(p1) < Precision::Confusion()) {
1807
                    signs[i] = +1.0;
1808
                }
1809
                else if (p.Distance(p2) < Precision::Confusion()) {
1810
                    signs[i] = -1.0;
1811
                }
1812
                else {
1813
                    throw Base::ValueError("AttachEngine3D::calculateAttachedPlacement: Folding - "
1814
                                           "edges to not share a vertex.");
1815
                }
1816
            }
1817

1818
            gp_Vec dirs[4];
1819
            for (int i = 0; i < 4; i++) {
1820
                assert(fabs(signs[i]) == 1.0);
1821
                dirs[i] = gp_Vec(lines[i].Direction()).Multiplied(signs[i]);
1822
            }
1823

1824
            double ang = this->calculateFoldAngle(dirs[1], dirs[2], dirs[0], dirs[3]);
1825

1826
            gp_Vec norm = dirs[1].Crossed(dirs[2]);
1827
            // rotation direction: when angle is positive, rotation is CCW when observing the vector
1828
            // so that the axis is pointing at you. Hence angle is negated here.
1829
            norm.Rotate(gp_Ax1(gp_Pnt(), gp_Dir(dirs[1])), -ang);
1830
            SketchNormal = norm.Reversed();
1831

1832
            SketchXAxis = dirs[1];
1833

1834
            SketchBasePoint = p;
1835

1836
        } break;
1837
        case mmOZX:
1838
        case mmOZY:
1839
        case mmOXY:
1840
        case mmOXZ:
1841
        case mmOYZ:
1842
        case mmOYX: {
1843
            const char orderStrings[6][4] = {
1844
                "ZXY",
1845
                "ZYX",
1846
                "XYZ",
1847
                "XZY",
1848
                "YZX",
1849
                "YXZ",
1850
            };
1851
            const char* orderString = orderStrings[mmode - mmOZX];
1852

1853
            enum dirIndex
1854
            {
1855
                X,
1856
                Y,
1857
                Z
1858
            };
1859
            int order[3];
1860
            for (int i = 0; i < 3; ++i) {
1861
                order[i] = orderString[i] - 'X';
1862
            }
1863

1864
            if (shapes.size() < 2) {
1865
                THROWM(Base::ValueError,
1866
                       "AttachEngine3D::calculateAttachedPlacement: not enough shapes linked (at "
1867
                       "least two are required).");
1868
            }
1869

1870
            gp_Vec dirs[3];
1871

1872
            // read out origin
1873
            if (shapes[0]->IsNull()) {
1874
                THROWM(Base::TypeError, "AttachEngine3D::calculateAttachedPlacement: null shape!")
1875
            }
1876
            if (shapes[0]->ShapeType() != TopAbs_VERTEX) {
1877
                THROWM(Base::TypeError,
1878
                       "AttachEngine3D::calculateAttachedPlacement: first reference must be a "
1879
                       "vertex, it's not")
1880
            }
1881
            SketchBasePoint = BRep_Tool::Pnt(TopoDS::Vertex(*(shapes[0])));
1882

1883
            // read out axes directions
1884
            for (size_t i = 1; i < 3 && i < shapes.size(); ++i) {
1885
                if (shapes[i]->IsNull()) {
1886
                    THROWM(Base::TypeError,
1887
                           "AttachEngine3D::calculateAttachedPlacement: null shape!")
1888
                }
1889
                if (shapes[i]->ShapeType() == TopAbs_VERTEX) {
1890
                    gp_Pnt p = BRep_Tool::Pnt(TopoDS::Vertex(*(shapes[i])));
1891
                    dirs[order[i - 1]] = gp_Vec(SketchBasePoint, p);
1892
                }
1893
                else if (shapes[i]->ShapeType() == TopAbs_EDGE) {
1894
                    const TopoDS_Edge& e = TopoDS::Edge(*(shapes[i]));
1895
                    BRepAdaptor_Curve crv(e);
1896
                    double u1 = crv.FirstParameter();
1897
                    double u2 = crv.LastParameter();
1898
                    if (Precision::IsInfinite(u1) || Precision::IsInfinite(u2)) {
1899
                        u1 = 0.0;
1900
                        u2 = 1.0;
1901
                    }
1902
                    gp_Pnt p1 = crv.Value(u1);
1903
                    gp_Pnt p2 = crv.Value(u2);
1904
                    dirs[order[i - 1]] = gp_Vec(p1, p2);
1905
                }
1906
            }
1907

1908
            // make the placement
1909
            Base::Rotation rot = Base::Rotation::makeRotationByAxes(
1910
                Base::Vector3d(dirs[0].X(), dirs[0].Y(), dirs[0].Z()),
1911
                Base::Vector3d(dirs[1].X(), dirs[1].Y(), dirs[1].Z()),
1912
                Base::Vector3d(dirs[2].X(), dirs[2].Y(), dirs[2].Z()),
1913
                orderString);
1914
            if (this->mapReverse) {
1915
                rot = rot * Base::Rotation(Base::Vector3d(0, 1, 0), D_PI);
1916
            }
1917

1918
            Base::Placement plm = Base::Placement(
1919
                Base::Vector3d(SketchBasePoint.X(), SketchBasePoint.Y(), SketchBasePoint.Z()),
1920
                rot);
1921
            plm *= this->attachmentOffset;
1922
            return plm;
1923
        } break;
1924
        default:
1925
            throwWrongMode(mmode);
1926
    }  // switch (MapMode)
1927

1928
    //----------calculate placement, based on point and vector
1929

1930
    Base::Placement plm =
1931
        this->placementFactory(SketchNormal,
1932
                               SketchXAxis,
1933
                               SketchBasePoint,
1934
                               gp_Pnt(),
1935
                               /*useRefOrg_Line = */ false,
1936
                               /*useRefOrg_Plane = */ false,
1937
                               /*makeYVertical = */ false,
1938
                               /*makeLegacyFlatFaceOrientation = */ mmode == mmFlatFace,
1939
                               &Place);
1940
    plm *= this->attachmentOffset;
1941
    return plm;
1942
}
1943

1944
double AttachEngine3D::calculateFoldAngle(gp_Vec axA, gp_Vec axB, gp_Vec edA, gp_Vec edB) const
1945
{
1946
    //DeepSOIC: this hardcore math can probably be replaced with a couple of
1947
    //clever OCC calls... See forum thread "Sketch mapping enhancement" for a
1948
    //picture on how this math was derived.
1949
    //https://forum.freecad.org/viewtopic.php?f=8&t=10511&sid=007946a934530ff2a6c9259fb32624ec&start=40#p87584
1950
    axA.Normalize();
1951
    axB.Normalize();
1952
    edA.Normalize();
1953
    edB.Normalize();
1954
    gp_Vec norm = axA.Crossed(axB);
1955
    if (norm.Magnitude() < Precision::Confusion())
1956
        throw AttachEngineException("calculateFoldAngle: Folding axes are parallel, folding angle cannot be computed.");
1957
    norm.Normalize();
1958
    double a = edA.Dot(axA);
1959
    double ra = edA.Crossed(axA).Magnitude();
1960
    if (fabs(ra) < Precision::Confusion())
1961
        throw AttachEngineException("calculateFoldAngle: axisA and edgeA are parallel, folding can't be computed.");
1962
    double b = edB.Dot(axB);
1963
    double costheta = axB.Dot(axA);
1964
    double sintheta = axA.Crossed(axB).Dot(norm);
1965
    double singama = -costheta;
1966
    double cosgama = sintheta;
1967
    double k = b*cosgama;
1968
    double l = a + b*singama;
1969
    double xa = k + l*singama/cosgama;
1970
    double cos_unfold = -xa/ra;
1971
    if (fabs(cos_unfold)>0.999)
1972
        throw AttachEngineException("calculateFoldAngle: cosine of folding angle is too close to or above 1.");
1973
    return acos(cos_unfold);
1974
}
1975

1976

1977
//=================================================================================
1978

1979
TYPESYSTEM_SOURCE(Attacher::AttachEnginePlane, Attacher::AttachEngine)
1980

1981
AttachEnginePlane::AttachEnginePlane()
1982
{
1983
    //re-used 3d modes: all of Attacher3d
1984
    AttachEngine3D attacher3D;
1985
    this->modeRefTypes = attacher3D.modeRefTypes;
1986
    this->EnableAllSupportedModes();
1987
}
1988

1989
AttachEnginePlane *AttachEnginePlane::copy() const
1990
{
1991
    AttachEnginePlane* p = new AttachEnginePlane;
1992
    p->setUp(*this);
1993
    return p;
1994
}
1995

1996
Base::Placement AttachEnginePlane::_calculateAttachedPlacement(
1997
    const std::vector<App::DocumentObject*>&objs,
1998
    const std::vector<std::string> &subs,
1999
    const Base::Placement &origPlacement) const
2000
{
2001
    //reuse Attacher3d
2002
    Base::Placement plm;
2003
    AttachEngine3D attacher3D;
2004
    attacher3D.setUp(*this);
2005
    plm = attacher3D._calculateAttachedPlacement(objs,subs,origPlacement);
2006
    return plm;
2007
}
2008

2009
//=================================================================================
2010

2011
TYPESYSTEM_SOURCE(Attacher::AttachEngineLine, Attacher::AttachEngine)
2012

2013
AttachEngineLine::AttachEngineLine()
2014
{
2015
    //fill type lists for modes
2016
    modeRefTypes.resize(mmDummy_NumberOfModes);
2017
    refTypeString s;
2018

2019
    //re-used 3d modes
2020
    AttachEngine3D attacher3D;
2021
    modeRefTypes[mm1AxisX] = attacher3D.modeRefTypes[mmObjectYZ];
2022
    modeRefTypes[mm1AxisY] = attacher3D.modeRefTypes[mmObjectXZ];
2023
    modeRefTypes[mm1AxisZ] = attacher3D.modeRefTypes[mmObjectXY];
2024
    modeRefTypes[mm1AxisCurv] = attacher3D.modeRefTypes[mmRevolutionSection];
2025
    modeRefTypes[mm1Binormal] = attacher3D.modeRefTypes[mmFrenetTN];
2026
    modeRefTypes[mm1Normal] = attacher3D.modeRefTypes[mmFrenetTB];
2027
    modeRefTypes[mm1Tangent] = attacher3D.modeRefTypes[mmNormalToPath];
2028

2029
    modeRefTypes[mm1TwoPoints].push_back(cat(rtVertex,rtVertex));
2030
    modeRefTypes[mm1TwoPoints].push_back(cat(rtLine));
2031

2032
    modeRefTypes[mm1Asymptote1].push_back(cat(rtHyperbola));
2033
    modeRefTypes[mm1Asymptote2].push_back(cat(rtHyperbola));
2034

2035
    modeRefTypes[mm1Directrix1].push_back(cat(rtConic));
2036

2037
    modeRefTypes[mm1Directrix2].push_back(cat(rtEllipse));
2038
    modeRefTypes[mm1Directrix2].push_back(cat(rtHyperbola));
2039

2040
    modeRefTypes[mm1Proximity].push_back(cat(rtAnything, rtAnything));
2041

2042
    modeRefTypes[mm1AxisInertia1].push_back(cat(rtAnything));
2043
    modeRefTypes[mm1AxisInertia1].push_back(cat(rtAnything,rtAnything));
2044
    modeRefTypes[mm1AxisInertia1].push_back(cat(rtAnything,rtAnything,rtAnything));
2045
    modeRefTypes[mm1AxisInertia1].push_back(cat(rtAnything,rtAnything,rtAnything,rtAnything));
2046
    modeRefTypes[mm1AxisInertia2] = modeRefTypes[mm1AxisInertia1];
2047
    modeRefTypes[mm1AxisInertia3] = modeRefTypes[mm1AxisInertia1];
2048

2049
    modeRefTypes[mm1FaceNormal] = attacher3D.modeRefTypes[mmTangentPlane];
2050

2051
    modeRefTypes[mm1Intersection].push_back(cat(rtFace,rtFace));
2052

2053
    this->EnableAllSupportedModes();
2054
}
2055

2056
AttachEngineLine *AttachEngineLine::copy() const
2057
{
2058
    AttachEngineLine* p = new AttachEngineLine;
2059
    p->setUp(*this);
2060
    return p;
2061
}
2062

2063
Base::Placement
2064
AttachEngineLine::_calculateAttachedPlacement(const std::vector<App::DocumentObject*>& objs,
2065
                                              const std::vector<std::string>& subs,
2066
                                              const Base::Placement& origPlacement) const
2067
{
2068
    eMapMode mmode = this->mapMode;
2069

2070
    // modes that are mirrors of attacher3D:
2071
    bool bReUsed = true;
2072
    Base::Placement presuperPlacement;
2073
    switch (mmode) {
2074
        case mmDeactivated:
2075
            throw ExceptionCancel();  // to be handled in positionBySupport, to not do anything if
2076
                                      // disabled
2077
        case mm1AxisX:
2078
            mmode = mmObjectYZ;
2079
            break;
2080
        case mm1AxisY:
2081
            mmode = mmObjectXZ;
2082
            break;
2083
        case mm1AxisZ:
2084
            mmode = mmObjectXY;
2085
            break;
2086
        case mm1AxisCurv:
2087
            mmode = mmRevolutionSection;
2088
            // the line should go along Y, not Z
2089
            presuperPlacement.setRotation(
2090
                Base::Rotation(Base::Vector3d(0.0, 0.0, 1.0), Base::Vector3d(0.0, 1.0, 0.0)));
2091
            break;
2092
        case mm1Binormal:
2093
            mmode = mmFrenetTN;
2094
            break;
2095
        case mm1Normal:
2096
            mmode = mmFrenetTB;
2097
            break;
2098
        case mm1Tangent:
2099
            mmode = mmNormalToPath;
2100
            break;
2101
        case mm1FaceNormal:
2102
            mmode = mmTangentPlane;
2103
            break;
2104
        default:
2105
            bReUsed = false;
2106
            break;
2107
    }
2108

2109
    Base::Placement plm;
2110
    if (!bReUsed) {
2111
        std::vector<App::GeoFeature*> parts;
2112
        std::vector<const TopoDS_Shape*> shapes;
2113
        std::vector<TopoDS_Shape> copiedShapeStorage;
2114
        std::vector<eRefType> types;
2115
        readLinks(objs, subs, parts, shapes, copiedShapeStorage, types);
2116

2117
        if (parts.empty()) {
2118
            throw ExceptionCancel();
2119
        }
2120

2121

2122
        // common stuff for all map modes
2123
        gp_Pnt refOrg(0.0, 0.0, 0.0);
2124
        Base::Placement Place = parts[0]->Placement.getValue();
2125
        refOrg = gp_Pnt(Place.getPosition().x, Place.getPosition().y, Place.getPosition().z);
2126

2127
        // variables to derive the actual placement.
2128
        // They are to be set, depending on the mode:
2129
        gp_Dir LineDir;
2130
        gp_Pnt LineBasePoint;  // the point the line goes through
2131

2132

2133
        switch (mmode) {
2134
            case mm1AxisInertia1:
2135
            case mm1AxisInertia2:
2136
            case mm1AxisInertia3: {
2137
                GProp_GProps gpr = AttachEngine::getInertialPropsOfShape(shapes);
2138
                LineBasePoint = gpr.CentreOfMass();
2139
                GProp_PrincipalProps pr = gpr.PrincipalProperties();
2140
                if (pr.HasSymmetryPoint()) {
2141
                    throw Base::ValueError(
2142
                        "AttachEngineLine::calculateAttachedPlacement:AxisOfInertia: inertia "
2143
                        "tensor is trivial, principal axes are undefined.");
2144
                }
2145

2146
                // query moments, to use them to check if axis is defined
2147
                // See AttachEngine3D::calculateAttachedPlacement:case mmInertial for comment
2148
                // explaining these comparisons
2149
                Standard_Real I1, I2, I3;
2150
                pr.Moments(I1, I2, I3);
2151
                Standard_Real d12, d23, d31;
2152
                d12 = fabs(I1 - I2);
2153
                d23 = fabs(I2 - I3);
2154
                d31 = fabs(I3 - I1);
2155

2156
                if (mmode == mm1AxisInertia1) {
2157
                    LineDir = pr.FirstAxisOfInertia();
2158
                    if (pr.HasSymmetryAxis() && !(d23 < d31 && d23 < d12)) {
2159
                        throw Base::ValueError(
2160
                            "AttachEngineLine::calculateAttachedPlacement:AxisOfInertia: inertia "
2161
                            "tensor has axis of symmetry; first axis of inertia is undefined.");
2162
                    }
2163
                }
2164
                else if (mmode == mm1AxisInertia2) {
2165
                    LineDir = pr.SecondAxisOfInertia();
2166
                    if (pr.HasSymmetryAxis() && !(d31 < d12 && d31 < d23)) {
2167
                        throw Base::ValueError(
2168
                            "AttachEngineLine::calculateAttachedPlacement:AxisOfInertia: inertia "
2169
                            "tensor has axis of symmetry; second axis of inertia is undefined.");
2170
                    }
2171
                }
2172
                else if (mmode == mm1AxisInertia3) {
2173
                    LineDir = pr.ThirdAxisOfInertia();
2174
                    if (pr.HasSymmetryAxis() && !(d12 < d23 && d12 < d31)) {
2175
                        throw Base::ValueError(
2176
                            "AttachEngineLine::calculateAttachedPlacement:AxisOfInertia: inertia "
2177
                            "tensor has axis of symmetry; third axis of inertia is undefined.");
2178
                    }
2179
                }
2180
            } break;
2181
            case mm1TwoPoints: {
2182
                std::vector<gp_Pnt> points;
2183

2184
                for (const auto & shape : shapes) {
2185
                    const TopoDS_Shape &sh = *shape;
2186
                    if (sh.IsNull()) {
2187
                        throw Base::ValueError(
2188
                            "Null shape in AttachEngineLine::calculateAttachedPlacement()!");
2189
                    }
2190
                    if (sh.ShapeType() == TopAbs_VERTEX) {
2191
                        const TopoDS_Vertex& v = TopoDS::Vertex(sh);
2192
                        points.push_back(BRep_Tool::Pnt(v));
2193
                    }
2194
                    else if (sh.ShapeType() == TopAbs_EDGE) {
2195
                        const TopoDS_Edge& e = TopoDS::Edge(sh);
2196
                        BRepAdaptor_Curve crv(e);
2197
                        double u1 = crv.FirstParameter();
2198
                        double u2 = crv.LastParameter();
2199
                        if (Precision::IsInfinite(u1) || Precision::IsInfinite(u2)) {
2200
                            u1 = 0.0;
2201
                            u2 = 1.0;
2202
                        }
2203
                        points.push_back(crv.Value(u1));
2204
                        points.push_back(crv.Value(u2));
2205
                    }
2206
                    if (points.size() >= 2) {
2207
                        break;
2208
                    }
2209
                }
2210

2211
                if (points.size() < 2) {
2212
                    throw Base::ValueError("AttachEngineLine::calculateAttachedPlacement: less "
2213
                                           "than 2 points are specified, cannot derive the line.");
2214
                }
2215

2216
                gp_Pnt p0 = points[0];
2217
                gp_Pnt p1 = points[1];
2218

2219
                LineDir = gp_Dir(gp_Vec(p0, p1));
2220
                LineBasePoint = p0;
2221

2222
            } break;
2223
            case mm1Asymptote1:
2224
            case mm1Asymptote2: {
2225
                if (shapes[0]->IsNull()) {
2226
                    throw Base::ValueError(
2227
                        "Null shape in AttachEngineLine::calculateAttachedPlacement()!");
2228
                }
2229
                TopoDS_Edge e;
2230
                try {
2231
                    e = TopoDS::Edge(*(shapes[0]));
2232
                }
2233
                catch (...) {
2234
                }
2235
                if (e.IsNull()) {
2236
                    throw Base::ValueError(
2237
                        "Null edge in AttachEngineLine::calculateAttachedPlacement()!");
2238
                }
2239
                BRepAdaptor_Curve adapt(e);
2240
                if (adapt.GetType() != GeomAbs_Hyperbola) {
2241
                    throw Base::ValueError(
2242
                        "AttachEngineLine::calculateAttachedPlacement: Asymptotes are available "
2243
                        "only for hyperbola-shaped edges, the one supplied is not.");
2244
                }
2245
                gp_Hypr hyp = adapt.Hyperbola();
2246
                if (mmode == mm1Asymptote1) {
2247
                    LineDir = hyp.Asymptote1().Direction();
2248
                }
2249
                else {
2250
                    LineDir = hyp.Asymptote2().Direction();
2251
                }
2252
                LineBasePoint = hyp.Location();
2253
            } break;
2254
            case mm1Directrix1:
2255
            case mm1Directrix2: {
2256
                if (shapes[0]->IsNull()) {
2257
                    throw Base::ValueError(
2258
                        "Null shape in AttachEngineLine::calculateAttachedPlacement()!");
2259
                }
2260
                TopoDS_Edge e;
2261
                try {
2262
                    e = TopoDS::Edge(*(shapes[0]));
2263
                }
2264
                catch (...) {
2265
                }
2266
                if (e.IsNull()) {
2267
                    throw Base::ValueError(
2268
                        "Null edge in AttachEngineLine::calculateAttachedPlacement()!");
2269
                }
2270
                BRepAdaptor_Curve adapt(e);
2271
                gp_Ax1 dx1, dx2;  // vars to receive directrices
2272
                switch (adapt.GetType()) {
2273
                    case GeomAbs_Ellipse: {
2274
                        gp_Elips cc = adapt.Ellipse();
2275
                        dx1 = cc.Directrix1();
2276
                        dx2 = cc.Directrix2();
2277
                    } break;
2278
                    case GeomAbs_Hyperbola: {
2279
                        gp_Hypr cc = adapt.Hyperbola();
2280
                        dx1 = cc.Directrix1();
2281
                        dx2 = cc.Directrix2();
2282
                    } break;
2283
                    case GeomAbs_Parabola: {
2284
                        gp_Parab cc = adapt.Parabola();
2285
                        dx1 = cc.Directrix();
2286
                        if (mmode == mm1Directrix2) {
2287
                            throw Base::ValueError("AttachEngineLine::calculateAttachedPlacement: "
2288
                                                   "Parabola has no second directrix");
2289
                        }
2290
                    } break;
2291
                    default:
2292
                        throw Base::ValueError(
2293
                            "AttachEngineLine::calculateAttachedPlacement: referenced edge is not "
2294
                            "a conic section with a directrix");
2295
                }
2296
                if (mmode == mm1Directrix1) {
2297
                    LineDir = dx1.Direction();
2298
                    LineBasePoint = dx1.Location();
2299
                }
2300
                else {
2301
                    LineDir = dx2.Direction();
2302
                    LineBasePoint = dx2.Location();
2303
                }
2304
            } break;
2305
            case mm1Intersection: {
2306
                if (shapes.size() < 2) {
2307
                    throw Base::ValueError(
2308
                        "AttachEngineLine::calculateAttachedPlacement: Intersection mode requires "
2309
                        "two shapes; only one is supplied");
2310
                }
2311
                if (shapes[0]->IsNull() || shapes[1]->IsNull()) {
2312
                    throw Base::ValueError(
2313
                        "Null shape in AttachEngineLine::calculateAttachedPlacement()!");
2314
                }
2315

2316
                const TopoDS_Face& face1 = TopoDS::Face(*(shapes[0]));
2317
                const TopoDS_Face& face2 = TopoDS::Face(*(shapes[1]));
2318

2319
                Handle(Geom_Surface) hSurf1 = BRep_Tool::Surface(face1);
2320
                Handle(Geom_Surface) hSurf2 = BRep_Tool::Surface(face2);
2321
                GeomAPI_IntSS intersector(hSurf1, hSurf2, Precision::Confusion());
2322
                if (!intersector.IsDone()) {
2323
                    throw Base::ValueError(
2324
                        "AttachEngineLine::calculateAttachedPlacement: Intersection failed");
2325
                }
2326

2327
                const Standard_Integer intLines = intersector.NbLines();
2328
                if (intLines == 0) {
2329
                    throw Base::ValueError("AttachEngineLine::calculateAttachedPlacement: The two "
2330
                                           "shapes don't intersect");
2331
                }
2332
                if (intLines != 1) {
2333
                    throw Base::ValueError("AttachEngineLine::calculateAttachedPlacement: "
2334
                                           "Intersection is not a single curve");
2335
                }
2336

2337
                GeomAdaptor_Curve adapt(intersector.Line(1));
2338
                if (adapt.GetType() != GeomAbs_Line) {
2339
                    throw Base::ValueError("AttachEngineLine::calculateAttachedPlacement: "
2340
                                           "Intersection is not a straight line");
2341
                }
2342

2343
                LineBasePoint = adapt.Line().Location();
2344
                LineDir = adapt.Line().Direction();
2345
            } break;
2346
            case mm1Proximity: {
2347
                if (shapes.size() < 2) {
2348
                    throw Base::ValueError(
2349
                        "AttachEngineLine::calculateAttachedPlacement: Proximity mode requires two "
2350
                        "shapes; only one is supplied");
2351
                }
2352
                if (shapes[0]->IsNull()) {
2353
                    throw Base::ValueError(
2354
                        "Null shape in AttachEngineLine::calculateAttachedPlacement()!");
2355
                }
2356
                if (shapes[1]->IsNull()) {
2357
                    throw Base::ValueError(
2358
                        "Null shape in AttachEngineLine::calculateAttachedPlacement()!");
2359
                }
2360
                BRepExtrema_DistShapeShape distancer(*(shapes[0]), *(shapes[1]));
2361
                if (!distancer.IsDone()) {
2362
                    throw Base::ValueError("AttachEngineLine::calculateAttachedPlacement: "
2363
                                           "proximity calculation failed.");
2364
                }
2365
                if (distancer.NbSolution() > 1) {
2366
                    Base::Console().Warning("AttachEngineLine::calculateAttachedPlacement: "
2367
                                            "proximity calculation gave %i solutions, ambiguous.\n",
2368
                                            int(distancer.NbSolution()));
2369
                }
2370
                gp_Pnt p1 = distancer.PointOnShape1(1);
2371
                gp_Pnt p2 = distancer.PointOnShape2(1);
2372
                LineBasePoint = p1;
2373
                gp_Vec dist = gp_Vec(p1, p2);
2374
                if (dist.Magnitude() < Precision::Confusion()) {
2375
                    throw Base::ValueError(
2376
                        "AttachEngineLine::calculateAttachedPlacement: can't make proximity line, "
2377
                        "because shapes touch or intersect");
2378
                }
2379
                LineDir = gp_Dir(dist);
2380
            } break;
2381
            default:
2382
                throwWrongMode(mmode);
2383
        }
2384

2385
        plm = this->placementFactory(LineDir,
2386
                                     gp_Vec(),
2387
                                     LineBasePoint,
2388
                                     refOrg,
2389
                                     /*useRefOrg_Line = */ true);
2390
    }
2391
    else {  // reuse 3d mode
2392
        AttachEngine3D attacher3D;
2393
        attacher3D.setUp(*this);
2394
        attacher3D.mapMode = mmode;
2395
        attacher3D.attachmentOffset =
2396
            Base::Placement();  // AttachmentOffset is applied separately here, afterwards. So we
2397
                                // are resetting it in sub-attacher to avoid applying it twice!
2398
        plm = attacher3D._calculateAttachedPlacement(objs, subs, origPlacement);
2399
        plm *= presuperPlacement;
2400
    }
2401
    plm *= this->attachmentOffset;
2402
    return plm;
2403
}
2404

2405

2406
//=================================================================================
2407

2408
TYPESYSTEM_SOURCE(Attacher::AttachEnginePoint, Attacher::AttachEngine)
2409

2410
AttachEnginePoint::AttachEnginePoint()
2411
{
2412
    //fill type lists for modes
2413
    modeRefTypes.resize(mmDummy_NumberOfModes);
2414
    refTypeString s;
2415

2416
    //re-used 3d modes
2417
    AttachEngine3D attacher3D;
2418
    modeRefTypes[mm0Origin] = attacher3D.modeRefTypes[mmObjectXY];
2419
    modeRefTypes[mm0CenterOfCurvature] = attacher3D.modeRefTypes[mmRevolutionSection];
2420
    modeRefTypes[mm0OnEdge] = attacher3D.modeRefTypes[mmNormalToPath];
2421

2422
    modeRefTypes[mm0Vertex].push_back(cat(rtVertex));
2423
    modeRefTypes[mm0Vertex].push_back(cat(rtLine));
2424

2425
    modeRefTypes[mm0Focus1].push_back(cat(rtConic));
2426

2427
    modeRefTypes[mm0Focus2].push_back(cat(rtEllipse));
2428
    modeRefTypes[mm0Focus2].push_back(cat(rtHyperbola));
2429

2430
    s = cat(rtAnything, rtAnything);
2431
    modeRefTypes[mm0ProximityPoint1].push_back(s);
2432
    modeRefTypes[mm0ProximityPoint2].push_back(s);
2433

2434
    modeRefTypes[mm0CenterOfMass].push_back(cat(rtAnything));
2435
    modeRefTypes[mm0CenterOfMass].push_back(cat(rtAnything,rtAnything));
2436
    modeRefTypes[mm0CenterOfMass].push_back(cat(rtAnything,rtAnything,rtAnything));
2437
    modeRefTypes[mm0CenterOfMass].push_back(cat(rtAnything,rtAnything,rtAnything,rtAnything));
2438

2439
    this->EnableAllSupportedModes();
2440
}
2441

2442
AttachEnginePoint *AttachEnginePoint::copy() const
2443
{
2444
    AttachEnginePoint* p = new AttachEnginePoint;
2445
    p->setUp(*this);
2446
    return p;
2447
}
2448

2449
Base::Placement
2450
AttachEnginePoint::_calculateAttachedPlacement(const std::vector<App::DocumentObject*>& objs,
2451
                                               const std::vector<std::string>& subs,
2452
                                               const Base::Placement& origPlacement) const
2453
{
2454
    eMapMode mmode = this->mapMode;
2455

2456
    // modes that are mirrors of attacher3D:
2457
    bool bReUsed = true;
2458
    switch (mmode) {
2459
        case mmDeactivated:
2460
            throw ExceptionCancel();  // to be handled in positionBySupport, to not do anything if
2461
                                      // disabled
2462
        case mm0Origin:
2463
            mmode = mmObjectXY;
2464
            break;
2465
        case mm0CenterOfCurvature:
2466
            mmode = mmRevolutionSection;
2467
            break;
2468
        case mm0OnEdge:
2469
            // todo: prevent thruPoint
2470
            mmode = mmNormalToPath;
2471
            break;
2472
        default:
2473
            bReUsed = false;
2474
    }
2475

2476
    Base::Placement plm;
2477
    if (!bReUsed) {
2478
        std::vector<App::GeoFeature*> parts;
2479
        std::vector<const TopoDS_Shape*> shapes;
2480
        std::vector<TopoDS_Shape> copiedShapeStorage;
2481
        std::vector<eRefType> types;
2482
        readLinks(objs, subs, parts, shapes, copiedShapeStorage, types);
2483

2484
        if (parts.empty()) {
2485
            throw ExceptionCancel();
2486
        }
2487

2488

2489
        // variables to derive the actual placement.
2490
        // They are to be set, depending on the mode:
2491
        gp_Pnt BasePoint;  // where to put the point
2492

2493

2494
        switch (mmode) {
2495
            case mm0Vertex: {
2496
                std::vector<gp_Pnt> points;
2497
                assert(!shapes.empty());
2498

2499
                const TopoDS_Shape& sh = *shapes[0];
2500
                if (sh.IsNull()) {
2501
                    throw Base::ValueError(
2502
                        "Null shape in AttachEnginePoint::calculateAttachedPlacement()!");
2503
                }
2504
                if (sh.ShapeType() == TopAbs_VERTEX) {
2505
                    const TopoDS_Vertex& v = TopoDS::Vertex(sh);
2506
                    BasePoint = BRep_Tool::Pnt(v);
2507
                }
2508
                else if (sh.ShapeType() == TopAbs_EDGE) {
2509
                    const TopoDS_Edge& e = TopoDS::Edge(sh);
2510
                    BRepAdaptor_Curve crv(e);
2511
                    double u = crv.FirstParameter();
2512
                    if (Precision::IsInfinite(u)) {
2513
                        throw Base::ValueError("Edge is infinite");
2514
                    }
2515
                    BasePoint = crv.Value(u);
2516
                }
2517

2518
            } break;
2519
            case mm0Focus1:
2520
            case mm0Focus2: {
2521
                if (shapes[0]->IsNull()) {
2522
                    throw Base::ValueError(
2523
                        "Null shape in AttachEnginePoint::calculateAttachedPlacement()!");
2524
                }
2525
                TopoDS_Edge e;
2526
                try {
2527
                    e = TopoDS::Edge(*(shapes[0]));
2528
                }
2529
                catch (...) {
2530
                }
2531
                if (e.IsNull()) {
2532
                    throw Base::ValueError(
2533
                        "Null edge in AttachEnginePoint::calculateAttachedPlacement()!");
2534
                }
2535
                BRepAdaptor_Curve adapt(e);
2536
                gp_Pnt f1, f2;
2537
                switch (adapt.GetType()) {
2538
                    case GeomAbs_Ellipse: {
2539
                        gp_Elips cc = adapt.Ellipse();
2540
                        f1 = cc.Focus1();
2541
                        f2 = cc.Focus2();
2542
                    } break;
2543
                    case GeomAbs_Hyperbola: {
2544
                        gp_Hypr cc = adapt.Hyperbola();
2545
                        f1 = cc.Focus1();
2546
                        f2 = cc.Focus2();
2547
                    } break;
2548
                    case GeomAbs_Parabola: {
2549
                        gp_Parab cc = adapt.Parabola();
2550
                        f1 = cc.Focus();
2551
                        if (mmode == mm0Focus2) {
2552
                            throw Base::ValueError("AttachEnginePoint::calculateAttachedPlacement: "
2553
                                                   "Parabola has no second focus");
2554
                        }
2555
                    } break;
2556
                    default:
2557
                        throw Base::ValueError(
2558
                            "AttachEnginePoint::calculateAttachedPlacement: referenced edge is not "
2559
                            "a conic section with a directrix");
2560
                }
2561
                if (mmode == mm0Focus1) {
2562
                    BasePoint = f1;
2563
                }
2564
                else {
2565
                    BasePoint = f2;
2566
                }
2567
            } break;
2568
            case mm0ProximityPoint1:
2569
            case mm0ProximityPoint2: {
2570
                if (shapes.size() < 2) {
2571
                    throw Base::ValueError(
2572
                        "AttachEnginePoint::calculateAttachedPlacement: Proximity mode requires "
2573
                        "two shapes; only one is supplied");
2574
                }
2575
                if (shapes[0]->IsNull()) {
2576
                    throw Base::ValueError(
2577
                        "Null shape in AttachEnginePoint::calculateAttachedPlacement()!");
2578
                }
2579
                if (shapes[1]->IsNull()) {
2580
                    throw Base::ValueError(
2581
                        "Null shape in AttachEnginePoint::calculateAttachedPlacement()!");
2582
                }
2583

2584
                BasePoint = getProximityPoint(mmode, *(shapes[0]), *(shapes[1]));
2585
            } break;
2586
            case mm0CenterOfMass: {
2587
                GProp_GProps gpr = AttachEngine::getInertialPropsOfShape(shapes);
2588
                BasePoint = gpr.CentreOfMass();
2589
            } break;
2590
            default:
2591
                throwWrongMode(mmode);
2592
        }
2593

2594
        plm = this->placementFactory(gp_Vec(0.0, 0.0, 1.0),
2595
                                     gp_Vec(1.0, 0.0, 0.0),
2596
                                     BasePoint,
2597
                                     gp_Pnt());
2598
    }
2599
    else {  // reuse 3d mode
2600
        AttachEngine3D attacher3D;
2601
        attacher3D.setUp(*this);
2602
        attacher3D.mapMode = mmode;
2603
        attacher3D.attachmentOffset =
2604
            Base::Placement();  // AttachmentOffset is applied separately here, afterwards. So we
2605
                                // are resetting it in sub-attacher to avoid applying it twice!
2606
        plm = attacher3D._calculateAttachedPlacement(objs, subs, origPlacement);
2607
    }
2608
    plm *= this->attachmentOffset;
2609
    return plm;
2610
}
2611

2612
gp_Pnt AttachEnginePoint::getProximityPoint(eMapMode mmode, const TopoDS_Shape& s1, const TopoDS_Shape& s2) const
2613
{
2614
    // #0003921: Crash when opening document with datum point intersecting line and plane
2615
    //
2616
    // BRepExtrema_DistanceSS is used inside BRepExtrema_DistShapeShape and can cause
2617
    // a crash if the input shape is an unlimited face.
2618
    // So, when the input is a face and an edge then before checking for minimum distances
2619
    // try to determine intersection points.
2620
    try {
2621
        TopoDS_Shape face, edge;
2622
        if (s1.ShapeType() == TopAbs_FACE &&
2623
            s2.ShapeType() == TopAbs_EDGE) {
2624
            face = s1;
2625
            edge = s2;
2626
        }
2627
        else if (s1.ShapeType() == TopAbs_EDGE &&
2628
                 s2.ShapeType() == TopAbs_FACE) {
2629
            edge = s1;
2630
            face = s2;
2631
        }
2632

2633
        // edge and face
2634
        if (!edge.IsNull() && !face.IsNull()) {
2635

2636
            BRepAdaptor_Curve crv(TopoDS::Edge(edge));
2637

2638
            GeomAdaptor_Curve typedcrv;
2639
            try {
2640
                // Important note about BRepIntCurveSurface_Inter and GeomAdaptor_Curve
2641
                //
2642
                // A GeomAdaptor_Curve obtained directly from BRepAdaptor_Curve will lose the information
2643
                // about Location/orientation of the edge.
2644
                //
2645
                // That's why GeomAdaptor::MakeCurve() is used to create a new geometry with the
2646
                // transformation applied.
2647
                typedcrv.Load(GeomAdaptor::MakeCurve(crv));
2648
            }
2649
            catch (const Standard_DomainError&) {
2650
                Handle(Geom_Curve) curve = crv.Curve().Curve();
2651
                if (curve.IsNull()) {
2652
                    // Can this ever happen?
2653
                    typedcrv = crv.Curve();
2654
                }
2655
                else {
2656
                    curve = Handle(Geom_Curve)::DownCast(curve->Copy());
2657
                    curve->Transform(crv.Trsf());
2658
                    typedcrv.Load(curve);
2659
                }
2660
            }
2661

2662
            BRepIntCurveSurface_Inter intCS;
2663
            intCS.Init(face, typedcrv, Precision::Confusion());
2664
            std::vector<gp_Pnt> points;
2665
            for (; intCS.More(); intCS.Next()) {
2666
                gp_Pnt pnt = intCS.Pnt();
2667
                points.push_back(pnt);
2668
            }
2669

2670
            if (points.size() > 1)
2671
                Base::Console().Warning("AttachEnginePoint::calculateAttachedPlacement: proximity calculation gave %d solutions, ambiguous.\n", int(points.size()));
2672

2673
            // if an intersection is found return the first hit
2674
            // otherwise continue with BRepExtrema_DistShapeShape
2675
            if (!points.empty())
2676
                return points.front();
2677
        }
2678
    }
2679
    catch (const Standard_Failure&) {
2680
        // ignore
2681
    }
2682

2683
    BRepExtrema_DistShapeShape distancer (s1, s2);
2684
    if (!distancer.IsDone())
2685
        throw Base::ValueError("AttachEnginePoint::calculateAttachedPlacement: proximity calculation failed.");
2686
    if (distancer.NbSolution() > 1)
2687
        Base::Console().Warning("AttachEnginePoint::calculateAttachedPlacement: proximity calculation gave %i solutions, ambiguous.\n",int(distancer.NbSolution()));
2688

2689
    gp_Pnt p1 = distancer.PointOnShape1(1);
2690
    gp_Pnt p2 = distancer.PointOnShape2(1);
2691
    if (mmode == mm0ProximityPoint1)
2692
        return p1;
2693
    else
2694
        return p2;
2695
}
2696

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.