diff --git a/src/Mod/TechDraw/App/DrawView.pyi b/src/Mod/TechDraw/App/DrawView.pyi index d255f7d5d4..a9581c5c5a 100644 --- a/src/Mod/TechDraw/App/DrawView.pyi +++ b/src/Mod/TechDraw/App/DrawView.pyi @@ -4,7 +4,7 @@ from __future__ import annotations from typing import Any -from Base.Metadata import export +from Base.Metadata import export, constmethod from App.DocumentObject import DocumentObject @@ -27,10 +27,19 @@ class DrawView(DocumentObject): No return value. Replace the current label with a translated version where possible. """ ... - + @constmethod def getScale(self) -> Any: """ float scale = getScale(). Returns the correct scale for this view. Handles whether to use this view's scale property or a parent's view (as in a projection group). """ ... + + @constmethod + def findParentPage(self) -> Any: + """ + DrawPage parent = findParentPage(). Returns the parent page that contains this view. + """ + ... + + diff --git a/src/Mod/TechDraw/App/DrawViewPyImp.cpp b/src/Mod/TechDraw/App/DrawViewPyImp.cpp index 2419459187..09154702f0 100644 --- a/src/Mod/TechDraw/App/DrawViewPyImp.cpp +++ b/src/Mod/TechDraw/App/DrawViewPyImp.cpp @@ -25,6 +25,7 @@ // inclusion of the generated files (generated out of DrawViewPy.xml) #include #include +#include using namespace TechDraw; @@ -78,7 +79,7 @@ PyObject* DrawViewPy::translateLabel(PyObject *args) } //! return the correct scale for this view -PyObject* DrawViewPy::getScale(PyObject *args) +PyObject* DrawViewPy::getScale(PyObject *args) const { if (!PyArg_ParseTuple(args, "")) { throw Py::TypeError("Do not understand passed parameter."); @@ -89,6 +90,18 @@ PyObject* DrawViewPy::getScale(PyObject *args) return PyFloat_FromDouble(dv->getScale()); } +//! return the correct scale for this view +PyObject* DrawViewPy::findParentPage(PyObject *args) const +{ + if (!PyArg_ParseTuple(args, "")) { + throw Py::TypeError("Do not understand passed parameter."); + } + + DrawView* dv = getDrawViewPtr(); + DrawPage* parent = dv->findParentPage(); + + return Py::new_reference_to(parent->getPyObject()); +} PyObject *DrawViewPy::getCustomAttributes(const char* /*attr*/) const diff --git a/src/Mod/TechDraw/TechDrawTools/CommandAxoLengthDimension.py b/src/Mod/TechDraw/TechDrawTools/CommandAxoLengthDimension.py index 23a17689e0..4dd4fe2c8a 100644 --- a/src/Mod/TechDraw/TechDrawTools/CommandAxoLengthDimension.py +++ b/src/Mod/TechDraw/TechDrawTools/CommandAxoLengthDimension.py @@ -38,7 +38,24 @@ import FreeCADGui as Gui import TechDrawTools.TDToolsUtil as Utils import TechDraw -from math import degrees +import math + +# A hack to deal with garbage in the low bits causing wrong decisions. dimensionLineAngle is in radians. If +# dimensionLineAngle is 90deg +/- AngularTolerance, we treat it as a vertical dimension line. This affects which +# side of the dimension line is used for the dimension text. +def makePlumb(dimensionLineAngle): + HalfPi = math.pi / 2.0 + + AngularTolerance = 0.01 # This value is a guess. It works for the file in issue #16172, but + # seems small (~0.5deg). + + if math.isclose(dimensionLineAngle, HalfPi, abs_tol=AngularTolerance): + return HalfPi + elif math.isclose(dimensionLineAngle, -HalfPi, abs_tol=AngularTolerance): + return -HalfPie + + return dimensionLineAngle + class CommandAxoLengthDimension: """Creates a 3D length dimension.""" @@ -68,33 +85,57 @@ class CommandAxoLengthDimension: edges = Utils.getSelEdges(2) vertexes = Utils.getSelVertexes(0) + vertNames = list() + edgeNames = list() if len(vertexes)<2: vertexes.append(edges[0].Vertexes[0]) vertexes.append(edges[0].Vertexes[1]) + edgeNames = Utils.getSelEdgeNames(2) + else: + vertNames = Utils.getSelVertexNames(2) view = Utils.getSelView() + page = view.findParentPage() scale = view.getScale() StartPt, EndPt = edges[1].Vertexes[0].Point, edges[1].Vertexes[1].Point extLineVec = EndPt.sub(StartPt) StartPt, EndPt = edges[0].Vertexes[0].Point, edges[0].Vertexes[1].Point dimLineVec = EndPt.sub(StartPt) + xAxis = App.Vector(1,0,0) - extAngle = degrees(extLineVec.getAngle(xAxis)) - lineAngle = degrees(dimLineVec.getAngle(xAxis)) + extAngle = math.degrees(extLineVec.getAngle(xAxis)) + lineAngle = math.degrees(makePlumb(dimLineVec.getAngle(xAxis))) if extLineVec.y < 0.0: extAngle = 180-extAngle if dimLineVec.y < 0.0: lineAngle = 180-lineAngle + if abs(extAngle-lineAngle)>0.1: - distanceDim=TechDraw.makeDistanceDim(view,'Distance',vertexes[0].Point*scale,vertexes[1].Point*scale) + # re issue: https://github.com/FreeCAD/FreeCAD/issues/13677 + # Instead of using makeDistanceDim (which is meant for extent dimensions), we use the + # same steps as in CommandCreateDims.cpp to create a regular length dimension. This avoids + # the creation of CosmeticVertex objects to serve as dimension points. These CosmeticVertex + # objects are never deleted, but are no longer used once their dimension is deleted. + # distanceDim=TechDraw.makeDistanceDim(view,'Distance',vertexes[0].Point*scale,vertexes[1].Point*scale) + distanceDim = view.Document.addObject("TechDraw::DrawViewDimension", "Dimension") + distanceDim.Type = "Distance" + distanceDim.MeasureType = "Projected" #should this not be True? + self.setReferences(distanceDim, view, edgeNames, vertNames) + page.addView(distanceDim) + distanceDim.AngleOverride = True distanceDim.LineAngle = lineAngle distanceDim.ExtensionAngle = extAngle - distanceDim.X = scale*(vertexes[0].Point.x+vertexes[1].Point.x)/2 - distanceDim.Y = scale*(vertexes[0].Point.y+vertexes[1].Point.y)/2 - distanceDim.recompute() + + distanceDim.recompute() # ensure linearPoints has been set + + # as in CmdCreateDims::positionDimText: + linearPoints = distanceDim.getLinearPoints() + mid = (linearPoints[0] + linearPoints[1]) / 2 + distanceDim.X = mid.x + distanceDim.Y = -mid.y (px,py,pz) = Utils.getCoordinateVectors(view) arrowTips = distanceDim.getArrowPositions() @@ -114,8 +155,10 @@ class CommandAxoLengthDimension: distanceDim.recompute() view.requestPaint() + Gui.Selection.clearSelection() App.closeActiveTransaction() + view.touch() # make view claim its new child def IsActive(self): """Return True when the command should be active or False when it should be disabled (greyed).""" @@ -146,6 +189,16 @@ class CommandAxoLengthDimension: else: return formatSpec.format(value) + def setReferences(self, dimension, view, edgeNameList, vertexNameList): + references = list() + if vertexNameList: + for vert in vertexNameList: + references.append((view, vert)) + else: + references.append((view, edgeNameList[0])) + + dimension.References2D = references + # # The command must be "registered" with a unique name by calling its class. Gui.addCommand('TechDraw_AxoLengthDimension', CommandAxoLengthDimension()) diff --git a/src/Mod/TechDraw/TechDrawTools/TDToolsUtil.py b/src/Mod/TechDraw/TechDrawTools/TDToolsUtil.py index 328164cd04..95078aa8ad 100644 --- a/src/Mod/TechDraw/TechDrawTools/TDToolsUtil.py +++ b/src/Mod/TechDraw/TechDrawTools/TDToolsUtil.py @@ -155,3 +155,68 @@ def getCoordinateVectors(view): py = wire2D.Edges[1].Vertexes[0].Point pz = wire2D.Edges[2].Vertexes[0].Point return (px,py,pz) + + +# +/- the same as getSelVertexes, but returns the vertex name instead of the vertex object. +def getSelVertexNames(nVertex=1, nSel=0): + ''' + vertexNames = getSelVertexNames(nVertex) + nVertex=1 ... min. number of selected vertexes + nSel=0 ... number of selected view, 0 = first selected + Return a list of the names of the selected vertexes if at least nVertex vertexes are selected, otherwise return False + ''' + view = getSelView(nSel) + if not view: + return False + + if not Gui.Selection.getSelectionEx(): + displayMessage('TechDraw_Utils', + QT_TRANSLATE_NOOP('TechDraw_Utils','No vertex selected')) + return False + objectList = Gui.Selection.getSelectionEx()[nSel].SubElementNames + + vertexNames = [] + for objectString in objectList: + if objectString[0:6] == 'Vertex': + vertexNames.append(objectString) + + if (len(vertexNames) < nVertex): + displayMessage('TechDraw_Utils', + QT_TRANSLATE_NOOP('TechDraw_Utils','Select at least ')+ + str(nVertex)+ + QT_TRANSLATE_NOOP('TechDraw_Utils',' vertexes')) + return False + + return vertexNames + +# +/- the same as getSelEdges, but returns the edge name instead of the vertex object. +def getSelEdgeNames(nEdge=1, nSel=0): + ''' + edges = getSelEdgeNames(nEdge) + nEdge=1 ... min. number of selected edges + nSel=0 ... number of selected view, 0 = first selected + Return a list of names for selected edges if at least nedge edges are selected, otherwise return False + ''' + view = getSelView(nSel) + if not view: + return False + + if not Gui.Selection.getSelectionEx(): + displayMessage('TechDraw_Utils', + QT_TRANSLATE_NOOP('TechDraw_Utils','No edge selected')) + return False + objectList = Gui.Selection.getSelectionEx()[nSel].SubElementNames + + edgeNames = [] + for objectString in objectList: + if objectString[0:4] == 'Edge': + edgeNames.append(objectString) + + if (len(edgeNames) < nEdge): + displayMessage('TechDraw_Utils', + QT_TRANSLATE_NOOP('TechDraw_Utils','Select at least ')+ + str(nEdge)+ + QT_TRANSLATE_NOOP('TechDraw_Utils',' edges')) + return False + else: + return edgeNames