Merge branch 'FreeCAD:main' into main
This commit is contained in:
@@ -78,7 +78,7 @@ class WidgetProgressBar(QtWidgets.QWidget):
|
||||
self.progress_bar.setMaximum(_TOTAL_INCREMENTS)
|
||||
self.stop_button.clicked.connect(self.stop_clicked)
|
||||
self.stop_button.setIcon(
|
||||
QtGui.QIcon.fromTheme("stop", QtGui.QIcon(":/icons/media-playback-stop.svg"))
|
||||
QtGui.QIcon.fromTheme("stop", QtGui.QIcon(":/icons/debug-stop.svg"))
|
||||
)
|
||||
self.vertical_layout.addLayout(self.horizontal_layout)
|
||||
self.vertical_layout.addWidget(self.status_label)
|
||||
|
||||
@@ -259,6 +259,7 @@ class CurtainWall(ArchComponent.Component):
|
||||
obj.addProperty("App::PropertyVector","VerticalDirection","CurtainWall",
|
||||
QT_TRANSLATE_NOOP("App::Property","The vertical direction reference to be used by this object to deduce vertical/horizontal directions. Keep it close to the actual vertical direction of your curtain wall"))
|
||||
obj.VerticalDirection = FreeCAD.Vector(0,0,1)
|
||||
self.Type = "CurtainWall"
|
||||
|
||||
def onDocumentRestored(self,obj):
|
||||
|
||||
|
||||
@@ -332,6 +332,7 @@ class _ArchSchedule:
|
||||
q = None
|
||||
if obj.Unit[i]:
|
||||
unit = obj.Unit[i]
|
||||
unit = unit.replace("^","") # get rid of existing power symbol
|
||||
unit = unit.replace("2","^2")
|
||||
unit = unit.replace("3","^3")
|
||||
unit = unit.replace("²","^2")
|
||||
|
||||
@@ -163,7 +163,11 @@ target_link_libraries(area area-native ${area_LIBS} ${area_native_LIBS})
|
||||
# TODO why CMAKE_SHARED_LINKER_FLAGS is not used here?
|
||||
# This is a dirty workaround!
|
||||
if(NOT BUILD_DYNAMIC_LINK_PYTHON AND CMAKE_COMPILER_IS_CLANGXX)
|
||||
target_link_libraries(area "-Wl,--undefined,dynamic_lookup")
|
||||
if(APPLE)
|
||||
target_link_libraries(area "-Wl,-undefined,dynamic_lookup")
|
||||
else(UNIX)
|
||||
target_link_libraries(area "-Wl,--undefined,dynamic_lookup")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
SET_BIN_DIR(area area /Mod/CAM)
|
||||
|
||||
@@ -77,6 +77,7 @@ class Line(gui_base_original.Creator):
|
||||
|
||||
self.obj = self.doc.addObject("Part::Feature", self.featureName)
|
||||
gui_utils.format_object(self.obj)
|
||||
self.obj.ViewObject.ShowInTree = False
|
||||
|
||||
self.call = self.view.addEventCallback("SoEvent", self.action)
|
||||
_toolmsg(translate("draft", "Pick first point"))
|
||||
|
||||
@@ -45,8 +45,7 @@ class ViewProviderPoint(ViewProviderDraft):
|
||||
vobj.setEditorMode('DisplayMode', mode)
|
||||
vobj.setEditorMode('Lighting', mode)
|
||||
vobj.setEditorMode('LineMaterial', mode)
|
||||
vobj.setEditorMode('ShapeColor', mode)
|
||||
vobj.setEditorMode('ShapeMaterial', mode)
|
||||
vobj.setEditorMode('ShapeAppearance', mode)
|
||||
vobj.setEditorMode('Transparency', mode)
|
||||
|
||||
def getIcon(self):
|
||||
|
||||
@@ -67,7 +67,7 @@ public:
|
||||
virtual QString getResultString();
|
||||
|
||||
virtual std::vector<std::string> getInputProps();
|
||||
virtual App::Property* getResultProp() {return {};};
|
||||
virtual App::Property* getResultProp() {return {};}
|
||||
virtual Base::Placement getPlacement();
|
||||
|
||||
// Return the objects that are measured
|
||||
|
||||
@@ -63,7 +63,7 @@ public:
|
||||
App::Property* getResultProp() override {return &this->Length;}
|
||||
|
||||
// Return a placement for the viewprovider, just use the first element for now
|
||||
Base::Placement getPlacement();
|
||||
Base::Placement getPlacement() override;
|
||||
|
||||
// Return the object we are measuring
|
||||
std::vector<App::DocumentObject*> getSubject() const override;
|
||||
|
||||
@@ -28,6 +28,5 @@
|
||||
using namespace std;
|
||||
|
||||
void CreateMeasureCommands() {
|
||||
Base::Console().Message("Init MeasureGui\n");
|
||||
Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager();
|
||||
}
|
||||
Base::Console().Log("Init MeasureGui\n");
|
||||
}
|
||||
|
||||
@@ -53,13 +53,6 @@
|
||||
#include <Base/Rotation.h>
|
||||
#include <Base/Vector3D.h>
|
||||
|
||||
#include <Mod/Measure/App/MeasureAngle.h>
|
||||
#include <Mod/Measure/App/MeasureDistance.h>
|
||||
#include <Mod/Measure/App/MeasureLength.h>
|
||||
#include <Mod/Measure/App/MeasurePosition.h>
|
||||
#include <Mod/Measure/App/MeasureArea.h>
|
||||
#include <Mod/Measure/App/MeasureRadius.h>
|
||||
|
||||
#include "VectorAdapter.h"
|
||||
#include "PartFeature.h"
|
||||
|
||||
|
||||
@@ -4081,6 +4081,7 @@ bool TopoShape::findPlane(gp_Pln& pln, double tol, double atol) const
|
||||
}
|
||||
#else
|
||||
bool TopoShape::findPlane(gp_Pln &pln, double tol, double atol) const {
|
||||
(void)atol;
|
||||
if(_Shape.IsNull())
|
||||
return false;
|
||||
TopoDS_Shape shape = _Shape;
|
||||
|
||||
@@ -261,6 +261,13 @@ enum class OpenResult
|
||||
allowOpenResult
|
||||
};
|
||||
|
||||
// See BRepFeat_MakeRevol
|
||||
enum class RevolMode {
|
||||
CutFromBase = 0,
|
||||
FuseWithBase = 1,
|
||||
None = 2
|
||||
};
|
||||
|
||||
/** The representation for a CAD Shape
|
||||
*/
|
||||
// NOLINTNEXTLINE cppcoreguidelines-special-member-functions
|
||||
@@ -1081,7 +1088,6 @@ public:
|
||||
|
||||
/** Make revolved shell around a basis shape
|
||||
*
|
||||
* @param base: the basis shape
|
||||
* @param axis: the revolving axis
|
||||
* @param d: rotation angle in degree
|
||||
* @param face_maker: optional type name of the the maker used to make a
|
||||
@@ -1097,6 +1103,67 @@ public:
|
||||
}
|
||||
|
||||
|
||||
/** Make revolved shell around a basis shape
|
||||
*
|
||||
* @param base: the basis shape
|
||||
* @param axis: the revolving axis
|
||||
* @param d: rotation angle in degree
|
||||
* @param face_maker: optional type name of the the maker used to make a
|
||||
* face from basis shape
|
||||
* @param supportface: the bottom face for the revolution, or null
|
||||
* @param uptoface: the upper limit face for the revolution, or null
|
||||
* @param Mode: the opencascade defined modes
|
||||
* @param Modify: if opencascade should modify existing shapes
|
||||
* @param op: optional string to be encoded into topo naming for indicating
|
||||
* the operation
|
||||
*
|
||||
* @return Return the generated new shape. The TopoShape itself is not modified.
|
||||
*/
|
||||
TopoShape& makeElementRevolution(const TopoShape& _base,
|
||||
const gp_Ax1& axis,
|
||||
double d,
|
||||
const TopoDS_Face& supportface,
|
||||
const TopoDS_Face& uptoface,
|
||||
const char* face_maker = nullptr,
|
||||
RevolMode Mode = RevolMode::None,
|
||||
Standard_Boolean Modify = Standard_True,
|
||||
const char* op = nullptr);
|
||||
|
||||
/** Make revolved shell around a basis shape
|
||||
*
|
||||
* @param axis: the revolving axis
|
||||
* @param d: rotation angle in degree
|
||||
* @param face_maker: optional type name of the the maker used to make a
|
||||
* face from basis shape
|
||||
* @param supportface: the bottom face for the revolution, or null
|
||||
* @param uptoface: the upper limit face for the revolution, or null
|
||||
* @param Mode: the opencascade defined modes
|
||||
* @param Modify: if opencascade should modify existing shapes
|
||||
* @param op: optional string to be encoded into topo naming for indicating
|
||||
* the operation
|
||||
*
|
||||
* @return Return the generated new shape. The TopoShape itself is not modified.
|
||||
*/
|
||||
TopoShape& makeElementRevolution(const gp_Ax1& axis,
|
||||
double d,
|
||||
const TopoDS_Face& supportface,
|
||||
const TopoDS_Face& uptoface,
|
||||
const char* face_maker = nullptr,
|
||||
RevolMode Mode = RevolMode::None,
|
||||
Standard_Boolean Modify = Standard_True,
|
||||
const char* op = nullptr) const
|
||||
{
|
||||
return TopoShape(0, Hasher).makeElementRevolution(*this,
|
||||
axis,
|
||||
d,
|
||||
supportface,
|
||||
uptoface,
|
||||
face_maker,
|
||||
Mode,
|
||||
Modify,
|
||||
op);
|
||||
}
|
||||
|
||||
/** Make a prism that is a linear sweep of a basis shape
|
||||
*
|
||||
* @param base: the basis shape
|
||||
|
||||
@@ -358,11 +358,11 @@ Part.show(s)
|
||||
split(paramval) -> Wire
|
||||
--
|
||||
Args:
|
||||
paramval (float or int): The parameter value along the Edge at which to
|
||||
paramval (float or list_of_floats): The parameter values along the Edge at which to
|
||||
split it e.g:
|
||||
|
||||
x = Part.makeCircle(1, FreeCAD.Vector(0,0,0), FreeCAD.Vector(0,0,1), 0, 90)
|
||||
y = x.derivative3At(x.FirstParameter + 0.5 * (x.LastParameter - x.FirstParameter))
|
||||
edge = Part.makeCircle(1, FreeCAD.Vector(0,0,0), FreeCAD.Vector(0,0,1), 0, 90)
|
||||
wire = edge.split([0.5, 1.0])
|
||||
|
||||
Returns:
|
||||
Wire: wire made up of two Edges
|
||||
|
||||
@@ -711,10 +711,16 @@ PyObject* TopoShapeEdgePy::split(PyObject *args)
|
||||
|
||||
BRepBuilderAPI_MakeWire mkWire;
|
||||
Handle(Geom_Curve) c = adapt.Curve().Curve();
|
||||
const TopoDS_Edge& edge = TopoDS::Edge(this->getTopoShapePtr()->getShape());
|
||||
BRep_Builder builder;
|
||||
TopoDS_Edge e;
|
||||
std::vector<Standard_Real>::iterator end = par.end() - 1;
|
||||
for (std::vector<Standard_Real>::iterator it = par.begin(); it != end; ++it) {
|
||||
BRepBuilderAPI_MakeEdge mkBuilder(c, it[0], it[1]);
|
||||
mkWire.Add(mkBuilder.Edge());
|
||||
BRepBuilderAPI_MakeEdge mke(c, it[0], it[1]);
|
||||
e = mke.Edge();
|
||||
builder.Transfert(edge, e);
|
||||
builder.Range(e, it[0], it[1], false);
|
||||
mkWire.Add(e);
|
||||
}
|
||||
|
||||
return new TopoShapeWirePy(new TopoShape(mkWire.Shape()));
|
||||
|
||||
@@ -106,6 +106,7 @@
|
||||
#include <App/ElementNamingUtils.h>
|
||||
#include <ShapeAnalysis_FreeBoundsProperties.hxx>
|
||||
#include <BRepBuilderAPI_MakeSolid.hxx>
|
||||
#include <BRepFeat_MakeRevol.hxx>
|
||||
|
||||
FC_LOG_LEVEL_INIT("TopoShape", true, true) // NOLINT
|
||||
|
||||
@@ -4439,6 +4440,51 @@ TopoShape& TopoShape::makeElementRevolve(const TopoShape& _base,
|
||||
return makeElementShape(mkRevol, base, op);
|
||||
}
|
||||
|
||||
TopoShape& TopoShape::makeElementRevolution(const TopoShape& _base,
|
||||
const gp_Ax1& axis,
|
||||
double d,
|
||||
const TopoDS_Face& supportface,
|
||||
const TopoDS_Face& uptoface,
|
||||
const char* face_maker,
|
||||
RevolMode Mode,
|
||||
Standard_Boolean Modify,
|
||||
const char* op)
|
||||
{
|
||||
if (!op) {
|
||||
op = Part::OpCodes::Revolve;
|
||||
}
|
||||
|
||||
TopoShape base(_base);
|
||||
if (base.isNull()) {
|
||||
FC_THROWM(NullShapeException, "Null shape");
|
||||
}
|
||||
if (face_maker && !base.hasSubShape(TopAbs_FACE)) {
|
||||
if (!base.hasSubShape(TopAbs_WIRE)) {
|
||||
base = base.makeElementWires();
|
||||
}
|
||||
base = base.makeElementFace(nullptr, face_maker, nullptr);
|
||||
}
|
||||
|
||||
BRepFeat_MakeRevol mkRevol;
|
||||
for (TopExp_Explorer xp(base.getShape(), TopAbs_FACE); xp.More(); xp.Next()) {
|
||||
mkRevol.Init(_base.getShape(),
|
||||
xp.Current(),
|
||||
supportface,
|
||||
axis,
|
||||
static_cast<int>(Mode),
|
||||
Modify);
|
||||
mkRevol.Perform(uptoface);
|
||||
if (!mkRevol.IsDone()) {
|
||||
throw Base::RuntimeError("Revolution: Up to face: Could not revolve the sketch!");
|
||||
}
|
||||
base = mkRevol.Shape();
|
||||
if (Mode == RevolMode::None) {
|
||||
Mode = RevolMode::FuseWithBase;
|
||||
}
|
||||
}
|
||||
return makeElementShape(mkRevol, base, op);
|
||||
}
|
||||
|
||||
TopoShape& TopoShape::makeElementDraft(const TopoShape& shape,
|
||||
const std::vector<TopoShape>& _faces,
|
||||
const gp_Dir& pullDirection,
|
||||
|
||||
@@ -10,7 +10,7 @@ class BRepTests(unittest.TestCase):
|
||||
This is a unit test for PR #13507
|
||||
"""
|
||||
num = 18
|
||||
alt = [0,1] * num
|
||||
alt = [0, 1] * num
|
||||
pts = [App.Vector(i, alt[i], 0) for i in range(num)]
|
||||
|
||||
bsc = Part.BSplineCurve()
|
||||
@@ -24,3 +24,26 @@ class BRepTests(unittest.TestCase):
|
||||
self.assertFalse(proj.isNull())
|
||||
self.assertEqual(len(proj.Edges), 1)
|
||||
|
||||
def testEdgeSplitFace(self):
|
||||
coords2d = [(0.5, -0.5), (1.0, -0.5), (1.0, 0.5), (0.5, 0.5)]
|
||||
pts2d = [App.Base.Vector2d(u, v) for u, v in coords2d]
|
||||
pts2d.append(pts2d[0])
|
||||
|
||||
sphere = Part.Sphere()
|
||||
edges = []
|
||||
for i in range(1, len(pts2d)):
|
||||
ls = Part.Geom2d.Line2dSegment(pts2d[i - 1], pts2d[i])
|
||||
edges.append(ls.toShape(sphere))
|
||||
|
||||
split = edges[0].split(0.25)
|
||||
new_edges = split.Edges + edges[1:]
|
||||
wire = Part.Wire(new_edges)
|
||||
face = Part.Face(wire, "Part::FaceMakerSimple")
|
||||
self.assertTrue(face.isValid())
|
||||
|
||||
def testEdgeSplitReplace(self):
|
||||
cyl = Part.makeCylinder(2, 5)
|
||||
e1 = cyl.Edge3
|
||||
split = e1.split([1.0, 2.0])
|
||||
newcyl = cyl.replaceShape([(e1, split), (cyl.Vertex2, split.Vertex1)])
|
||||
self.assertTrue(newcyl.isValid())
|
||||
|
||||
@@ -431,7 +431,6 @@ App::DocumentObjectExecReturn* FeatureExtrude::buildExtrusion(ExtrudeOptions opt
|
||||
bool makeface = options.testFlag(ExtrudeOption::MakeFace);
|
||||
bool fuse = options.testFlag(ExtrudeOption::MakeFuse);
|
||||
bool legacyPocket = options.testFlag(ExtrudeOption::LegacyPocket);
|
||||
bool inverseDirection = options.testFlag(ExtrudeOption::InverseDirection);
|
||||
|
||||
std::string method(Type.getValueAsString());
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include <Base/Tools.h>
|
||||
|
||||
#include "FeatureRevolution.h"
|
||||
#include "Mod/Part/App/TopoShapeOpCode.h"
|
||||
|
||||
using namespace PartDesign;
|
||||
|
||||
@@ -78,24 +79,29 @@ short Revolution::mustExecute() const
|
||||
return ProfileBased::mustExecute();
|
||||
}
|
||||
|
||||
App::DocumentObjectExecReturn *Revolution::execute()
|
||||
App::DocumentObjectExecReturn* Revolution::execute()
|
||||
{
|
||||
// Validate parameters
|
||||
// All angles are in radians unless explicitly stated
|
||||
double angleDeg = Angle.getValue();
|
||||
if (angleDeg > 360.0)
|
||||
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Angle of revolution too large"));
|
||||
if (angleDeg > 360.0) {
|
||||
return new App::DocumentObjectExecReturn(
|
||||
QT_TRANSLATE_NOOP("Exception", "Angle of revolution too large"));
|
||||
}
|
||||
|
||||
double angle = Base::toRadians<double>(angleDeg);
|
||||
if (angle < Precision::Angular())
|
||||
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Angle of revolution too small"));
|
||||
if (angle < Precision::Angular()) {
|
||||
return new App::DocumentObjectExecReturn(
|
||||
QT_TRANSLATE_NOOP("Exception", "Angle of revolution too small"));
|
||||
}
|
||||
|
||||
double angle2 = Base::toRadians(Angle2.getValue());
|
||||
|
||||
TopoDS_Shape sketchshape;
|
||||
TopoShape sketchshape;
|
||||
try {
|
||||
sketchshape = getVerifiedFace();
|
||||
} catch (const Base::Exception& e) {
|
||||
}
|
||||
catch (const Base::Exception& e) {
|
||||
return new App::DocumentObjectExecReturn(e.what());
|
||||
}
|
||||
|
||||
@@ -103,27 +109,36 @@ App::DocumentObjectExecReturn *Revolution::execute()
|
||||
TopoShape base;
|
||||
try {
|
||||
base = getBaseTopoShape();
|
||||
} catch (const Base::Exception&) {
|
||||
}
|
||||
catch (const Base::Exception&) {
|
||||
// fall back to support (for legacy features)
|
||||
base = TopoShape();
|
||||
}
|
||||
|
||||
// update Axis from ReferenceAxis
|
||||
try {
|
||||
updateAxis();
|
||||
} catch (const Base::Exception& e) {
|
||||
}
|
||||
catch (const Base::Exception& e) {
|
||||
return new App::DocumentObjectExecReturn(e.what());
|
||||
}
|
||||
|
||||
// get revolve axis
|
||||
Base::Vector3d b = Base.getValue();
|
||||
gp_Pnt pnt(b.x,b.y,b.z);
|
||||
Base::Vector3d v = Axis.getValue();
|
||||
gp_Dir dir(v.x,v.y,v.z);
|
||||
|
||||
try {
|
||||
if (sketchshape.IsNull())
|
||||
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Creating a face from sketch failed"));
|
||||
// get revolve axis
|
||||
Base::Vector3d b = Base.getValue();
|
||||
gp_Pnt pnt(b.x, b.y, b.z);
|
||||
Base::Vector3d v = Axis.getValue();
|
||||
|
||||
if (v.IsNull()) {
|
||||
return new App::DocumentObjectExecReturn(
|
||||
QT_TRANSLATE_NOOP("Exception", "Reference axis is invalid"));
|
||||
}
|
||||
|
||||
gp_Dir dir(v.x, v.y, v.z);
|
||||
|
||||
if (sketchshape.isNull()) {
|
||||
return new App::DocumentObjectExecReturn(
|
||||
QT_TRANSLATE_NOOP("Exception", "Creating a face from sketch failed"));
|
||||
}
|
||||
|
||||
RevolMethod method = methodFromString(Type.getValueAsString());
|
||||
|
||||
@@ -132,51 +147,99 @@ App::DocumentObjectExecReturn *Revolution::execute()
|
||||
pnt.Transform(invObjLoc.Transformation());
|
||||
dir.Transform(invObjLoc.Transformation());
|
||||
base.move(invObjLoc);
|
||||
sketchshape.Move(invObjLoc);
|
||||
sketchshape.move(invObjLoc);
|
||||
|
||||
// Check distance between sketchshape and axis - to avoid failures and crashes
|
||||
TopExp_Explorer xp;
|
||||
xp.Init(sketchshape, TopAbs_FACE);
|
||||
for (;xp.More(); xp.Next()) {
|
||||
if (checkLineCrossesFace(gp_Lin(pnt, dir), TopoDS::Face(xp.Current())))
|
||||
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Revolve axis intersects the sketch"));
|
||||
xp.Init(sketchshape.getShape(), TopAbs_FACE);
|
||||
for (; xp.More(); xp.Next()) {
|
||||
if (checkLineCrossesFace(gp_Lin(pnt, dir), TopoDS::Face(xp.Current()))) {
|
||||
return new App::DocumentObjectExecReturn(
|
||||
QT_TRANSLATE_NOOP("Exception", "Revolve axis intersects the sketch"));
|
||||
}
|
||||
}
|
||||
|
||||
// Create a fresh support even when base exists so that it can be used for patterns
|
||||
#ifdef FC_USE_TNP_FIX
|
||||
TopoShape result;
|
||||
#else
|
||||
TopoDS_Shape result;
|
||||
#endif
|
||||
TopoDS_Face supportface = getSupportFace();
|
||||
supportface.Move(invObjLoc);
|
||||
|
||||
if (method == RevolMethod::ToFace || method == RevolMethod::ToFirst || method == RevolMethod::ToLast) {
|
||||
if (method == RevolMethod::ToFace || method == RevolMethod::ToFirst
|
||||
|| method == RevolMethod::ToLast) {
|
||||
TopoDS_Face upToFace;
|
||||
if (method == RevolMethod::ToFace) {
|
||||
getFaceFromLinkSub(upToFace, UpToFace);
|
||||
upToFace.Move(invObjLoc);
|
||||
}
|
||||
else
|
||||
throw Base::RuntimeError("ProfileBased: Revolution up to first/last is not yet supported");
|
||||
else {
|
||||
throw Base::RuntimeError(
|
||||
"ProfileBased: Revolution up to first/last is not yet supported");
|
||||
}
|
||||
|
||||
// TODO: This method is designed for extrusions. needs to be adapted for revolutions.
|
||||
// getUpToFace(upToFace, base, supportface, sketchshape, method, dir);
|
||||
|
||||
TopoDS_Face supportface = getSupportFace();
|
||||
// TopoDS_Face supportface = getSupportFace();
|
||||
supportface.Move(invObjLoc);
|
||||
|
||||
if (Reversed.getValue())
|
||||
if (Reversed.getValue()) {
|
||||
dir.Reverse();
|
||||
}
|
||||
|
||||
TopExp_Explorer Ex(supportface,TopAbs_WIRE);
|
||||
if (!Ex.More())
|
||||
TopExp_Explorer Ex(supportface, TopAbs_WIRE);
|
||||
if (!Ex.More()) {
|
||||
supportface = TopoDS_Face();
|
||||
}
|
||||
RevolMode mode = RevolMode::None;
|
||||
generateRevolution(result, base.getShape(), sketchshape, supportface, upToFace, gp_Ax1(pnt, dir), method, mode, Standard_True);
|
||||
#ifdef FC_USE_TNP_FIX
|
||||
// revolve the face to a solid
|
||||
// TopoShape result(0);
|
||||
try {
|
||||
result = base.makeElementRevolution(gp_Ax1(pnt, dir), angle, supportface, upToFace);
|
||||
}
|
||||
catch (Standard_Failure&) {
|
||||
return new App::DocumentObjectExecReturn("Could not revolve the sketch!");
|
||||
}
|
||||
#else
|
||||
generateRevolution(result,
|
||||
base.getShape(),
|
||||
sketchshape.getShape(),
|
||||
supportface,
|
||||
upToFace,
|
||||
gp_Ax1(pnt, dir),
|
||||
method,
|
||||
mode,
|
||||
Standard_True);
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
bool midplane = Midplane.getValue();
|
||||
bool reversed = Reversed.getValue();
|
||||
generateRevolution(result, sketchshape, gp_Ax1(pnt, dir), angle, angle2, midplane, reversed, method);
|
||||
generateRevolution(result,
|
||||
sketchshape.getShape(),
|
||||
gp_Ax1(pnt, dir),
|
||||
angle,
|
||||
angle2,
|
||||
midplane,
|
||||
reversed,
|
||||
method);
|
||||
}
|
||||
|
||||
#ifdef FC_USE_TNP_FIX
|
||||
if (!result.isNull()) {
|
||||
result = refineShapeIfActive(result);
|
||||
// set the additive shape property for later usage in e.g. pattern
|
||||
this->AddSubShape.setValue(result);
|
||||
|
||||
if (!base.isNull()) {
|
||||
result = result.makeElementFuse(base);
|
||||
result = refineShapeIfActive(result);
|
||||
}
|
||||
#else
|
||||
if (!result.IsNull()) {
|
||||
result = refineShapeIfActive(result);
|
||||
// set the additive shape property for later usage in e.g. pattern
|
||||
@@ -186,16 +249,21 @@ App::DocumentObjectExecReturn *Revolution::execute()
|
||||
// Let's call algorithm computing a fuse operation:
|
||||
BRepAlgoAPI_Fuse mkFuse(base.getShape(), result);
|
||||
// Let's check if the fusion has been successful
|
||||
if (!mkFuse.IsDone())
|
||||
throw Part::BooleanException(QT_TRANSLATE_NOOP("Exception", "Fusion with base feature failed"));
|
||||
if (!mkFuse.IsDone()) {
|
||||
throw Part::BooleanException(
|
||||
QT_TRANSLATE_NOOP("Exception", "Fusion with base feature failed"));
|
||||
}
|
||||
result = mkFuse.Shape();
|
||||
result = refineShapeIfActive(result);
|
||||
}
|
||||
#endif
|
||||
|
||||
this->Shape.setValue(getSolid(result));
|
||||
}
|
||||
else
|
||||
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Could not revolve the sketch!"));
|
||||
else {
|
||||
return new App::DocumentObjectExecReturn(
|
||||
QT_TRANSLATE_NOOP("Exception", "Could not revolve the sketch!"));
|
||||
}
|
||||
|
||||
// eventually disable some settings that are not valid for the current method
|
||||
updateProperties(method);
|
||||
@@ -204,11 +272,15 @@ App::DocumentObjectExecReturn *Revolution::execute()
|
||||
}
|
||||
catch (Standard_Failure& e) {
|
||||
|
||||
if (std::string(e.GetMessageString()) == "TopoDS::Face")
|
||||
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Could not create face from sketch.\n"
|
||||
"Intersecting sketch entities in a sketch are not allowed."));
|
||||
else
|
||||
if (std::string(e.GetMessageString()) == "TopoDS::Face") {
|
||||
return new App::DocumentObjectExecReturn(
|
||||
QT_TRANSLATE_NOOP("Exception",
|
||||
"Could not create face from sketch.\n"
|
||||
"Intersecting sketch entities in a sketch are not allowed."));
|
||||
}
|
||||
else {
|
||||
return new App::DocumentObjectExecReturn(e.GetMessageString());
|
||||
}
|
||||
}
|
||||
catch (Base::Exception& e) {
|
||||
return new App::DocumentObjectExecReturn(e.what());
|
||||
@@ -257,7 +329,12 @@ Revolution::RevolMethod Revolution::methodFromString(const std::string& methodSt
|
||||
return RevolMethod::Dimension;
|
||||
}
|
||||
|
||||
#ifdef FC_USE_TNP_FIX
|
||||
void Revolution::generateRevolution(TopoShape& revol,
|
||||
#else
|
||||
void Revolution::generateRevolution(TopoDS_Shape& revol,
|
||||
|
||||
#endif
|
||||
const TopoDS_Shape& sketchshape,
|
||||
const gp_Ax1& axis,
|
||||
const double angle,
|
||||
@@ -267,46 +344,49 @@ void Revolution::generateRevolution(TopoDS_Shape& revol,
|
||||
RevolMethod method)
|
||||
{
|
||||
if (method == RevolMethod::Dimension || method == RevolMethod::TwoDimensions || method == RevolMethod::ThroughAll) {
|
||||
double angleTotal = angle;
|
||||
double angleOffset = 0.;
|
||||
double angleTotal = angle;
|
||||
double angleOffset = 0.;
|
||||
|
||||
if (method == RevolMethod::TwoDimensions) {
|
||||
// Rotate the face by `angle2`/`angle` to get "second" angle
|
||||
angleTotal += angle2;
|
||||
angleOffset = angle2 * -1.0;
|
||||
}
|
||||
else if (midplane) {
|
||||
// Rotate the face by half the angle to get Revolution symmetric to sketch plane
|
||||
angleOffset = -angle / 2;
|
||||
}
|
||||
if (method == RevolMethod::TwoDimensions) {
|
||||
// Rotate the face by `angle2`/`angle` to get "second" angle
|
||||
angleTotal += angle2;
|
||||
angleOffset = angle2 * -1.0;
|
||||
}
|
||||
else if (midplane) {
|
||||
// Rotate the face by half the angle to get Revolution symmetric to sketch plane
|
||||
angleOffset = -angle / 2;
|
||||
}
|
||||
|
||||
if (fabs(angleTotal) < Precision::Angular())
|
||||
throw Base::ValueError("Cannot create a revolution with zero angle.");
|
||||
if (fabs(angleTotal) < Precision::Angular())
|
||||
throw Base::ValueError("Cannot create a revolution with zero angle.");
|
||||
|
||||
gp_Ax1 revolAx(axis);
|
||||
if (reversed) {
|
||||
revolAx.Reverse();
|
||||
}
|
||||
gp_Ax1 revolAx(axis);
|
||||
if (reversed) {
|
||||
revolAx.Reverse();
|
||||
}
|
||||
|
||||
TopoDS_Shape from = sketchshape;
|
||||
if (method == RevolMethod::TwoDimensions || midplane) {
|
||||
gp_Trsf mov;
|
||||
mov.SetRotation(revolAx, angleOffset);
|
||||
TopLoc_Location loc(mov);
|
||||
from.Move(loc);
|
||||
}
|
||||
TopoDS_Shape from = sketchshape;
|
||||
if (method == RevolMethod::TwoDimensions || midplane) {
|
||||
gp_Trsf mov;
|
||||
mov.SetRotation(revolAx, angleOffset);
|
||||
TopLoc_Location loc(mov);
|
||||
from.Move(loc);
|
||||
}
|
||||
|
||||
// revolve the face to a solid
|
||||
// BRepPrimAPI is the only option that allows use of this shape for patterns.
|
||||
// See https://forum.freecadweb.org/viewtopic.php?f=8&t=70185&p=611673#p611673.
|
||||
BRepPrimAPI_MakeRevol RevolMaker(from, revolAx, angleTotal);
|
||||
#ifdef FC_USE_TNP_FIX
|
||||
revol = TopoShape(from).makeElementRevolve(revolAx,angleTotal);
|
||||
#else
|
||||
// revolve the face to a solid
|
||||
// BRepPrimAPI is the only option that allows use of this shape for patterns.
|
||||
// See https://forum.freecadweb.org/viewtopic.php?f=8&t=70185&p=611673#p611673.
|
||||
BRepPrimAPI_MakeRevol RevolMaker(from, revolAx, angleTotal);
|
||||
|
||||
if (!RevolMaker.IsDone())
|
||||
throw Base::RuntimeError("ProfileBased: RevolMaker failed! Could not revolve the sketch!");
|
||||
else
|
||||
revol = RevolMaker.Shape();
|
||||
}
|
||||
else {
|
||||
if (!RevolMaker.IsDone())
|
||||
throw Base::RuntimeError("ProfileBased: RevolMaker failed! Could not revolve the sketch!");
|
||||
else
|
||||
revol = RevolMaker.Shape();
|
||||
#endif
|
||||
} else {
|
||||
std::stringstream str;
|
||||
str << "ProfileBased: Internal error: Unknown method for generateRevolution()";
|
||||
throw Base::RuntimeError(str.str());
|
||||
|
||||
@@ -95,7 +95,11 @@ protected:
|
||||
/**
|
||||
* Generates a revolution of the input sketchshape and stores it in the given \a revol.
|
||||
*/
|
||||
#ifdef FC_USE_TNP_FIX
|
||||
void generateRevolution(TopoShape& revol,
|
||||
#else
|
||||
void generateRevolution(TopoDS_Shape& revol,
|
||||
#endif
|
||||
const TopoDS_Shape& sketchshape,
|
||||
const gp_Ax1& ax1,
|
||||
const double angle,
|
||||
|
||||
@@ -280,7 +280,7 @@ TopoDS_Shape ProfileBased::getVerifiedFace(bool silent) const {
|
||||
}
|
||||
|
||||
TopoShape ProfileBased::getTopoShapeVerifiedFace(bool silent,
|
||||
bool doFit,
|
||||
[[maybe_unused]]bool doFit, // TODO: Remove parameter
|
||||
bool allowOpen,
|
||||
const App::DocumentObject* profile,
|
||||
const std::vector<std::string>& _subs) const
|
||||
|
||||
@@ -498,14 +498,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
self.Doc.recompute()
|
||||
# Assert
|
||||
# self.assertEqual(len(body.Shape.childShapes()), 1)
|
||||
if App.GuiUp:
|
||||
# Todo: This triggers a 'hasher mismatch' warning in TopoShape::mapSubElement as called by
|
||||
# flushElementMap. This appears to be the case whenever you have a parent with a hasher
|
||||
# that has children without hashmaps. The warning seems to be spurious in this case, but
|
||||
# perhaps there is a solution involving setting hashmaps on all elements.
|
||||
self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 30)
|
||||
else:
|
||||
self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 26)
|
||||
self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 30)
|
||||
self.assertEqual(body.Shape.ElementMapSize,30)
|
||||
self.assertEqual(sketch.Shape.ElementMapSize,12)
|
||||
self.assertEqual(pad.Shape.ElementMapSize,30)
|
||||
@@ -520,7 +513,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
return
|
||||
# Act
|
||||
revolution = self.Doc.addObject('PartDesign::Revolution', 'Revolution')
|
||||
revolution.ReferenceAxis = body.Origin.OriginFeatures[1]
|
||||
revolution.ReferenceAxis = (self.Doc.getObject('Sketch'),['V_Axis'])
|
||||
revolution.Profile = sketch # Causing segfault
|
||||
body.addObject(sketch)
|
||||
body.addObject(revolution)
|
||||
@@ -582,7 +575,7 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
# Act
|
||||
helix = self.Doc.addObject('PartDesign::AdditiveHelix', 'Helix')
|
||||
helix.Profile = sketch
|
||||
helix.ReferenceAxis = body.Origin.OriginFeatures[2]
|
||||
helix.ReferenceAxis = (self.Doc.getObject('Sketch'),['V_Axis'])
|
||||
body.addObject(sketch)
|
||||
body.addObject(helix)
|
||||
self.Doc.recompute()
|
||||
@@ -789,6 +782,174 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
self.assertEqual(plane.Shape.ElementMapSize, 0)
|
||||
self.assertEqual(pad.Shape.ElementMapSize, 26)
|
||||
|
||||
def testChangeSketch(self):
|
||||
# Arrange
|
||||
self.Body = self.Doc.addObject('PartDesign::Body', 'Body')
|
||||
# Make first offset cube Pad
|
||||
self.PadSketch = self.Doc.addObject('Sketcher::SketchObject', 'Sketch')
|
||||
self.Body.addObject(self.PadSketch)
|
||||
TestSketcherApp.CreateRectangleSketch(self.PadSketch, (0, 0), (31.37, 25.2))
|
||||
self.Doc.recompute()
|
||||
self.Pad = self.Doc.addObject("PartDesign::Pad", "Pad")
|
||||
self.Body.addObject(self.Pad)
|
||||
self.Pad.Profile = self.PadSketch
|
||||
self.Pad.Length = 10
|
||||
self.Doc.recompute()
|
||||
|
||||
self.Sketch001 = self.Body.newObject('Sketcher::SketchObject','Sketch001')
|
||||
self.Sketch001.AttachmentSupport = (self.Doc.getObject('Pad'),['Face6',])
|
||||
self.Sketch001.MapMode = 'FlatFace'
|
||||
App.ActiveDocument.recompute()
|
||||
|
||||
self.Sketch001.addExternal("Pad","Edge10")
|
||||
self.Sketch001.addExternal("Pad","Edge7")
|
||||
|
||||
geoList = []
|
||||
geoList.append(Part.Circle(App.Vector(15.093666, 13.036922, 0.000000),
|
||||
App.Vector(0.000000, 0.000000, 1.000000), 5.000000))
|
||||
self.Sketch001.addGeometry(geoList,False)
|
||||
del geoList
|
||||
self.Sketch001.addConstraint(Sketcher.Constraint('Radius',0,5.000000))
|
||||
self.Sketch001.addConstraint(Sketcher.Constraint('Symmetric',-3,2,-4,1,0,3))
|
||||
App.ActiveDocument.recompute()
|
||||
self.Doc.recompute()
|
||||
|
||||
self.Pad001 = self.Body.newObject('PartDesign::Pad','Pad001')
|
||||
self.Pad001.Profile = self.Doc.getObject('Sketch001')
|
||||
self.Pad001.Length = 10
|
||||
App.ActiveDocument.recompute()
|
||||
self.Pad001.ReferenceAxis = (self.Doc.getObject('Sketch001'),['N_Axis'])
|
||||
self.Sketch001.Visibility = False
|
||||
App.ActiveDocument.recompute()
|
||||
|
||||
self.Pad001.Length = 10.000000
|
||||
self.Pad001.TaperAngle = 0.000000
|
||||
self.Pad001.UseCustomVector = 0
|
||||
self.Pad001.Direction = (0, 0, 1)
|
||||
self.Pad001.ReferenceAxis = (self.Doc.getObject('Sketch001'), ['N_Axis'])
|
||||
self.Pad001.AlongSketchNormal = 1
|
||||
self.Pad001.Type = 0
|
||||
self.Pad001.UpToFace = None
|
||||
self.Pad001.Reversed = 0
|
||||
self.Pad001.Midplane = 0
|
||||
self.Pad001.Offset = 0
|
||||
self.Doc.recompute()
|
||||
self.Doc.getObject('Pad').Visibility = False
|
||||
|
||||
self.Doc.getObject('Sketch001').Visibility = False
|
||||
|
||||
# Modify the original sketch to generate TNP issue
|
||||
geoList = []
|
||||
geoList.append(Part.LineSegment(App.Vector(2.510468, 22.837425, 0.000000),
|
||||
App.Vector(2.510468, 19.933617, 0.000000)))
|
||||
geoList.append(Part.LineSegment(App.Vector(2.510468, 19.933617, 0.000000),
|
||||
App.Vector(4.869811, 19.933617, 0.000000)))
|
||||
geoList.append(Part.LineSegment(App.Vector(4.869811, 19.933617, 0.000000),
|
||||
App.Vector(4.869811, 22.837425, 0.000000)))
|
||||
geoList.append(Part.LineSegment(App.Vector(4.869811, 22.837425, 0.000000),
|
||||
App.Vector(2.510468, 22.837425, 0.000000)))
|
||||
self.PadSketch.addGeometry(geoList,False)
|
||||
del geoList
|
||||
|
||||
constraintList = []
|
||||
constraintList.append(Sketcher.Constraint('Coincident', 4, 2, 5, 1))
|
||||
constraintList.append(Sketcher.Constraint('Coincident', 5, 2, 6, 1))
|
||||
constraintList.append(Sketcher.Constraint('Coincident', 6, 2, 7, 1))
|
||||
constraintList.append(Sketcher.Constraint('Coincident', 7, 2, 4, 1))
|
||||
constraintList.append(Sketcher.Constraint('Vertical', 4))
|
||||
constraintList.append(Sketcher.Constraint('Vertical', 6))
|
||||
constraintList.append(Sketcher.Constraint('Horizontal', 5))
|
||||
constraintList.append(Sketcher.Constraint('Horizontal', 7))
|
||||
self.PadSketch.addConstraint(constraintList)
|
||||
del constraintList
|
||||
self.Doc.recompute()
|
||||
# Assert
|
||||
if self.Body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
self.assertEqual(self.Body.Shape.BoundBox.XMin,0)
|
||||
self.assertEqual(self.Body.Shape.BoundBox.YMin,0)
|
||||
self.assertEqual(self.Body.Shape.BoundBox.ZMin,0)
|
||||
self.assertEqual(self.Body.Shape.BoundBox.XMax,31.37)
|
||||
self.assertEqual(self.Body.Shape.BoundBox.YMax,25.2)
|
||||
self.assertEqual(self.Body.Shape.BoundBox.ZMax,20)
|
||||
|
||||
def testApplyFillet(self):
|
||||
# Arrange
|
||||
self.Body = self.Doc.addObject('PartDesign::Body', 'Body')
|
||||
# Make first offset cube Pad
|
||||
self.PadSketch = self.Doc.addObject('Sketcher::SketchObject', 'Sketch')
|
||||
self.Body.addObject(self.PadSketch)
|
||||
TestSketcherApp.CreateRectangleSketch(self.PadSketch, (0, 0), (31.37, 25.2))
|
||||
self.Doc.recompute()
|
||||
self.Pad = self.Doc.addObject("PartDesign::Pad", "Pad")
|
||||
self.Body.addObject(self.Pad)
|
||||
self.Pad.Profile = self.PadSketch
|
||||
self.Pad.Length = 10
|
||||
self.Doc.recompute()
|
||||
|
||||
self.Sketch001 = self.Body.newObject('Sketcher::SketchObject','Sketch001')
|
||||
self.Sketch001.AttachmentSupport = (self.Doc.getObject('Pad'),['Face6',])
|
||||
self.Sketch001.MapMode = 'FlatFace'
|
||||
App.ActiveDocument.recompute()
|
||||
|
||||
self.Sketch001.addExternal("Pad","Edge10")
|
||||
self.Sketch001.addExternal("Pad","Edge7")
|
||||
|
||||
geoList = []
|
||||
geoList.append(Part.Circle(App.Vector(15.093666, 13.036922, 0.000000),
|
||||
App.Vector(0.000000, 0.000000, 1.000000), 5.000000))
|
||||
self.Sketch001.addGeometry(geoList,False)
|
||||
del geoList
|
||||
self.Sketch001.addConstraint(Sketcher.Constraint('Radius',0,5.000000))
|
||||
self.Sketch001.addConstraint(Sketcher.Constraint('Symmetric',-3,2,-4,1,0,3))
|
||||
App.ActiveDocument.recompute()
|
||||
self.Doc.recompute()
|
||||
|
||||
self.Pad001 = self.Body.newObject('PartDesign::Pad','Pad001')
|
||||
self.Pad001.Profile = self.Doc.getObject('Sketch001')
|
||||
self.Pad001.Length = 10
|
||||
App.ActiveDocument.recompute()
|
||||
self.Pad001.ReferenceAxis = (self.Doc.getObject('Sketch001'),['N_Axis'])
|
||||
self.Sketch001.Visibility = False
|
||||
App.ActiveDocument.recompute()
|
||||
|
||||
self.Pad001.Length = 10.000000
|
||||
self.Pad001.TaperAngle = 0.000000
|
||||
self.Pad001.UseCustomVector = 0
|
||||
self.Pad001.Direction = (0, 0, 1)
|
||||
self.Pad001.ReferenceAxis = (self.Doc.getObject('Sketch001'), ['N_Axis'])
|
||||
self.Pad001.AlongSketchNormal = 1
|
||||
self.Pad001.Type = 0
|
||||
self.Pad001.UpToFace = None
|
||||
self.Pad001.Reversed = 0
|
||||
self.Pad001.Midplane = 0
|
||||
self.Pad001.Offset = 0
|
||||
self.Doc.recompute()
|
||||
self.Doc.getObject('Pad').Visibility = False
|
||||
|
||||
self.Doc.getObject('Sketch001').Visibility = False
|
||||
|
||||
area1 = self.Pad.Shape.Area
|
||||
# Act
|
||||
self.Doc.getObject('Sketch').fillet(2,3,App.Vector(6.673934,25.000000,0),App.Vector(0.000000,21.980343,0),4.740471,True,True,False)
|
||||
self.Doc.recompute()
|
||||
# filleted = self.Pad001.Shape.makeFillet(1,self.Pad001.Shape.Edges[0:2])
|
||||
# self.filleted = Part.show(filleted,"Filleted")
|
||||
# self.Body.addObject(self.filleted)
|
||||
area2 = self.Pad.Shape.Area
|
||||
print(area1,area2)
|
||||
|
||||
# Assert
|
||||
if self.Body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023.
|
||||
return
|
||||
self.assertEqual(self.Body.Shape.BoundBox.XMin,0)
|
||||
self.assertEqual(self.Body.Shape.BoundBox.YMin,0)
|
||||
self.assertEqual(self.Body.Shape.BoundBox.ZMin,0)
|
||||
self.assertEqual(self.Body.Shape.BoundBox.XMax,31.37)
|
||||
self.assertEqual(self.Body.Shape.BoundBox.YMax,25.2)
|
||||
self.assertEqual(self.Body.Shape.BoundBox.ZMax,20)
|
||||
|
||||
|
||||
def create_t_sketch(self):
|
||||
self.Doc.getObject('Body').newObject('Sketcher::SketchObject', 'Sketch')
|
||||
geo_list = [
|
||||
|
||||
@@ -996,7 +996,6 @@ void DrawSketchHandler::createAutoConstraints(const std::vector<AutoConstraint>&
|
||||
geoId2);
|
||||
} break;
|
||||
case Sketcher::Symmetric: {
|
||||
Sketcher::PointPos posId2 = cstr.PosId;
|
||||
Gui::cmdAppObjectArgs(
|
||||
sketchgui->getObject(),
|
||||
"addConstraint(Sketcher.Constraint('Symmetric',%d,1,%d,2,%d,%d)) ",
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
|
||||
using namespace Start;
|
||||
|
||||
FC_LOG_LEVEL_INIT(ExamplesModel)
|
||||
|
||||
ExamplesModel::ExamplesModel(QObject* parent)
|
||||
: DisplayedFilesModel(parent)
|
||||
|
||||
@@ -115,6 +115,7 @@ PyObject* initModule()
|
||||
PyMOD_INIT_FUNC(StartGui)
|
||||
{
|
||||
static StartGui::StartLauncher* launcher = new StartGui::StartLauncher();
|
||||
Q_UNUSED(launcher)
|
||||
|
||||
Base::Console().Log("Loading GUI of Start module... ");
|
||||
PyObject* mod = StartGui::initModule();
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
include_directories(
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
${Boost_INCLUDE_DIRS}
|
||||
${COIN3D_INCLUDE_DIRS}
|
||||
${PYTHON_INCLUDE_DIRS}
|
||||
${QtCore_INCLUDE_DIRS}
|
||||
${QtSvg_INCLUDE_DIRS}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#define FREECAD_START_GENERALSETTINGSWIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <3rdParty/GSL/include/gsl/pointers>
|
||||
#include <gsl/pointers>
|
||||
|
||||
class QLabel;
|
||||
class QComboBox;
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
#endif
|
||||
|
||||
#include "ThemeSelectorWidget.h"
|
||||
#include <3rdParty/GSL/include/gsl/pointers>
|
||||
#include <gsl/pointers>
|
||||
#include <App/Application.h>
|
||||
#include <Gui/Application.h>
|
||||
#include <Gui/PreferencePackManager.h>
|
||||
|
||||
@@ -100,6 +100,32 @@ DrawPage* DrawTemplate::getParentPage() const
|
||||
return page;
|
||||
}
|
||||
|
||||
// Return the counts related to pages, namely collated page index and total page count
|
||||
std::pair<int, int> DrawTemplate::getPageNumbers() const
|
||||
{
|
||||
std::vector<DocumentObject *> pages = getDocument()->getObjectsOfType(TechDraw::DrawPage::getClassTypeId());
|
||||
std::vector<QString> pageNames;
|
||||
for (auto page : pages) {
|
||||
if (page->isAttachedToDocument() &&
|
||||
!page->testStatus(App::ObjectStatus::Remove)) {
|
||||
pageNames.push_back(QString::fromUtf8(page->Label.getValue()));
|
||||
}
|
||||
}
|
||||
QCollator collator;
|
||||
std::sort(pageNames.begin(), pageNames.end(), collator);
|
||||
|
||||
int pos = 0;
|
||||
DrawPage *page = getParentPage();
|
||||
if (page) {
|
||||
auto it = std::find(pageNames.begin(), pageNames.end(), QString::fromUtf8(page->Label.getValue()));
|
||||
if (it != pageNames.end()) {
|
||||
pos = it - pageNames.begin() + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return std::pair<int, int>(pos, (int) pageNames.size());
|
||||
}
|
||||
|
||||
QString DrawTemplate::getAutofillValue(const QString &id) const
|
||||
{
|
||||
// author
|
||||
@@ -133,32 +159,23 @@ QString DrawTemplate::getAutofillValue(const QString &id) const
|
||||
}
|
||||
// sheet
|
||||
else if (id.compare(QString::fromUtf8(Autofill::Sheet)) == 0) {
|
||||
std::vector<DocumentObject *> pages = getDocument()->getObjectsOfType(TechDraw::DrawPage::getClassTypeId());
|
||||
std::vector<QString> pageNames;
|
||||
for (auto page : pages) {
|
||||
if (page->isAttachedToDocument() &&
|
||||
!page->testStatus(App::ObjectStatus::Remove)) {
|
||||
pageNames.push_back(QString::fromUtf8(page->Label.getValue()));
|
||||
}
|
||||
}
|
||||
QCollator collator;
|
||||
std::sort(pageNames.begin(), pageNames.end(), collator);
|
||||
|
||||
int pos = 0;
|
||||
DrawPage *page = getParentPage();
|
||||
if (page) {
|
||||
auto it = std::find(pageNames.begin(), pageNames.end(), QString::fromUtf8(page->Label.getValue()));
|
||||
if (it != pageNames.end()) {
|
||||
pos = it - pageNames.begin() + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return QString::asprintf("%d / %d", pos, (int) pageNames.size());
|
||||
std::pair<int, int> pageNumbers = getPageNumbers();
|
||||
return QString::asprintf("%d / %d", pageNumbers.first, pageNumbers.second);
|
||||
}
|
||||
// title
|
||||
else if (id.compare(QString::fromUtf8(Autofill::Title)) == 0) {
|
||||
return QString::fromUtf8(getDocument()->Label.getValue());
|
||||
}
|
||||
// page number
|
||||
else if (id.compare(QString::fromUtf8(Autofill::PageNumber)) == 0) {
|
||||
std::pair<int, int> pageNumbers = getPageNumbers();
|
||||
return QString::number(pageNumbers.first);
|
||||
}
|
||||
// page total
|
||||
else if (id.compare(QString::fromUtf8(Autofill::PageCount)) == 0) {
|
||||
std::pair<int, int> pageNumbers = getPageNumbers();
|
||||
return QString::number(pageNumbers.second);
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ public:
|
||||
virtual double getHeight() const;
|
||||
|
||||
virtual DrawPage* getParentPage() const;
|
||||
virtual std::pair<int, int> getPageNumbers() const;
|
||||
|
||||
virtual QString getAutofillValue(const QString &id) const;
|
||||
|
||||
@@ -76,6 +77,8 @@ public:
|
||||
static constexpr const char *Scale = "scale";
|
||||
static constexpr const char *Sheet = "sheet";
|
||||
static constexpr const char *Title = "title";
|
||||
static constexpr const char *PageNumber = "page_number";
|
||||
static constexpr const char *PageCount = "page_count";
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
@@ -178,7 +178,7 @@ int Preferences::balloonShape()
|
||||
QString Preferences::defaultTemplate()
|
||||
{
|
||||
std::string defaultDir = App::Application::getResourceDir() + "Mod/TechDraw/Templates/";
|
||||
std::string defaultFileName = defaultDir + "A4_LandscapeTD.svg";
|
||||
std::string defaultFileName = defaultDir + "Default_Template_A4_Landscape.svg";
|
||||
std::string prefFileName = getPreferenceGroup("Files")->GetASCII("TemplateFile", defaultFileName.c_str());
|
||||
if (prefFileName.empty()) {
|
||||
prefFileName = defaultFileName;
|
||||
|
||||
11
src/Mod/TechDraw/Templates/Default_Template_A4_Landscape.svg
Normal file
11
src/Mod/TechDraw/Templates/Default_Template_A4_Landscape.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="svg2" width="297mm" height="210mm" version="1.1" viewBox="0 0 297 210" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<metadata id="metadata8">
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 519 B |
105
src/Mod/Test/TestPerf.py
Normal file
105
src/Mod/Test/TestPerf.py
Normal file
@@ -0,0 +1,105 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# ***************************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2024 bgbsww@gmail.com *
|
||||
# * *
|
||||
# * This file is part of FreeCAD. *
|
||||
# * *
|
||||
# * FreeCAD is free software: you can redistribute it and/or modify it *
|
||||
# * under the terms of the GNU Lesser General Public License as *
|
||||
# * published by the Free Software Foundation, either version 2.1 of the *
|
||||
# * License, or (at your option) any later version. *
|
||||
# * *
|
||||
# * FreeCAD 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 *
|
||||
# * Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Lesser General Public *
|
||||
# * License along with FreeCAD. If not, see *
|
||||
# * <https://www.gnu.org/licenses/>. *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
import FreeCAD as App
|
||||
import Part
|
||||
|
||||
try:
|
||||
from guppy import hpy
|
||||
|
||||
Memtest = True
|
||||
except ImportError:
|
||||
Memtest = False
|
||||
|
||||
try:
|
||||
import cProfile
|
||||
|
||||
Pyprofile = True
|
||||
except ImportError:
|
||||
Pyprofile = False
|
||||
|
||||
|
||||
class PerfTestCase(unittest.TestCase):
|
||||
"""
|
||||
Special Test Case that takes a list of filenames after the "--pass" parameter to FreeCAD, and
|
||||
runs a performance test by opening them, starting instrumentation, calling recompute(), and
|
||||
then saving results.
|
||||
|
||||
Intended to be run as "<perf profiling> FreeCAD -t TestPerf --pass <modelname>
|
||||
|
||||
External perf profiling requires Python 3.12 or better, and a linux platform.
|
||||
cProfile profiling and guppy memory information can run anywhere.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
if "--pass" in sys.argv:
|
||||
self.fileList = sys.argv[sys.argv.index("--pass") + 1 :]
|
||||
else:
|
||||
raise FileNotFoundError("Must provide filename parameter(s) via --pass")
|
||||
if Part.Shape().ElementMapVersion == "":
|
||||
self.tnp = ""
|
||||
else:
|
||||
self.tnp = ".tnp"
|
||||
if Memtest:
|
||||
# Use filename of first model with ".mprofile" appended for python memory use info.
|
||||
self.memfile = open(self.fileList[0] + self.tnp + ".mprofile", "w", encoding="utf-8")
|
||||
|
||||
def testAll(self):
|
||||
if Pyprofile:
|
||||
# Generate a cProfile file as a python only time profile.
|
||||
profile = cProfile.Profile()
|
||||
profile.enable()
|
||||
try:
|
||||
# This is Python 3.12 on supported platforms ( linux ) only so that if we are run under
|
||||
# an external 'perf' command, we report the python data. This can be extremely useful,
|
||||
# because it contains not only time consumed, but python and c++ calls that took place
|
||||
# so deep analysis can be performed on the resulting file. See calling script in
|
||||
# tools/profile/perftest.sh for a wrapper.
|
||||
sys.activate_stack_trampoline("perf")
|
||||
except AttributeError:
|
||||
pass # Totally okay if we don't have that, we can use the cProfile if it's there.
|
||||
|
||||
# Walk all files after the --pass. Normally one to avoid result intermingling.
|
||||
for fileName in self.fileList:
|
||||
doc = App.openDocument(fileName)
|
||||
doc.recompute() # The heart of the performance measurement.
|
||||
if Memtest:
|
||||
# If guppy is available, take a heap snapshot and save it. Note that if multiple
|
||||
# files are provided then their heap data sets will be appended to the same file.
|
||||
dumpdata = hpy().heap()
|
||||
dumpdata.stat.dump(self.memfile)
|
||||
self.memfile.flush()
|
||||
App.closeDocument(doc.Name)
|
||||
|
||||
try:
|
||||
sys.deactivate_stack_trampoline()
|
||||
except AttributeError:
|
||||
pass
|
||||
if Pyprofile:
|
||||
profile.disable()
|
||||
# Use filename of first model with ".cprofile" appended for python profiling information.
|
||||
profile.dump_stats(self.fileList[0] + self.tnp + ".cprofile")
|
||||
if Memtest:
|
||||
self.memfile.close()
|
||||
Reference in New Issue
Block a user