FreeCAD

Форк
0
/
PartFeature.cpp 
1924 строки · 72.2 Кб
1
/***************************************************************************
2
 *   Copyright (c) 2002 Jürgen Riegel <juergen.riegel@web.de>              *
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

25
#ifndef _PreComp_
26
# include <sstream>
27
# include <Bnd_Box.hxx>
28
# include <BRepAdaptor_Curve.hxx>
29
# include <BRepAlgoAPI_Fuse.hxx>
30
# include <BRepAlgoAPI_Common.hxx>
31
# include <BRepBndLib.hxx>
32
# include <BRepBuilderAPI_MakeEdge.hxx>
33
# include <BRepBuilderAPI_MakeFace.hxx>
34
# include <BRepBuilderAPI_MakeShape.hxx>
35
# include <BRepBuilderAPI_MakeVertex.hxx>
36
# include <BRepExtrema_DistShapeShape.hxx>
37
# include <BRepGProp.hxx>
38
# include <BRepGProp_Face.hxx>
39
# include <BRepIntCurveSurface_Inter.hxx>
40
# include <gce_MakeDir.hxx>
41
# include <gce_MakeLin.hxx>
42
# include <gp_Ax1.hxx>
43
# include <gp_Dir.hxx>
44
# include <gp_Pln.hxx>
45
# include <gp_Trsf.hxx>
46
# include <GProp_GProps.hxx>
47
# include <IntCurveSurface_IntersectionPoint.hxx>
48
# include <Precision.hxx>
49
# include <Standard_Failure.hxx>
50
# include <Standard_Version.hxx>
51
# include <TopExp.hxx>
52
# include <TopExp_Explorer.hxx>
53
# include <TopoDS.hxx>
54
# include <TopTools_IndexedMapOfShape.hxx>
55
# include <TopTools_ListIteratorOfListOfShape.hxx>
56
#endif
57

58
#include <App/Application.h>
59
#include <App/Document.h>
60
#include <App/FeaturePythonPyImp.h>
61
#include <App/GeoFeature.h>
62
#include <App/Link.h>
63
#include <App/GeoFeatureGroupExtension.h>
64
#include <App/ElementNamingUtils.h>
65
#include <App/Placement.h>
66
#include <App/OriginFeature.h>
67
#include <Base/Exception.h>
68
#include <Base/Placement.h>
69
#include <Base/Rotation.h>
70
#include <Base/Stream.h>
71
#include <Mod/Material/App/MaterialManager.h>
72

73
#include "Geometry.h"
74
#include "PartFeature.h"
75
#include "PartFeaturePy.h"
76
#include "PartPyCXX.h"
77
#include "TopoShapePy.h"
78
#include "Base/Tools.h"
79

80
using namespace Part;
81
namespace sp = std::placeholders;
82

83
FC_LOG_LEVEL_INIT("Part",true,true)
84

85
PROPERTY_SOURCE(Part::Feature, App::GeoFeature)
86

87

88
Feature::Feature()
89
{
90
    ADD_PROPERTY(Shape, (TopoDS_Shape()));
91
    auto mat = Materials::MaterialManager::defaultMaterial();
92
    // ADD_PROPERTY_TYPE(ShapeMaterial, (mat), osgroup, App::Prop_None, "Shape material");
93
    ADD_PROPERTY(ShapeMaterial, (*mat));
94
}
95

96
Feature::~Feature() = default;
97

98
short Feature::mustExecute() const
99
{
100
    return GeoFeature::mustExecute();
101
}
102

103
App::DocumentObjectExecReturn *Feature::recompute()
104
{
105
    try {
106
        return App::GeoFeature::recompute();
107
    }
108
    catch (Standard_Failure& e) {
109

110
        App::DocumentObjectExecReturn* ret = new App::DocumentObjectExecReturn(e.GetMessageString());
111
        if (ret->Why.empty()) ret->Why = "Unknown OCC exception";
112
        return ret;
113
    }
114
}
115

116
App::DocumentObjectExecReturn *Feature::execute()
117
{
118
    this->Shape.touch();
119
    return GeoFeature::execute();
120
}
121

122
PyObject *Feature::getPyObject()
123
{
124
    if (PythonObject.is(Py::_None())){
125
        // ref counter is set to 1
126
        PythonObject = Py::Object(new PartFeaturePy(this),true);
127
    }
128
    return Py::new_reference_to(PythonObject);
129
}
130

131
/**
132
 * Override getElementName to support the Export type.  Other calls are passed to the original
133
 * method
134
 * @param name The name to search for, or if non existent, name of current Feature is returned
135
 * @param type An element type name.
136
 * @return a struct with the newName and oldName. New element name may be empty.
137
 */
138
App::ElementNamePair Feature::getElementName(const char* name,
139
                                                ElementNameType type) const
140
{
141
    if (type != ElementNameType::Export) {
142
        return App::GeoFeature::getElementName(name, type);
143
    }
144

145
    // This function is overridden to provide higher level shape topo names that
146
    // are generated on demand, e.g. Wire, Shell, Solid, etc.
147

148
    auto prop = Base::freecad_dynamic_cast<PropertyPartShape>(getPropertyOfGeometry());
149
    if (!prop) {
150
        return App::GeoFeature::getElementName(name, type);
151
    }
152
    return getExportElementName(prop->getShape(), name);
153
}
154

155
App::ElementNamePair Feature::getExportElementName(TopoShape shape,
156
                                                                  const char* name) const
157
{
158
    Data::MappedElement mapped = shape.getElementName(name);
159
    auto res = shape.shapeTypeAndIndex(mapped.index);
160
    static const int MinLowerTopoNames = 3;
161
    static const int MaxLowerTopoNames = 10;
162
    if (res.second && !mapped.name) {
163
        // Here means valid index name, but no mapped name, check to see if
164
        // we shall generate the high level topo name.
165
        //
166
        // The general idea of the algorithm is to find the minimum number of
167
        // lower elements that can identify the given higher element, and
168
        // combine their names to generate the name for the higher element.
169
        //
170
        // In theory, all it takes to find one lower element that only appear
171
        // in the given higher element. To make the algorithm more robust
172
        // against model changes, we shall take minimum MinLowerTopoNames lower
173
        // elements.
174
        //
175
        // On the other hand, it may be possible to take too many elements for
176
        // disambiguation. We shall limit to maximum MaxLowerTopoNames. If the
177
        // chosen elements are not enough to disambiguate the higher element,
178
        // we'll include an index for disambiguation.
179

180
        auto subshape = shape.getSubTopoShape(res.first, res.second, true);
181
        TopAbs_ShapeEnum lower;
182
        Data::IndexedName idxName;
183
        if (!subshape.isNull()) {
184
            switch (res.first) {
185
                case TopAbs_WIRE:
186
                    lower = TopAbs_EDGE;
187
                    idxName = Data::IndexedName::fromConst("Edge", 1);
188
                    break;
189
                case TopAbs_SHELL:
190
                case TopAbs_SOLID:
191
                case TopAbs_COMPOUND:
192
                case TopAbs_COMPSOLID:
193
                    lower = TopAbs_FACE;
194
                    idxName = Data::IndexedName::fromConst("Face", 1);
195
                    break;
196
                default:
197
                    lower = TopAbs_SHAPE;
198
            }
199
            if (lower != TopAbs_SHAPE) {
200
                typedef std::pair<size_t, std::vector<int>> NameEntry;
201
                std::vector<NameEntry> indices;
202
                std::vector<Data::MappedName> names;
203
                std::vector<int> ancestors;
204
                int count = 0;
205
                for (auto& ss : subshape.getSubTopoShapes(lower)) {
206
                    auto name = ss.getMappedName(idxName);
207
                    if (!name) {
208
                        continue;
209
                    }
210
                    indices.emplace_back(name.size(),
211
                                         shape.findAncestors(ss.getShape(), res.first));
212
                    names.push_back(name);
213
                    if (indices.back().second.size() == 1 && ++count >= MinLowerTopoNames) {
214
                        break;
215
                    }
216
                }
217

218
                if (names.size() >= MaxLowerTopoNames) {
219
                    std::stable_sort(indices.begin(),
220
                                     indices.end(),
221
                                     [](const NameEntry& a, const NameEntry& b) {
222
                                         return a.second.size() < b.second.size();
223
                                     });
224
                    std::vector<Data::MappedName> sorted;
225
                    auto pos = 0;
226
                    sorted.reserve(names.size());
227
                    for (auto& v : indices) {
228
                        size_t size = ancestors.size();
229
                        if (size == 0) {
230
                            ancestors = v.second;
231
                        }
232
                        else if (size > 1) {
233
                            for (auto it = ancestors.begin(); it != ancestors.end();) {
234
                                if (std::find(v.second.begin(), v.second.end(), *it)
235
                                    == v.second.end()) {
236
                                    it = ancestors.erase(it);
237
                                    if (ancestors.size() == 1) {
238
                                        break;
239
                                    }
240
                                }
241
                                else {
242
                                    ++it;
243
                                }
244
                            }
245
                        }
246
                        auto itPos = sorted.end();
247
                        if (size == 1 || size != ancestors.size()) {
248
                            itPos = sorted.begin() + pos;
249
                            ++pos;
250
                        }
251
                        sorted.insert(itPos, names[v.first]);
252
                        if (size == 1 && sorted.size() >= MinLowerTopoNames) {
253
                            break;
254
                        }
255
                    }
256
                }
257

258
                names.resize(std::min((int)names.size(), MaxLowerTopoNames));
259
                if (names.size()) {
260
                    std::string op;
261
                    if (ancestors.size() > 1) {
262
                        // The current chosen elements are not enough to
263
                        // identify the higher element, generate an index for
264
                        // disambiguation.
265
                        auto it = std::find(ancestors.begin(), ancestors.end(), res.second);
266
                        if (it == ancestors.end()) {
267
                            assert(0 && "ancestor not found");  // this shouldn't happen
268
                        }
269
                        else {
270
                            op = Data::POSTFIX_INDEX + std::to_string(it - ancestors.begin());
271
                        }
272
                    }
273

274
                    // Note: setting names to shape will change its underlying
275
                    // shared element name table. This actually violates the
276
                    // const'ness of this function.
277
                    //
278
                    // To be const correct, we should have made the element
279
                    // name table to be implicit sharing (i.e. copy on change).
280
                    //
281
                    // Not sure if there is any side effect of indirectly
282
                    // change the element map inside the Shape property without
283
                    // recording the change in undo stack.
284
                    //
285
                    mapped.name = shape.setElementComboName(mapped.index,
286
                                                            names,
287
                                                            mapped.index.getType(),
288
                                                            op.c_str());
289
                }
290
            }
291
        }
292
    }
293
    else if (!res.second && mapped.name) {
294
        const char* dot = strchr(name, '.');
295
        if (dot) {
296
            ++dot;
297
            // Here means valid mapped name, but cannot find the corresponding
298
            // indexed name. This usually means the model has been changed. The
299
            // original indexed name is usually appended to the mapped name
300
            // separated by a dot. We use it as a clue to decode the combo name
301
            // set above, and try to single out one sub shape that has all the
302
            // lower elements encoded in the combo name. But since we don't
303
            // always use all the lower elements for encoding, this can only be
304
            // consider a heuristics.
305
            if (Data::hasMissingElement(dot)) {
306
                dot += strlen(Data::MISSING_PREFIX);
307
            }
308
            std::pair<TopAbs_ShapeEnum, int> occindex = shape.shapeTypeAndIndex(dot);
309
            if (occindex.second > 0) {
310
                auto idxName = Data::IndexedName::fromConst(shape.shapeName(occindex.first).c_str(),
311
                                                            occindex.second);
312
                std::string postfix;
313
                auto names =
314
                    shape.decodeElementComboName(idxName, mapped.name, idxName.getType(), &postfix);
315
                std::vector<int> ancestors;
316
                // TODO:  if names.empty() then the existing heuristic has failed to find anything
317
                //   and we're going to flag this element as missing.  This is the place to add
318
                //   heuristics as we develop them.
319
                for (auto& name : names) {
320
                    auto index = shape.getIndexedName(name);
321
                    if (!index) {
322
                        ancestors.clear();
323
                        break;
324
                    }
325
                    auto oidx = shape.shapeTypeAndIndex(index);
326
                    auto subshape = shape.getSubShape(oidx.first, oidx.second);
327
                    if (subshape.IsNull()) {
328
                        ancestors.clear();
329
                        break;
330
                    }
331
                    auto current = shape.findAncestors(subshape, occindex.first);
332
                    if (ancestors.empty()) {
333
                        ancestors = std::move(current);
334
                    }
335
                    else {
336
                        for (auto it = ancestors.begin(); it != ancestors.end();) {
337
                            if (std::find(current.begin(), current.end(), *it) == current.end()) {
338
                                it = ancestors.erase(it);
339
                            }
340
                            else {
341
                                ++it;
342
                            }
343
                        }
344
                        if (ancestors.empty()) {  // model changed beyond recognition, bail!
345
                            break;
346
                        }
347
                    }
348
                }
349
                if (ancestors.size() > 1 && boost::starts_with(postfix, Data::POSTFIX_INDEX)) {
350
                    std::istringstream iss(postfix.c_str() + strlen(Data::POSTFIX_INDEX));
351
                    int idx;
352
                    if (iss >> idx && idx >= 0 && idx < (int)ancestors.size()) {
353
                        ancestors.resize(1, ancestors[idx]);
354
                    }
355
                }
356
                if (ancestors.size() == 1) {
357
                    idxName.setIndex(ancestors.front());
358
                    mapped.index = idxName;
359
                }
360
            }
361
        }
362
    }
363
    return App::GeoFeature::_getElementName(name, mapped);
364
}
365

366
App::DocumentObject* Feature::getSubObject(const char* subname,
367
                                           PyObject** pyObj,
368
                                           Base::Matrix4D* pmat,
369
                                           bool transform,
370
                                           int depth) const
371
{
372
    while(subname && *subname=='.') ++subname; // skip leading .
373

374
    // having '.' inside subname means it is referencing some children object,
375
    // instead of any sub-element from ourself
376
    if (subname && !Data::isMappedElement(subname) && strchr(subname, '.')) {
377
        return App::DocumentObject::getSubObject(subname, pyObj, pmat, transform, depth);
378
    }
379

380
    Base::Matrix4D _mat;
381
    auto& mat = pmat ? *pmat : _mat;
382
    if (transform) {
383
        mat *= Placement.getValue().toMatrix();
384
    }
385

386
    if (!pyObj) {
387
        // TopoShape::hasSubShape is kind of slow, let's cut outself some slack here.
388
        return const_cast<Feature*>(this);
389
    }
390

391
    try {
392
        TopoShape ts(Shape.getShape());
393
        bool doTransform = mat != ts.getTransform();
394
        if (doTransform) {
395
            ts.setShape(ts.getShape().Located(TopLoc_Location()), false);
396
        }
397
        if (subname && *subname && !ts.isNull()) {
398
            ts = ts.getSubTopoShape(subname,true);
399
        }
400
        if (doTransform && !ts.isNull()) {
401
            static int sCopy = -1;
402
            if (sCopy < 0) {
403
                ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
404
                    "User parameter:BaseApp/Preferences/Mod/Part/General");
405
                sCopy = hGrp->GetBool("CopySubShape", false) ? 1 : 0;
406
            }
407
            bool copy = sCopy ? true : false;
408
            if (!copy) {
409
                // Work around OCC bug on transforming circular edge with an
410
                // offset surface. The bug probably affect other shape type,
411
                // too.
412
                TopExp_Explorer exp(ts.getShape(), TopAbs_EDGE);
413
                if (exp.More()) {
414
                    auto edge = TopoDS::Edge(exp.Current());
415
                    exp.Next();
416
                    if (!exp.More()) {
417
                        BRepAdaptor_Curve curve(edge);
418
                        copy = curve.GetType() == GeomAbs_Circle;
419
                    }
420
                }
421
            }
422
            ts.transformShape(mat, copy, true);
423
        }
424
        *pyObj = Py::new_reference_to(shape2pyshape(ts));
425
        return const_cast<Feature*>(this);
426
    }
427
    catch (Standard_Failure& e) {
428
        // FIXME: Do not handle the exception here because it leads to a flood of irrelevant and
429
        // annoying error messages.
430
        // For example: https://forum.freecad.org/viewtopic.php?f=19&t=42216
431
        // Instead either raise a sub-class of Base::Exception and let it handle by the calling
432
        // instance or do simply nothing. For now the error message is degraded to a log message.
433
        std::ostringstream str;
434
        Standard_CString msg = e.GetMessageString();
435

436
        // Avoid name mangling
437
        str << e.DynamicType()->get_type_name() << " ";
438

439
        if (msg) {
440
            str << msg;
441
        }
442
        else {
443
            str << "No OCCT Exception Message";
444
        }
445
        str << ": " << getFullName();
446
        if (subname) {
447
            str << '.' << subname;
448
        }
449
        FC_LOG(str.str());
450
        return nullptr;
451
    }
452
}
453

454
static std::vector<std::pair<long, Data::MappedName>> getElementSource(App::DocumentObject* owner,
455
                                                                       TopoShape shape,
456
                                                                       const Data::MappedName& name,
457
                                                                       char type)
458
{
459
    std::set<std::pair<App::Document*, long>> tagSet;
460
    std::vector<std::pair<long, Data::MappedName>> ret;
461
    ret.emplace_back(0, name);
462
    int depth = 0;
463
    while (1) {
464
        Data::MappedName original;
465
        std::vector<Data::MappedName> history;
466
        // It is possible the name does not belong to the shape, e.g. when user
467
        // changes modeling order in PartDesign. So we try to assign the
468
        // document hasher here in case getElementHistory() needs to de-hash
469
        if (!shape.Hasher && owner) {
470
            shape.Hasher = owner->getDocument()->getStringHasher();
471
        }
472
        long tag = shape.getElementHistory(ret.back().second, &original, &history);
473
        if (!tag) {
474
            break;
475
        }
476
        auto obj = owner;
477
        App::Document* doc = nullptr;
478
        if (owner) {
479
            doc = owner->getDocument();
480
            for (;; ++depth) {
481
                auto linked = owner->getLinkedObject(false, nullptr, false, depth);
482
                if (linked == owner) {
483
                    break;
484
                }
485
                owner = linked;
486
                if (owner->getDocument() != doc) {
487
                    doc = owner->getDocument();
488
                    break;
489
                }
490
            }
491
            if (owner->isDerivedFrom(App::GeoFeature::getClassTypeId())) {
492
                auto ownerGeoFeature =
493
                    static_cast<App::GeoFeature*>(owner)->getElementOwner(ret.back().second);
494
                if (ownerGeoFeature) {
495
                    doc = ownerGeoFeature->getDocument();
496
                }
497
            }
498
            obj = doc->getObjectByID(tag < 0 ? -tag : tag);
499
            if (type) {
500
                for (auto& hist : history) {
501
                    if (shape.elementType(hist) != type) {
502
                        return ret;
503
                    }
504
                }
505
            }
506
        }
507
        owner = 0;
508
        if (!obj) {
509
            // Object maybe deleted, but it is still possible to extract the
510
            // source element name from hasher table.
511
            shape.setShape(TopoDS_Shape());
512
            doc = nullptr;
513
        }
514
        else {
515
            shape = Part::Feature::getTopoShape(obj, 0, false, 0, &owner);
516
        }
517
        if (type && shape.elementType(original) != type) {
518
            break;
519
        }
520

521
        if (std::abs(tag) != ret.back().first && !tagSet.insert(std::make_pair(doc, tag)).second) {
522
            // Because an object might be deleted, which may be a link/binder
523
            // that points to an external object that contain element name
524
            // using external hash table. We shall prepare for circular element
525
            // map due to looking up in the wrong table.
526
            if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
527
                FC_WARN("circular element mapping");
528
            }
529
            break;
530
        }
531
        ret.emplace_back(tag, original);
532
    }
533
    return ret;
534
}
535

536
std::list<Data::HistoryItem> Feature::getElementHistory(App::DocumentObject* feature,
537
                                                        const char* name,
538
                                                        bool recursive,
539
                                                        bool sameType)
540
{
541
    std::list<Data::HistoryItem> ret;
542
    TopoShape shape = getTopoShape(feature);
543
    Data::IndexedName idx(name);
544
    Data::MappedName element;
545
    Data::MappedName prevElement;
546
    if (idx) {
547
        element = shape.getMappedName(idx, true);
548
    }
549
    else if (Data::isMappedElement(name)) {
550
        element = Data::MappedName(Data::newElementName(name));
551
    }
552
    else {
553
        element = Data::MappedName(name);
554
    }
555
    char element_type = 0;
556
    if (sameType) {
557
        element_type = shape.elementType(element);
558
    }
559
    int depth = 0;
560
    do {
561
        Data::MappedName original;
562
        ret.emplace_back(feature, element);
563
        long tag = shape.getElementHistory(element, &original, &ret.back().intermediates);
564

565
        ret.back().index = shape.getIndexedName(element);
566
        if (!ret.back().index && prevElement) {
567
            ret.back().index = shape.getIndexedName(prevElement);
568
            if (ret.back().index) {
569
                ret.back().intermediates.insert(ret.back().intermediates.begin(), element);
570
                ret.back().element = prevElement;
571
            }
572
        }
573
        if (ret.back().intermediates.size()) {
574
            prevElement = ret.back().intermediates.back();
575
        }
576
        else {
577
            prevElement = Data::MappedName();
578
        }
579

580
        App::DocumentObject* obj = nullptr;
581
        if (tag) {
582
            App::Document* doc = feature->getDocument();
583
            for (;; ++depth) {
584
                auto linked = feature->getLinkedObject(false, nullptr, false, depth);
585
                if (linked == feature) {
586
                    break;
587
                }
588
                feature = linked;
589
                if (feature->getDocument() != doc) {
590
                    doc = feature->getDocument();
591
                    break;
592
                }
593
            }
594
            if (feature->isDerivedFrom(App::GeoFeature::getClassTypeId())) {
595
                auto ownerGeoFeature =
596
                    static_cast<App::GeoFeature*>(feature)->getElementOwner(element);
597
                if (ownerGeoFeature) {
598
                    doc = ownerGeoFeature->getDocument();
599
                }
600
            }
601
            obj = doc->getObjectByID(std::abs(tag));
602
        }
603
        if (!recursive) {
604
            ret.emplace_back(obj, original);
605
            ret.back().tag = tag;
606
            return ret;
607
        }
608
        if (!obj) {
609
            break;
610
        }
611
        if (element_type) {
612
            for (auto& hist : ret.back().intermediates) {
613
                if (shape.elementType(hist) != element_type) {
614
                    return ret;
615
                }
616
            }
617
        }
618
        feature = obj;
619
        shape = Feature::getTopoShape(feature);
620
        element = original;
621
        if (element_type && shape.elementType(original) != element_type) {
622
            break;
623
        }
624
    } while (feature);
625
    return ret;
626
}
627

628
QVector<Data::MappedElement> Feature::getElementFromSource(App::DocumentObject* obj,
629
                                                           const char* subname,
630
                                                           App::DocumentObject* src,
631
                                                           const char* srcSub,
632
                                                           bool single)
633
{
634
    QVector<Data::MappedElement> res;
635
    if (!obj || !src) {
636
        return res;
637
    }
638

639
    auto shape = getTopoShape(obj,
640
                              subname,
641
                              false,
642
                              nullptr,
643
                              nullptr,
644
                              true,
645
                              /*transform = */ false);
646
    App::DocumentObject* owner = nullptr;
647
    auto srcShape = getTopoShape(src, srcSub, false, nullptr, &owner);
648
    int tagChanges;
649
    Data::MappedElement element;
650
    Data::IndexedName checkingSubname;
651
    std::string sub = Data::noElementName(subname);
652
    auto checkHistory = [&](const Data::MappedName& name, size_t, long, long tag) {
653
        if (std::abs(tag) == owner->getID()) {
654
            if (!tagChanges) {
655
                tagChanges = 1;
656
            }
657
        }
658
        else if (tagChanges && ++tagChanges > 3) {
659
            // Once we found the tag, trace no more than 2 addition tag changes
660
            // to limited the search depth.
661
            return true;
662
        }
663
        if (name == element.name) {
664
            App::ElementNamePair objElement;
665
            std::size_t len = sub.size();
666
            checkingSubname.appendToStringBuffer(sub);
667
            GeoFeature::resolveElement(obj, sub.c_str(), objElement);
668
            sub.resize(len);
669
            if (objElement.oldName.size()) {
670
                res.push_back(Data::MappedElement(Data::MappedName(objElement.newName),
671
                                                  Data::IndexedName(objElement.oldName.c_str())));
672
                return true;
673
            }
674
        }
675
        return false;
676
    };
677

678
    // obtain both the old and new style element name
679
    App::ElementNamePair objElement;
680
    GeoFeature::resolveElement(src, srcSub, objElement, false);
681

682
    element.index = Data::IndexedName(objElement.oldName.c_str());
683
    if (!objElement.newName.empty()) {
684
        // Strip prefix and indexed based name at the tail of the new style element name
685
        auto mappedName = Data::newElementName(objElement.newName.c_str());
686
        auto mapped = Data::isMappedElement(mappedName.c_str());
687
        if (mapped) {
688
            element.name = Data::MappedName(mapped);
689
        }
690
    }
691

692
    // Translate the element name for datum
693
    if (objElement.oldName == "Plane") {
694
        objElement.oldName = "Face1";
695
    }
696
    else if (objElement.oldName == "Line") {
697
        objElement.oldName = "Edge1";
698
    }
699
    else if (objElement.oldName == "Point") {
700
        objElement.oldName = "Vertex1";
701
    }
702

703
    // Use the old style name to obtain the shape type
704
    auto type = TopoShape::shapeType(Data::findElementName(objElement.oldName.c_str()));
705
    // If the given shape has the same number of sub shapes as the source (e.g.
706
    // a compound operation), then take a shortcut and assume the element index
707
    // remains the same. But we still need to trace the shape history to
708
    // confirm.
709
    if (type != TopAbs_SHAPE && element.name
710
        && shape.countSubShapes(type) == srcShape.countSubShapes(type)) {
711
        tagChanges = 0;
712
        checkingSubname = element.index;
713
        auto mapped = shape.getMappedName(element.index);
714
        shape.traceElement(mapped, checkHistory);
715
        if (res.size()) {
716
            return res;
717
        }
718
    }
719

720
    // Try geometry search first
721
    auto subShape = srcShape.getSubShape(objElement.oldName.c_str());
722
    std::vector<std::string> names;
723
    shape.findSubShapesWithSharedVertex(subShape, &names);
724
    if (names.size()) {
725
        for (auto& name : names) {
726
            Data::MappedElement e;
727
            e.index = Data::IndexedName(name.c_str());
728
            e.name = shape.getMappedName(e.index, true);
729
            res.append(e);
730
            if (single) {
731
                break;
732
            }
733
        }
734
        return res;
735
    }
736

737
    if (!element.name || type == TopAbs_SHAPE) {
738
        return res;
739
    }
740

741
    // No shortcut, need to search every element of the same type. This may
742
    // result in multiple matches, e.g. a compound of array of the same
743
    // instance.
744
    const char* shapetype = TopoShape::shapeName(type).c_str();
745
    for (int i = 0, count = shape.countSubShapes(type); i < count; ++i) {
746
        checkingSubname = Data::IndexedName::fromConst(shapetype, i + 1);
747
        auto mapped = shape.getMappedName(checkingSubname);
748
        tagChanges = 0;
749
        shape.traceElement(mapped, checkHistory);
750
        if (single && res.size()) {
751
            break;
752
        }
753
    }
754
    return res;
755
}
756

757
QVector<Data::MappedElement> Feature::getRelatedElements(App::DocumentObject* obj,
758
                                                         const char* name,
759
                                                         HistoryTraceType sameType,
760
                                                         bool withCache)
761
{
762
    auto owner = obj;
763
    auto shape = getTopoShape(obj, nullptr, false, 0, &owner);
764
    QVector<Data::MappedElement> ret;
765
    Data::MappedElement mapped = shape.getElementName(name);
766
    if (!mapped.name) {
767
        return ret;
768
    }
769
    if (withCache && shape.getRelatedElementsCached(mapped.name, sameType, ret)) {
770
        return ret;
771
    }
772

773
    char element_type = shape.elementType(mapped.name);
774
    TopAbs_ShapeEnum type = TopoShape::shapeType(element_type, true);
775
    if (type == TopAbs_SHAPE) {
776
        return ret;
777
    }
778

779
    auto source =
780
        getElementSource(owner,
781
                         shape,
782
                         mapped.name,
783
                         sameType == HistoryTraceType::followTypeChange ? element_type : 0);
784
    for (auto& src : source) {
785
        auto srcIndex = shape.getIndexedName(src.second);
786
        if (srcIndex) {
787
            ret.push_back(Data::MappedElement(src.second, srcIndex));
788
            shape.cacheRelatedElements(mapped.name, sameType, ret);
789
            return ret;
790
        }
791
    }
792

793
    std::map<int, QVector<Data::MappedElement>> retMap;
794

795
    const char* shapetype = TopoShape::shapeName(type).c_str();
796
    std::ostringstream ss;
797
    for (size_t i = 1; i <= shape.countSubShapes(type); ++i) {
798
        Data::MappedElement related;
799
        related.index = Data::IndexedName::fromConst(shapetype, i);
800
        related.name = shape.getMappedName(related.index);
801
        if (!related.name) {
802
            continue;
803
        }
804
        auto src =
805
            getElementSource(owner,
806
                             shape,
807
                             related.name,
808
                             sameType == HistoryTraceType::followTypeChange ? element_type : 0);
809
        int idx = (int)source.size() - 1;
810
        for (auto rit = src.rbegin(); idx >= 0 && rit != src.rend(); ++rit, --idx) {
811
            // TODO: shall we ignore source tag when comparing? It could cause
812
            // matching unrelated element, but it does help dealing with feature
813
            // recording in PartDesign::Body.
814
            if (rit->second != source[idx].second) {
815
                ++idx;
816
                break;
817
            }
818
        }
819
        if (idx < (int)source.size()) {
820
            retMap[idx].push_back(related);
821
        }
822
    }
823
    if (retMap.size()) {
824
        ret = retMap.begin()->second;
825
    }
826
    shape.cacheRelatedElements(mapped.name, sameType, ret);
827
    return ret;
828
}
829

830
TopoDS_Shape Feature::getShape(const App::DocumentObject *obj, const char *subname,
831
        bool needSubElement, Base::Matrix4D *pmat, App::DocumentObject **powner,
832
        bool resolveLink, bool transform)
833
{
834
    return getTopoShape(obj,subname,needSubElement,pmat,powner,resolveLink,transform,true).getShape();
835
}
836

837
App::Material Feature::getMaterialAppearance() const
838
{
839
    return ShapeMaterial.getValue().getMaterialAppearance();
840
}
841

842
void Feature::setMaterialAppearance(const App::Material& material)
843
{
844
    ShapeMaterial.setValue(material);
845
}
846

847
// Toponaming project March 2024:  This method should be going away when we get to the python layer.
848
void Feature::clearShapeCache() {
849
//    _ShapeCache.cache.clear();
850
}
851

852
static TopoShape _getTopoShape(const App::DocumentObject* obj,
853
                               const char* subname,
854
                               bool needSubElement,
855
                               Base::Matrix4D* pmat,
856
                               App::DocumentObject** powner,
857
                               bool resolveLink,
858
                               bool noElementMap,
859
                               const std::set<std::string> hiddens,
860
                               const App::DocumentObject* lastLink)
861

862
{
863
    TopoShape shape;
864

865
    if (!obj) {
866
        return shape;
867
    }
868

869
    PyObject* pyobj = nullptr;
870
    Base::Matrix4D mat;
871
    if (powner) {
872
        *powner = nullptr;
873
    }
874

875
    std::string _subname;
876
    auto subelement = Data::findElementName(subname);
877
    if (!needSubElement && subname) {
878
        // strip out element name if not needed
879
        if (subelement && *subelement) {
880
            _subname = std::string(subname, subelement);
881
            subname = _subname.c_str();
882
        }
883
    }
884

885
    auto canCache = [&](const App::DocumentObject* o) {
886
        return !lastLink || (hiddens.empty() && !App::GeoFeatureGroupExtension::isNonGeoGroup(o));
887
    };
888

889
    if (canCache(obj) && PropertyShapeCache::getShape(obj, shape, subname)) {
890
        if (noElementMap) {
891
            shape.resetElementMap();
892
            shape.Tag = 0;
893
            if ( shape.Hasher ) {
894
                shape.Hasher = nullptr;
895
            }
896
        }
897
    }
898

899
    App::DocumentObject* linked = nullptr;
900
    App::DocumentObject* owner = nullptr;
901
    Base::Matrix4D linkMat;
902
    App::StringHasherRef hasher;
903
    long tag;
904
    {
905
        Base::PyGILStateLocker lock;
906
        owner = obj->getSubObject(subname, shape.isNull() ? &pyobj : nullptr, &mat, false);
907
        if (!owner) {
908
            return shape;
909
        }
910
        tag = owner->getID();
911
        hasher = owner->getDocument()->getStringHasher();
912
        linked = owner->getLinkedObject(true, &linkMat, false);
913
        if (pmat) {
914
            if (resolveLink && obj != owner) {
915
                *pmat = mat * linkMat;
916
            }
917
            else {
918
                *pmat = mat;
919
            }
920
        }
921
        if (!linked) {
922
            linked = owner;
923
        }
924
        if (powner) {
925
            *powner = resolveLink ? linked : owner;
926
        }
927

928
        if (!shape.isNull()) {
929
            return shape;
930
        }
931

932
        if (pyobj && PyObject_TypeCheck(pyobj, &TopoShapePy::Type)) {
933
            shape = *static_cast<TopoShapePy*>(pyobj)->getTopoShapePtr();
934
            if (!shape.isNull()) {
935
                if (canCache(obj)) {
936
                    if (obj->getDocument() != linked->getDocument()
937
                        || mat.hasScale() != Base::ScaleType::NoScaling
938
                        || (linked != owner && linkMat.hasScale() != Base::ScaleType::NoScaling)) {
939
                        PropertyShapeCache::setShape(obj, shape, subname);
940
                    }
941
                }
942
                if (noElementMap) {
943
                    shape.resetElementMap();
944
                    shape.Tag = 0;
945
                    if ( shape.Hasher ) {
946
                        shape.Hasher = nullptr;
947
                    }
948
                }
949
                Py_DECREF(pyobj);
950
                return shape;
951
            }
952
        }
953
        else {
954
            if (linked->isDerivedFrom(App::Line::getClassTypeId())) {
955
                static TopoDS_Shape _shape;
956
                if (_shape.IsNull()) {
957
                    BRepBuilderAPI_MakeEdge builder(gp_Lin(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0)));
958
                    _shape = builder.Shape();
959
                    _shape.Infinite(Standard_True);
960
                }
961
                shape = TopoShape(tag, hasher, _shape);
962
            }
963
            else if (linked->isDerivedFrom(App::Plane::getClassTypeId())) {
964
                static TopoDS_Shape _shape;
965
                if (_shape.IsNull()) {
966
                    BRepBuilderAPI_MakeFace builder(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
967
                    _shape = builder.Shape();
968
                    _shape.Infinite(Standard_True);
969
                }
970
                shape = TopoShape(tag, hasher, _shape);
971
            }
972
            else if (linked->isDerivedFrom(App::Placement::getClassTypeId())) {
973
                auto element = Data::findElementName(subname);
974
                if (element) {
975
                    if (boost::iequals("x", element) || boost::iequals("x-axis", element)
976
                        || boost::iequals("y", element) || boost::iequals("y-axis", element)
977
                        || boost::iequals("z", element) || boost::iequals("z-axis", element)) {
978
                        static TopoDS_Shape _shape;
979
                        if (_shape.IsNull()) {
980
                            BRepBuilderAPI_MakeEdge builder(
981
                                gp_Lin(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
982
                            _shape = builder.Shape();
983
                            _shape.Infinite(Standard_True);
984
                        }
985
                        shape = TopoShape(tag, hasher, _shape);
986
                    }
987
                    else if (boost::iequals("o", element) || boost::iequals("origin", element)) {
988
                        static TopoDS_Shape _shape;
989
                        if (_shape.IsNull()) {
990
                            BRepBuilderAPI_MakeVertex builder(gp_Pnt(0, 0, 0));
991
                            _shape = builder.Shape();
992
                            _shape.Infinite(Standard_True);
993
                        }
994
                        shape = TopoShape(tag, hasher, _shape);
995
                    }
996
                }
997
                if (shape.isNull()) {
998
                    static TopoDS_Shape _shape;
999
                    if (_shape.IsNull()) {
1000
                        BRepBuilderAPI_MakeFace builder(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
1001
                        _shape = builder.Shape();
1002
                        _shape.Infinite(Standard_True);
1003
                    }
1004
                    shape = TopoShape(tag, hasher, _shape);
1005
                }
1006
            }
1007
            if (!shape.isNull()) {
1008
                shape.transformShape(mat * linkMat, false, true);
1009
                return shape;
1010
            }
1011
        }
1012

1013
        Py_XDECREF(pyobj);
1014
    }
1015

1016
    // nothing can be done if there is sub-element references
1017
    if (needSubElement && subelement && *subelement) {
1018
        return shape;
1019
    }
1020

1021
    if (obj != owner) {
1022
        if (canCache(owner) && PropertyShapeCache::getShape(owner, shape)) {
1023
            bool scaled = shape.transformShape(mat, false, true);
1024
            if (owner->getDocument() != obj->getDocument()) {
1025
                shape.reTagElementMap(obj->getID(), obj->getDocument()->getStringHasher());
1026
                PropertyShapeCache::setShape(obj, shape, subname);
1027
            }
1028
            else if (scaled
1029
                     || (linked != owner && linkMat.hasScale() != Base::ScaleType::NoScaling)) {
1030
                PropertyShapeCache::setShape(obj, shape, subname);
1031
            }
1032
        }
1033
        if (!shape.isNull()) {
1034
            if (noElementMap) {
1035
                shape.resetElementMap();
1036
                shape.Tag = 0;
1037
                if ( shape.Hasher) {
1038
                    shape.Hasher = nullptr;
1039
                }
1040
            }
1041
            return shape;
1042
        }
1043
    }
1044

1045
    bool cacheable = true;
1046

1047
    auto link = owner->getExtensionByType<App::LinkBaseExtension>(true);
1048
    if (owner != linked
1049
        && (!link || (!link->_ChildCache.getSize() && link->getSubElements().size() <= 1))) {
1050
        // if there is a linked object, and there is no child cache (which is used
1051
        // for special handling of plain group), obtain shape from the linked object
1052
        shape = Feature::getTopoShape(linked, nullptr, false, nullptr, nullptr, false, false);
1053
        if (shape.isNull()) {
1054
            return shape;
1055
        }
1056
        if (owner == obj) {
1057
            shape.transformShape(mat * linkMat, false, true);
1058
        }
1059
        else {
1060
            shape.transformShape(linkMat, false, true);
1061
        }
1062
        shape.reTagElementMap(tag, hasher);
1063
    }
1064
    else {
1065
        // Construct a compound of sub objects
1066
        std::vector<TopoShape> shapes;
1067

1068
        // Acceleration for link array. Unlike non-array link, a link array does
1069
        // not return the linked object when calling getLinkedObject().
1070
        // Therefore, it should be handled here.
1071
        TopoShape baseShape;
1072
        Base::Matrix4D baseMat;
1073
        std::string op;
1074
        if (link && link->getElementCountValue()) {
1075
            linked = link->getTrueLinkedObject(false, &baseMat);
1076
            if (linked && linked != owner) {
1077
                baseShape =
1078
                    Feature::getTopoShape(linked, nullptr, false, nullptr, nullptr, false, false);
1079
                if (!link->getShowElementValue()) {
1080
                    baseShape.reTagElementMap(owner->getID(),
1081
                                              owner->getDocument()->getStringHasher());
1082
                }
1083
            }
1084
        }
1085
        for (auto& sub : owner->getSubObjects()) {
1086
            if (sub.empty()) {
1087
                continue;
1088
            }
1089
            int visible;
1090
            std::string childName;
1091
            App::DocumentObject* parent = nullptr;
1092
            Base::Matrix4D mat = baseMat;
1093
            App::DocumentObject* subObj = nullptr;
1094
            if (sub.find('.') == std::string::npos) {
1095
                visible = 1;
1096
            }
1097
            else {
1098
                subObj =
1099
                    owner->resolve(sub.c_str(), &parent, &childName, nullptr, nullptr, &mat, false);
1100
                if (!parent || !subObj) {
1101
                    continue;
1102
                }
1103
                if (lastLink && App::GeoFeatureGroupExtension::isNonGeoGroup(parent)) {
1104
                    visible = lastLink->isElementVisible(childName.c_str());
1105
                }
1106
                else {
1107
                    visible = parent->isElementVisible(childName.c_str());
1108
                }
1109
            }
1110
            if (visible == 0) {
1111
                continue;
1112
            }
1113

1114
            std::set<std::string> nextHiddens = hiddens;
1115
            const App::DocumentObject* nextLink = lastLink;
1116
            // Todo: This might belong.
1117
            // Toponaming project March 2024:  This appears to be a non toponaming feature:
1118
//            if (!checkLinkVisibility(nextHiddens, true, nextLink, owner, sub.c_str())) {
1119
//                cacheable = false;
1120
//                continue;
1121
//            }
1122

1123
            TopoShape shape;
1124

1125
            bool doGetShape = (!subObj || baseShape.isNull());
1126
            if (!doGetShape) {
1127
                auto type = mat.hasScale();
1128
                if (type != Base::ScaleType::NoScaling && type != Base::ScaleType::Uniform) {
1129
                    doGetShape = true;
1130
                }
1131
            }
1132
            if (doGetShape) {
1133
                shape = _getTopoShape(owner,
1134
                                      sub.c_str(),
1135
                                      true,
1136
                                      0,
1137
                                      &subObj,
1138
                                      false,
1139
                                      false,
1140
                                      nextHiddens,
1141
                                      nextLink);
1142
                if (shape.isNull()) {
1143
                    continue;
1144
                }
1145
                if (visible < 0 && subObj && !subObj->Visibility.getValue()) {
1146
                    continue;
1147
                }
1148
            }
1149
            else {
1150
                if (link && !link->getShowElementValue()) {
1151
                    shape =
1152
                        baseShape.makeElementTransform(mat,
1153
                                                (Data::POSTFIX_INDEX + childName).c_str());
1154
                }
1155
                else {
1156
                    shape = baseShape.makeElementTransform(mat);
1157
                    shape.reTagElementMap(subObj->getID(),
1158
                                          subObj->getDocument()->getStringHasher());
1159
                }
1160
            }
1161
            shapes.push_back(shape);
1162
        }
1163

1164
        if (shapes.empty()) {
1165
            return shape;
1166
        }
1167
        shape.Tag = tag;
1168
        shape.Hasher = hasher;
1169
        shape.makeElementCompound(shapes);
1170
    }
1171

1172
    if (cacheable && canCache(owner)) {
1173
        PropertyShapeCache::setShape(owner, shape);
1174
    }
1175

1176
    if (owner != obj) {
1177
        bool scaled = shape.transformShape(mat, false, true);
1178
        if (owner->getDocument() != obj->getDocument()) {
1179
            shape.reTagElementMap(obj->getID(), obj->getDocument()->getStringHasher());
1180
            scaled = true;  // force cache
1181
        }
1182
        if (canCache(obj) && scaled) {
1183
            PropertyShapeCache::setShape(obj, shape, subname);
1184
        }
1185
    }
1186
    if (noElementMap) {
1187
        shape.resetElementMap();
1188
        shape.Tag = 0;
1189
        if ( shape.Hasher ) {
1190
            shape.Hasher = nullptr;
1191
        }
1192
    }
1193
    return shape;
1194
}
1195

1196
TopoShape Feature::getTopoShape(const App::DocumentObject* obj,
1197
                                const char* subname,
1198
                                bool needSubElement,
1199
                                Base::Matrix4D* pmat,
1200
                                App::DocumentObject** powner,
1201
                                bool resolveLink,
1202
                                bool transform,
1203
                                bool noElementMap)
1204
{
1205
    if (!obj || !obj->getNameInDocument()) {
1206
        return TopoShape();
1207
    }
1208

1209
    const App::DocumentObject* lastLink = 0;
1210
    std::set<std::string> hiddens;
1211
    // Toponaming project March 2024:  This appears to be a non toponaming feature:
1212
    // Todo is this a cause behind #13886 ?
1213
//    if (!checkLinkVisibility(hiddens, false, lastLink, obj, subname)) {
1214
//        return TopoShape();
1215
//    }
1216

1217
    // NOTE! _getTopoShape() always return shape without top level
1218
    // transformation for easy shape caching, i.e.  with `transform` set
1219
    // to false. So we manually apply the top level transform if asked.
1220

1221
    if (needSubElement && (!pmat || *pmat == Base::Matrix4D())
1222
        && obj->isDerivedFrom(Part::Feature::getClassTypeId())
1223
        && !obj->hasExtension(App::LinkBaseExtension::getExtensionClassTypeId())) {
1224
        // Some OCC shape making is very sensitive to shape transformation. So
1225
        // check here if a direct sub shape is required, and bypass all extra
1226
        // processing here.
1227
        if (subname && *subname && Data::findElementName(subname) == subname) {
1228
            TopoShape ts = static_cast<const Part::Feature*>(obj)->Shape.getShape();
1229
            if (!transform) {
1230
                ts.setShape(ts.getShape().Located(TopLoc_Location()), false);
1231
            }
1232
            if (noElementMap) {
1233
                ts = ts.getSubShape(subname, true);
1234
            }
1235
            else {
1236
                ts = ts.getSubTopoShape(subname, true);
1237
            }
1238
            if (!ts.isNull()) {
1239
                if (powner) {
1240
                    *powner = const_cast<App::DocumentObject*>(obj);
1241
                }
1242
                if (pmat && transform) {
1243
                    *pmat = static_cast<const Part::Feature*>(obj)->Placement.getValue().toMatrix();
1244
                }
1245
                return ts;
1246
            }
1247
        }
1248
    }
1249

1250
    Base::Matrix4D mat;
1251
    auto shape = _getTopoShape(obj,
1252
                               subname,
1253
                               needSubElement,
1254
                               &mat,
1255
                               powner,
1256
                               resolveLink,
1257
                               noElementMap,
1258
                               hiddens,
1259
                               lastLink);
1260
    if (needSubElement && shape.shapeType(true) == TopAbs_COMPOUND) {
1261
        if (shape.countSubShapes(TopAbs_SOLID) == 1)
1262
            shape = shape.getSubTopoShape(TopAbs_SOLID, 1);
1263
        else if (shape.countSubShapes(TopAbs_COMPSOLID) == 1)
1264
            shape = shape.getSubTopoShape(TopAbs_COMPSOLID, 1);
1265
        else if (shape.countSubShapes(TopAbs_FACE) == 1)
1266
            shape = shape.getSubTopoShape(TopAbs_FACE, 1);
1267
        else if (shape.countSubShapes(TopAbs_SHELL) == 1)
1268
            shape = shape.getSubTopoShape(TopAbs_SHELL, 1);
1269
        else if (shape.countSubShapes(TopAbs_EDGE) == 1)
1270
            shape = shape.getSubTopoShape(TopAbs_EDGE, 1);
1271
        else if (shape.countSubShapes(TopAbs_WIRE) == 1)
1272
            shape = shape.getSubTopoShape(TopAbs_WIRE, 1);
1273
        else if (shape.countSubShapes(TopAbs_VERTEX) == 1)
1274
            shape = shape.getSubTopoShape(TopAbs_VERTEX, 1);
1275
    }
1276
    Base::Matrix4D topMat;
1277
    if (pmat || transform) {
1278
        // Obtain top level transformation
1279
        if (pmat) {
1280
            topMat = *pmat;
1281
        }
1282
        if (transform) {
1283
            obj->getSubObject(nullptr, nullptr, &topMat);
1284
        }
1285

1286
        // Apply the top level transformation
1287
        if (!shape.isNull()) {
1288
            shape.transformShape(topMat, false, true);
1289
        }
1290

1291
        if (pmat) {
1292
            *pmat = topMat * mat;
1293
        }
1294
    }
1295

1296
    return shape;
1297
}
1298

1299
App::DocumentObject *Feature::getShapeOwner(const App::DocumentObject *obj, const char *subname)
1300
{
1301
    if(!obj)
1302
        return nullptr;
1303
    auto owner = obj->getSubObject(subname);
1304
    if(owner) {
1305
        auto linked = owner->getLinkedObject(true);
1306
        if(linked)
1307
            owner = linked;
1308
    }
1309
    return owner;
1310
}
1311

1312
struct Feature::ElementCache
1313
{
1314
    TopoShape shape;
1315
    mutable std::vector<std::string> names;
1316
    mutable bool searched;
1317
};
1318

1319
void Feature::registerElementCache(const std::string& prefix, PropertyPartShape* prop)
1320
{
1321
    if (prop) {
1322
        _elementCachePrefixMap.emplace_back(prefix, prop);
1323
        return;
1324
    }
1325
    for (auto it = _elementCachePrefixMap.begin(); it != _elementCachePrefixMap.end();) {
1326
        if (it->first == prefix) {
1327
            _elementCachePrefixMap.erase(it);
1328
            break;
1329
        }
1330
    }
1331
}
1332

1333
void Feature::onBeforeChange(const App::Property* prop)
1334
{
1335
    PropertyPartShape* propShape = nullptr;
1336
    const std::string* prefix = nullptr;
1337
    if (prop == &Shape) {
1338
        propShape = &Shape;
1339
    }
1340
    else {
1341
        for (const auto& v : _elementCachePrefixMap) {
1342
            if (prop == v.second) {
1343
                prefix = &v.first;
1344
                propShape = v.second;
1345
            }
1346
        }
1347
    }
1348
    if (propShape) {
1349
        if (_elementCachePrefixMap.empty()) {
1350
            _elementCache.clear();
1351
        }
1352
        else {
1353
            for (auto it = _elementCache.begin(); it != _elementCache.end();) {
1354
                bool remove;
1355
                if (prefix) {
1356
                    remove = boost::starts_with(it->first, *prefix);
1357
                }
1358
                else {
1359
                    remove = true;
1360
                    for (const auto& v : _elementCache) {
1361
                        if (boost::starts_with(it->first, v.first)) {
1362
                            remove = false;
1363
                            break;
1364
                        }
1365
                    }
1366
                }
1367
                if (remove) {
1368
                    it = _elementCache.erase(it);
1369
                }
1370
                else {
1371
                    ++it;
1372
                }
1373
            }
1374
        }
1375
        if (getDocument() && !getDocument()->testStatus(App::Document::Restoring)
1376
            && !getDocument()->isPerformingTransaction()) {
1377
            std::vector<App::DocumentObject*> objs;
1378
            std::vector<std::string> subs;
1379
            for (auto prop : App::PropertyLinkBase::getElementReferences(this)) {
1380
                if (!prop->getContainer()) {
1381
                    continue;
1382
                }
1383
                objs.clear();
1384
                subs.clear();
1385
                prop->getLinks(objs, true, &subs, false);
1386
                for (auto& sub : subs) {
1387
                    auto element = Data::findElementName(sub.c_str());
1388
                    if (!element || !element[0] || Data::hasMissingElement(element)) {
1389
                        continue;
1390
                    }
1391
                    if (prefix) {
1392
                        if (!boost::starts_with(element, *prefix)) {
1393
                            continue;
1394
                        }
1395
                    }
1396
                    else {
1397
                        bool found = false;
1398
                        for (const auto& v : _elementCachePrefixMap) {
1399
                            if (boost::starts_with(element, v.first)) {
1400
                                found = true;
1401
                                break;
1402
                            }
1403
                        }
1404
                        if (found) {
1405
                            continue;
1406
                        }
1407
                    }
1408
                    auto res =
1409
                        _elementCache.insert(std::make_pair(std::string(element), ElementCache()));
1410
                    if (res.second) {
1411
                        res.first->second.searched = false;
1412
                        res.first->second.shape = propShape->getShape().getSubTopoShape(
1413
                            element + (prefix ? prefix->size() : 0),
1414
                            true);
1415
                    }
1416
                }
1417
            }
1418
        }
1419
    }
1420
    GeoFeature::onBeforeChange(prop);
1421
}
1422

1423
void Feature::onChanged(const App::Property* prop)
1424
{
1425
    // if the placement has changed apply the change to the point data as well
1426
    if (prop == &this->Placement) {
1427
        TopoShape shape = this->Shape.getShape();
1428
        auto oldTransform = shape.getTransform();
1429
        auto newTransform = this->Placement.getValue().toMatrix();
1430
        shape.setTransform(newTransform);
1431
        Base::ObjectStatusLocker<App::Property::Status, App::Property> guard(
1432
            App::Property::NoRecompute,
1433
            &this->Shape);
1434
        if ( oldTransform != newTransform) {
1435
            this->Shape.setValue(shape);
1436
        }
1437
    }
1438
    // if the point data has changed check and adjust the transformation as well
1439
    else if (prop == &this->Shape) {
1440
        if (this->isRecomputing()) {
1441
            this->Shape._Shape.setTransform(this->Placement.getValue().toMatrix());
1442
        }
1443
        else {
1444
            Base::Placement p;
1445
            // shape must not be null to override the placement
1446
            if (!this->Shape.getValue().IsNull()) {
1447
                try {
1448
                    p.fromMatrix(this->Shape.getShape().getTransform());
1449
                    this->Placement.setValueIfChanged(p);
1450
                }
1451
                catch (const Base::ValueError&) {
1452
                }
1453
            }
1454
        }
1455
    }
1456

1457
    GeoFeature::onChanged(prop);
1458
}
1459

1460

1461
const std::vector<std::string>& Feature::searchElementCache(const std::string& element,
1462
                                                            Data::SearchOptions options,
1463
                                                            double tol,
1464
                                                            double atol) const
1465
{
1466
    static std::vector<std::string> none;
1467
    if (element.empty()) {
1468
        return none;
1469
    }
1470
    auto it = _elementCache.find(element);
1471
    if (it == _elementCache.end() || it->second.shape.isNull()) {
1472
        return none;
1473
    }
1474
    if (!it->second.searched) {
1475
        auto propShape = &Shape;
1476
        const std::string* prefix = nullptr;
1477
        for (const auto& v : _elementCachePrefixMap) {
1478
            if (boost::starts_with(element, v.first)) {
1479
                propShape = v.second;
1480
                prefix = &v.first;
1481
                break;
1482
            }
1483
        }
1484
        it->second.searched = true;
1485
        propShape->getShape().findSubShapesWithSharedVertex(it->second.shape,
1486
                                                            &it->second.names,
1487
                                                            options,
1488
                                                            tol,
1489
                                                            atol);
1490
        if (prefix) {
1491
            for (auto& name : it->second.names) {
1492
                if (auto dot = strrchr(name.c_str(), '.')) {
1493
                    name.insert(dot + 1 - name.c_str(), *prefix);
1494
                }
1495
                else {
1496
                    name.insert(0, *prefix);
1497
                }
1498
            }
1499
        }
1500
    }
1501
    return it->second.names;
1502
}
1503

1504
TopLoc_Location Feature::getLocation() const
1505
{
1506
    Base::Placement pl = this->Placement.getValue();
1507
    Base::Rotation rot(pl.getRotation());
1508
    Base::Vector3d axis;
1509
    double angle;
1510
    rot.getValue(axis, angle);
1511
    gp_Trsf trf;
1512
    trf.SetRotation(gp_Ax1(gp_Pnt(), gp_Dir(axis.x, axis.y, axis.z)), angle);
1513
    trf.SetTranslationPart(gp_Vec(pl.getPosition().x,pl.getPosition().y,pl.getPosition().z));
1514
    return TopLoc_Location(trf);
1515
}
1516

1517
Feature* Feature::create(const TopoShape& shape, const char* name, App::Document* document)
1518
{
1519
    if (!name || !name[0]) {
1520
        name = "Shape";
1521
    }
1522
    if (!document) {
1523
        document = App::GetApplication().getActiveDocument();
1524
        if (!document) {
1525
            document = App::GetApplication().newDocument();
1526
        }
1527
    }
1528
    auto res = static_cast<Part::Feature*>(document->addObject("Part::Feature", name));
1529
    res->Shape.setValue(shape);
1530
    res->purgeTouched();
1531
    return res;
1532
}
1533

1534
ShapeHistory Feature::buildHistory(BRepBuilderAPI_MakeShape& mkShape, TopAbs_ShapeEnum type,
1535
                                   const TopoDS_Shape& newS, const TopoDS_Shape& oldS)
1536
{
1537
    ShapeHistory history;
1538
    history.type = type;
1539

1540
    TopTools_IndexedMapOfShape newM, oldM;
1541
    TopExp::MapShapes(newS, type, newM); // map containing all old objects of type "type"
1542
    TopExp::MapShapes(oldS, type, oldM); // map containing all new objects of type "type"
1543

1544
    // Look at all objects in the old shape and try to find the modified object in the new shape
1545
    for (int i=1; i<=oldM.Extent(); i++) {
1546
        bool found = false;
1547
        TopTools_ListIteratorOfListOfShape it;
1548
        // Find all new objects that are a modification of the old object (e.g. a face was resized)
1549
        for (it.Initialize(mkShape.Modified(oldM(i))); it.More(); it.Next()) {
1550
            found = true;
1551
            for (int j=1; j<=newM.Extent(); j++) { // one old object might create several new ones!
1552
                if (newM(j).IsPartner(it.Value())) {
1553
                    history.shapeMap[i-1].push_back(j-1); // adjust indices to start at zero
1554
                    break;
1555
                }
1556
            }
1557
        }
1558

1559
        // Find all new objects that were generated from an old object (e.g. a face generated from an edge)
1560
        for (it.Initialize(mkShape.Generated(oldM(i))); it.More(); it.Next()) {
1561
            found = true;
1562
            for (int j=1; j<=newM.Extent(); j++) {
1563
                if (newM(j).IsPartner(it.Value())) {
1564
                    history.shapeMap[i-1].push_back(j-1);
1565
                    break;
1566
                }
1567
            }
1568
        }
1569

1570
        if (!found) {
1571
            // Find all old objects that don't exist any more (e.g. a face was completely cut away)
1572
            if (mkShape.IsDeleted(oldM(i))) {
1573
                history.shapeMap[i-1] = std::vector<int>();
1574
            }
1575
            else {
1576
                // Mop up the rest (will this ever be reached?)
1577
                for (int j=1; j<=newM.Extent(); j++) {
1578
                    if (newM(j).IsPartner(oldM(i))) {
1579
                        history.shapeMap[i-1].push_back(j-1);
1580
                        break;
1581
                    }
1582
                }
1583
            }
1584
        }
1585
    }
1586

1587
    return history;
1588
}
1589

1590
ShapeHistory Feature::joinHistory(const ShapeHistory& oldH, const ShapeHistory& newH)
1591
{
1592
    ShapeHistory join;
1593
    join.type = oldH.type;
1594

1595
    for (const auto & it : oldH.shapeMap) {
1596
        int old_shape_index = it.first;
1597
        if (it.second.empty())
1598
            join.shapeMap[old_shape_index] = ShapeHistory::List();
1599
        for (const auto& jt : it.second) {
1600
            const auto& kt = newH.shapeMap.find(jt);
1601
            if (kt != newH.shapeMap.end()) {
1602
                ShapeHistory::List& ary = join.shapeMap[old_shape_index];
1603
                ary.insert(ary.end(), kt->second.begin(), kt->second.end());
1604
            }
1605
        }
1606
    }
1607

1608
    return join;
1609
}
1610

1611
    /// returns the type name of the ViewProvider
1612
const char* Feature::getViewProviderName() const {
1613
    return "PartGui::ViewProviderPart";
1614
}
1615

1616
const App::PropertyComplexGeoData* Feature::getPropertyOfGeometry() const
1617
{
1618
    return &Shape;
1619
}
1620

1621
bool Feature::isElementMappingDisabled(App::PropertyContainer* container)
1622
{
1623
    (void)container;
1624
    return false;
1625

1626
    // TODO:  March 2024 consider if any of this RT branch logic makes sense:
1627
//    if (!container) {
1628
//        return false;
1629
//    }
1630
//    auto prop = propDisableMapping(container, /*forced*/ false);
1631
//    if (prop && prop->getValue()) {
1632
//        return true;
1633
//    }
1634
//    if (auto obj = Base::freecad_dynamic_cast<App::DocumentObject>(container)) {
1635
//        if (auto doc = obj->getDocument()) {
1636
//            if (auto prop = propDisableMapping(doc, /*forced*/ false)) {
1637
//                return prop->getValue();
1638
//            }
1639
//        }
1640
//    }
1641
//    return false;
1642
}
1643

1644
bool Feature::getCameraAlignmentDirection(Base::Vector3d& direction, const char* subname) const
1645
{
1646
    const auto topoShape = getTopoShape(this, subname, true);
1647

1648
    if (topoShape.isNull()) {
1649
        return false;
1650
    }
1651

1652
    // Face normal
1653
    if (topoShape.isPlanar()) {
1654
        try {
1655
            const auto face = TopoDS::Face(topoShape.getShape());
1656
            gp_Pnt point;
1657
            gp_Vec vector;
1658
            BRepGProp_Face(face).Normal(0, 0, point, vector);
1659
            direction = Base::Vector3d(vector.X(), vector.Y(), vector.Z()).Normalize();
1660
            return true;
1661
        }
1662
        catch (Standard_TypeMismatch&) {
1663
            // Shape is not a face, do nothing
1664
        }
1665
    }
1666

1667
    // Edge direction
1668
    const size_t edgeCount = topoShape.countSubShapes(TopAbs_EDGE);
1669
    if (edgeCount == 1 && topoShape.isLinearEdge()) {
1670
        if (const std::unique_ptr<Geometry> geometry = Geometry::fromShape(topoShape.getSubShape(TopAbs_EDGE, 1), true)) {
1671
            const std::unique_ptr<GeomLine> geomLine(static_cast<GeomCurve*>(geometry.get())->toLine());
1672
            if (geomLine) {
1673
                direction = geomLine->getDir().Normalize();
1674
                return true;
1675
            }
1676
        }
1677
    }
1678

1679
    return GeoFeature::getCameraAlignmentDirection(direction, subname);
1680
}
1681

1682
void Feature::guessNewLink(std::string &replacementName, DocumentObject *base, const char *oldLink) {
1683
    for (auto &element : Part::Feature::getRelatedElements(base, oldLink)) {
1684
        replacementName.clear();
1685
        element.index.appendToStringBuffer(replacementName);
1686
        FC_WARN("Feature guess element reference " << oldLink << " -> " << replacementName);
1687
        return;
1688
    }
1689
    replacementName = oldLink;
1690
}
1691

1692
// ---------------------------------------------------------
1693

1694
PROPERTY_SOURCE(Part::FilletBase, Part::Feature)
1695

1696
FilletBase::FilletBase()
1697
{
1698
    ADD_PROPERTY(Base,(nullptr));
1699
    ADD_PROPERTY(Edges,(0,0,0));
1700
    ADD_PROPERTY_TYPE(EdgeLinks,(0), 0,
1701
                      (App::PropertyType)(App::Prop_ReadOnly|App::Prop_Hidden),0);
1702
    Edges.setSize(0);
1703
}
1704

1705
short FilletBase::mustExecute() const
1706
{
1707
    if (Base.isTouched() || Edges.isTouched() || EdgeLinks.isTouched())
1708
        return 1;
1709
    return 0;
1710
}
1711

1712
void FilletBase::onChanged(const App::Property *prop) {
1713
    if(getDocument() && !getDocument()->testStatus(App::Document::Restoring)) {
1714
        if(prop == &Edges || prop == &Base) {
1715
            if(!prop->testStatus(App::Property::User3))
1716
                syncEdgeLink();
1717
        }
1718
    }
1719
    Feature::onChanged(prop);
1720
}
1721

1722
void FilletBase::onDocumentRestored() {
1723
    if(EdgeLinks.getSubValues().empty())
1724
        syncEdgeLink();
1725
    Feature::onDocumentRestored();
1726
}
1727

1728
void FilletBase::syncEdgeLink() {
1729
    if(!Base.getValue() || !Edges.getSize()) {
1730
        EdgeLinks.setValue(0);
1731
        return;
1732
    }
1733
    std::vector<std::string> subs;
1734
    std::string sub("Edge");
1735
    for(auto &info : Edges.getValues())
1736
        subs.emplace_back(sub+std::to_string(info.edgeid));
1737
    EdgeLinks.setValue(Base.getValue(),subs);
1738
}
1739

1740
void FilletBase::onUpdateElementReference(const App::Property *prop) {
1741
    if(prop!=&EdgeLinks || !getNameInDocument())
1742
        return;
1743
    auto values = Edges.getValues();
1744
    const auto &subs = EdgeLinks.getSubValues();
1745
    for(size_t i=0;i<values.size();++i) {
1746
        if(i>=subs.size()) {
1747
            FC_WARN("fillet edge count mismatch in object " << getFullName());
1748
            break;
1749
        }
1750
        int idx = 0;
1751
        sscanf(subs[i].c_str(),"Edge%d",&idx);
1752
        if(idx)
1753
            values[i].edgeid = idx;
1754
        else
1755
            FC_WARN("invalid fillet edge link '" << subs[i] << "' in object "
1756
                                                 << getFullName());
1757
    }
1758
    Edges.setStatus(App::Property::User3,true);
1759
    Edges.setValues(values);
1760
    Edges.setStatus(App::Property::User3,false);
1761
}
1762

1763
// ---------------------------------------------------------
1764

1765
PROPERTY_SOURCE(Part::FeatureExt, Part::Feature)
1766

1767

1768

1769
namespace App {
1770
/// @cond DOXERR
1771
PROPERTY_SOURCE_TEMPLATE(Part::FeaturePython, Part::Feature)
1772
template<> const char* Part::FeaturePython::getViewProviderName() const {
1773
    return "PartGui::ViewProviderPython";
1774
}
1775
template<> PyObject* Part::FeaturePython::getPyObject() {
1776
    if (PythonObject.is(Py::_None())) {
1777
        // ref counter is set to 1
1778
        PythonObject = Py::Object(new FeaturePythonPyT<Part::PartFeaturePy>(this),true);
1779
    }
1780
    return Py::new_reference_to(PythonObject);
1781
}
1782
/// @endcond
1783

1784
// explicit template instantiation
1785
template class PartExport FeaturePythonT<Part::Feature>;
1786
}
1787

1788
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method.  Remove when possible.
1789
std::vector<Part::cutFaces> Part::findAllFacesCutBy(
1790
        const TopoDS_Shape& shape, const TopoDS_Shape& face, const gp_Dir& dir)
1791
{
1792
    // Find the centre of gravity of the face
1793
    GProp_GProps props;
1794
    BRepGProp::SurfaceProperties(face,props);
1795
    gp_Pnt cog = props.CentreOfMass();
1796

1797
    // create a line through the centre of gravity
1798
    gp_Lin line = gce_MakeLin(cog, dir);
1799

1800
    // Find intersection of line with all faces of the shape
1801
    std::vector<cutFaces> result;
1802
    BRepIntCurveSurface_Inter mkSection;
1803
    // TODO: Less precision than Confusion() should be OK?
1804

1805
    for (mkSection.Init(shape, line, Precision::Confusion()); mkSection.More(); mkSection.Next()) {
1806
        gp_Pnt iPnt = mkSection.Pnt();
1807
        double dsq = cog.SquareDistance(iPnt);
1808

1809
        if (dsq < Precision::Confusion())
1810
            continue; // intersection with original face
1811

1812
        // Find out which side of the original face the intersection is on
1813
        gce_MakeDir mkDir(cog, iPnt);
1814
        if (!mkDir.IsDone())
1815
            continue; // some error (appears highly unlikely to happen, though...)
1816

1817
        if (mkDir.Value().IsOpposite(dir, Precision::Confusion()))
1818
            continue; // wrong side of face (opposite to extrusion direction)
1819

1820
        cutFaces newF;
1821
        newF.face = mkSection.Face();
1822
        newF.distsq = dsq;
1823
        result.push_back(newF);
1824
    }
1825

1826
    return result;
1827
}
1828

1829
std::vector<Part::cutTopoShapeFaces>
1830
Part::findAllFacesCutBy(const TopoShape& shape, const TopoShape& face, const gp_Dir& dir)
1831
{
1832
    // Find the centre of gravity of the face
1833
    GProp_GProps props;
1834
    BRepGProp::SurfaceProperties(face.getShape(), props);
1835
    gp_Pnt cog = props.CentreOfMass();
1836

1837
    // create a line through the centre of gravity
1838
    gp_Lin line = gce_MakeLin(cog, dir);
1839

1840
    // Find intersection of line with all faces of the shape
1841
    std::vector<cutTopoShapeFaces> result;
1842
    BRepIntCurveSurface_Inter mkSection;
1843
    // TODO: Less precision than Confusion() should be OK?
1844

1845
    for (mkSection.Init(shape.getShape(), line, Precision::Confusion()); mkSection.More();
1846
         mkSection.Next()) {
1847
        gp_Pnt iPnt = mkSection.Pnt();
1848
        double dsq = cog.SquareDistance(iPnt);
1849

1850
        if (dsq < Precision::Confusion()) {
1851
            continue;  // intersection with original face
1852
        }
1853

1854
        // Find out which side of the original face the intersection is on
1855
        gce_MakeDir mkDir(cog, iPnt);
1856
        if (!mkDir.IsDone()) {
1857
            continue;  // some error (appears highly unlikely to happen, though...)
1858
        }
1859

1860
        if (mkDir.Value().IsOpposite(dir, Precision::Confusion())) {
1861
            continue;  // wrong side of face (opposite to extrusion direction)
1862
        }
1863

1864
        cutTopoShapeFaces newF;
1865
        newF.face = mkSection.Face();
1866
        newF.face.mapSubElement(shape);
1867
        newF.distsq = dsq;
1868
        result.push_back(newF);
1869
    }
1870

1871
    return result;
1872
}
1873

1874
bool Part::checkIntersection(const TopoDS_Shape& first, const TopoDS_Shape& second,
1875
                             const bool quick, const bool touch_is_intersection) {
1876

1877
    Bnd_Box first_bb, second_bb;
1878
    BRepBndLib::Add(first, first_bb);
1879
    first_bb.SetGap(0);
1880
    BRepBndLib::Add(second, second_bb);
1881
    second_bb.SetGap(0);
1882

1883
    // Note: This test fails if the objects are touching one another at zero distance
1884

1885
    // Improving reliability: If it fails sometimes when touching and touching is intersection,
1886
    // then please check further unless the user asked for a quick potentially unreliable result
1887
    if (first_bb.IsOut(second_bb) && !touch_is_intersection)
1888
        return false; // no intersection
1889
    if (quick && !first_bb.IsOut(second_bb))
1890
        return true; // assumed intersection
1891

1892
    if (touch_is_intersection) {
1893
        // If both shapes fuse to a single solid, then they intersect
1894
        BRepAlgoAPI_Fuse mkFuse(first, second);
1895
        if (!mkFuse.IsDone())
1896
            return false;
1897
        if (mkFuse.Shape().IsNull())
1898
            return false;
1899

1900
        // Did we get one or two solids?
1901
        TopExp_Explorer xp;
1902
        xp.Init(mkFuse.Shape(),TopAbs_SOLID);
1903
        if (xp.More()) {
1904
            // At least one solid
1905
            xp.Next();
1906
            return (xp.More() == Standard_False);
1907
        } else {
1908
            return false;
1909
        }
1910
    } else {
1911
        // If both shapes have common material, then they intersect
1912
        BRepAlgoAPI_Common mkCommon(first, second);
1913
        if (!mkCommon.IsDone())
1914
            return false;
1915
        if (mkCommon.Shape().IsNull())
1916
            return false;
1917

1918
        // Did we get a solid?
1919
        TopExp_Explorer xp;
1920
        xp.Init(mkCommon.Shape(),TopAbs_SOLID);
1921
        return (xp.More() == Standard_True);
1922
    }
1923

1924
}
1925

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

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

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

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