PartDesign changes

* Mostly for supporting in-place editing

* Add new SubShapeBinder that support cross coordinate system,
  external, and sub-object binding
This commit is contained in:
Zheng, Lei
2019-07-13 18:13:21 +08:00
committed by wmayer
parent 11321bb996
commit cd2b7e297c
55 changed files with 1944 additions and 755 deletions

View File

@@ -117,6 +117,7 @@ PyMOD_INIT_FUNC(_PartDesign)
PartDesign::AdditiveLoft ::init();
PartDesign::SubtractiveLoft ::init();
PartDesign::ShapeBinder ::init();
PartDesign::SubShapeBinder ::init();
PartDesign::Plane ::init();
PartDesign::Line ::init();
PartDesign::Point ::init();

View File

@@ -116,21 +116,6 @@ short Body::mustExecute() const
return Part::BodyBase::mustExecute();
}
App::DocumentObject* Body::getPrevFeature(App::DocumentObject *start) const
{
std::vector<App::DocumentObject*> features = Group.getValues();
if (features.empty()) return NULL;
App::DocumentObject* st = (start == NULL ? Tip.getValue() : start);
if (st == NULL)
return st; // Tip is NULL
std::vector<App::DocumentObject*>::iterator it = std::find(features.begin(), features.end(), st);
if (it == features.end()) return NULL; // Invalid start object
it--;
return *it;
}
App::DocumentObject* Body::getPrevSolidFeature(App::DocumentObject *start)
{
if (!start) { // default to tip
@@ -246,7 +231,8 @@ bool Body::isAllowed(const App::DocumentObject* f)
f->getTypeId().isDerivedFrom(Part::Datum::getClassTypeId()) ||
// TODO Shouldn't we replace it with Sketcher::SketchObject? (2015-08-13, Fat-Zer)
f->getTypeId().isDerivedFrom(Part::Part2DObject::getClassTypeId()) ||
f->getTypeId().isDerivedFrom(PartDesign::ShapeBinder::getClassTypeId())
f->getTypeId().isDerivedFrom(PartDesign::ShapeBinder::getClassTypeId()) ||
f->getTypeId().isDerivedFrom(PartDesign::SubShapeBinder::getClassTypeId())
// TODO Why this lines was here? why should we allow anything of those? (2015-08-13, Fat-Zer)
//f->getTypeId().isDerivedFrom(Part::FeaturePython::getClassTypeId()) // trouble with this line on Windows!? Linker fails to find getClassTypeId() of the Part::FeaturePython...
//f->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId())
@@ -281,6 +267,17 @@ std::vector<App::DocumentObject*> Body::addObject(App::DocumentObject *feature)
if (isSolidFeature(feature)) {
Tip.setValue (feature);
}
if(feature->Visibility.getValue()
&& feature->isDerivedFrom(PartDesign::Feature::getClassTypeId()))
{
for(auto obj : Group.getValues()) {
if(obj->Visibility.getValue()
&& obj!=feature
&& obj->isDerivedFrom(PartDesign::Feature::getClassTypeId()))
obj->Visibility.setValue(false);
}
}
std::vector<App::DocumentObject*> result = {feature};
return result;
@@ -330,6 +327,9 @@ void Body::insertObject(App::DocumentObject* feature, App::DocumentObject* targe
Group.setValues (model);
if(feature->isDerivedFrom(PartDesign::Feature::getClassTypeId()))
static_cast<PartDesign::Feature*>(feature)->_Body.setValue(this);
// Set the BaseFeature property
setBaseProperty(feature);
}
@@ -445,7 +445,9 @@ void Body::onSettingDocument() {
void Body::onChanged (const App::Property* prop) {
// we neither load a project nor perform undo/redo
if (!this->isRestoring() && !this->getDocument()->isPerformingTransaction()) {
if (!this->isRestoring()
&& this->getDocument()
&& !this->getDocument()->isPerformingTransaction()) {
if (prop == &BaseFeature) {
FeatureBase* bf = nullptr;
auto first = Group.getValues().empty() ? nullptr : Group.getValues().front();
@@ -523,3 +525,12 @@ App::DocumentObject *Body::getSubObject(const char *subname,
*pmat *= Placement.getValue().toMatrix();
return const_cast<Body*>(this);
}
void Body::onDocumentRestored()
{
for(auto obj : Group.getValues()) {
if(obj->isDerivedFrom(PartDesign::Feature::getClassTypeId()))
static_cast<PartDesign::Feature*>(obj)->_Body.setValue(this);
}
DocumentObject::onDocumentRestored();
}

View File

@@ -61,9 +61,6 @@ public:
}
//@}
/// Return the previous feature
App::DocumentObject* getPrevFeature(App::DocumentObject *start = NULL) const;
/**
* Add the feature into the body at the current insert point.
* The insertion poin is the before next solid after the Tip feature
@@ -128,12 +125,6 @@ public:
showTip = enable;
}
protected:
virtual void onSettingDocument() override;
/// Adjusts the first solid's feature's base on BaseFeature getting set
virtual void onChanged (const App::Property* prop) override;
/**
* Return the solid feature before the given feature, or before the Tip feature
* That is, sketches and datum features are skipped
@@ -146,11 +137,19 @@ protected:
*/
App::DocumentObject *getNextSolidFeature(App::DocumentObject* start = NULL);
protected:
virtual void onSettingDocument() override;
/// Adjusts the first solid's feature's base on BaseFeature getting set
virtual void onChanged (const App::Property* prop) override;
/// Creates the corresponding Origin object
virtual void setupObject () override;
/// Removes all planes and axis if they are still linked to the document
virtual void unsetupObject () override;
virtual void onDocumentRestored() override;
private:
boost::signals2::scoped_connection connection;
bool showTip = false;

View File

@@ -45,6 +45,7 @@
#include "Mod/Part/App/DatumFeature.h"
#include <Base/Console.h>
FC_LOG_LEVEL_INIT("PartDesign",true,true)
namespace PartDesign {
@@ -55,6 +56,8 @@ PROPERTY_SOURCE(PartDesign::Feature,Part::Feature)
Feature::Feature()
{
ADD_PROPERTY(BaseFeature,(0));
ADD_PROPERTY_TYPE(_Body,(0),"Base",(App::PropertyType)(
App::Prop_ReadOnly|App::Prop_Hidden|App::Prop_Output|App::Prop_Transient),0);
Placement.setStatus(App::Property::Hidden, true);
BaseFeature.setStatus(App::Property::Hidden, true);
}
@@ -136,7 +139,9 @@ Part::Feature* Feature::getBaseObject(bool silent) const {
const TopoDS_Shape& Feature::getBaseShape() const {
const Part::Feature* BaseObject = getBaseObject();
if (BaseObject->isDerivedFrom(PartDesign::ShapeBinder::getClassTypeId())) {
if (BaseObject->isDerivedFrom(PartDesign::ShapeBinder::getClassTypeId())||
BaseObject->isDerivedFrom(PartDesign::SubShapeBinder::getClassTypeId()))
{
throw Base::ValueError("Base shape of shape binder cannot be used");
}
@@ -201,7 +206,11 @@ TopoDS_Shape Feature::makeShapeFromPlane(const App::DocumentObject* obj)
return builder.Shape();
}
Body* Feature::getFeatureBody() {
Body* Feature::getFeatureBody() const {
auto body = Base::freecad_dynamic_cast<Body>(_Body.getValue());
if(body)
return body;
auto list = getInList();
for (auto in : list) {

View File

@@ -35,6 +35,8 @@ class gp_Pln;
namespace PartDesign
{
typedef Part::TopoShape TopoShape;
class Body;
/** PartDesign feature
@@ -50,6 +52,7 @@ public:
/// Base feature which this feature will be fused into or cut out of
App::PropertyLink BaseFeature;
App::PropertyLinkHidden _Body;
short mustExecute() const;
@@ -57,7 +60,7 @@ public:
static bool isDatum(const App::DocumentObject* feature);
/// Returns the body the feature is in, or none
Body* getFeatureBody();
Body* getFeatureBody() const;
/**
* Returns the BaseFeature property's object (if any)

View File

@@ -32,11 +32,17 @@
# include <BRepBuilderAPI_MakeFace.hxx>
#endif
#include <boost/algorithm/string/predicate.hpp>
#include <Base/Console.h>
#include <App/Application.h>
#include <App/Document.h>
#include "ShapeBinder.h"
#include <App/Document.h>
#include <App/GroupExtension.h>
#include <App/OriginFeature.h>
#include <Mod/Part/App/TopoShape.h>
FC_LOG_LEVEL_INIT("PartDesign",true,true);
#ifndef M_PI
#define M_PI 3.14159265358979323846
@@ -261,3 +267,348 @@ void ShapeBinder::slotChangedObject(const App::DocumentObject& Obj, const App::P
}
}
}
// ============================================================================
PROPERTY_SOURCE(PartDesign::SubShapeBinder, Part::Feature)
SubShapeBinder::SubShapeBinder()
{
ADD_PROPERTY_TYPE(Support, (0), "",(App::PropertyType)(App::Prop_Hidden|App::Prop_None),
"Support of the geometry");
Support.setStatus(App::Property::Immutable,true);
ADD_PROPERTY_TYPE(Fuse, (false), "Base",App::Prop_None,"Fused linked solid shapes");
ADD_PROPERTY_TYPE(MakeFace, (true), "Base",App::Prop_None,"Create face for linked wires");
ADD_PROPERTY_TYPE(ClaimChildren, (false), "Base",App::Prop_Output,"Claim linked object as children");
ADD_PROPERTY_TYPE(Relative, (true), "Base",App::Prop_None,"Enable relative sub-object linking");
ADD_PROPERTY_TYPE(BindMode, ((long)0), "Base", App::Prop_None, "Binding mode");
ADD_PROPERTY_TYPE(PartialLoad, (false), "Base", App::Prop_None, "Enable partial loading");
PartialLoad.setStatus(App::Property::PartialTrigger,true);
static const char *BindModeEnum[] = {"Synchronized", "Frozen", "Detached", 0};
BindMode.setEnums(BindModeEnum);
ADD_PROPERTY_TYPE(Context, (0), "Base", App::Prop_Hidden, "Enable partial loading");
Context.setScope(App::LinkScope::Hidden);
ADD_PROPERTY_TYPE(_Version,(0),"Base",(App::PropertyType)(
App::Prop_Hidden|App::Prop_ReadOnly), "");
}
void SubShapeBinder::setupObject() {
_Version.setValue(1);
checkPropertyStatus();
}
void SubShapeBinder::update() {
Part::TopoShape result;
std::vector<Part ::TopoShape> shapes;
std::string errMsg;
auto parent = Context.getValue();
std::string parentSub = Context.getSubName(false);
if(!Relative.getValue())
parent = 0;
else {
if(parent && parent->getSubObject(parentSub.c_str())==this) {
auto parents = parent->getParents();
if(parents.size()) {
parent = parents.begin()->first;
parentSub = parents.begin()->second + parentSub;
}
} else
parent = 0;
if(!parent && parentSub.empty()) {
auto parents = getParents();
if(parents.size()) {
parent = parents.begin()->first;
parentSub = parents.begin()->second;
}
}
if(parent && (parent!=Context.getValue() || parentSub!=Context.getSubName(false)))
Context.setValue(parent,parentSub.c_str());
}
bool first = false;
std::map<const App::DocumentObject*, Base::Matrix4D> mats;
for(auto &l : Support.getSubListValues()) {
auto obj = l.getValue();
if(!obj || !obj->getNameInDocument())
continue;
auto res = mats.emplace(obj,Base::Matrix4D());
if(parent && res.second) {
std::string resolvedSub = parentSub;
std::string linkSub;
auto link = obj;
auto resolved = parent->resolveRelativeLink(resolvedSub,link,linkSub);
if(!resolved) {
if(!link) {
FC_WARN(getFullName() << " cannot resolve relative link of "
<< parent->getFullName() << '.' << parentSub
<< " -> " << obj->getFullName());
}
}else{
Base::Matrix4D mat;
auto sobj = resolved->getSubObject(resolvedSub.c_str(),0,&mat);
if(sobj!=this) {
FC_LOG(getFullName() << " skip invalid parent " << resolved->getFullName()
<< '.' << resolvedSub);
}else if(_Version.getValue()==0) {
// For existing legacy SubShapeBinder, we use its Placement
// to store the position adjustment of the first Support
if(first) {
auto pla = Placement.getValue()*Base::Placement(mat).inverse();
Placement.setValue(pla);
first = false;
} else {
// The remaining support will cancel the Placement
mat.inverse();
res.first->second = mat;
}
}else{
// For newer SubShapeBinder, the Placement property is free
// to use by the user to add additional offset to the
// binding object
mat.inverse();
res.first->second = Placement.getValue().toMatrix()*mat;
}
}
}
const auto &subvals = l.getSubValues();
std::set<std::string> subs(subvals.begin(),subvals.end());
static std::string none("");
if(subs.empty())
subs.insert(none);
else if(subs.size()>1)
subs.erase(none);
for(const auto &sub : subs) {
try {
auto shape = Part::Feature::getTopoShape(obj,sub.c_str(),true);
if(!shape.isNull()) {
shape = shape.makETransform(res.first->second);
// if(shape.Hasher
// && shape.getElementMapSize()
// && shape.Hasher != getDocument()->getStringHasher())
// {
// shape.reTagElementMap(getID(),
// getDocument()->getStringHasher(),TOPOP_SHAPEBINDER);
// }
shapes.push_back(shape);
}
} catch(Base::Exception &e) {
e.ReportException();
FC_ERR(getFullName() << " failed to obtain shape from "
<< obj->getFullName() << '.' << sub);
if(errMsg.empty()) {
std::ostringstream ss;
ss << "Failed to obtain shape " <<
obj->getFullName() << '.'
<< Data::ComplexGeoData::oldElementName(sub.c_str());
errMsg = ss.str();
}
}
}
}
if(errMsg.size())
FC_THROWM(Base::RuntimeError, errMsg);
if(shapes.size()==_Cache.size()) {
bool hit = true;
for(size_t i=0;i<shapes.size();++i) {
if(!shapes[i].getShape().IsPartner(_Cache[i].getShape())
|| shapes[i].getPlacement() != _Cache[i].getPlacement())
{
hit = false;
break;
}
}
if(hit)
return;
}
_Cache = std::move(shapes);
if(_Cache.empty()) {
// Shape.resetElementMapVersion();
return;
}
result.makECompound(_Cache);
bool fused = false;
if(Fuse.getValue()) {
// If the compound has solid, fuse them together, and ignore other type of
// shapes
std::vector<TopoDS_Shape> solids;
for(auto &s : _Cache) {
if(s.shapeType(true) == TopAbs_SOLID)
solids.push_back(s.getShape());
}
if(solids.size()) {
result.fuse(solids);
result = result.makERefine();
fused = true;
}
}
if(!fused && MakeFace.getValue() &&
!result.hasSubShape(TopAbs_FACE) &&
result.hasSubShape(TopAbs_EDGE))
{
result = result.makEWires();
try {
result = result.makEFace(0);
}catch(...){}
}
Shape.setValue(result);
}
void SubShapeBinder::slotRecomputedObject(const App::DocumentObject& Obj) {
if(Context.getValue() == &Obj
&& !this->testStatus(App::ObjectStatus::Recompute2))
{
update();
}
}
App::DocumentObjectExecReturn* SubShapeBinder::execute(void) {
if(BindMode.getValue()==0)
update();
return inherited::execute();
}
void SubShapeBinder::onDocumentRestored() {
if(Shape.testStatus(App::Property::Transient))
update();
inherited::onDocumentRestored();
}
void SubShapeBinder::onChanged(const App::Property *prop) {
if(prop == &Context || prop == &Relative) {
if(!Context.getValue() || !Relative.getValue()) {
connRecomputedObj.disconnect();
} else if(contextDoc != Context.getValue()->getDocument()
|| !connRecomputedObj.connected())
{
contextDoc = Context.getValue()->getDocument();
connRecomputedObj = contextDoc->signalRecomputedObject.connect(
boost::bind(&SubShapeBinder::slotRecomputedObject, this, _1));
}
}else if(!isRestoring()) {
if(prop == &Support) {
if(Support.getSubListValues().size()) {
update();
if(BindMode.getValue() == 2)
Support.setValue(0);
}
}else if(prop == &BindMode) {
if(BindMode.getValue() == 2)
Support.setValue(0);
else if(BindMode.getValue() == 0)
update();
checkPropertyStatus();
}else if(prop == &PartialLoad) {
checkPropertyStatus();
}
}
inherited::onChanged(prop);
}
void SubShapeBinder::checkPropertyStatus() {
Support.setAllowPartial(PartialLoad.getValue());
// Make Shape transient can reduce some file size, and maybe reduce file
// loading time as well. But there maybe complication arise when doing
// TopoShape version upgrade. So we DO NOT set trasient at the moment.
//
// Shape.setStatus(App::Property::Transient, !PartialLoad.getValue() && BindMode.getValue()==0);
}
void SubShapeBinder::setLinks(std::map<App::DocumentObject *, std::vector<std::string> >&&values, bool reset)
{
if(values.empty()) {
if(reset) {
Support.setValue(0);
Shape.setValue(Part::TopoShape());
}
return;
}
auto inSet = getInListEx(true);
inSet.insert(this);
for(auto &v : values) {
if(!v.first || !v.first->getNameInDocument())
FC_THROWM(Base::ValueError,"Invalid document object");
if(inSet.find(v.first)!=inSet.end())
FC_THROWM(Base::ValueError, "Cyclic referece to " << v.first->getFullName());
if(v.second.empty()) {
v.second.push_back("");
continue;
}
std::vector<std::string> wholeSubs;
for(auto &sub : v.second) {
if(sub.empty()) {
wholeSubs.clear();
v.second.resize(1);
v.second[0].clear();
break;
}else if(sub[sub.size()-1] == '.')
wholeSubs.push_back(sub);
}
for(auto &whole : wholeSubs) {
for(auto it=v.second.begin();it!=v.second.end();) {
auto &sub = *it;
if(!boost::starts_with(sub,whole) || sub.size()==whole.size())
++it;
else {
FC_LOG("Remove subname " << sub <<" because of whole selection " << whole);
it = v.second.erase(it);
}
}
}
}
if(!reset) {
for(auto &link : Support.getSubListValues()) {
auto subs = link.getSubValues();
auto &s = values[link.getValue()];
if(s.empty()) {
s = std::move(subs);
continue;
}else if(subs.empty() || s[0].empty())
continue;
for(auto &sub : s) {
for(auto it=subs.begin();it!=subs.end();) {
if(sub[sub.size()-1] == '.') {
if(boost::starts_with(*it,sub)) {
FC_LOG("Remove subname " << *it <<" because of whole selection " << sub);
it = subs.erase(it);
} else
++it;
}else if(it->empty()
|| (it->back()=='.' && boost::starts_with(sub,*it)))
{
FC_LOG("Remove whole subname " << *it <<" because of " << sub);
it = subs.erase(it);
} else
++it;
}
}
subs.insert(subs.end(),s.begin(),s.end());
s = std::move(subs);
}
}
Support.setValues(std::move(values));
}
void SubShapeBinder::handleChangedPropertyType(
Base::XMLReader &reader, const char * TypeName, App::Property * prop)
{
if(prop == &Support) {
Support.upgrade(reader,TypeName);
return;
}
inherited::handleChangedPropertyType(reader,TypeName,prop);
}

View File

@@ -71,6 +71,57 @@ private:
Connection connectDocumentChangedObject;
};
class PartDesignExport SubShapeBinder : public Part::Feature {
PROPERTY_HEADER(PartDesign::SubShapeBinder);
public:
typedef Part::Feature inherited;
SubShapeBinder();
const char* getViewProviderName(void) const {
return "PartDesignGui::ViewProviderSubShapeBinder";
}
void setLinks(std::map<App::DocumentObject *, std::vector<std::string> > &&values, bool reset=false);
App::PropertyXLinkSubList Support;
App::PropertyBool ClaimChildren;
App::PropertyBool Relative;
App::PropertyBool Fuse;
App::PropertyBool MakeFace;
App::PropertyEnumeration BindMode;
App::PropertyBool PartialLoad;
App::PropertyXLink Context;
App::PropertyInteger _Version;
void update();
virtual int canLoadPartial() const override {
return PartialLoad.getValue()?1:0;
}
virtual bool canLinkProperties() const {return false;}
protected:
virtual App::DocumentObjectExecReturn* execute(void) override;
virtual void onChanged(const App::Property *prop) override;
virtual void handleChangedPropertyType(
Base::XMLReader &reader, const char * TypeName, App::Property * prop) override;
virtual void onDocumentRestored() override;
virtual void setupObject() override;
void checkPropertyStatus();
void slotRecomputedObject(const App::DocumentObject& Obj);
typedef boost::signals2::scoped_connection Connection;
Connection connRecomputedObj;
App::Document *contextDoc=0;
std::vector<Part::TopoShape> _Cache;
};
} //namespace PartDesign