From 38734f4f868f381a94389b107d24b89947618d73 Mon Sep 17 00:00:00 2001 From: donovaly Date: Sat, 5 Dec 2020 03:58:39 +0100 Subject: [PATCH 01/17] [TD] treat dimensions the same way, no matter if with unit or not fixes the issue reported here: https://forum.freecadweb.org/viewtopic.php?f=35&t=52545&start=40#p454483 --- src/Mod/TechDraw/App/DrawViewDimension.cpp | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/Mod/TechDraw/App/DrawViewDimension.cpp b/src/Mod/TechDraw/App/DrawViewDimension.cpp index ef1dea950b..8849cedead 100644 --- a/src/Mod/TechDraw/App/DrawViewDimension.cpp +++ b/src/Mod/TechDraw/App/DrawViewDimension.cpp @@ -691,21 +691,12 @@ std::string DrawViewDimension::formatValue(qreal value, QString qFormatSpec, int // - the value in the base unit but without displayed unit // - the value + unit (not necessarily the base unit!) // the user can overwrite the decimal settings, so we must in every case use the formatSpecifier - // if useDecimals(), then formatSpecifier = global decimals, otherwise it is %.2f + // the default is: if useDecimals(), then formatSpecifier = global decimals, otherwise it is %.2f QLocale loc; double userVal; - bool checkDecimals = true; if (showUnits() || (Type.isValue("Angle")) || (Type.isValue("Angle3Pt"))) { - formattedValue = qUserString; // result value + unit (not necessarily base unit!) - // remove unit - formattedValue.remove(rxUnits); - // to number - userVal = loc.toDouble(formattedValue); - if (userVal >= 1.0) - // we can assure we didn't make an error > 10% via getUserString() - checkDecimals = false; - } - if (checkDecimals){ + userVal = asQuantity.getValue(); + } else { // get value in the base unit with default decimals // for the conversion we use the same method as in DlgUnitsCalculator::valueChanged // get the conversion factor for the unit @@ -750,7 +741,6 @@ std::string DrawViewDimension::formatValue(qreal value, QString qFormatSpec, int //qUserString from Quantity includes units - prefix + R + nnn ft + suffix qMultiValueStr = formatPrefix + qGenPrefix + qUserString + formatSuffix; } - formattedValue = qMultiValueStr; } From 8620d77bc025c2d925a65989d6f1f0561e211d00 Mon Sep 17 00:00:00 2001 From: JAndersM Date: Mon, 7 Dec 2020 19:51:29 +0100 Subject: [PATCH 02/17] Update task_scale.py The changes solves the Ticket #3691 - Bug in draft scale. Only integers work --- src/Mod/Draft/drafttaskpanels/task_scale.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Mod/Draft/drafttaskpanels/task_scale.py b/src/Mod/Draft/drafttaskpanels/task_scale.py index 7d6e93b289..a19c75fc7e 100644 --- a/src/Mod/Draft/drafttaskpanels/task_scale.py +++ b/src/Mod/Draft/drafttaskpanels/task_scale.py @@ -53,21 +53,21 @@ class ScaleTaskPanel: layout.addWidget(self.xLabel, 0, 0, 1, 1) self.xValue = QtGui.QDoubleSpinBox() self.xValue.setRange(0.0000001, 1000000.0) - self.xValue.setDecimals(Draft.getParam("precision")) + self.xValue.setDecimals(Draft.precision()) self.xValue.setValue(1) layout.addWidget(self.xValue,0,1,1,1) self.yLabel = QtGui.QLabel() layout.addWidget(self.yLabel,1,0,1,1) self.yValue = QtGui.QDoubleSpinBox() self.yValue.setRange(.0000001,1000000.0) - self.yValue.setDecimals(Draft.getParam("precision")) + self.yValue.setDecimals(Draft.precision()) self.yValue.setValue(1) layout.addWidget(self.yValue,1,1,1,1) self.zLabel = QtGui.QLabel() layout.addWidget(self.zLabel,2,0,1,1) self.zValue = QtGui.QDoubleSpinBox() self.zValue.setRange(.0000001,1000000.0) - self.zValue.setDecimals(Draft.getParam("precision")) + self.zValue.setDecimals(Draft.precision()) self.zValue.setValue(1) layout.addWidget(self.zValue,2,1,1,1) self.lock = QtGui.QCheckBox() From ccc4e5cd21757155bbd690dca819f69965c6256a Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Mon, 7 Dec 2020 13:13:34 -0600 Subject: [PATCH 03/17] [Draft] Fix airfoil data detection regex The regular expression that Draft's airfoil data importer uses did not allow for lowercase "e" when using scientific notation. This corrects that, and silences Python Developer Mode warnings about deprecated escape sequences by converting most of the regex to use raw strings. --- src/Mod/Draft/importAirfoilDAT.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Mod/Draft/importAirfoilDAT.py b/src/Mod/Draft/importAirfoilDAT.py index 4b19e34ab7..c49fa61efa 100644 --- a/src/Mod/Draft/importAirfoilDAT.py +++ b/src/Mod/Draft/importAirfoilDAT.py @@ -157,9 +157,9 @@ def process(doc, filename): with the parsed information. """ # Regex to identify data rows and throw away unused metadata - xval = '(?P(\-|\d*)\.\d+(E\-?\d+)?)' - yval = '(?P\-?\s*\d*\.\d+(E\-?\d+)?)' - _regex = '^\s*' + xval + '\,?\s*' + yval + '\s*$' + xval = r'(?P(\-|\d*)\.\d+([Ee]\-?\d+)?)' + yval = r'(?P\-?\s*\d*\.\d+([Ee]\-?\d+)?)' + _regex = r'^\s*' + xval + r'\,?\s*' + yval + r'\s*$' regex = re.compile(_regex) afile = pythonopen(filename, 'r') From 438169b33517a675fb74718c03eb314c001f8879 Mon Sep 17 00:00:00 2001 From: Aapo Date: Fri, 20 Nov 2020 23:43:54 +0200 Subject: [PATCH 04/17] [TD] Add Balloon property EndTypeScale for scaling line end decorations (arrows etc). --- src/Mod/TechDraw/App/DrawViewBalloon.cpp | 3 +++ src/Mod/TechDraw/App/DrawViewBalloon.h | 17 +++++++++-------- src/Mod/TechDraw/Gui/QGIViewBalloon.cpp | 4 ++-- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/Mod/TechDraw/App/DrawViewBalloon.cpp b/src/Mod/TechDraw/App/DrawViewBalloon.cpp index b58c3328fd..2b8950b73c 100644 --- a/src/Mod/TechDraw/App/DrawViewBalloon.cpp +++ b/src/Mod/TechDraw/App/DrawViewBalloon.cpp @@ -105,6 +105,9 @@ DrawViewBalloon::DrawViewBalloon(void) ADD_PROPERTY_TYPE(ShapeScale,(1.0),"",(App::PropertyType)(App::Prop_None),"Balloon shape scale"); ShapeScale.setConstraints(&SymbolScaleRange); + ADD_PROPERTY_TYPE(EndTypeScale,(1.0),"",(App::PropertyType)(App::Prop_None),"EndType shape scale"); + ShapeScale.setConstraints(&SymbolScaleRange); + ADD_PROPERTY_TYPE(TextWrapLen,(-1),"",(App::PropertyType)(App::Prop_None),"Text wrap length; -1 means no wrap"); ADD_PROPERTY_TYPE(KinkLength,(prefKinkLength()),"",(App::PropertyType)(App::Prop_None), diff --git a/src/Mod/TechDraw/App/DrawViewBalloon.h b/src/Mod/TechDraw/App/DrawViewBalloon.h index a039ca2398..ac72ae8699 100644 --- a/src/Mod/TechDraw/App/DrawViewBalloon.h +++ b/src/Mod/TechDraw/App/DrawViewBalloon.h @@ -49,15 +49,16 @@ public: DrawViewBalloon(); virtual ~DrawViewBalloon(); - App::PropertyLink SourceView; - App::PropertyString Text; - App::PropertyEnumeration EndType; - App::PropertyEnumeration BubbleShape; + App::PropertyLink SourceView; + App::PropertyString Text; + App::PropertyEnumeration EndType; + App::PropertyEnumeration BubbleShape; App::PropertyFloatConstraint ShapeScale; - App::PropertyDistance OriginX; - App::PropertyDistance OriginY; - App::PropertyFloat TextWrapLen; - App::PropertyDistance KinkLength; + App::PropertyFloatConstraint EndTypeScale; + App::PropertyDistance OriginX; + App::PropertyDistance OriginY; + App::PropertyFloat TextWrapLen; + App::PropertyDistance KinkLength; short mustExecute() const override; diff --git a/src/Mod/TechDraw/Gui/QGIViewBalloon.cpp b/src/Mod/TechDraw/Gui/QGIViewBalloon.cpp index 9b590d6b03..c0be4e63bb 100644 --- a/src/Mod/TechDraw/Gui/QGIViewBalloon.cpp +++ b/src/Mod/TechDraw/Gui/QGIViewBalloon.cpp @@ -717,14 +717,14 @@ void QGIViewBalloon::draw() double yAdj = 0.0; int endType = balloon->EndType.getValue(); double arrowAdj = QGIArrow::getOverlapAdjust(endType, - QGIArrow::getPrefArrowSize()); + balloon->EndTypeScale.getValue()*QGIArrow::getPrefArrowSize()); if (endType == ArrowType::NONE) { arrow->hide(); } else { arrow->setStyle(endType); - arrow->setSize(QGIArrow::getPrefArrowSize()); + arrow->setSize(balloon->EndTypeScale.getValue()*QGIArrow::getPrefArrowSize()); arrow->draw(); Base::Vector3d arrowTipPos(arrowTipX, arrowTipY, 0.0); From c39ede3b5fa0e67470a3ec4feac3722dffa98d8f Mon Sep 17 00:00:00 2001 From: Aapo Date: Sat, 21 Nov 2020 00:26:49 +0200 Subject: [PATCH 05/17] [TD] Add Balloon property LineVisible for making the Balloon line visible or hidden. --- src/Mod/TechDraw/App/DrawViewBalloon.cpp | 2 ++ src/Mod/TechDraw/App/DrawViewBalloon.h | 1 + src/Mod/TechDraw/Gui/QGIViewBalloon.cpp | 7 ++++++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Mod/TechDraw/App/DrawViewBalloon.cpp b/src/Mod/TechDraw/App/DrawViewBalloon.cpp index 2b8950b73c..9f8a397c39 100644 --- a/src/Mod/TechDraw/App/DrawViewBalloon.cpp +++ b/src/Mod/TechDraw/App/DrawViewBalloon.cpp @@ -113,6 +113,8 @@ DrawViewBalloon::DrawViewBalloon(void) ADD_PROPERTY_TYPE(KinkLength,(prefKinkLength()),"",(App::PropertyType)(App::Prop_None), "Distance from symbol to leader kink"); + ADD_PROPERTY_TYPE(LineVisible,(true),"",(App::PropertyType)(App::Prop_None),"Balloon line visible or hidden"); + SourceView.setScope(App::LinkScope::Global); Rotation.setStatus(App::Property::Hidden,true); Caption.setStatus(App::Property::Hidden,true); diff --git a/src/Mod/TechDraw/App/DrawViewBalloon.h b/src/Mod/TechDraw/App/DrawViewBalloon.h index ac72ae8699..0892aac32a 100644 --- a/src/Mod/TechDraw/App/DrawViewBalloon.h +++ b/src/Mod/TechDraw/App/DrawViewBalloon.h @@ -59,6 +59,7 @@ public: App::PropertyDistance OriginY; App::PropertyFloat TextWrapLen; App::PropertyDistance KinkLength; + App::PropertyBool LineVisible; short mustExecute() const override; diff --git a/src/Mod/TechDraw/Gui/QGIViewBalloon.cpp b/src/Mod/TechDraw/Gui/QGIViewBalloon.cpp index c0be4e63bb..d8404d9619 100644 --- a/src/Mod/TechDraw/Gui/QGIViewBalloon.cpp +++ b/src/Mod/TechDraw/Gui/QGIViewBalloon.cpp @@ -738,7 +738,7 @@ void QGIViewBalloon::draw() float arAngle = atan2(dirballoonLinesLine.y, dirballoonLinesLine.x) * 180 / M_PI; arrow->setPos(arrowTipX, arrowTipY); - if ( (endType == ArrowType::FILLED_TRIANGLE) && + if ( (endType == ArrowType::FILLED_TRIANGLE) && (prefOrthoPyramid()) ) { if (arAngle < 0.0) { arAngle += 360.0; @@ -765,6 +765,11 @@ void QGIViewBalloon::draw() dLinePath.lineTo(arrowTipX - xAdj, arrowTipY - yAdj); balloonLines->setPath(dLinePath); + if (!balloon->LineVisible.getValue()) { + arrow->hide(); + balloonLines->setPath(QPainterPath()); + } + // redraw the Balloon and the parent View if (hasHover && !isSelected()) { setPrettyPre(); From 3d85114f091405a2983daedc63459fb2ef4db43e Mon Sep 17 00:00:00 2001 From: Aapo Date: Mon, 7 Dec 2020 23:48:28 +0200 Subject: [PATCH 06/17] [TD] Add a comment about QGIViewBalloon.cpp draw() function needing refactoring. --- src/Mod/TechDraw/Gui/QGIViewBalloon.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Mod/TechDraw/Gui/QGIViewBalloon.cpp b/src/Mod/TechDraw/Gui/QGIViewBalloon.cpp index d8404d9619..1b7b062a0e 100644 --- a/src/Mod/TechDraw/Gui/QGIViewBalloon.cpp +++ b/src/Mod/TechDraw/Gui/QGIViewBalloon.cpp @@ -765,6 +765,7 @@ void QGIViewBalloon::draw() dLinePath.lineTo(arrowTipX - xAdj, arrowTipY - yAdj); balloonLines->setPath(dLinePath); + // This overwrites the previously created QPainterPath with empty one, in case it should be hidden. Should be refactored. if (!balloon->LineVisible.getValue()) { arrow->hide(); balloonLines->setPath(QPainterPath()); From a6e29e32aeccec7ce71f045fbed00c68671aec32 Mon Sep 17 00:00:00 2001 From: Andreas Brinner Date: Wed, 9 Dec 2020 11:21:59 +0100 Subject: [PATCH 07/17] Check return value of getMDIViewPage() a second time When exporting a hidden TechDraw::Page with exportPageAs[Pdf|SVG] a Segmentation Fault occured. The return value of getMDIViewPage() was only checked for the first call, but not for the second call. showMDIViewPage() silently returns, when the TechDraw::Page is hidden and does not load the page. --- src/Mod/TechDraw/Gui/AppTechDrawGuiPy.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Mod/TechDraw/Gui/AppTechDrawGuiPy.cpp b/src/Mod/TechDraw/Gui/AppTechDrawGuiPy.cpp index c15f0ec13c..14dc2882c3 100644 --- a/src/Mod/TechDraw/Gui/AppTechDrawGuiPy.cpp +++ b/src/Mod/TechDraw/Gui/AppTechDrawGuiPy.cpp @@ -193,7 +193,11 @@ private: } else { vpp->showMDIViewPage(); mdi = vpp->getMDIViewPage(); - mdi->printPdf(filePath); + if (mdi) { + mdi->printPdf(filePath); + } else { + throw Py::TypeError("Page not available! Is it Hidden?"); + } } } } @@ -234,7 +238,11 @@ private: } else { vpp->showMDIViewPage(); mdi = vpp->getMDIViewPage(); - mdi->saveSVG(filePath); + if (mdi) { + mdi->saveSVG(filePath); + } else { + throw Py::TypeError("Page not available! Is it Hidden?"); + } } } } From 4f96e3c5680adf14c5555d0d6c0d82ea0d044b22 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Wed, 9 Dec 2020 08:32:43 -0600 Subject: [PATCH 08/17] Add support for integer values. --- src/Mod/Draft/importAirfoilDAT.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mod/Draft/importAirfoilDAT.py b/src/Mod/Draft/importAirfoilDAT.py index c49fa61efa..e496667a4c 100644 --- a/src/Mod/Draft/importAirfoilDAT.py +++ b/src/Mod/Draft/importAirfoilDAT.py @@ -157,8 +157,8 @@ def process(doc, filename): with the parsed information. """ # Regex to identify data rows and throw away unused metadata - xval = r'(?P(\-|\d*)\.\d+([Ee]\-?\d+)?)' - yval = r'(?P\-?\s*\d*\.\d+([Ee]\-?\d+)?)' + xval = r'(?P(\-|\d*)\.*\d*([Ee]\-?\d+)?)' + yval = r'(?P\-?\s*\d*\.*\d*([Ee]\-?\d+)?)' _regex = r'^\s*' + xval + r'\,?\s*' + yval + r'\s*$' regex = re.compile(_regex) From c9d1d0c4621354bebd465e85e7703ab4a69249c8 Mon Sep 17 00:00:00 2001 From: marioalexis Date: Sat, 28 Nov 2020 17:06:16 -0300 Subject: [PATCH 09/17] Draft: Improve makeWires in upgrade function --- src/Mod/Draft/draftfunctions/upgrade.py | 118 ++++++++++-------- src/Mod/Draft/drafttests/test_modification.py | 20 ++- 2 files changed, 80 insertions(+), 58 deletions(-) diff --git a/src/Mod/Draft/draftfunctions/upgrade.py b/src/Mod/Draft/draftfunctions/upgrade.py index 8768d0d916..c449015bde 100644 --- a/src/Mod/Draft/draftfunctions/upgrade.py +++ b/src/Mod/Draft/draftfunctions/upgrade.py @@ -42,6 +42,7 @@ import draftmake.make_block as make_block from draftutils.messages import _msg, _err from draftutils.translate import _tr +from draftgeoutils.geometry import is_straight_line # Delay import of module until first use because it is heavy Part = lz.LazyLoader("Part", globals(), "Part") @@ -160,6 +161,8 @@ def upgrade(objects, delete=False, force=None): return None if len(obj.Shape.Edges) == 1: return None + if is_straight_line(obj.Shape) == True: + return None if utils.get_type(obj) == "Wire": obj.Closed = True return True @@ -333,26 +336,35 @@ def upgrade(objects, delete=False, force=None): def makeWires(objectslist): """Join edges in the given objects list into wires.""" edges = [] - for o in objectslist: - for e in o.Shape.Edges: - edges.append(e) + for object in objectslist: + for edge in object.Shape.Edges: + edges.append(edge) + try: - nedges = Part.__sortEdges__(edges[:]) + sorted_edges = Part.sortEdges(edges) if _DEBUG: - for e in nedges: - print("Curve: {}".format(e.Curve)) - print("first: {}, last: {}".format(e.Vertexes[0].Point, + for item_sorted_edges in sorted_edges: + for e in item_sorted_edges: + print("Curve: {}".format(e.Curve)) + print("first: {}, last: {}".format(e.Vertexes[0].Point, e.Vertexes[-1].Point)) - w = Part.Wire(nedges) + wires = [Part.Wire(e) for e in sorted_edges] except Part.OCCError: return None else: - if len(w.Edges) == len(edges): + for wire in wires: newobj = doc.addObject("Part::Feature", "Wire") - newobj.Shape = w + newobj.Shape = wire add_list.append(newobj) - delete_list.extend(objectslist) - return True + # delete object only if there are no links to it + # TODO: A more refined criteria to delete object + for object in objectslist: + if object.InList: + if App.GuiUp: + object.ViewObject.Visibility = False + else: + delete_list.append(object) + return True return None # analyzing what we have in our selection @@ -475,54 +487,56 @@ def upgrade(objects, delete=False, force=None): _msg(_tr("Found 1 non-parametric objects: " "draftifying it")) - # we have only one object that contains one edge - elif not faces and len(objects) == 1 and len(edges) == 1: - # we have a closed sketch: extract a face - if (objects[0].isDerivedFrom("Sketcher::SketchObject") - and len(edges[0].Vertexes) == 1): - result = makeSketchFace(objects[0]) + # in the following cases there are no faces + elif not faces: + # we have only closed wires + if wires and not openwires and not loneedges: + # we have a sketch: extract a face + if (len(objects) == 1 + and objects[0].isDerivedFrom("Sketcher::SketchObject")): + result = makeSketchFace(objects[0]) + if result: + _msg(_tr("Found 1 closed sketch object: " + "creating a face from it")) + # only closed wires + else: + result = makeFaces(objects) + if result: + _msg(_tr("Found closed wires: creating faces")) + # wires or edges: we try to join them + elif len(wires) > 1 or len(loneedges) > 1: + result = makeWires(objects) if result: - _msg(_tr("Found 1 closed sketch object: " - "creating a face from it")) - else: + _msg(_tr("Found several wires or edges: wiring them")) + # TODO: improve draftify function + # only one object: if not parametric, we "draftify" it + # elif (len(objects) == 1 + # and not objects[0].isDerivedFrom("Part::Part2DObjectPython")): + # result = ext_draftify.draftify(objects[0]) + # if result: + # _msg(_tr("Found 1 non-parametric objects: " + # "draftifying it")) + # special case, we have only one open wire. We close it, + # unless it has only 1 edge! + elif len(objects) == 1 and len(openwires) == 1: + result = closeWire(objects[0]) + _msg(_tr("trying: closing it")) + if result: + _msg(_tr("Found 1 open wire: closing it")) + # we have only one object that contains one edge + # TODO: this case should be considered in draftify + elif len(objects) == 1 and len(edges) == 1: # turn to Draft Line e = objects[0].Shape.Edges[0] if isinstance(e.Curve, (Part.LineSegment, Part.Line)): result = turnToLine(objects[0]) if result: _msg(_tr("Found 1 linear object: converting to line")) - - # we have only closed wires, no faces - elif wires and not faces and not openwires: - # we have a sketch: extract a face - if (len(objects) == 1 - and objects[0].isDerivedFrom("Sketcher::SketchObject")): - result = makeSketchFace(objects[0]) + # only points, no edges + elif not edges and len(objects) > 1: + result = makeCompound(objects) if result: - _msg(_tr("Found 1 closed sketch object: " - "creating a face from it")) - # only closed wires - else: - result = makeFaces(objects) - if result: - _msg(_tr("Found closed wires: creating faces")) - - # special case, we have only one open wire. We close it, - # unless it has only 1 edge! - elif len(openwires) == 1 and not faces and not loneedges: - result = closeWire(objects[0]) - if result: - _msg(_tr("Found 1 open wire: closing it")) - # only open wires and edges: we try to join their edges - elif openwires and not wires and not faces: - result = makeWires(objects) - if result: - _msg(_tr("Found several open wires: joining them")) - # only loneedges: we try to join them - elif loneedges and not facewires: - result = makeWires(objects) - if result: - _msg(_tr("Found several edges: wiring them")) + _msg(_tr("Found points: creating compound")) # all other cases, if more than 1 object, make a compound elif len(objects) > 1: result = makeCompound(objects) diff --git a/src/Mod/Draft/drafttests/test_modification.py b/src/Mod/Draft/drafttests/test_modification.py index 97fcee07f7..4d5d621fcc 100644 --- a/src/Mod/Draft/drafttests/test_modification.py +++ b/src/Mod/Draft/drafttests/test_modification.py @@ -232,7 +232,7 @@ class DraftModification(unittest.TestCase): self.assertTrue(obj, "'{}' failed".format(operation)) def test_upgrade(self): - """Upgrade two Draft Lines into a closed Draft Wire.""" + """Upgrade two Lines into a closed Wire, then draftify it.""" operation = "Draft Upgrade" _msg(" Test '{}'".format(operation)) a = Vector(0, 0, 0) @@ -242,8 +242,12 @@ class DraftModification(unittest.TestCase): _msg(" a={0}, b={1}".format(a, b)) _msg(" Line 2") _msg(" b={0}, c={1}".format(b, c)) - line_1 = Draft.make_line(a, b) - line_2 = Draft.make_line(b, c) + shape_line_1 = Part.makeLine(a, b) + shape_line_2 = Part.makeLine(b, c) + line_1 = App.ActiveDocument.addObject("Part::Feature") + line_2 = App.ActiveDocument.addObject("Part::Feature") + line_1.Shape = shape_line_1 + line_2.Shape = shape_line_2 App.ActiveDocument.recompute() obj = Draft.upgrade([line_1, line_2], delete=True) @@ -255,8 +259,7 @@ class DraftModification(unittest.TestCase): obj2 = Draft.upgrade(obj[0], delete=True) App.ActiveDocument.recompute() s2 = obj2[0][0] - _msg(" 2: Result '{0}' ({1})".format(s2.Shape.ShapeType, - s2.TypeId)) + _msg(" 2: Result '{0}' ({1})".format(s2.Shape.ShapeType, s2.TypeId)) self.assertTrue(bool(obj2[0]), "'{}' failed".format(operation)) obj3 = Draft.upgrade(obj2[0], delete=True) @@ -265,10 +268,15 @@ class DraftModification(unittest.TestCase): _msg(" 3: Result '{0}' ({1})".format(s3.Shape.ShapeType, s3.TypeId)) self.assertTrue(bool(obj3[0]), "'{}' failed".format(operation)) - obj4 = Draft.upgrade(obj3[0], delete=True) + # when draftify, upgrade dont return a new object + Draft.upgrade(obj3[0], delete=True) App.ActiveDocument.recompute() wire = App.ActiveDocument.Wire _msg(" 4: Result '{0}' ({1})".format(wire.Proxy.Type, wire.TypeId)) + self.assertTrue(bool(wire), "'{}' failed".format(operation)) + + obj4 = Draft.upgrade(wire, delete=True) + App.ActiveDocument.recompute() _msg(" The last object cannot be upgraded further") self.assertFalse(bool(obj4[0]), "'{}' failed".format(operation)) From 2386df93aaf14cf8b68dc4898e31db4834332d43 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Sun, 13 Dec 2020 15:21:57 +0100 Subject: [PATCH 10/17] Arch: Make ArchWall use Sketcher GeometryFacade --- src/Mod/Arch/ArchWall.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Mod/Arch/ArchWall.py b/src/Mod/Arch/ArchWall.py index 3404239f0d..3eaa17934d 100644 --- a/src/Mod/Arch/ArchWall.py +++ b/src/Mod/Arch/ArchWall.py @@ -1241,14 +1241,14 @@ class _Wall(ArchComponent.Component): # in some corner case != getSortedClusters() elif obj.Base.isDerivedFrom("Sketcher::SketchObject"): self.basewires = [] - skGeom = obj.Base.Geometry + skGeom = obj.Base.GeometryFacadeList skGeomEdges = [] skPlacement = obj.Base.Placement # Get Sketch's placement to restore later for i in skGeom: if not i.Construction: # support Line, Arc, Circle for Sketch as Base at the moment - if isinstance(i, (Part.LineSegment, Part.Circle, Part.ArcOfCircle)): - skGeomEdgesI = i.toShape() + if isinstance(i.Geometry, (Part.LineSegment, Part.Circle, Part.ArcOfCircle)): + skGeomEdgesI = i.Geometry.toShape() skGeomEdges.append(skGeomEdgesI) for cluster in Part.getSortedClusters(skGeomEdges): clusterTransformed = [] From e7085cb47e091e84382e9d7ef7c60fb7c6b868d1 Mon Sep 17 00:00:00 2001 From: Stefan Endres Date: Sun, 13 Dec 2020 14:33:18 +0100 Subject: [PATCH 11/17] Path: Sort edges before splitting at selected vertex --- src/Mod/Path/PathScripts/PathEngraveBase.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathEngraveBase.py b/src/Mod/Path/PathScripts/PathEngraveBase.py index 4fdbc18a6b..abaf413dc8 100644 --- a/src/Mod/Path/PathScripts/PathEngraveBase.py +++ b/src/Mod/Path/PathScripts/PathEngraveBase.py @@ -30,6 +30,7 @@ import copy # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader DraftGeomUtils = LazyLoader('DraftGeomUtils', globals(), 'DraftGeomUtils') +Part = LazyLoader('Part', globals(), 'Part') from PySide import QtCore @@ -69,9 +70,11 @@ class ObjectOp(PathOp.ObjectOp): # reorder the wire if hasattr(obj, 'StartVertex'): - offset = DraftGeomUtils.rebaseWire(offset, obj.StartVertex) + start_idx = obj.StartVertex edges = copy.copy(PathOpTools.orientWire(offset, forward).Edges) + edges = Part.sortEdges(edges)[0]; + last = None for z in zValues: From 4ad41c96d009c5c6dfb4e17fba903c115248f82f Mon Sep 17 00:00:00 2001 From: Stefan Endres Date: Sat, 12 Dec 2020 20:50:21 +0100 Subject: [PATCH 12/17] Path: Check if startpoint is not lower than bounds in order to work always from top to bottom --- src/Mod/Path/App/Area.cpp | 51 +++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index 0f80a7094d..b3f2440dcc 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -2963,20 +2963,19 @@ std::list Area::sortWires(const std::list &shapes, pstart = *_pstart; bool use_bound = !has_start || _pstart==NULL; - if(use_bound || sort_mode == SortMode2D5 || sort_mode == SortModeGreedy) { - //Second stage, group shape by its plane, and find overall boundary + //Second stage, group shape by its plane, and find overall boundary - if(arcPlaneFound || use_bound) { - for(auto &info : shape_list) { - if(arcPlaneFound) { - info.myShape.Move(trsf); - if(info.myPlanar) info.myPln.Transform(trsf); - } - if(use_bound) - BRepBndLib::Add(info.myShape, bounds, Standard_False); - } + for(auto &info : shape_list) { + if(arcPlaneFound) { + info.myShape.Move(trsf); + if(info.myPlanar) info.myPln.Transform(trsf); } + BRepBndLib::Add(info.myShape, bounds, Standard_False); + } + + if(use_bound || sort_mode == SortMode2D5 || sort_mode == SortModeGreedy) { + for(auto itNext=shape_list.begin(),it=itNext;it!=shape_list.end();it=itNext) { ++itNext; if(!it->myPlanar) continue; @@ -3005,16 +3004,36 @@ std::list Area::sortWires(const std::list &shapes, //FC_DURATION_DECL_INIT(td); + bounds.SetGap(0.0); + Standard_Real xMin, yMin, zMin, xMax, yMax, zMax; + bounds.Get(xMin, yMin, zMin, xMax, yMax, zMax); + AREA_TRACE("bound (" << xMin<<", "< Date: Sun, 13 Dec 2020 22:12:08 +0100 Subject: [PATCH 13/17] Path: Added missing signl-connection causing spindle speed not to be updated using ToolControllerDlg --- src/Mod/Path/PathScripts/PathToolControllerGui.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Mod/Path/PathScripts/PathToolControllerGui.py b/src/Mod/Path/PathScripts/PathToolControllerGui.py index 34224a3535..62709086e0 100644 --- a/src/Mod/Path/PathScripts/PathToolControllerGui.py +++ b/src/Mod/Path/PathScripts/PathToolControllerGui.py @@ -253,6 +253,8 @@ class ToolControllerEditor(object): self.form.vertFeed.editingFinished.connect(self.refresh) self.form.horizRapid.editingFinished.connect(self.refresh) self.form.vertRapid.editingFinished.connect(self.refresh) + self.form.spindleSpeed.editingFinished.connect(self.refresh) + self.form.spindleDirection.currentIndexChanged.connect(self.refresh) class TaskPanel: From 6575023f5c242972f93a891ee67fa5190ffb31da Mon Sep 17 00:00:00 2001 From: wmayer Date: Mon, 14 Dec 2020 11:22:12 +0100 Subject: [PATCH 14/17] Tools: [skip ci] extend doc string of FreeCAD thumbnailer --- src/Tools/freecad-thumbnailer | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/Tools/freecad-thumbnailer b/src/Tools/freecad-thumbnailer index b141a2905d..8e3a439b0a 100644 --- a/src/Tools/freecad-thumbnailer +++ b/src/Tools/freecad-thumbnailer @@ -3,22 +3,32 @@ Installation: - This executable file should be on the PATH so it can be found - "$ sudo cp freecad-thumbnailer /usr/share/bin" -- the application/x-extension-fcstd MIME type should be registered - Check that a corresponding /usr/share/mime/packages/freecad.xml file exists - Make sure the MIME database is up to date - "$ sudo update-mime-database /usr/share/mime" -- Register this thumbnailer - Adding a file /usr/share/thumbnailers/FreeCAD.thumbnailer with the following content: + "$ sudo cp freecad-thumbnailer /usr/bin" + and must have execution rights + "$ sudo chmod +x /usr/bin/freecad-thumbnailer" - [Thumbnailer Entry] - TryExec=freecad-thumbnailer - Exec=freecad-thumbnailer -s %s %i %o - MimeType=application/x-extension-fcstd; +- If a FreeCAD project file doesn't include a thumbnail the file freecad.png is used. + Thus, the file src/Gui/Icons/freecad-icon-48.png must be installed. + "$ sudo cp freecad-icon-48.png /usr/share/icons/hicolor/48x48/apps/freecad.png" + +- The application/x-extension-fcstd MIME type should be registered + Check that a corresponding /usr/share/mime/packages/freecad.xml file exists + Make sure the MIME database is up to date + "$ sudo update-mime-database /usr/share/mime" + +- Register this thumbnailer + Adding a file /usr/share/thumbnailers/FreeCAD.thumbnailer with the following content: + + [Thumbnailer Entry] + TryExec=freecad-thumbnailer + Exec=freecad-thumbnailer -s %s %i %o + MimeType=application/x-extension-fcstd; + +HINT: Make sure that the symlink /usr/bin/python exists and points to the Python executable NOTE: To make sure FreeCAD saves thumbnail information: - Edit -> Preferences... -> Document -> Save thumbnail into project when saving document + Edit -> Preferences... -> Document -> Save thumbnail into project when saving document """ import sys import zipfile From c25ff3ac165b4f12212934f69d718b05632a0193 Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Mon, 14 Dec 2020 15:21:25 +0100 Subject: [PATCH 15/17] Draft: Hide undo button from modify operations --- src/Mod/Draft/DraftGui.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Mod/Draft/DraftGui.py b/src/Mod/Draft/DraftGui.py index 74a944f8c6..0b34c1ad1e 100644 --- a/src/Mod/Draft/DraftGui.py +++ b/src/Mod/Draft/DraftGui.py @@ -1107,6 +1107,7 @@ class DraftToolBar: self.continueCmd.show() def modUi(self): + self.undoButton.hide() self.isCopy.show() self.isSubelementMode.show() p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft") From 0a35cb5b01a8536ec4378d5356ea7cc807ef6040 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Wed, 9 Dec 2020 10:38:50 +0800 Subject: [PATCH 16/17] Part: improve TopoShape::findPlane() Make the returned plane normal consistent with the underlying face geometry. --- src/Mod/Part/App/TopoShape.cpp | 43 ++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/src/Mod/Part/App/TopoShape.cpp b/src/Mod/Part/App/TopoShape.cpp index dfa107766f..3246fbe4b5 100644 --- a/src/Mod/Part/App/TopoShape.cpp +++ b/src/Mod/Part/App/TopoShape.cpp @@ -171,6 +171,8 @@ # include # include # include +# include +# include #if OCC_VERSION_HEX < 0x070300 # include @@ -4161,26 +4163,13 @@ bool TopoShape::findPlane(gp_Pln &pln, double tol) const { if(_Shape.IsNull()) return false; TopoDS_Shape shape = _Shape; - TopExp_Explorer exp(_Shape,TopAbs_FACE); + TopExp_Explorer exp(_Shape,TopAbs_EDGE); if(exp.More()) { - auto face = exp.Current(); + TopoDS_Shape edge = exp.Current(); exp.Next(); - if(!exp.More()) { - BRepAdaptor_Surface adapt(TopoDS::Face(face)); - if(adapt.GetType() != GeomAbs_Plane) - return false; - pln = adapt.Plane(); - return true; - } - }else{ - TopExp_Explorer exp(_Shape,TopAbs_EDGE); - if(exp.More()) { - TopoDS_Shape edge = exp.Current(); - exp.Next(); - if(!exp.More()) { - // To deal with OCCT bug of wrong edge transformation - shape = BRepBuilderAPI_Copy(edge).Shape(); - } + if (!exp.More()) { + // To deal with OCCT bug of wrong edge transformation + shape = BRepBuilderAPI_Copy(_Shape).Shape(); } } try { @@ -4188,6 +4177,24 @@ bool TopoShape::findPlane(gp_Pln &pln, double tol) const { if (!finder.Found()) return false; pln = GeomAdaptor_Surface(finder.Surface()).Plane(); + + // To make the returned plane normal more stable, if the shape has any + // face, use the normal of the first face. + TopExp_Explorer exp(shape, TopAbs_FACE); + if(exp.More()) { + BRepAdaptor_Surface adapt(TopoDS::Face(exp.Current())); + double u = adapt.FirstUParameter() + + (adapt.LastUParameter() - adapt.FirstUParameter())/2.; + double v = adapt.FirstVParameter() + + (adapt.LastVParameter() - adapt.FirstVParameter())/2.; + BRepLProp_SLProps prop(adapt,u,v,2,Precision::Confusion()); + if(prop.IsNormalDefined()) { + gp_Pnt pnt; gp_Vec vec; + // handles the orientation state of the shape + BRepGProp_Face(TopoDS::Face(exp.Current())).Normal(u,v,pnt,vec); + pln = gp_Pln(pnt, gp_Dir(vec)); + } + } return true; }catch (Standard_Failure &e) { // For some reason the above BRepBuilderAPI_Copy failed to copy From 9b39c6784e0be7d790da416da08c13de2532065a Mon Sep 17 00:00:00 2001 From: wmayer Date: Mon, 14 Dec 2020 17:19:52 +0100 Subject: [PATCH 17/17] Path: [skip ci] fix memory leak in TooltablePy::getTools() For some background information see: https://forum.freecadweb.org/viewtopic.php?f=15&t=50583&start=20#p457516 --- src/Mod/Path/App/TooltablePyImp.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Mod/Path/App/TooltablePyImp.cpp b/src/Mod/Path/App/TooltablePyImp.cpp index 1e517f50a2..a48223a516 100644 --- a/src/Mod/Path/App/TooltablePyImp.cpp +++ b/src/Mod/Path/App/TooltablePyImp.cpp @@ -103,12 +103,12 @@ int TooltablePy::PyInit(PyObject* args, PyObject* /*kwd*/) Py::Dict TooltablePy::getTools(void) const { - PyObject *dict = PyDict_New(); + Py::Dict dict; for(std::map::iterator i = getTooltablePtr()->Tools.begin(); i != getTooltablePtr()->Tools.end(); ++i) { PyObject *tool = new Path::ToolPy(i->second); - PyDict_SetItem(dict,PYINT_FROMLONG(i->first),tool); + dict.setItem(Py::Long(i->first), Py::asObject(tool)); } - return Py::Dict(dict); + return dict; } void TooltablePy::setTools(Py::Dict arg)