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:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user