Download src/Mod/PartDesign/App/FeatureHelix.cpp from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 32.2 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/PartDesign/App/FeatureHelix.cpp
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/Mod/PartDesign/App/FeatureHelix.cpp
-
curl -L -o FeatureHelix.cpp https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/PartDesign/App/FeatureHelix.cpp
32.2 kB
| /*************************************************************************** | |
| * Copyright (c) 2010 Juergen Riegel <FreeCAD@juergen-riegel.net> * | |
| * 2020 David Österberg * | |
| * This file is part of the FreeCAD CAx development system. * | |
| * * | |
| * This library is free software; you can redistribute it and/or * | |
| * modify it under the terms of the GNU Library General Public * | |
| * License as published by the Free Software Foundation; either * | |
| * version 2 of the License, or (at your option) any later version. * | |
| * * | |
| * This library is distributed in the hope that it will be useful, * | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of * | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * | |
| * GNU Library General Public License for more details. * | |
| * * | |
| * You should have received a copy of the GNU Library General Public * | |
| * License along with this library; see the file COPYING.LIB. If not, * | |
| * write to the Free Software Foundation, Inc., 59 Temple Place, * | |
| * Suite 330, Boston, MA 02111-1307, USA * | |
| * * | |
| ***************************************************************************/ | |
| using namespace PartDesign; | |
| const char* Helix::ModeEnums[] | |
| = {"pitch-height-angle", "pitch-turns-angle", "height-turns-angle", "height-turns-growth", nullptr}; | |
| PROPERTY_SOURCE(PartDesign::Helix, PartDesign::ProfileBased) | |
| // we purposely use not FLT_MAX because this would not be computable | |
| const App::PropertyFloatConstraint::Constraints Helix::floatTurns | |
| = {Precision::Confusion(), std::numeric_limits<int>::max(), 1.0}; | |
| const App::PropertyFloatConstraint::Constraints Helix::floatTolerance | |
| = {0.1, std::numeric_limits<int>::max(), 1.0}; | |
| const App::PropertyAngle::Constraints Helix::floatAngle = {-89.0, 89.0, 1.0}; | |
| Helix::Helix() | |
| { | |
| addSubType = FeatureAddSub::Additive; | |
| auto initialMode = HelixMode::pitch_height_angle; | |
| const char* group = "Helix"; | |
| ADD_PROPERTY_TYPE( | |
| Base, | |
| (Base::Vector3d(0.0, 0.0, 0.0)), | |
| group, | |
| App::Prop_ReadOnly, | |
| QT_TRANSLATE_NOOP( | |
| "App::Property", | |
| "The center point of the helix' start; derived from the reference axis." | |
| ) | |
| ); | |
| ADD_PROPERTY_TYPE( | |
| Axis, | |
| (Base::Vector3d(0.0, 1.0, 0.0)), | |
| group, | |
| App::Prop_ReadOnly, | |
| QT_TRANSLATE_NOOP("App::Property", "The helix' direction; derived from the reference axis.") | |
| ); | |
| ADD_PROPERTY_TYPE( | |
| ReferenceAxis, | |
| (nullptr), | |
| group, | |
| App::Prop_None, | |
| QT_TRANSLATE_NOOP("App::Property", "The reference axis of the helix.") | |
| ); | |
| ADD_PROPERTY_TYPE( | |
| Mode, | |
| (long(initialMode)), | |
| group, | |
| App::Prop_None, | |
| QT_TRANSLATE_NOOP( | |
| "App::Property", | |
| "The helix input mode specifies which properties are set by the user.\n" | |
| "Dependent properties are then calculated." | |
| ) | |
| ); | |
| Mode.setEnums(ModeEnums); | |
| ADD_PROPERTY_TYPE( | |
| Pitch, | |
| (10.0), | |
| group, | |
| App::Prop_None, | |
| QT_TRANSLATE_NOOP("App::Property", "The axial distance between two turns.") | |
| ); | |
| ADD_PROPERTY_TYPE( | |
| Height, | |
| (30.0), | |
| group, | |
| App::Prop_None, | |
| QT_TRANSLATE_NOOP( | |
| "App::Property", | |
| "The height of the helix' path, not accounting for the extent of the profile." | |
| ) | |
| ); | |
| ADD_PROPERTY_TYPE( | |
| Turns, | |
| (3.0), | |
| group, | |
| App::Prop_None, | |
| QT_TRANSLATE_NOOP("App::Property", "The number of turns in the helix.") | |
| ); | |
| Turns.setConstraints(&floatTurns); | |
| ADD_PROPERTY_TYPE( | |
| Angle, | |
| (0.0), | |
| group, | |
| App::Prop_None, | |
| QT_TRANSLATE_NOOP( | |
| "App::Property", | |
| "The angle of the cone that forms a hull around the helix.\n" | |
| "Non-zero values turn the helix into a conical spiral.\n" | |
| "Positive values make the radius grow, negative shrinks." | |
| ) | |
| ); | |
| Angle.setConstraints(&floatAngle); | |
| ADD_PROPERTY_TYPE( | |
| Growth, | |
| (0.0), | |
| group, | |
| App::Prop_None, | |
| QT_TRANSLATE_NOOP( | |
| "App::Property", | |
| "The growth of the helix' radius per turn.\n" | |
| "Non-zero values turn the helix into a conical spiral." | |
| ) | |
| ); | |
| ADD_PROPERTY_TYPE( | |
| LeftHanded, | |
| (false), | |
| group, | |
| App::Prop_None, | |
| QT_TRANSLATE_NOOP( | |
| "App::Property", | |
| "Sets the turning direction to left handed,\n" | |
| "i.e. counter-clockwise when moving along its axis." | |
| ) | |
| ); | |
| ADD_PROPERTY_TYPE( | |
| Reversed, | |
| (false), | |
| group, | |
| App::Prop_None, | |
| QT_TRANSLATE_NOOP( | |
| "App::Property", | |
| "Determines whether the helix points in the opposite direction of the axis." | |
| ) | |
| ); | |
| ADD_PROPERTY_TYPE( | |
| Outside, | |
| (false), | |
| group, | |
| App::Prop_None, | |
| QT_TRANSLATE_NOOP( | |
| "App::Property", | |
| "If set, the result will be the intersection of the profile and the preexisting body." | |
| ) | |
| ); | |
| ADD_PROPERTY_TYPE( | |
| HasBeenEdited, | |
| (false), | |
| group, | |
| App::Prop_Hidden, | |
| QT_TRANSLATE_NOOP( | |
| "App::Property", | |
| "If false, the tool will propose an initial value for the pitch based on the profile " | |
| "bounding box,\n" | |
| "so that self intersection is avoided." | |
| ) | |
| ); | |
| ADD_PROPERTY_TYPE( | |
| Tolerance, | |
| (0.1), | |
| group, | |
| App::Prop_None, | |
| QT_TRANSLATE_NOOP("App::Property", "Fusion Tolerance for the Helix, increase if helical shape does not merge nicely with part.") | |
| ); | |
| Tolerance.setConstraints(&floatTolerance); | |
| setReadWriteStatusForMode(initialMode); | |
| } | |
| short Helix::mustExecute() const | |
| { | |
| if (Placement.isTouched() || ReferenceAxis.isTouched() || Axis.isTouched() || Base.isTouched() | |
| || Angle.isTouched()) { | |
| return 1; | |
| } | |
| return ProfileBased::mustExecute(); | |
| } | |
| App::DocumentObjectExecReturn* Helix::execute() | |
| { | |
| if (onlyHaveRefined()) { | |
| return App::DocumentObject::StdReturn; | |
| } | |
| // Validate and normalize parameters | |
| HelixMode mode = static_cast<HelixMode>(Mode.getValue()); | |
| if (mode == HelixMode::pitch_height_angle) { | |
| if (Pitch.getValue() < Precision::Confusion()) { | |
| return new App::DocumentObjectExecReturn( | |
| QT_TRANSLATE_NOOP("Exception", "Error: Pitch too small!") | |
| ); | |
| } | |
| if (Height.getValue() < Precision::Confusion()) { | |
| return new App::DocumentObjectExecReturn( | |
| QT_TRANSLATE_NOOP("Exception", "Error: height too small!") | |
| ); | |
| } | |
| Turns.setValue(Height.getValue() / Pitch.getValue()); | |
| Growth.setValue(Pitch.getValue() * tan(Base::toRadians(Angle.getValue()))); | |
| } | |
| else if (mode == HelixMode::pitch_turns_angle) { | |
| if (Pitch.getValue() < Precision::Confusion()) { | |
| return new App::DocumentObjectExecReturn( | |
| QT_TRANSLATE_NOOP("Exception", "Error: pitch too small!") | |
| ); | |
| } | |
| if (Turns.getValue() < Precision::Confusion()) { | |
| return new App::DocumentObjectExecReturn( | |
| QT_TRANSLATE_NOOP("Exception", "Error: turns too small!") | |
| ); | |
| } | |
| Height.setValue(Turns.getValue() * Pitch.getValue()); | |
| Growth.setValue(Pitch.getValue() * tan(Base::toRadians(Angle.getValue()))); | |
| } | |
| else if (mode == HelixMode::height_turns_angle) { | |
| if (Height.getValue() < Precision::Confusion()) { | |
| return new App::DocumentObjectExecReturn( | |
| QT_TRANSLATE_NOOP("Exception", "Error: height too small!") | |
| ); | |
| } | |
| if (Turns.getValue() < Precision::Confusion()) { | |
| return new App::DocumentObjectExecReturn( | |
| QT_TRANSLATE_NOOP("Exception", "Error: turns too small!") | |
| ); | |
| } | |
| Pitch.setValue(Height.getValue() / Turns.getValue()); | |
| Growth.setValue(Pitch.getValue() * tan(Base::toRadians(Angle.getValue()))); | |
| } | |
| else if (mode == HelixMode::height_turns_growth) { | |
| if (Turns.getValue() < Precision::Confusion()) { | |
| return new App::DocumentObjectExecReturn( | |
| QT_TRANSLATE_NOOP("Exception", "Error: turns too small!") | |
| ); | |
| } | |
| if ((Height.getValue() < Precision::Confusion()) | |
| && (abs(Growth.getValue()) < Precision::Confusion()) && Turns.getValue() > 1.0) { | |
| return new App::DocumentObjectExecReturn( | |
| QT_TRANSLATE_NOOP("Exception", "Error: either height or growth must not be zero!") | |
| ); | |
| } | |
| Pitch.setValue(Height.getValue() / Turns.getValue()); | |
| if (Height.getValue() > 0) { | |
| Angle.setValue( | |
| Base::toDegrees(atan(Turns.getValue() * Growth.getValue() / Height.getValue())) | |
| ); | |
| } | |
| else { | |
| // On purpose, we're doing nothing here; the else-branch is just for this comment. | |
| // - we don't print a warning, as for a flat spiral a zero-height is perfectly fine | |
| // - we don't void the angle (somehow) so that it keeps its value. This allows in | |
| // interactive usage to just go back to another mode and everything keeps working | |
| } | |
| } | |
| else { | |
| return new App::DocumentObjectExecReturn( | |
| QT_TRANSLATE_NOOP("Exception", "Error: unsupported mode") | |
| ); | |
| } | |
| TopoDS_Shape sketchshape; // Fixme: Should this be TopoShape here and below? | |
| try { | |
| sketchshape = getVerifiedFace(); | |
| } | |
| catch (const Base::Exception& e) { | |
| return new App::DocumentObjectExecReturn(e.what()); | |
| } | |
| if (sketchshape.IsNull()) { | |
| return new App::DocumentObjectExecReturn( | |
| QT_TRANSLATE_NOOP("Exception", "Error: No valid sketch or face") | |
| ); | |
| } | |
| else { | |
| // TODO: currently we only allow planar faces. the reason for this is that with other faces | |
| // in front, we could not use the current simulate approach and build the start and end face | |
| // from the wires. As the shell begins always at the spine and not the profile, the | |
| // sketchshape cannot be used directly as front face. We would need a method to translate | |
| // the front shape to match the shell starting position somehow... | |
| TopoDS_Face face = TopoDS::Face(sketchshape); | |
| BRepAdaptor_Surface adapt(face); | |
| if (adapt.GetType() != GeomAbs_Plane) { | |
| return new App::DocumentObjectExecReturn( | |
| QT_TRANSLATE_NOOP("Exception", "Error: Face must be planar") | |
| ); | |
| } | |
| } | |
| // if the Base property has a valid shape, fuse the AddShape into it | |
| TopoShape base; | |
| try { | |
| base = getBaseTopoShape(); | |
| } | |
| catch (const Base::Exception&) { | |
| // fall back to support (for legacy features) | |
| base = TopoShape(); | |
| } | |
| // update Axis from ReferenceAxis | |
| try { | |
| updateAxis(); | |
| } | |
| catch (const Base::Exception& e) { | |
| return new App::DocumentObjectExecReturn(e.what()); | |
| } | |
| try { | |
| this->positionByPrevious(); | |
| TopLoc_Location invObjLoc = this->getLocation().Inverted(); | |
| base.move(invObjLoc); | |
| TopoDS_Shape result; | |
| // generate the helix path | |
| TopoDS_Shape path; | |
| if (Angle.getValue() == 0.) { | |
| // breaking the path at each turn prevents an OCC issue | |
| path = generateHelixPath(); | |
| } | |
| else { | |
| // don't break the path or the generated solid is invalid | |
| path = generateHelixPath(1000.); | |
| } | |
| TopoDS_Shape face = sketchshape; | |
| face.Move(invObjLoc); | |
| Bnd_Box bounds; | |
| BRepBndLib::Add(path, bounds); | |
| double size = sqrt(bounds.SquareExtent()); | |
| ShapeFix_ShapeTolerance fix; | |
| fix.LimitTolerance(path, Precision::Confusion() * 1e-6 * size); // needed to produce valid | |
| // Pipe for very big parts | |
| // We introduce final part tolerance with the second call to LimitTolerance below, however | |
| // OCCT has a bug where the side-walls of the Pipe disappear with very large (km range) | |
| // pieces increasing a tiny bit of extra tolerance to the path fixes this. This will in any | |
| // case be less than the tolerance lower limit below, but sufficient to avoid the bug | |
| BRepOffsetAPI_MakePipe | |
| mkPS(TopoDS::Wire(path), face, GeomFill_Trihedron::GeomFill_IsFrenet, Standard_False); | |
| result = mkPS.Shape(); | |
| BRepClass3d_SolidClassifier SC(result); | |
| SC.PerformInfinitePoint(Precision::Confusion()); | |
| if (SC.State() == TopAbs_IN) { | |
| result.Reverse(); | |
| } | |
| fix.LimitTolerance( | |
| result, | |
| Precision::Confusion() * size * Tolerance.getValue() | |
| ); // significant precision reduction due to helical approximation - needed to allow fusion | |
| // to succeed | |
| // try to auto-fix possible invalid result | |
| ShapeFix_Solid fixer; | |
| fixer.Init(TopoDS::Solid(result)); | |
| if (fixer.Perform()) { | |
| result = fixer.Solid(); | |
| } | |
| AddSubShape.setValue(result); | |
| if (base.isNull()) { | |
| if (getAddSubType() == FeatureAddSub::Subtractive) { | |
| return new App::DocumentObjectExecReturn( | |
| QT_TRANSLATE_NOOP("Exception", "Error: There is nothing to subtract") | |
| ); | |
| } | |
| if (!isSingleSolidRuleSatisfied(result)) { | |
| return new App::DocumentObjectExecReturn( | |
| QT_TRANSLATE_NOOP("Exception", "Error: Result has multiple solids") | |
| ); | |
| } | |
| // store shape before refinement | |
| this->rawShape = result; | |
| Shape.setValue(getSolid(result)); | |
| return App::DocumentObject::StdReturn; | |
| } | |
| if (getAddSubType() == FeatureAddSub::Additive) { | |
| FCBRepAlgoAPI_Fuse mkFuse(base.getShape(), result); | |
| if (!mkFuse.IsDone()) { | |
| return new App::DocumentObjectExecReturn( | |
| QT_TRANSLATE_NOOP("Exception", "Error: Adding the helix failed") | |
| ); | |
| } | |
| // we have to get the solids (fuse sometimes creates compounds) | |
| TopoShape boolOp = this->getSolid(mkFuse.Shape()); | |
| // lets check if the result is a solid | |
| if (boolOp.isNull()) { | |
| return new App::DocumentObjectExecReturn( | |
| QT_TRANSLATE_NOOP("Exception", "Error: Result is not a solid") | |
| ); | |
| } | |
| if (!isSingleSolidRuleSatisfied(boolOp.getShape())) { | |
| return new App::DocumentObjectExecReturn( | |
| QT_TRANSLATE_NOOP("Exception", "Error: Result has multiple solids") | |
| ); | |
| } | |
| // store shape before refinement | |
| this->rawShape = boolOp; | |
| boolOp = refineShapeIfActive(boolOp, RefineErrorPolicy::Warn); | |
| Shape.setValue(getSolid(boolOp)); | |
| } | |
| else if (getAddSubType() == FeatureAddSub::Subtractive) { | |
| TopoShape boolOp; | |
| if (Outside.getValue()) { // are we subtracting the inside or the outside of the profile. | |
| FCBRepAlgoAPI_Common mkCom(result, base.getShape()); | |
| if (!mkCom.IsDone()) { | |
| return new App::DocumentObjectExecReturn( | |
| QT_TRANSLATE_NOOP("Exception", "Error: Intersecting the helix failed") | |
| ); | |
| } | |
| boolOp = this->getSolid(mkCom.Shape()); | |
| } | |
| else { | |
| FCBRepAlgoAPI_Cut mkCut(base.getShape(), result); | |
| if (!mkCut.IsDone()) { | |
| return new App::DocumentObjectExecReturn( | |
| QT_TRANSLATE_NOOP("Exception", "Error: Subtracting the helix failed") | |
| ); | |
| } | |
| boolOp = this->getSolid(mkCut.Shape()); | |
| } | |
| // lets check if the result is a solid | |
| if (boolOp.isNull()) { | |
| return new App::DocumentObjectExecReturn( | |
| QT_TRANSLATE_NOOP("Exception", "Error: Result is not a solid") | |
| ); | |
| } | |
| if (!isSingleSolidRuleSatisfied(boolOp.getShape())) { | |
| return new App::DocumentObjectExecReturn( | |
| QT_TRANSLATE_NOOP("Exception", "Error: Result has multiple solids") | |
| ); | |
| } | |
| // store shape before refinement | |
| this->rawShape = boolOp; | |
| boolOp = refineShapeIfActive(boolOp, RefineErrorPolicy::Warn); | |
| Shape.setValue(getSolid(boolOp)); | |
| } | |
| return App::DocumentObject::StdReturn; | |
| } | |
| catch (Standard_Failure& e) { | |
| if (std::string(e.GetMessageString()) == "TopoDS::Face") { | |
| return new App::DocumentObjectExecReturn( | |
| QT_TRANSLATE_NOOP("Exception", "Error: Could not create face from sketch") | |
| ); | |
| } | |
| else { | |
| return new App::DocumentObjectExecReturn(e.GetMessageString()); | |
| } | |
| } | |
| catch (Base::Exception& e) { | |
| return new App::DocumentObjectExecReturn(e.what()); | |
| } | |
| } | |
| void Helix::updateAxis() | |
| { | |
| App::DocumentObject* pcReferenceAxis = ReferenceAxis.getValue(); | |
| const std::vector<std::string>& subReferenceAxis = ReferenceAxis.getSubValues(); | |
| Base::Vector3d base; | |
| Base::Vector3d dir; | |
| getAxis(pcReferenceAxis, subReferenceAxis, base, dir, ForbiddenAxis::NoCheck); | |
| Base.setValue(base.x, base.y, base.z); | |
| Axis.setValue(dir.x, dir.y, dir.z); | |
| } | |
| TopoDS_Shape Helix::generateHelixPath(double breakAtTurn) | |
| { | |
| double turns = Turns.getValue(); | |
| double height = Height.getValue(); | |
| bool leftHanded = LeftHanded.getValue(); | |
| bool reversed = Reversed.getValue(); | |
| double angle = Angle.getValue(); | |
| double growth = Growth.getValue(); | |
| if (fabs(angle) < Precision::Confusion()) { | |
| angle = 0.0; | |
| } | |
| // get revolve axis | |
| Base::Vector3d baseVector = Base.getValue(); | |
| gp_Pnt pnt(baseVector.x, baseVector.y, baseVector.z); | |
| Base::Vector3d axisVector = Axis.getValue(); | |
| gp_Dir dir(axisVector.x, axisVector.y, axisVector.z); | |
| Base::Vector3d normal = getProfileNormal(); | |
| Base::Vector3d start = axisVector.Cross(normal); // pointing towards the desired helix start point. | |
| // if our axis is (nearly) aligned with the profile's normal, we're only interested in the | |
| // "twist" of the helix. The actual starting point, and thus the radius, isn't important as long | |
| // as it's somewhere in the profile's plane: an arbitrary vector perpendicular to the normal. | |
| if (start.IsNull()) { | |
| auto hopefullyNotParallel = Base::Vector3d(1.0, 2.0, 3.0); | |
| start = normal.Cross(hopefullyNotParallel); | |
| if (start.IsNull()) { | |
| // bad luck | |
| hopefullyNotParallel = Base::Vector3d(3.0, 2.0, 1.0); | |
| start = normal.Cross(hopefullyNotParallel); | |
| } | |
| } | |
| gp_Dir dir_start(start.x, start.y, start.z); | |
| // Find out in what quadrant relative to the axis the profile is located, and the exact position. | |
| Base::Vector3d profileCenter = getProfileCenterPoint(); | |
| // The factor of 100 below ensures that profile size is small compared to the curvature of the | |
| // helix. This improves the issue reported in | |
| // https://forum.freecad.org/viewtopic.php?f=10&t=65048 | |
| double axisOffset = 100.0 * (profileCenter * start - baseVector * start); | |
| double radius = std::fabs(axisOffset); | |
| bool turned = axisOffset < 0; | |
| // since the factor does not only change the radius but also the path position, we must shift | |
| // its offset back using the square of the factor | |
| double startOffset = 10000.0 | |
| * std::fabs((angle <= 0. ? 1. : 0.) * (profileCenter * axisVector) - baseVector * axisVector); | |
| if (radius < Precision::Confusion()) { | |
| // in this case ensure that axis is not in the sketch plane | |
| if (fabs(axisVector * normal) < Precision::Confusion()) { | |
| throw Base::ValueError("Error: Result is self intersecting"); | |
| } | |
| radius = 1000.0; // fallback to radius 1000 | |
| } | |
| bool growthMode = std::string(Mode.getValueAsString()).find("growth") != std::string::npos; | |
| double radiusTop; | |
| if (growthMode) { | |
| radiusTop = radius + turns * growth; | |
| } | |
| else { | |
| radiusTop = radius + height * tan(Base::toRadians(angle)); | |
| } | |
| // build the helix path | |
| TopoDS_Shape path | |
| = TopoShape().makeSpiralHelix(radius, radiusTop, height, turns, breakAtTurn, leftHanded); | |
| /* | |
| * The helix wire is created with the axis coinciding with z-axis and the start point at | |
| * (radius, 0, 0) We want to move it so that the axis becomes aligned with "dir" and "pnt", we | |
| * also want (radius,0,0) to map to the sketch plane. | |
| */ | |
| gp_Pnt origo(0.0, 0.0, 0.0); | |
| gp_Dir dir_axis1(0.0, 0.0, 1.0); // pointing along the helix axis, as created. | |
| gp_Dir dir_axis2(1.0, 0.0, 0.0); // pointing towards the helix start point, as created. | |
| gp_Trsf mov; | |
| if (abs(startOffset) > 0) { // translate the helix so that the starting point aligns with the | |
| // profile | |
| mov.SetTranslation(startOffset * gp_Vec(dir_axis1)); | |
| TopLoc_Location loc(mov); | |
| path.Move(loc); | |
| } | |
| // because of the radius factor we used above, we must reverse after the | |
| // startOffset movement (that brings the path back to the desired position) | |
| if (reversed) { | |
| mov.SetRotation(gp_Ax1(origo, dir_axis2), std::numbers::pi); | |
| TopLoc_Location loc(mov); | |
| path.Move(loc); | |
| } | |
| if (turned) { // turn the helix so that the starting point aligns with the profile | |
| mov.SetRotation(gp_Ax1(origo, dir_axis1), std::numbers::pi); | |
| TopLoc_Location loc(mov); | |
| path.Move(loc); | |
| } | |
| gp_Ax3 sourceCS(origo, dir_axis1, dir_axis2); | |
| gp_Ax3 targetCS(pnt, dir, dir_start); | |
| mov.SetTransformation(sourceCS, targetCS); | |
| TopLoc_Location loc(mov); | |
| path.Move(loc.Inverted()); | |
| TopLoc_Location invObjLoc = this->getLocation().Inverted(); | |
| path.Move(invObjLoc); | |
| return path; | |
| } | |
| // this function calculates self intersection safe pitch based on the profile bounding box. | |
| double Helix::safePitch() | |
| { | |
| Base::Vector3d axisVec = Axis.getValue(); | |
| Base::Vector3d startVec = axisVec.Cross(getProfileNormal()); // pointing towards the helix | |
| // start point | |
| HelixMode mode = static_cast<HelixMode>(Mode.getValue()); | |
| double growthValue = Growth.getValue(); | |
| double turnsValue = Turns.getValue(); | |
| // handle case if axis is orthogonal to profile | |
| // since startVec.IsNull() fails sometimes, for e.g. (0.0, 0.0, -0.0) | |
| // we take the precision into account | |
| if (startVec.Length() < Precision::Confusion()) { | |
| // when not in growth mode any pitch > 0 is safe | |
| if (mode != HelixMode::height_turns_growth) { | |
| return Precision::Confusion(); | |
| } | |
| // if growth is not zero, there will in many cases be intersections | |
| // when the turn is >= 1, thus return an 'infinite' pitch | |
| // Note: The resulting helix body is in this case often garbage since | |
| // the OCC algorithm to create the helix fails. | |
| // Nevertheless, the result is a valid body so it should be valuable for users | |
| // to get this correct warning anyway. | |
| else { | |
| if (abs(turnsValue) >= 1.0 && abs(growthValue) > 0.0) { | |
| return Precision::Infinite(); | |
| } | |
| } | |
| } | |
| double angle = Base::toRadians(Angle.getValue()); | |
| gp_Dir direction(axisVec.x, axisVec.y, axisVec.z); | |
| gp_Dir directionStart(startVec.x, startVec.y, startVec.z); | |
| TopoDS_Shape sketchshape = getVerifiedFace(); | |
| Bnd_Box boundingBox; | |
| BRepBndLib::Add(sketchshape, boundingBox); | |
| // get boundary and dimensions of boundingBox | |
| double Xmin, Ymin, Zmin, Xmax, Ymax, Zmax; | |
| boundingBox.Get(Xmin, Ymin, Zmin, Xmax, Ymax, Zmax); | |
| double X = Xmax - Xmin, Y = Ymax - Ymin, Z = Zmax - Zmin; | |
| gp_Vec boundingBoxVec(X, Y, Z); | |
| // Below is an approximation because since we take the bounding box it is | |
| // impossible to calculate it precisely. For example a circle has as bounding | |
| // box a square and thus results in a larger pitch than really necessary | |
| // minimal safe pitch if the angle or growth is 0 | |
| double pitch0 = boundingBoxVec * direction; | |
| if (mode == HelixMode::height_turns_growth) { | |
| // if the distance perpendicular to axisVec | |
| // between two turns is larger than the bounding box size in this direction | |
| // the minimal necessary pitch is zero | |
| if (abs(growthValue) > abs(boundingBoxVec * directionStart)) { | |
| return 0.0; | |
| } | |
| else { | |
| // if less than one turn, every pitch is safe | |
| // Note: at the moment helices with a growth end with a plane | |
| // whose normal is the final direction of the helix path. | |
| // In case this might be changed in future, also 1.0 turn would be safe. | |
| if (turnsValue < 1.0) { | |
| return 0.0; | |
| } | |
| else { | |
| return pitch0; | |
| } | |
| } | |
| } | |
| else { | |
| // if the angle is so large that the distance perpendicular to axisVec | |
| // between two turns is larger than the bounding box size in this direction | |
| // the pitch can be smaller than pitch0 | |
| if (tan(abs(angle)) * pitch0 > abs(boundingBoxVec * directionStart)) { | |
| return abs(boundingBoxVec * directionStart) / tan(abs(angle)); | |
| } | |
| else { | |
| return pitch0; | |
| } | |
| } | |
| } | |
| // this function proposes pitch and height | |
| void Helix::proposeParameters(bool force) | |
| { | |
| if (force || !HasBeenEdited.getValue()) { | |
| TopoDS_Shape sketchshape = getVerifiedFace(); | |
| Bnd_Box bb; | |
| BRepBndLib::Add(sketchshape, bb); | |
| bb.SetGap(0.0); | |
| double pitch = 1.1 * sqrt(bb.SquareExtent()); | |
| Pitch.setValue(pitch); | |
| Height.setValue(pitch * 3.0); | |
| HasBeenEdited.setValue(true); | |
| } | |
| } | |
| Base::Vector3d Helix::getProfileCenterPoint() | |
| { | |
| TopoDS_Shape profileshape; | |
| profileshape = getVerifiedFace(); | |
| Bnd_Box box; | |
| BRepBndLib::Add(profileshape, box); | |
| box.SetGap(0.0); | |
| double xmin, ymin, zmin, xmax, ymax, zmax; | |
| box.Get(xmin, ymin, zmin, xmax, ymax, zmax); | |
| return Base::Vector3d(0.5 * (xmin + xmax), 0.5 * (ymin + ymax), 0.5 * (zmin + zmax)); | |
| } | |
| void Helix::handleChangedPropertyType(Base::XMLReader& reader, const char* TypeName, App::Property* prop) | |
| { | |
| // property Turns had the App::PropertyFloat and was changed to App::PropertyFloatConstraint | |
| if (prop == &Turns && strcmp(TypeName, "App::PropertyFloat") == 0) { | |
| App::PropertyFloat TurnsProperty; | |
| // restore the PropertyFloat to be able to set its value | |
| TurnsProperty.Restore(reader); | |
| Turns.setValue(TurnsProperty.getValue()); | |
| } | |
| // property Growth had the App::PropertyLength and was changed to App::PropertyDistance | |
| else if (prop == &Growth && strcmp(TypeName, "App::PropertyLength") == 0) { | |
| App::PropertyLength GrowthProperty; | |
| // restore the PropertyLength to be able to set its value | |
| GrowthProperty.Restore(reader); | |
| Growth.setValue(GrowthProperty.getValue()); | |
| } | |
| else { | |
| ProfileBased::handleChangedPropertyType(reader, TypeName, prop); | |
| } | |
| } | |
| void Helix::onChanged(const App::Property* prop) | |
| { | |
| if (prop == &Mode) { | |
| // Depending on the mode, the derived properties are set read-only | |
| auto inputMode = static_cast<HelixMode>(Mode.getValue()); | |
| setReadWriteStatusForMode(inputMode); | |
| } | |
| ProfileBased::onChanged(prop); | |
| } | |
| void Helix::setReadWriteStatusForMode(HelixMode inputMode) | |
| { | |
| switch (inputMode) { | |
| case HelixMode::pitch_height_angle: | |
| // primary input: | |
| Pitch.setStatus(App::Property::ReadOnly, false); | |
| Height.setStatus(App::Property::ReadOnly, false); | |
| Angle.setStatus(App::Property::ReadOnly, false); | |
| // derived props: | |
| Turns.setStatus(App::Property::ReadOnly, true); | |
| Growth.setStatus(App::Property::ReadOnly, true); | |
| break; | |
| case HelixMode::pitch_turns_angle: | |
| // primary input: | |
| Pitch.setStatus(App::Property::ReadOnly, false); | |
| Turns.setStatus(App::Property::ReadOnly, false); | |
| Angle.setStatus(App::Property::ReadOnly, false); | |
| // derived props: | |
| Height.setStatus(App::Property::ReadOnly, true); | |
| Growth.setStatus(App::Property::ReadOnly, true); | |
| break; | |
| case HelixMode::height_turns_angle: | |
| // primary input: | |
| Height.setStatus(App::Property::ReadOnly, false); | |
| Turns.setStatus(App::Property::ReadOnly, false); | |
| Angle.setStatus(App::Property::ReadOnly, false); | |
| // derived props: | |
| Pitch.setStatus(App::Property::ReadOnly, true); | |
| Growth.setStatus(App::Property::ReadOnly, true); | |
| break; | |
| case HelixMode::height_turns_growth: | |
| // primary input: | |
| Height.setStatus(App::Property::ReadOnly, false); | |
| Turns.setStatus(App::Property::ReadOnly, false); | |
| Growth.setStatus(App::Property::ReadOnly, false); | |
| // derived props: | |
| Pitch.setStatus(App::Property::ReadOnly, true); | |
| Angle.setStatus(App::Property::ReadOnly, true); | |
| break; | |
| default: | |
| Pitch.setStatus(App::Property::ReadOnly, false); | |
| Height.setStatus(App::Property::ReadOnly, false); | |
| Turns.setStatus(App::Property::ReadOnly, false); | |
| Angle.setStatus(App::Property::ReadOnly, false); | |
| Growth.setStatus(App::Property::ReadOnly, false); | |
| break; | |
| } | |
| } | |
| PROPERTY_SOURCE(PartDesign::AdditiveHelix, PartDesign::Helix) | |
| AdditiveHelix::AdditiveHelix() | |
| { | |
| addSubType = Additive; | |
| Outside.setStatus(App::Property::Hidden, true); | |
| } | |
| PROPERTY_SOURCE(PartDesign::SubtractiveHelix, PartDesign::Helix) | |
| SubtractiveHelix::SubtractiveHelix() | |
| { | |
| addSubType = Subtractive; | |
| Outside.setStatus(App::Property::Hidden, false); | |
| } | |