PartDesign: Transform rework
This commit is contained in:
@@ -35,6 +35,7 @@
|
||||
|
||||
#include <App/Datums.h>
|
||||
#include <Base/Axis.h>
|
||||
#include <Mod/Part/App/Tools.h>
|
||||
#include <Mod/Part/App/TopoShape.h>
|
||||
#include <Mod/Part/App/Part2DObject.h>
|
||||
|
||||
@@ -53,21 +54,35 @@ PROPERTY_SOURCE(PartDesign::LinearPattern, PartDesign::Transformed)
|
||||
const App::PropertyIntegerConstraint::Constraints LinearPattern::intOccurrences = {
|
||||
1, std::numeric_limits<int>::max(), 1 };
|
||||
|
||||
const char* LinearPattern::ModeEnums[] = { "length", "offset", nullptr };
|
||||
const char* LinearPattern::ModeEnums[] = { "Extent", "Spacing", nullptr };
|
||||
|
||||
LinearPattern::LinearPattern()
|
||||
{
|
||||
auto initialMode = LinearPatternMode::length;
|
||||
auto initialMode = LinearPatternMode::Extent;
|
||||
|
||||
ADD_PROPERTY_TYPE(Direction,(nullptr),"LinearPattern",(App::PropertyType)(App::Prop_None),"Direction");
|
||||
ADD_PROPERTY(Reversed,(0));
|
||||
ADD_PROPERTY(Mode, (long(initialMode)));
|
||||
ADD_PROPERTY(Length,(100.0));
|
||||
ADD_PROPERTY(Offset,(10.0));
|
||||
ADD_PROPERTY(Occurrences,(3));
|
||||
ADD_PROPERTY_TYPE(Direction,(nullptr),"Direction 1",(App::PropertyType)(App::Prop_None),"Direction");
|
||||
ADD_PROPERTY_TYPE(Reversed,(0), "Direction 1", (App::PropertyType)(App::Prop_None), "Direction");
|
||||
ADD_PROPERTY_TYPE(Mode, (long(initialMode)), "Direction 1", (App::PropertyType)(App::Prop_None), "Direction");
|
||||
ADD_PROPERTY_TYPE(Length,(100.0), "Direction 1", (App::PropertyType)(App::Prop_None), "Direction");
|
||||
ADD_PROPERTY_TYPE(Offset,(10.0), "Direction 1", (App::PropertyType)(App::Prop_None), "Direction");
|
||||
ADD_PROPERTY_TYPE(Spacings, ({ -1.0 }), "Direction 1", (App::PropertyType)(App::Prop_None), "Direction");
|
||||
ADD_PROPERTY_TYPE(SpacingPattern, ({}), "Direction 1", (App::PropertyType)(App::Prop_None), "Direction");
|
||||
ADD_PROPERTY_TYPE(Occurrences,(2), "Direction 1", (App::PropertyType)(App::Prop_None), "Direction");
|
||||
Occurrences.setConstraints(&intOccurrences);
|
||||
Mode.setEnums(ModeEnums);
|
||||
setReadWriteStatusForMode(initialMode);
|
||||
setReadWriteStatusForMode(LinearPatternDirection::First);
|
||||
|
||||
ADD_PROPERTY_TYPE(Direction2,(nullptr),"Direction 2",(App::PropertyType)(App::Prop_None),"Direction");
|
||||
ADD_PROPERTY_TYPE(Reversed2,(0), "Direction 2", (App::PropertyType)(App::Prop_None), "Direction");
|
||||
ADD_PROPERTY_TYPE(Mode2, (long(initialMode)), "Direction 2", (App::PropertyType)(App::Prop_None), "Direction");
|
||||
ADD_PROPERTY_TYPE(Length2,(100.0), "Direction 2", (App::PropertyType)(App::Prop_None), "Direction");
|
||||
ADD_PROPERTY_TYPE(Offset2,(10.0), "Direction 2", (App::PropertyType)(App::Prop_None), "Direction");
|
||||
ADD_PROPERTY_TYPE(Spacings2, ({}), "Direction 2", (App::PropertyType)(App::Prop_None), "Direction");
|
||||
ADD_PROPERTY_TYPE(SpacingPattern2, ({}), "Direction 2", (App::PropertyType)(App::Prop_None), "Direction");
|
||||
ADD_PROPERTY_TYPE(Occurrences2,(1), "Direction 2", (App::PropertyType)(App::Prop_None), "Direction");
|
||||
Occurrences2.setConstraints(&intOccurrences);
|
||||
Mode2.setEnums(ModeEnums);
|
||||
setReadWriteStatusForMode(LinearPatternDirection::Second);
|
||||
}
|
||||
|
||||
short LinearPattern::mustExecute() const
|
||||
@@ -78,42 +93,170 @@ short LinearPattern::mustExecute() const
|
||||
// Length and Offset are mutually exclusive, only one could be updated at once
|
||||
Length.isTouched() ||
|
||||
Offset.isTouched() ||
|
||||
Occurrences.isTouched())
|
||||
Spacings.isTouched() ||
|
||||
SpacingPattern.isTouched() ||
|
||||
Occurrences.isTouched() ||
|
||||
Direction2.isTouched() ||
|
||||
Reversed2.isTouched() ||
|
||||
Mode2.isTouched() ||
|
||||
Length2.isTouched() ||
|
||||
Offset2.isTouched() ||
|
||||
Spacings2.isTouched() ||
|
||||
SpacingPattern2.isTouched() ||
|
||||
Occurrences2.isTouched())
|
||||
return 1;
|
||||
return Transformed::mustExecute();
|
||||
}
|
||||
|
||||
void LinearPattern::setReadWriteStatusForMode(LinearPatternMode mode)
|
||||
void LinearPattern::setReadWriteStatusForMode(LinearPatternDirection dir)
|
||||
{
|
||||
Length.setReadOnly(mode != LinearPatternMode::length);
|
||||
Offset.setReadOnly(mode != LinearPatternMode::offset);
|
||||
bool isExtentMode = false;
|
||||
if (dir == LinearPatternDirection::First) {
|
||||
isExtentMode = (Mode.getValue() == static_cast<long>(LinearPatternMode::Extent));
|
||||
Length.setReadOnly(!isExtentMode);
|
||||
Offset.setReadOnly(isExtentMode);
|
||||
}
|
||||
else {
|
||||
isExtentMode = (Mode2.getValue() == static_cast<long>(LinearPatternMode::Extent));
|
||||
Length2.setReadOnly(!isExtentMode);
|
||||
Offset2.setReadOnly(isExtentMode);
|
||||
}
|
||||
}
|
||||
|
||||
const std::list<gp_Trsf> LinearPattern::getTransformations(const std::vector<App::DocumentObject*>)
|
||||
{
|
||||
int occurrences = Occurrences.getValue();
|
||||
if (occurrences < 1)
|
||||
int occurrences2 = Occurrences2.getValue();
|
||||
if (occurrences < 1 || occurrences2 < 1) {
|
||||
throw Base::ValueError("At least one occurrence required");
|
||||
}
|
||||
|
||||
if (occurrences == 1)
|
||||
if (occurrences == 1 && occurrences2 == 1) {
|
||||
return {gp_Trsf()};
|
||||
}
|
||||
|
||||
double distance = Length.getValue();
|
||||
if (distance < Precision::Confusion())
|
||||
// make sure spacings are correct size :
|
||||
updateSpacings();
|
||||
|
||||
// Calculate the base offset vector and final step positions for each direction
|
||||
gp_Vec offset1 = calculateOffsetVector(LinearPatternDirection::First);
|
||||
std::vector<gp_Vec> steps1 = calculateSteps(LinearPatternDirection::First, offset1);
|
||||
|
||||
gp_Vec offset2 = calculateOffsetVector(LinearPatternDirection::Second);
|
||||
std::vector<gp_Vec> steps2 = calculateSteps(LinearPatternDirection::Second, offset2);
|
||||
|
||||
// Combine the steps from both directions
|
||||
std::list<gp_Trsf> transformations;
|
||||
for (const auto& step1 : steps1) {
|
||||
for (const auto& step2 : steps2) {
|
||||
gp_Trsf trans;
|
||||
trans.SetTranslation(step1 + step2);
|
||||
transformations.push_back(trans);
|
||||
}
|
||||
}
|
||||
|
||||
return transformations;
|
||||
}
|
||||
|
||||
gp_Vec LinearPattern::calculateOffsetVector(LinearPatternDirection dir) const
|
||||
{
|
||||
const auto& occurrencesProp =
|
||||
(dir == LinearPatternDirection::First) ? Occurrences : Occurrences2;
|
||||
int occurrences = occurrencesProp.getValue();
|
||||
if (occurrences <= 1) {
|
||||
return gp_Vec(); // Return zero vector if no transformation is needed
|
||||
}
|
||||
|
||||
const auto& dirProp = (dir == LinearPatternDirection::First) ? Direction : Direction2;
|
||||
const auto& reversedProp = (dir == LinearPatternDirection::First) ? Reversed : Reversed2;
|
||||
const auto& modeProp = (dir == LinearPatternDirection::First) ? Mode : Mode2;
|
||||
const auto& lengthProp = (dir == LinearPatternDirection::First) ? Length : Length2;
|
||||
|
||||
double distance = lengthProp.getValue();
|
||||
if (distance < Precision::Confusion()) {
|
||||
throw Base::ValueError("Pattern length too small");
|
||||
bool reversed = Reversed.getValue();
|
||||
}
|
||||
|
||||
App::DocumentObject* refObject = Direction.getValue();
|
||||
if (!refObject)
|
||||
throw Base::ValueError("No direction reference specified");
|
||||
gp_Vec offset = getDirectionFromProperty(dirProp);
|
||||
if (reversedProp.getValue()) {
|
||||
offset.Reverse();
|
||||
}
|
||||
|
||||
std::vector<std::string> subStrings = Direction.getSubValues();
|
||||
if (subStrings.empty())
|
||||
// For 'Extent' mode, the vector represents one full step.
|
||||
// For 'Spacing' mode, it's just a normalized direction vector.
|
||||
if (static_cast<LinearPatternMode>(modeProp.getValue()) == LinearPatternMode::Extent) {
|
||||
offset *= distance / (occurrences - 1);
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
std::vector<gp_Vec> LinearPattern::calculateSteps(LinearPatternDirection dir,
|
||||
const gp_Vec& offsetVector) const
|
||||
{
|
||||
const auto& occurrencesProp =
|
||||
(dir == LinearPatternDirection::First) ? Occurrences : Occurrences2;
|
||||
const auto& modeProp = (dir == LinearPatternDirection::First) ? Mode : Mode2;
|
||||
const auto& offsetValueProp = (dir == LinearPatternDirection::First) ? Offset : Offset2;
|
||||
const auto& spacingsProp = (dir == LinearPatternDirection::First) ? Spacings : Spacings2;
|
||||
const auto& spacingPatternProp =
|
||||
(dir == LinearPatternDirection::First) ? SpacingPattern : SpacingPattern2;
|
||||
|
||||
int occurrences = occurrencesProp.getValue();
|
||||
std::vector<gp_Vec> steps {gp_Vec()}; // First step is always zero
|
||||
steps.reserve(occurrences);
|
||||
|
||||
if (occurrences <= 1) {
|
||||
return steps;
|
||||
}
|
||||
|
||||
if (modeProp.getValue() == static_cast<int>(LinearPatternMode::Spacing)) {
|
||||
const std::vector<double> spacings = spacingsProp.getValues();
|
||||
const std::vector<double> pattern = spacingPatternProp.getValues();
|
||||
bool usePattern = pattern.size() > 1;
|
||||
double cumulativeDistance = 0.0;
|
||||
|
||||
// Spacing priority: individual spacing > pattern > global offset
|
||||
const auto spacingAt = [&](unsigned i) {
|
||||
if (spacings.at(i - 1) != -1.0) {
|
||||
return spacings.at(i - 1);
|
||||
}
|
||||
|
||||
if (usePattern) {
|
||||
return pattern.at(static_cast<size_t>(fmod(i - 1, pattern.size())));
|
||||
}
|
||||
|
||||
return offsetValueProp.getValue();
|
||||
};
|
||||
|
||||
for (int i = 1; i < occurrences; ++i) {
|
||||
cumulativeDistance += spacingAt(i);
|
||||
steps.push_back(offsetVector * cumulativeDistance);
|
||||
}
|
||||
}
|
||||
else { // Extent Mode
|
||||
for (int i = 1; i < occurrences; ++i) {
|
||||
steps.push_back(offsetVector * i);
|
||||
}
|
||||
}
|
||||
|
||||
return steps;
|
||||
}
|
||||
|
||||
gp_Dir LinearPattern::getDirectionFromProperty(const App::PropertyLinkSub& dirProp) const
|
||||
{
|
||||
App::DocumentObject* refObject = dirProp.getValue();
|
||||
if (!refObject) {
|
||||
throw Base::ValueError("No direction reference specified");
|
||||
}
|
||||
|
||||
std::vector<std::string> subStrings = dirProp.getSubValues();
|
||||
if (subStrings.empty()) {
|
||||
throw Base::ValueError("No direction reference specified");
|
||||
}
|
||||
|
||||
gp_Dir dir;
|
||||
if (refObject->isDerivedFrom<Part::Part2DObject>()) {
|
||||
Part::Part2DObject* refSketch = static_cast<Part::Part2DObject*>(refObject);
|
||||
if (auto* refSketch = freecad_cast<Part::Part2DObject*>(refObject)) {
|
||||
Base::Axis axis;
|
||||
if (subStrings[0] == "H_Axis") {
|
||||
axis = refSketch->getAxis(Part::Part2DObject::H_Axis);
|
||||
@@ -128,7 +271,7 @@ const std::list<gp_Trsf> LinearPattern::getTransformations(const std::vector<App
|
||||
axis *= refSketch->Placement.getValue();
|
||||
}
|
||||
else if (subStrings[0].compare(0, 4, "Axis") == 0) {
|
||||
int AxId = std::atoi(subStrings[0].substr(4,4000).c_str());
|
||||
int AxId = std::atoi(subStrings[0].substr(4, 4000).c_str());
|
||||
if (AxId >= 0 && AxId < refSketch->getAxisCount()) {
|
||||
axis = refSketch->getAxis(AxId);
|
||||
axis *= refSketch->Placement.getValue();
|
||||
@@ -153,22 +296,23 @@ const std::list<gp_Trsf> LinearPattern::getTransformations(const std::vector<App
|
||||
axis.setDirection(Base::Vector3d(d.X(), d.Y(), d.Z()));
|
||||
}
|
||||
dir = gp_Dir(axis.getDirection().x, axis.getDirection().y, axis.getDirection().z);
|
||||
} else if (refObject->isDerivedFrom<PartDesign::Plane>()) {
|
||||
PartDesign::Plane* plane = static_cast<PartDesign::Plane*>(refObject);
|
||||
}
|
||||
else if (auto* plane = freecad_cast<PartDesign::Plane*>(refObject)) {
|
||||
Base::Vector3d d = plane->getNormal();
|
||||
dir = gp_Dir(d.x, d.y, d.z);
|
||||
} else if (refObject->isDerivedFrom<PartDesign::Line>()) {
|
||||
PartDesign::Line* line = static_cast<PartDesign::Line*>(refObject);
|
||||
}
|
||||
else if (auto* line = freecad_cast<PartDesign::Line*>(refObject)) {
|
||||
Base::Vector3d d = line->getDirection();
|
||||
dir = gp_Dir(d.x, d.y, d.z);
|
||||
} else if (refObject->isDerivedFrom<App::Line>()) {
|
||||
App::Line* line = static_cast<App::Line*>(refObject);
|
||||
}
|
||||
else if (auto* line = freecad_cast<App::Line*>(refObject)) {
|
||||
Base::Vector3d d = line->getDirection();
|
||||
dir = gp_Dir(d.x, d.y, d.z);
|
||||
} else if (refObject->isDerivedFrom<Part::Feature>()) {
|
||||
if (subStrings[0].empty())
|
||||
}
|
||||
else if (auto* refFeature = freecad_cast<Part::Feature*>(refObject)) {
|
||||
if (subStrings[0].empty()) {
|
||||
throw Base::ValueError("No direction reference specified");
|
||||
Part::Feature* refFeature = static_cast<Part::Feature*>(refObject);
|
||||
}
|
||||
Part::TopoShape refShape = refFeature->Shape.getShape();
|
||||
TopoDS_Shape ref = refShape.getSubShape(subStrings[0].c_str());
|
||||
|
||||
@@ -181,7 +325,8 @@ const std::list<gp_Trsf> LinearPattern::getTransformations(const std::vector<App
|
||||
throw Base::TypeError("Direction face must be planar");
|
||||
|
||||
dir = adapt.Plane().Axis().Direction();
|
||||
} else if (ref.ShapeType() == TopAbs_EDGE) {
|
||||
}
|
||||
else if (ref.ShapeType() == TopAbs_EDGE) {
|
||||
TopoDS_Edge refEdge = TopoDS::Edge(ref);
|
||||
if (refEdge.IsNull())
|
||||
throw Base::ValueError("Failed to extract direction edge");
|
||||
@@ -190,45 +335,58 @@ const std::list<gp_Trsf> LinearPattern::getTransformations(const std::vector<App
|
||||
throw Base::ValueError("Direction edge must be a straight line");
|
||||
|
||||
dir = adapt.Line().Direction();
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
throw Base::ValueError("Direction reference must be edge or face");
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
throw Base::ValueError("Direction reference must be edge/face of a feature or a datum line/plane");
|
||||
}
|
||||
|
||||
TopLoc_Location invObjLoc = this->getLocation().Inverted();
|
||||
dir.Transform(invObjLoc.Transformation());
|
||||
|
||||
gp_Vec offset(dir.X(), dir.Y(), dir.Z());
|
||||
return Base::convertTo<gp_Vec>(dir);
|
||||
}
|
||||
|
||||
switch (static_cast<LinearPatternMode>(Mode.getValue())) {
|
||||
case LinearPatternMode::length:
|
||||
offset *= distance / (occurrences - 1);
|
||||
break;
|
||||
void LinearPattern::updateSpacings()
|
||||
{
|
||||
updateSpacings(LinearPatternDirection::First);
|
||||
updateSpacings(LinearPatternDirection::Second);
|
||||
}
|
||||
|
||||
case LinearPatternMode::offset:
|
||||
offset *= Offset.getValue();
|
||||
break;
|
||||
void LinearPattern::updateSpacings(LinearPatternDirection dir)
|
||||
{
|
||||
bool isSecondDir = dir == LinearPatternDirection::Second;
|
||||
|
||||
default:
|
||||
throw Base::ValueError("Invalid mode");
|
||||
App::PropertyFloatList& spacingsProp = isSecondDir ? Spacings2 : Spacings;
|
||||
App::PropertyLength& offsetProp = isSecondDir ? Offset2 : Offset;
|
||||
const App::PropertyIntegerConstraint& occurrencesProp = isSecondDir ? Occurrences2 : Occurrences;
|
||||
|
||||
std::vector<double> spacings = spacingsProp.getValues();
|
||||
size_t targetCount = occurrencesProp.getValue() - 1; // 1 less spacing than there are occurrences.
|
||||
|
||||
for (auto& spacing : spacings) {
|
||||
if (spacing == offsetProp.getValue()) {
|
||||
spacing = -1.0;
|
||||
}
|
||||
}
|
||||
|
||||
if (reversed)
|
||||
offset.Reverse();
|
||||
|
||||
std::list<gp_Trsf> transformations;
|
||||
gp_Trsf trans;
|
||||
transformations.push_back(trans);
|
||||
|
||||
// NOTE: The original feature is already included in the list of transformations!
|
||||
// Therefore we start with occurrence number 1
|
||||
for (int i = 1; i < occurrences; i++) {
|
||||
trans.SetTranslation(offset * i);
|
||||
transformations.push_back(trans);
|
||||
if (spacings.size() == targetCount) {
|
||||
return;
|
||||
}
|
||||
else if (spacings.size() < targetCount) {
|
||||
spacings.reserve(targetCount);
|
||||
while (spacings.size() < targetCount) {
|
||||
spacings.push_back(-1.0);
|
||||
}
|
||||
}
|
||||
else if ((int)spacings.size() > targetCount) {
|
||||
spacings.resize(targetCount);
|
||||
}
|
||||
|
||||
return transformations;
|
||||
spacingsProp.setValues(spacings);
|
||||
}
|
||||
|
||||
void LinearPattern::handleChangedPropertyType(Base::XMLReader& reader, const char* TypeName, App::Property* prop)
|
||||
@@ -249,23 +407,60 @@ void LinearPattern::handleChangedPropertyType(Base::XMLReader& reader, const cha
|
||||
void LinearPattern::onChanged(const App::Property* prop)
|
||||
{
|
||||
auto mode = static_cast<LinearPatternMode>(Mode.getValue());
|
||||
auto mode2 = static_cast<LinearPatternMode>(Mode2.getValue());
|
||||
|
||||
if (prop == &Mode) {
|
||||
setReadWriteStatusForMode(mode);
|
||||
setReadWriteStatusForMode(LinearPatternDirection::First);
|
||||
}
|
||||
else if (prop == &Occurrences) {
|
||||
updateSpacings(LinearPatternDirection::First);
|
||||
syncLengthAndOffset(LinearPatternDirection::First);
|
||||
}
|
||||
else if (prop == &Offset && mode == LinearPatternMode::Spacing) {
|
||||
syncLengthAndOffset(LinearPatternDirection::First);
|
||||
}
|
||||
else if (prop == &Length && mode == LinearPatternMode::Extent) {
|
||||
syncLengthAndOffset(LinearPatternDirection::First);
|
||||
}
|
||||
|
||||
// Keep Length in sync with Offset, catch Occurrences changes
|
||||
if (mode == LinearPatternMode::offset && (prop == &Offset || prop == &Occurrences)
|
||||
&& !Length.testStatus(App::Property::Status::Immutable)) {
|
||||
Length.setValue(Offset.getValue() * (Occurrences.getValue() - 1));
|
||||
else if (prop == &Mode2) {
|
||||
setReadWriteStatusForMode(LinearPatternDirection::Second);
|
||||
}
|
||||
|
||||
if (mode == LinearPatternMode::length && (prop == &Length || prop == &Occurrences)
|
||||
&& !Offset.testStatus(App::Property::Status::Immutable)) {
|
||||
Offset.setValue(Length.getValue() / (Occurrences.getValue() - 1));
|
||||
else if (prop == &Occurrences2) {
|
||||
updateSpacings(LinearPatternDirection::Second);
|
||||
syncLengthAndOffset(LinearPatternDirection::Second);
|
||||
}
|
||||
else if (prop == &Offset2 && mode2 == LinearPatternMode::Spacing) {
|
||||
syncLengthAndOffset(LinearPatternDirection::Second);
|
||||
}
|
||||
else if (prop == &Length2 && mode2 == LinearPatternMode::Extent) {
|
||||
syncLengthAndOffset(LinearPatternDirection::Second);
|
||||
}
|
||||
|
||||
Transformed::onChanged(prop);
|
||||
}
|
||||
|
||||
void LinearPattern::syncLengthAndOffset(LinearPatternDirection dir)
|
||||
{
|
||||
// Get references to the correct properties based on the direction
|
||||
auto& modeProp = (dir == LinearPatternDirection::First) ? Mode : Mode2;
|
||||
auto& lengthProp = (dir == LinearPatternDirection::First) ? Length : Length2;
|
||||
auto& offsetProp = (dir == LinearPatternDirection::First) ? Offset : Offset2;
|
||||
auto& occurrencesProp = (dir == LinearPatternDirection::First) ? Occurrences : Occurrences2;
|
||||
|
||||
auto mode = static_cast<LinearPatternMode>(modeProp.getValue());
|
||||
int occurrences = occurrencesProp.getValue();
|
||||
occurrences = occurrences <= 1 ? 1 : occurrences - 1;
|
||||
|
||||
if (mode == LinearPatternMode::Spacing
|
||||
&& !lengthProp.testStatus(App::Property::Status::Immutable)) {
|
||||
lengthProp.setValue(offsetProp.getValue() * occurrences);
|
||||
}
|
||||
else if (mode == LinearPatternMode::Extent
|
||||
&& !offsetProp.testStatus(App::Property::Status::Immutable)) {
|
||||
offsetProp.setValue(lengthProp.getValue() / occurrences);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user