From 443a7c1b84f62e3b46f88e2722f472e39a63d728 Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Sun, 12 Mar 2023 15:45:21 +0100 Subject: [PATCH 01/13] wip - determine folding points on each wire and try to connect them Determine the folding points of each wire by considering straight lines start/end points, and chunks of edges that aren't straight lines. Then, try to connect these points. The technique used here (which produces inedequate output) consists in projecting the points of a face on the other face and match the closest points. C.f. https://forum.freecad.org/viewtopic.php?p=666788#p666788 --- test_generatrices.FCMacro | 185 ++++++++++++++++++++++++-------------- 1 file changed, 117 insertions(+), 68 deletions(-) diff --git a/test_generatrices.FCMacro b/test_generatrices.FCMacro index 6a3447e..f64fa52 100644 --- a/test_generatrices.FCMacro +++ b/test_generatrices.FCMacro @@ -1,8 +1,12 @@ # -*- coding: utf-8 -*- import FreeCAD -from math import floor +from math import ceil, floor from DraftGeomUtils import isPtOnEdge, findIntersection +from DraftVecUtils import closest + +def serializeVector(v: FreeCAD.Vector) -> str: + return "%f;%f;%f" % (v.x, v.y, v.z) def getPlaneCenter(plane: Part.Plane) -> FreeCAD.Vector: @@ -179,78 +183,104 @@ def solveRayAngleFaceB(planeA, planeB, planesAxis, rayInitVertexA, rayInitVertex return angle * -1 -def buildFoldingPointsOnWire(w, wPlane, rayInitVertex, angle, showRays): - rayLength = getWireMaxBoundBox(w) - planeCenter = getPlaneCenter(wPlane) - rotationAxis = FreeCAD.Vector(wPlane.Axis) - refRay = Part.makeLine(planeCenter, buildInitialRayEndVertice(planeCenter, rayInitVertex, rayLength)) - ray = refRay.rotated(planeCenter, rotationAxis, 0) - rays = [] +def getTotalLengthOfNonLinearEdges(edges: [Part.Edge]) -> float: + totalLength = 0.0 + + for edge in edges: + if isinstance(edge.Curve, Part.Line): + continue - stepCount = floor(360 / abs(angle)) - currentStep = 0 - foldingPoints = [] + totalLength += edge.Length - while currentStep < stepCount: - intersectionPoint = getIntersectionPoint(ray, w) + return totalLength - if showRays: - rays.append(Part.makeLine(ray.Vertexes[0].Point, ray.Vertexes[1].Point)) - # prepare next iteration - currentStep += 1 - ray = refRay.rotated(planeCenter, rotationAxis, currentStep * angle) +def buildFoldingPointsOnWire(wire: Part.Wire, maxPointsCount: int) -> [FreeCAD.Vector]: + foldingPoints = dict() + totalLengthOfNonLinearEdges = getTotalLengthOfNonLinearEdges(wire.Edges) + + chunksOffset = 0 + if len(wire.Edges) == 1: + chunksOffset = 1 + + for edge in wire.Edges: + if isinstance(edge.Curve, Part.Line): + # pick the start and end points of a straight line + for pt in [edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter)]: + foldingPoints[serializeVector(pt)] = pt - if not intersectionPoint: continue - relatedEdge = getEdgeOnWhichPointIsOn(intersectionPoint, w.Edges) + # compare the length of this non linear edge to the total length of + # non linear edges to know how many folding points we should place on it + pointsToPlaceCount = round((edge.Length * maxPointsCount) / totalLengthOfNonLinearEdges) + + # pick the evenly spaced points on edge + for pt in edge.discretize(Number=pointsToPlaceCount + chunksOffset): + foldingPoints[serializeVector(pt)] = pt - # @TODO? : add relatedEdge start and end points and be able to filter them. - # ALso check if we're on an edge start/end. - # If not, it may require to change the angle to add the edge start/end - # when it is close enough. Also make sure to keep the amount of points - # equal with the other face's wire. + return list(foldingPoints.values()) - # do not place a bend end on a line, as it should stay straight - if relatedEdge and isinstance(relatedEdge.Curve, Part.Line): - intersectionPoint = getEdgeEndClosestToPoint(intersectionPoint, relatedEdge) - foldingPoints.append(intersectionPoint) +class FoldingLine: + def __init__(self, pointFaceA: FreeCAD.Vector, pointFaceB: FreeCAD.Vector): + self.pointFaceA = pointFaceA + self.pointFaceB = pointFaceB + def getPointFaceA(self) -> FreeCAD.Vector: + return self.pointFaceA - if showRays: - Part.show(Part.makeCompound(rays)) + def getPointFaceB(self) -> FreeCAD.Vector: + return self.pointFaceB - return foldingPoints + +def buildFoldingLines(pointsToProject, projectionFace, projectionAxis, pointsOnProjectionFace, isProjectionOnFaceB): + foldingLines: [FoldingLine] = [] + projectionPlaneCenter = getPlaneCenter(projectionFace) + + for pointToProject in pointsToProject: + projection = FreeCAD.Vector(pointToProject.x, pointToProject.y, pointToProject.z) + projection.projectToPlane(projectionPlaneCenter, projectionAxis) + + closestPointIndex = closest(projection, pointsOnProjectionFace) + closestPoint = pointsOnProjectionFace[closestPointIndex] + + if isProjectionOnFaceB: + foldingLine = FoldingLine(pointToProject, closestPoint) + else: + foldingLine = FoldingLine(closestPoint, pointToProject) + + foldingLines.append(foldingLine) + + return foldingLines def arePointsIdentical(pointA, pointB): return 0.0 == pointA.distanceToPoint(pointB) -def filterPoints(pointsA, pointsB): - """When consecutive points are on the same emplacement, skip them. - """ - filteredPointsA = [] - filteredPointsB = [] +# def filterPoints(pointsA, pointsB): +# """When consecutive points are on the same emplacement, skip them. +# """ +# filteredPointsA = [] +# filteredPointsB = [] - for i in range(len(pointsA)): - currentPointA = pointsA[i] - currentPointB = pointsB[i] +# for i in range(len(pointsA)): +# currentPointA = pointsA[i] +# currentPointB = pointsB[i] - if i > 0: - if arePointsIdentical(currentPointA, pointsA[0]) and arePointsIdentical(currentPointB, pointsB[0]): - continue +# if i > 0: +# if arePointsIdentical(currentPointA, pointsA[0]) and arePointsIdentical(currentPointB, pointsB[0]): +# continue - if arePointsIdentical(currentPointA, pointsA[i - 1]) and arePointsIdentical(currentPointB, pointsB[i - 1]): - continue +# if arePointsIdentical(currentPointA, pointsA[i - 1]) and arePointsIdentical(currentPointB, pointsB[i - 1]): +# continue - filteredPointsA.append(currentPointA) - filteredPointsB.append(currentPointB) +# filteredPointsA.append(currentPointA) +# filteredPointsB.append(currentPointB) - return (filteredPointsA, filteredPointsB) +# return (filteredPointsA, filteredPointsB) def connectPointsOn3DShape(pointsA, pointsB): @@ -352,41 +382,60 @@ def drawFlattenRepresentation(flattenPointsA, flattenPointsB): Part.show(Part.makeCompound(edges)) +def getWiresFromGuiSelection() -> (Part.Wire, Part.Wire): + sel = Gui.Selection.getSelection() + + if 2 != len(sel): + raise ValueError("Please select two sketches.") + + for s in sel: + if "Sketcher::SketchObject" != s.TypeId: + raise ValueError("Wrong selection, only sketches are allowed.") + + skA = sel[0] + skB = sel[1] + + return (skA.Shape, skB.Shape) + + + def main(): - # @TODO : GUI to set angle, showRays opt, show folding lines on 3D shape opt + # @TODO : GUI to set angle, show folding lines on 3D shape opt angle = 10.0 - showRays = False - - # @TODO : sanitise selection - faceA = Gui.Selection.getSelectionEx()[0].SubObjects[0] - faceB = Gui.Selection.getSelectionEx()[0].SubObjects[1] + maxPointsCount = ceil(360 / angle) - wireFaceA = faceA.Wires[0] - wireFaceB = faceB.Wires[0] + wireFaceA, wireFaceB = getWiresFromGuiSelection() planeA = wireFaceA.findPlane() planeB = wireFaceB.findPlane() planesAxis = getVectorBewteenTwoPlanes(planeA, planeB) # where to place the initial ray on each face - rayInitVertexA, rayInitVertexB = solveRayInitVertexes(wireFaceA, wireFaceB, planeB) + # rayInitVertexA, rayInitVertexB = solveRayInitVertexes(wireFaceA, wireFaceB, planeB) - rayAngleFaceA = angle + # rayAngleFaceA = angle # could be in the opposite direction of rayAngleFaceA - rayAngleFaceB = solveRayAngleFaceB(planeA, planeB, planesAxis, rayInitVertexA, rayInitVertexB, angle) + # rayAngleFaceB = solveRayAngleFaceB(planeA, planeB, planesAxis, rayInitVertexA, rayInitVertexB, angle) - pointsWireA = buildFoldingPointsOnWire(wireFaceA, planeA, rayInitVertexA, rayAngleFaceA, showRays) - pointsWireB = buildFoldingPointsOnWire(wireFaceB, planeB, rayInitVertexB, rayAngleFaceB, showRays) + pointsWireA = buildFoldingPointsOnWire(wireFaceA, maxPointsCount) + pointsWireB = buildFoldingPointsOnWire(wireFaceB, maxPointsCount) - if len(pointsWireA) != len(pointsWireB): - # should have the same amount of points - return - pointsWireA, pointsWireB = filterPoints(pointsWireA, pointsWireB) + # if len(pointsWireB) > len(pointsWireA): + if wireFaceB.Length > wireFaceA.Length: + foldingLines = buildFoldingLines(pointsWireB, planeA, planesAxis, pointsWireA, False) + else: + foldingLines = buildFoldingLines(pointsWireA, planeB, planesAxis, pointsWireB, True) + + lines = [] + for foldingLine in foldingLines: + lines.append(Part.makeLine(foldingLine.getPointFaceA(), foldingLine.getPointFaceB())) + Part.show(Part.makeCompound(lines)) + - connectPointsOn3DShape(pointsWireA, pointsWireB) - flattenPointsA, flattenPointsB = flattenWiresPoints(pointsWireA, pointsWireB) - drawFlattenRepresentation(flattenPointsA, flattenPointsB) + # connectPointsOn3DShape(pointsWireA, pointsWireB) + # flattenPointsA, flattenPointsB = flattenWiresPoints(pointsWireA, pointsWireB) + # drawFlattenRepresentation(flattenPointsA, flattenPointsB) main() From a8012641bb63cb899e4c69b06ac4a2358623d71e Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Mon, 13 Mar 2023 19:27:55 +0100 Subject: [PATCH 02/13] draw flatten representation of [FoldingLine] array --- test_generatrices.FCMacro | 106 ++++++++++++++++++++------------------ 1 file changed, 57 insertions(+), 49 deletions(-) diff --git a/test_generatrices.FCMacro b/test_generatrices.FCMacro index f64fa52..05ba10d 100644 --- a/test_generatrices.FCMacro +++ b/test_generatrices.FCMacro @@ -223,7 +223,8 @@ def buildFoldingPointsOnWire(wire: Part.Wire, maxPointsCount: int) -> [FreeCAD.V return list(foldingPoints.values()) -class FoldingLine: +class FoldingLine(): + """A folding line on the 3D shape.""" def __init__(self, pointFaceA: FreeCAD.Vector, pointFaceB: FreeCAD.Vector): self.pointFaceA = pointFaceA self.pointFaceB = pointFaceB @@ -235,7 +236,12 @@ class FoldingLine: return self.pointFaceB -def buildFoldingLines(pointsToProject, projectionFace, projectionAxis, pointsOnProjectionFace, isProjectionOnFaceB): +class FlatFoldingLine(FoldingLine): + """A flatten representation of a FoldingLine""" + pass + + +def build3DFoldingLines(pointsToProject, projectionFace, projectionAxis, pointsOnProjectionFace, isProjectionOnFaceB): foldingLines: [FoldingLine] = [] projectionPlaneCenter = getPlaneCenter(projectionFace) @@ -283,26 +289,33 @@ def arePointsIdentical(pointA, pointB): # return (filteredPointsA, filteredPointsB) -def connectPointsOn3DShape(pointsA, pointsB): - """Draw the folding lines on the 3D shape +def showFoldingLinesOn3DShape(foldingLines: [FoldingLine]) -> None: + """Draw the folding lines on the 3D shape. """ - foldingLines = [] - for i in range(len(pointsA)): + lines = [] - foldingLines.append(Part.makeLine(pointsA[i], pointsB[i])) + for foldingLine in foldingLines: + lines.append(Part.makeLine(foldingLine.getPointFaceA(), foldingLine.getPointFaceB())) - Part.show(Part.makeCompound(foldingLines)) + Part.show(Part.makeCompound(lines)) -def flattenSection(pointsA, pointsB, i, previousAFlat, previousBFlat): - """Computes the points of a flatten folding line""" +def flattenFoldingLine(foldingLines: [FoldingLine], previousFlatFoldingLine: FlatFoldingLine, i: int) -> FlatFoldingLine: + """Computes the flatten representation of a folding line.""" if (i < 1): return - aStepLength = pointsA[i - 1].distanceToPoint(pointsA[i]) - bStepLength = pointsB[i - 1].distanceToPoint(pointsB[i]) - distAB = pointsA[i].distanceToPoint(pointsB[i]) - diagLength = pointsA[i - 1].distanceToPoint(pointsB[i]) + pointA = foldingLines[i].getPointFaceA() + pointB = foldingLines[i].getPointFaceB() + previousPointA = foldingLines[i - 1].getPointFaceA() + previousPointB = foldingLines[i - 1].getPointFaceB() + previousAFlat = previousFlatFoldingLine.getPointFaceA() + previousBFlat = previousFlatFoldingLine.getPointFaceB() + + aStepLength = previousPointA.distanceToPoint(pointA) + bStepLength = previousPointB.distanceToPoint(pointB) + distAB = pointA.distanceToPoint(pointB) + diagLength = previousPointA.distanceToPoint(pointB) # use circles to find the position of the bFlat point circleBStep = Part.makeCircle(bStepLength, previousBFlat) @@ -314,35 +327,30 @@ def flattenSection(pointsA, pointsB, i, previousAFlat, previousBFlat): circleAFlatBFlat = Part.makeCircle(distAB, bFlat) aFlat = findIntersection(circleAStep, circleAFlatBFlat)[-1] - return (aFlat, bFlat) + return FlatFoldingLine(aFlat, bFlat) -def flattenWiresPoints(pointsA, pointsB): +def flattenFoldingLines(foldingLines: [FoldingLine]) -> [FlatFoldingLine]: """Computes the points of the flatten folding lines""" - flattenPointsA = [] - flattenPointsB = [] + flatFoldingLines = [] i = 0 - aFlat = FreeCAD.Vector(0.0, 0.0, 0.0) - distAB = pointsA[i].distanceToPoint(pointsB[i]) - bFlat = FreeCAD.Vector(distAB, 0.0, 0.0) + distAB = foldingLines[i].getPointFaceA().distanceToPoint(foldingLines[i].getPointFaceB()) + flatFoldingLine = FlatFoldingLine(FreeCAD.Vector(0.0, 0.0, 0.0), FreeCAD.Vector(distAB, 0.0, 0.0)) - previousAFlat = aFlat - previousBFlat = bFlat - flattenPointsA.append(previousAFlat) - flattenPointsB.append(previousBFlat) + previousFlatFoldingLine = flatFoldingLine + flatFoldingLines.append(previousFlatFoldingLine) i += 1 - while (i < len(pointsA)): - previousAFlat, previousBFlat = flattenSection(pointsA, pointsB, i, previousAFlat, previousBFlat) - flattenPointsA.append(previousAFlat) - flattenPointsB.append(previousBFlat) + while (i < len(foldingLines)): + previousFlatFoldingLine = flattenFoldingLine(foldingLines, previousFlatFoldingLine, i) + flatFoldingLines.append(previousFlatFoldingLine) i += 1 - return (flattenPointsA, flattenPointsB) + return flatFoldingLines -def buildEdges(points, edges = []): +def buildEdges(points: [FreeCAD.Vector], edges = []) -> None: """Link the given points by making edges, and add the created edges to the given edges array parameter. """ @@ -359,20 +367,27 @@ def buildEdges(points, edges = []): edges.append(Part.makeLine(previousPoint, currentPoint)) -def drawFlattenRepresentation(flattenPointsA, flattenPointsB): - """Draw the flatten representation of the shape and folding lines. +def drawFlattenRepresentation(flatFoldingLines: [FlatFoldingLine]) -> None: + """Draw the flatten representation of the shape and its folding lines. """ edges = [] - i = 0 - pointsCount = len(flattenPointsA) + foldingLinesCount = len(flatFoldingLines) + + flattenPointsA = [] + flattenPointsB = [] + + for i in range(foldingLinesCount): + flattenPointsA.append(flatFoldingLines[i].getPointFaceA()) + flattenPointsB.append(flatFoldingLines[i].getPointFaceB()) # the flat edges of A & B wires buildEdges(flattenPointsA, edges) buildEdges(flattenPointsB, edges) - while (i < pointsCount): + i = 0 + while (i < foldingLinesCount): # the folding lines - edges.append(Part.makeLine(flattenPointsA[i], flattenPointsB[i])) + edges.append(Part.makeLine(flatFoldingLines[i].getPointFaceA(), flatFoldingLines[i].getPointFaceB())) i += 1 # Sometimes there's a `BRepAdaptor_Curve::No geometry` error when trying to draw @@ -420,22 +435,15 @@ def main(): pointsWireA = buildFoldingPointsOnWire(wireFaceA, maxPointsCount) pointsWireB = buildFoldingPointsOnWire(wireFaceB, maxPointsCount) - - # if len(pointsWireB) > len(pointsWireA): if wireFaceB.Length > wireFaceA.Length: - foldingLines = buildFoldingLines(pointsWireB, planeA, planesAxis, pointsWireA, False) + foldingLines = build3DFoldingLines(pointsWireB, planeA, planesAxis, pointsWireA, False) else: - foldingLines = buildFoldingLines(pointsWireA, planeB, planesAxis, pointsWireB, True) - - lines = [] - for foldingLine in foldingLines: - lines.append(Part.makeLine(foldingLine.getPointFaceA(), foldingLine.getPointFaceB())) - Part.show(Part.makeCompound(lines)) + foldingLines = build3DFoldingLines(pointsWireA, planeB, planesAxis, pointsWireB, True) + showFoldingLinesOn3DShape(foldingLines) - # connectPointsOn3DShape(pointsWireA, pointsWireB) - # flattenPointsA, flattenPointsB = flattenWiresPoints(pointsWireA, pointsWireB) - # drawFlattenRepresentation(flattenPointsA, flattenPointsB) + flatFoldingLines = flattenFoldingLines(foldingLines) + drawFlattenRepresentation(flatFoldingLines) main() From b285f05ea8417e0fcd1efea5a780b6eec0cf42fa Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Tue, 14 Mar 2023 20:49:50 +0100 Subject: [PATCH 03/13] build folding lines at straight edges ends --- test_generatrices.FCMacro | 146 +++++++++++++++++++++++++++++--------- 1 file changed, 112 insertions(+), 34 deletions(-) diff --git a/test_generatrices.FCMacro b/test_generatrices.FCMacro index 05ba10d..d3f7b01 100644 --- a/test_generatrices.FCMacro +++ b/test_generatrices.FCMacro @@ -56,33 +56,43 @@ def getEdgeEndClosestToPoint(point: FreeCAD.Vector, edge: Part.Edge | Part.Line) return end2 -def getVectorBewteenTwoPlanes(pa: Part.Plane, pb: Part.Plane) -> FreeCAD.Vector: - """Returns a vector aligned with the given planes centers. +def getVectorBewteenTwoVertexes(va: FreeCAD.Vector, vb: FreeCAD.Vector) -> FreeCAD.Vector: + """Returns a vector aligned with the given vertexes. Parameters ---------- - pa : Part.Plane - pb : Part.Plane + va : FreeCAD.Vector + vb : FreeCAD.Vector Returns ------- FreeCAD.Vector """ - aCenter = getPlaneCenter(pa) - bCenter = getPlaneCenter(pb) + vec: FreeCAD.Vector = None - nVec: FreeCAD.Vector = None + if va.Length > vb.Length: + vec = va.sub(vb) + else: + vec = vb.sub(va) - aLength = aCenter.Length - bLength = bCenter.Length + return vec - if aLength > bLength: - nVec = aCenter.sub(bCenter) - else: - nVec = bCenter.sub(aCenter) - return nVec +def getVectorBewteenTwoPlanes(pa: Part.Plane, pb: Part.Plane) -> FreeCAD.Vector: + """Returns a vector aligned with the given planes centers. + + Parameters + ---------- + pa : Part.Plane + pb : Part.Plane + + Returns + ------- + FreeCAD.Vector + """ + + return getVectorBewteenTwoVertexes(getPlaneCenter(pa), getPlaneCenter(pb)) def solveRayInitVertexes(wireA: Part.Wire, wireB: Part.Wire, planeB: Part.Plane) -> (FreeCAD.Vector, FreeCAD.Vector): @@ -195,19 +205,25 @@ def getTotalLengthOfNonLinearEdges(edges: [Part.Edge]) -> float: return totalLength +def getFoldingPointsOnStraightEdge(edge: Part.Edge) -> [FreeCAD.Vector]: + return [edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter)] + def buildFoldingPointsOnWire(wire: Part.Wire, maxPointsCount: int) -> [FreeCAD.Vector]: + # order the edges in a geometrical way (the end of one edge matches the start of the next one) + # edges = Part.__sortEdges__(wire.Edges) + edges = wire.Edges foldingPoints = dict() - totalLengthOfNonLinearEdges = getTotalLengthOfNonLinearEdges(wire.Edges) + totalLengthOfNonLinearEdges = getTotalLengthOfNonLinearEdges(edges) chunksOffset = 0 - if len(wire.Edges) == 1: + if len(edges) == 1: chunksOffset = 1 - for edge in wire.Edges: + for edge in edges: if isinstance(edge.Curve, Part.Line): # pick the start and end points of a straight line - for pt in [edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter)]: + for pt in getFoldingPointsOnStraightEdge(edge): foldingPoints[serializeVector(pt)] = pt continue @@ -241,23 +257,62 @@ class FlatFoldingLine(FoldingLine): pass -def build3DFoldingLines(pointsToProject, projectionFace, projectionAxis, pointsOnProjectionFace, isProjectionOnFaceB): +# def build3DFoldingLines(pointsToProject, projectionFace, projectionAxis, pointsOnProjectionFace, isProjectionOnFaceB): +# foldingLines: [FoldingLine] = [] +# projectionPlaneCenter = getPlaneCenter(projectionFace) + +# for pointToProject in pointsToProject: +# projection = FreeCAD.Vector(pointToProject.x, pointToProject.y, pointToProject.z) +# projection.projectToPlane(projectionPlaneCenter, projectionAxis) + +# closestPointIndex = closest(projection, pointsOnProjectionFace) +# closestPoint = pointsOnProjectionFace[closestPointIndex] + +# if isProjectionOnFaceB: +# foldingLine = FoldingLine(pointToProject, closestPoint) +# else: +# foldingLine = FoldingLine(closestPoint, pointToProject) + +# foldingLines.append(foldingLine) + +# return foldingLines + + +def findVectorHavingTheClosestDirectionToPlanesAxis(baseVertex: FreeCAD.Vector, vectorsPool: [FreeCAD.Vector], planesAxis: FreeCAD.Vector) -> FreeCAD.Vector: + foundVector: FreeCAD.Vector = None + foundDot = 0.0 + normalizedPlanesAxis = FreeCAD.Vector(planesAxis.x, planesAxis.y, planesAxis.z) + normalizedPlanesAxis.normalize() + + for v in vectorsPool: + baseVertexToTestedVectorAxis = getVectorBewteenTwoVertexes(baseVertex, v) + baseVertexToTestedVectorAxis.normalize() + dot = baseVertexToTestedVectorAxis.dot(normalizedPlanesAxis) + + if dot > foundDot: + foundDot = dot + foundVector = v + + return foundVector + + +def buildFoldingLinesAtStraightEdgesEnds(wire: Part.Wire, otherFacePoints: [FreeCAD.Vector], planesAxis: FreeCAD.Vector) -> [FoldingLine]: foldingLines: [FoldingLine] = [] - projectionPlaneCenter = getPlaneCenter(projectionFace) + edges = wire.Edges - for pointToProject in pointsToProject: - projection = FreeCAD.Vector(pointToProject.x, pointToProject.y, pointToProject.z) - projection.projectToPlane(projectionPlaneCenter, projectionAxis) + for edge in edges: + if not isinstance(edge.Curve, Part.Line): + continue - closestPointIndex = closest(projection, pointsOnProjectionFace) - closestPoint = pointsOnProjectionFace[closestPointIndex] + edgeEnds = getFoldingPointsOnStraightEdge(edge) + edgeStart = edgeEnds[0] + edgeEnd = edgeEnds[1] + edgeMiddle = edge.valueAt(edge.LastParameter / 2) - if isProjectionOnFaceB: - foldingLine = FoldingLine(pointToProject, closestPoint) - else: - foldingLine = FoldingLine(closestPoint, pointToProject) + relatedPoint = findVectorHavingTheClosestDirectionToPlanesAxis(edgeMiddle, otherFacePoints, planesAxis) - foldingLines.append(foldingLine) + foldingLines.append(FoldingLine(relatedPoint, edgeStart)) + foldingLines.append(FoldingLine(relatedPoint, edgeEnd)) return foldingLines @@ -435,10 +490,33 @@ def main(): pointsWireA = buildFoldingPointsOnWire(wireFaceA, maxPointsCount) pointsWireB = buildFoldingPointsOnWire(wireFaceB, maxPointsCount) - if wireFaceB.Length > wireFaceA.Length: - foldingLines = build3DFoldingLines(pointsWireB, planeA, planesAxis, pointsWireA, False) - else: - foldingLines = build3DFoldingLines(pointsWireA, planeB, planesAxis, pointsWireB, True) + + def dbgg(foldingLines): + lines = [] + for foldingLine in foldingLines: + lines.append(Part.makeLine(foldingLine.getPointFaceA(), foldingLine.getPointFaceB())) + + if (len(lines) > 0): + Part.show(Part.makeCompound(lines)) + + dbgg(buildFoldingLinesAtStraightEdgesEnds(wireFaceA, pointsWireB, planesAxis)) + dbgg(buildFoldingLinesAtStraightEdgesEnds(wireFaceB, pointsWireA, planesAxis)) + + return + + # def dbgg(pts): + # for pt in pts: + # Part.show(Part.Vertex(pt.x, pt.y, pt.z)) + + # dbgg(pointsWireA) + # dbgg(pointsWireB) + # return + + # if len(pointsWireB) > len(pointsWireA): + # # if wireFaceB.Length > wireFaceA.Length: + # foldingLines = build3DFoldingLines(pointsWireB, planeA, planesAxis, pointsWireA, False) + # else: + # foldingLines = build3DFoldingLines(pointsWireA, planeB, planesAxis, pointsWireB, True) showFoldingLinesOn3DShape(foldingLines) From 1b063590ef1c58e74f2dc851d199d02bcf7500ec Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Fri, 24 Mar 2023 13:24:00 +0100 Subject: [PATCH 04/13] try to implement buildFoldingLinesOnCurvedEdges (doesn't work) Try to implement buildFoldingLinesOnCurvedEdges from buildFoldingLinesOnStraightEdges, but it doesn't lead to any satisfying output. Just committing it in order to continue research. See https://forum.freecad.org/viewtopic.php?p=667765#p667765 --- test_generatrices.FCMacro | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/test_generatrices.FCMacro b/test_generatrices.FCMacro index d3f7b01..4c32ac1 100644 --- a/test_generatrices.FCMacro +++ b/test_generatrices.FCMacro @@ -287,7 +287,7 @@ def findVectorHavingTheClosestDirectionToPlanesAxis(baseVertex: FreeCAD.Vector, for v in vectorsPool: baseVertexToTestedVectorAxis = getVectorBewteenTwoVertexes(baseVertex, v) baseVertexToTestedVectorAxis.normalize() - dot = baseVertexToTestedVectorAxis.dot(normalizedPlanesAxis) + dot = abs(baseVertexToTestedVectorAxis.dot(normalizedPlanesAxis)) if dot > foundDot: foundDot = dot @@ -317,6 +317,35 @@ def buildFoldingLinesAtStraightEdgesEnds(wire: Part.Wire, otherFacePoints: [Free return foldingLines +def buildFoldingLinesOnCurvedEdges(wire: Part.Wire, otherFacePoints: [FreeCAD.Vector], planesAxis: FreeCAD.Vector, maxPointsCount: int) -> [FoldingLine]: + foldingLines: [FoldingLine] = [] + edges = wire.Edges + totalLengthOfNonLinearEdges = getTotalLengthOfNonLinearEdges(edges) + + chunksOffset = 0 + if len(edges) == 1: + chunksOffset = 1 + + for edge in edges: + if isinstance(edge.Curve, Part.Line): + continue + + # compare the length of this non linear edge to the total length of + # non linear edges to know how many folding points we should place on it + pointsToPlaceCount = round((edge.Length * maxPointsCount) / totalLengthOfNonLinearEdges) + + # pick the evenly spaced points on edge + for pt in edge.discretize(Number=pointsToPlaceCount + chunksOffset): + relatedPoint = findVectorHavingTheClosestDirectionToPlanesAxis(pt, otherFacePoints, planesAxis) + + if not relatedPoint: + continue + + foldingLines.append(FoldingLine(relatedPoint, pt)) + + return foldingLines + + def arePointsIdentical(pointA, pointB): return 0.0 == pointA.distanceToPoint(pointB) @@ -501,6 +530,8 @@ def main(): dbgg(buildFoldingLinesAtStraightEdgesEnds(wireFaceA, pointsWireB, planesAxis)) dbgg(buildFoldingLinesAtStraightEdgesEnds(wireFaceB, pointsWireA, planesAxis)) + dbgg(buildFoldingLinesOnCurvedEdges(wireFaceA, pointsWireB, planesAxis, maxPointsCount)) + dbgg(buildFoldingLinesOnCurvedEdges(wireFaceB, pointsWireA, planesAxis, maxPointsCount)) return From 01617976d2fe8d66ec49e3983b33105e9031f3fd Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Fri, 24 Mar 2023 21:14:47 +0100 Subject: [PATCH 05/13] try to build folding lines by using coplanarity via vector's triple product see https://www.researchgate.net/publication/269052821_Convolutas_-_Piece-wise_Developable_Surfaces_using_two_Curved_Guidelines see https://arxiv.org/pdf/2212.06589.pdf --- test_generatrices.FCMacro | 205 +++++++++++++++++++++++++------------- 1 file changed, 133 insertions(+), 72 deletions(-) diff --git a/test_generatrices.FCMacro b/test_generatrices.FCMacro index 4c32ac1..c47ce2e 100644 --- a/test_generatrices.FCMacro +++ b/test_generatrices.FCMacro @@ -205,11 +205,47 @@ def getTotalLengthOfNonLinearEdges(edges: [Part.Edge]) -> float: return totalLength -def getFoldingPointsOnStraightEdge(edge: Part.Edge) -> [FreeCAD.Vector]: - return [edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter)] + +class FoldingLine(): + """A folding line on the 3D shape.""" + def __init__(self, pointFaceA: FreeCAD.Vector, pointFaceB: FreeCAD.Vector): + self.pointFaceA = pointFaceA + self.pointFaceB = pointFaceB + + def getPointFaceA(self) -> FreeCAD.Vector: + return self.pointFaceA + + def getPointFaceB(self) -> FreeCAD.Vector: + return self.pointFaceB + + +class FlatFoldingLine(FoldingLine): + """A flatten representation of a FoldingLine""" + pass -def buildFoldingPointsOnWire(wire: Part.Wire, maxPointsCount: int) -> [FreeCAD.Vector]: +class FoldingPoint(): + """A point used at one extremity of a FoldingLine""" + def __init__(self, point: FreeCAD.Vector, tangent: FreeCAD.Vector): + self.point = point + self.tangent = tangent + + def getPoint(self) -> FreeCAD.Vector: + return self.point + + def getTangent(self) -> FreeCAD.Vector: + """Returns the tangent to the point on the edge on which the point was picked""" + return self.tangent + + +def getFoldingPointsOnStraightEdge(edge: Part.Edge) -> [FoldingPoint]: + return [ + FoldingPoint(edge.valueAt(edge.FirstParameter), edge.tangentAt(edge.FirstParameter)), + FoldingPoint(edge.valueAt(edge.LastParameter), edge.tangentAt(edge.LastParameter)) + ] + + +def buildFoldingPointsOnWire(wire: Part.Wire, maxPointsCount: int) -> [FoldingPoint]: # order the edges in a geometrical way (the end of one edge matches the start of the next one) # edges = Part.__sortEdges__(wire.Edges) edges = wire.Edges @@ -223,38 +259,59 @@ def buildFoldingPointsOnWire(wire: Part.Wire, maxPointsCount: int) -> [FreeCAD.V for edge in edges: if isinstance(edge.Curve, Part.Line): # pick the start and end points of a straight line - for pt in getFoldingPointsOnStraightEdge(edge): - foldingPoints[serializeVector(pt)] = pt + for foldingPoint in getFoldingPointsOnStraightEdge(edge): + foldingPoints[serializeVector(foldingPoint.getPoint())] = foldingPoint continue # compare the length of this non linear edge to the total length of # non linear edges to know how many folding points we should place on it pointsToPlaceCount = round((edge.Length * maxPointsCount) / totalLengthOfNonLinearEdges) + stepSize = 1 / pointsToPlaceCount # pick the evenly spaced points on edge - for pt in edge.discretize(Number=pointsToPlaceCount + chunksOffset): - foldingPoints[serializeVector(pt)] = pt + for i in range(pointsToPlaceCount + chunksOffset): + at = edge.FirstParameter + (stepSize * i) * (edge.LastParameter - edge.FirstParameter) + foldingPoint = FoldingPoint(edge.valueAt(at), edge.tangentAt(at)) + foldingPoints[serializeVector(foldingPoint.getPoint())] = foldingPoint return list(foldingPoints.values()) -class FoldingLine(): - """A folding line on the 3D shape.""" - def __init__(self, pointFaceA: FreeCAD.Vector, pointFaceB: FreeCAD.Vector): - self.pointFaceA = pointFaceA - self.pointFaceB = pointFaceB +# @see https://www.researchgate.net/publication/269052821_Convolutas_-_Piece-wise_Developable_Surfaces_using_two_Curved_Guidelines +# @see https://arxiv.org/pdf/2212.06589.pdf +def buildFoldingLines(pointsWireA: [FoldingPoint], pointsWireB: [FoldingPoint], tolerance: float) -> [FoldingLine]: + mostPoints: [FoldingPoint] = [] + leastPoints: [FoldingPoint] = [] + foldingLines: [FoldingLine] = [] - def getPointFaceA(self) -> FreeCAD.Vector: - return self.pointFaceA + if len(pointsWireB) > len(pointsWireA): + mostPoints = pointsWireB + leastPoints = pointsWireA + else: + mostPoints = pointsWireA + leastPoints = pointsWireB - def getPointFaceB(self) -> FreeCAD.Vector: - return self.pointFaceB + for mostPoint in mostPoints: + for leastPoint in leastPoints: + # @see https://en.wikipedia.org/wiki/Coplanarity + # @see https://en.wikipedia.org/wiki/Triple_product#Scalar_triple_product + # @see https://github.com/Rentlau/WorkFeature/blob/eccc84fb1024a7394c158ba801772f858f1a4bc7/WorkFeature/WF.py#L11960 + a = mostPoint.getPoint().sub(leastPoint.getPoint()) + b = mostPoint.getPoint().sub(mostPoint.getTangent()) + c = leastPoint.getPoint().sub(leastPoint.getTangent()) + a.normalize() + b.normalize() + c.normalize() -class FlatFoldingLine(FoldingLine): - """A flatten representation of a FoldingLine""" - pass + res = abs(a.dot(b.cross(c))) + + if (res < tolerance): + foldingLines.append(FoldingLine(mostPoint.getPoint(), leastPoint.getPoint())) + + + return foldingLines # def build3DFoldingLines(pointsToProject, projectionFace, projectionAxis, pointsOnProjectionFace, isProjectionOnFaceB): @@ -278,73 +335,72 @@ class FlatFoldingLine(FoldingLine): # return foldingLines -def findVectorHavingTheClosestDirectionToPlanesAxis(baseVertex: FreeCAD.Vector, vectorsPool: [FreeCAD.Vector], planesAxis: FreeCAD.Vector) -> FreeCAD.Vector: - foundVector: FreeCAD.Vector = None - foundDot = 0.0 - normalizedPlanesAxis = FreeCAD.Vector(planesAxis.x, planesAxis.y, planesAxis.z) - normalizedPlanesAxis.normalize() +# def findVectorHavingTheClosestDirectionToPlanesAxis(baseVertex: FreeCAD.Vector, vectorsPool: [FreeCAD.Vector], planesAxis: FreeCAD.Vector) -> FreeCAD.Vector: +# foundVector: FreeCAD.Vector = None +# foundDot = 0.0 +# normalizedPlanesAxis = FreeCAD.Vector(planesAxis.x, planesAxis.y, planesAxis.z) +# normalizedPlanesAxis.normalize() - for v in vectorsPool: - baseVertexToTestedVectorAxis = getVectorBewteenTwoVertexes(baseVertex, v) - baseVertexToTestedVectorAxis.normalize() - dot = abs(baseVertexToTestedVectorAxis.dot(normalizedPlanesAxis)) +# for v in vectorsPool: +# baseVertexToTestedVectorAxis = getVectorBewteenTwoVertexes(baseVertex, v) +# baseVertexToTestedVectorAxis.normalize() +# dot = abs(baseVertexToTestedVectorAxis.dot(normalizedPlanesAxis)) - if dot > foundDot: - foundDot = dot - foundVector = v +# if dot > foundDot: +# foundDot = dot +# foundVector = v - return foundVector +# return foundVector -def buildFoldingLinesAtStraightEdgesEnds(wire: Part.Wire, otherFacePoints: [FreeCAD.Vector], planesAxis: FreeCAD.Vector) -> [FoldingLine]: - foldingLines: [FoldingLine] = [] - edges = wire.Edges - - for edge in edges: - if not isinstance(edge.Curve, Part.Line): - continue +# def buildFoldingLinesAtStraightEdgesEnds(wire: Part.Wire, otherFacePoints: [FreeCAD.Vector], planesAxis: FreeCAD.Vector) -> [FoldingLine]: +# foldingLines: [FoldingLine] = [] +# edges = wire.Edges - edgeEnds = getFoldingPointsOnStraightEdge(edge) - edgeStart = edgeEnds[0] - edgeEnd = edgeEnds[1] - edgeMiddle = edge.valueAt(edge.LastParameter / 2) +# for edge in edges: +# if not isinstance(edge.Curve, Part.Line): +# continue - relatedPoint = findVectorHavingTheClosestDirectionToPlanesAxis(edgeMiddle, otherFacePoints, planesAxis) +# edgeEnds = getFoldingPointsOnStraightEdge(edge) +# edgeStart = edgeEnds[0] +# edgeEnd = edgeEnds[1] +# edgeMiddle = edge.valueAt(edge.LastParameter / 2) - foldingLines.append(FoldingLine(relatedPoint, edgeStart)) - foldingLines.append(FoldingLine(relatedPoint, edgeEnd)) +# relatedPoint = findVectorHavingTheClosestDirectionToPlanesAxis(edgeMiddle, otherFacePoints, planesAxis) - return foldingLines +# foldingLines.append(FoldingLine(relatedPoint, edgeStart)) +# foldingLines.append(FoldingLine(relatedPoint, edgeEnd)) +# return foldingLines -def buildFoldingLinesOnCurvedEdges(wire: Part.Wire, otherFacePoints: [FreeCAD.Vector], planesAxis: FreeCAD.Vector, maxPointsCount: int) -> [FoldingLine]: - foldingLines: [FoldingLine] = [] - edges = wire.Edges - totalLengthOfNonLinearEdges = getTotalLengthOfNonLinearEdges(edges) - chunksOffset = 0 - if len(edges) == 1: - chunksOffset = 1 +# def buildFoldingLinesOnCurvedEdges(wire: Part.Wire, otherFacePoints: [FreeCAD.Vector], planesAxis: FreeCAD.Vector, maxPointsCount: int) -> [FoldingLine]: +# foldingLines: [FoldingLine] = [] +# edges = wire.Edges +# totalLengthOfNonLinearEdges = getTotalLengthOfNonLinearEdges(edges) - for edge in edges: - if isinstance(edge.Curve, Part.Line): - continue +# chunksOffset = 0 +# if len(edges) == 1: +# chunksOffset = 1 - # compare the length of this non linear edge to the total length of - # non linear edges to know how many folding points we should place on it - pointsToPlaceCount = round((edge.Length * maxPointsCount) / totalLengthOfNonLinearEdges) +# for edge in edges: +# if isinstance(edge.Curve, Part.Line): +# continue - # pick the evenly spaced points on edge - for pt in edge.discretize(Number=pointsToPlaceCount + chunksOffset): - relatedPoint = findVectorHavingTheClosestDirectionToPlanesAxis(pt, otherFacePoints, planesAxis) +# # compare the length of this non linear edge to the total length of +# # non linear edges to know how many folding points we should place on it +# pointsToPlaceCount = round((edge.Length * maxPointsCount) / totalLengthOfNonLinearEdges) - if not relatedPoint: - continue +# # pick the evenly spaced points on edge +# for pt in edge.discretize(Number=pointsToPlaceCount + chunksOffset): +# relatedPoint = findVectorHavingTheClosestDirectionToPlanesAxis(pt, otherFacePoints, planesAxis) - foldingLines.append(FoldingLine(relatedPoint, pt)) +# if not relatedPoint: +# continue - return foldingLines +# foldingLines.append(FoldingLine(relatedPoint, pt)) +# return foldingLines def arePointsIdentical(pointA, pointB): return 0.0 == pointA.distanceToPoint(pointB) @@ -499,9 +555,10 @@ def getWiresFromGuiSelection() -> (Part.Wire, Part.Wire): def main(): - # @TODO : GUI to set angle, show folding lines on 3D shape opt + # @TODO : GUI to set angle, show folding lines on 3D shape opt, tolerance angle = 10.0 maxPointsCount = ceil(360 / angle) + tolerance = 1e-10 wireFaceA, wireFaceB = getWiresFromGuiSelection() @@ -519,6 +576,8 @@ def main(): pointsWireA = buildFoldingPointsOnWire(wireFaceA, maxPointsCount) pointsWireB = buildFoldingPointsOnWire(wireFaceB, maxPointsCount) + foldingLines = buildFoldingLines(pointsWireA, pointsWireB, tolerance) + def dbgg(foldingLines): lines = [] @@ -528,10 +587,12 @@ def main(): if (len(lines) > 0): Part.show(Part.makeCompound(lines)) - dbgg(buildFoldingLinesAtStraightEdgesEnds(wireFaceA, pointsWireB, planesAxis)) - dbgg(buildFoldingLinesAtStraightEdgesEnds(wireFaceB, pointsWireA, planesAxis)) - dbgg(buildFoldingLinesOnCurvedEdges(wireFaceA, pointsWireB, planesAxis, maxPointsCount)) - dbgg(buildFoldingLinesOnCurvedEdges(wireFaceB, pointsWireA, planesAxis, maxPointsCount)) + + dbgg(foldingLines) + # dbgg(buildFoldingLinesAtStraightEdgesEnds(wireFaceA, pointsWireB, planesAxis)) + # dbgg(buildFoldingLinesAtStraightEdgesEnds(wireFaceB, pointsWireA, planesAxis)) + # dbgg(buildFoldingLinesOnCurvedEdges(wireFaceA, pointsWireB, planesAxis, maxPointsCount)) + # dbgg(buildFoldingLinesOnCurvedEdges(wireFaceB, pointsWireA, planesAxis, maxPointsCount)) return From 7243258e46f851141e8d3615eef4c3cdcd883ca9 Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Fri, 24 Mar 2023 21:44:37 +0100 Subject: [PATCH 06/13] fix straight lines ends tangent direction --- test_generatrices.FCMacro | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/test_generatrices.FCMacro b/test_generatrices.FCMacro index c47ce2e..9413f19 100644 --- a/test_generatrices.FCMacro +++ b/test_generatrices.FCMacro @@ -239,9 +239,19 @@ class FoldingPoint(): def getFoldingPointsOnStraightEdge(edge: Part.Edge) -> [FoldingPoint]: + firstPoint = edge.valueAt(edge.FirstParameter) + lastPoint = edge.valueAt(edge.LastParameter) + + firstTangent = edge.tangentAt(edge.FirstParameter) + lastTangent = edge.tangentAt(edge.LastParameter) + + # @see https://forum.freecad.org/viewtopic.php?p=670516#p670516 + firstTangent.multiply(-1.0) + lastTangent.multiply(-1.0) + return [ - FoldingPoint(edge.valueAt(edge.FirstParameter), edge.tangentAt(edge.FirstParameter)), - FoldingPoint(edge.valueAt(edge.LastParameter), edge.tangentAt(edge.LastParameter)) + FoldingPoint(firstPoint, firstTangent), + FoldingPoint(lastPoint, lastTangent) ] @@ -273,7 +283,10 @@ def buildFoldingPointsOnWire(wire: Part.Wire, maxPointsCount: int) -> [FoldingPo for i in range(pointsToPlaceCount + chunksOffset): at = edge.FirstParameter + (stepSize * i) * (edge.LastParameter - edge.FirstParameter) foldingPoint = FoldingPoint(edge.valueAt(at), edge.tangentAt(at)) - foldingPoints[serializeVector(foldingPoint.getPoint())] = foldingPoint + serialized = serializeVector(foldingPoint.getPoint()) + + if not serialized in foldingPoints: + foldingPoints[serialized] = foldingPoint return list(foldingPoints.values()) From 7e686b91dda7812c1020e06b2325b1bba61e4e75 Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Fri, 24 Mar 2023 21:57:39 +0100 Subject: [PATCH 07/13] better fix for straight lines end tangent direction fixes commit 7243258e46f851141e8d3615eef4c3cdcd883ca9 . Blindly inverting the tangent direction of straight lines ends was working for some cases but not for others. Using the continuous sorted edges array is a better solution. --- test_generatrices.FCMacro | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/test_generatrices.FCMacro b/test_generatrices.FCMacro index 9413f19..f2648de 100644 --- a/test_generatrices.FCMacro +++ b/test_generatrices.FCMacro @@ -245,10 +245,6 @@ def getFoldingPointsOnStraightEdge(edge: Part.Edge) -> [FoldingPoint]: firstTangent = edge.tangentAt(edge.FirstParameter) lastTangent = edge.tangentAt(edge.LastParameter) - # @see https://forum.freecad.org/viewtopic.php?p=670516#p670516 - firstTangent.multiply(-1.0) - lastTangent.multiply(-1.0) - return [ FoldingPoint(firstPoint, firstTangent), FoldingPoint(lastPoint, lastTangent) @@ -257,8 +253,8 @@ def getFoldingPointsOnStraightEdge(edge: Part.Edge) -> [FoldingPoint]: def buildFoldingPointsOnWire(wire: Part.Wire, maxPointsCount: int) -> [FoldingPoint]: # order the edges in a geometrical way (the end of one edge matches the start of the next one) - # edges = Part.__sortEdges__(wire.Edges) - edges = wire.Edges + edges = Part.__sortEdges__(wire.Edges) + # edges = wire.Edges foldingPoints = dict() totalLengthOfNonLinearEdges = getTotalLengthOfNonLinearEdges(edges) From 0ccd3b4c4d2be6d2be2c522a4a93bf6c995e06c5 Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Fri, 24 Mar 2023 22:19:11 +0100 Subject: [PATCH 08/13] code cleanup --- test_generatrices.FCMacro | 386 ++------------------------------------ 1 file changed, 17 insertions(+), 369 deletions(-) diff --git a/test_generatrices.FCMacro b/test_generatrices.FCMacro index f2648de..dd9df64 100644 --- a/test_generatrices.FCMacro +++ b/test_generatrices.FCMacro @@ -1,197 +1,15 @@ # -*- coding: utf-8 -*- import FreeCAD -from math import ceil, floor -from DraftGeomUtils import isPtOnEdge, findIntersection -from DraftVecUtils import closest +from math import ceil +from DraftGeomUtils import findIntersection def serializeVector(v: FreeCAD.Vector) -> str: return "%f;%f;%f" % (v.x, v.y, v.z) -def getPlaneCenter(plane: Part.Plane) -> FreeCAD.Vector: - return FreeCAD.Vector(plane.Position.x, plane.Position.y, plane.Position.z) - - -def getWireMaxBoundBox(wire: Part.Wire) -> float: - return max(wire.BoundBox.XMax, wire.BoundBox.YMax, wire.BoundBox.ZMax) - -def getIntersectionPoint(line: Part.Line, wire: Part.Wire) -> FreeCAD.Vector | None: - dists = line.distToShape(wire) - - if not len(dists) > 1: - return None - - if not len(dists[1]) > 0: - return None - - if not len(dists[1][0]) > 1: - return None - - return dists[1][0][1] - - -def getEdgeOnWhichPointIsOn(point, edges) -> Part.Edge | Part.Line | None: - for edge in edges: - if isPtOnEdge(point, edge): - return edge - - return None - - -def getEdgeEndClosestToPoint(point: FreeCAD.Vector, edge: Part.Edge | Part.Line) -> FreeCAD.Vector: - vertexes = edge.Vertexes - end1 = vertexes[0].Point - - if not len(vertexes) > 1: - return end1 - - end2 = vertexes[1].Point - dist1 = point.distanceToPoint(end1) - dist2 = point.distanceToPoint(end2) - - if dist1 < dist2: - return end1 - else: - return end2 - - -def getVectorBewteenTwoVertexes(va: FreeCAD.Vector, vb: FreeCAD.Vector) -> FreeCAD.Vector: - """Returns a vector aligned with the given vertexes. - - Parameters - ---------- - va : FreeCAD.Vector - vb : FreeCAD.Vector - - Returns - ------- - FreeCAD.Vector - """ - - vec: FreeCAD.Vector = None - - if va.Length > vb.Length: - vec = va.sub(vb) - else: - vec = vb.sub(va) - - return vec - - -def getVectorBewteenTwoPlanes(pa: Part.Plane, pb: Part.Plane) -> FreeCAD.Vector: - """Returns a vector aligned with the given planes centers. - - Parameters - ---------- - pa : Part.Plane - pb : Part.Plane - - Returns - ------- - FreeCAD.Vector - """ - - return getVectorBewteenTwoVertexes(getPlaneCenter(pa), getPlaneCenter(pb)) - - -def solveRayInitVertexes(wireA: Part.Wire, wireB: Part.Wire, planeB: Part.Plane) -> (FreeCAD.Vector, FreeCAD.Vector): - """Returns a tuple of vertexes to use on planeA and planeB to initialize the - first ray orientation (i.e. the ray goes by the plane center and this vertex). - - Parameters - ---------- - wireA : Part.Wire - wireB : Part.Wire - planeB: Part.Plane - - Returns - ------- - (rayInitVertexA: FreeCAD.Vector, rayInitVertexB: FreeCAD.Vector) - """ - rayInitVertexA = FreeCAD.Vector(wireA.Vertexes[0].Point) - - projectionPlaneCenter = getPlaneCenter(planeB) - planeAxis = FreeCAD.Vector(planeB.Axis) - projectionPlane = Part.Plane(projectionPlaneCenter, planeAxis) - - # project the rayInitVertexA on planeB - aProjection = FreeCAD.Vector(rayInitVertexA) - aProjection.projectToPlane(projectionPlaneCenter, planeAxis) - - rayLength = getWireMaxBoundBox(wireB) - ray = Part.makeLine(projectionPlaneCenter, buildInitialRayEndVertice(projectionPlaneCenter, aProjection, rayLength)) - - intersectionPoint = getIntersectionPoint(ray, wireB) - edge = getEdgeOnWhichPointIsOn(intersectionPoint, wireB.Edges) - - if not intersectionPoint or not edge: - # fallback to naive solution using the closest points bewteen the two wires - dists = wireFaceA.distToShape(wireFaceB) - rayInitVertexA = dists[1][0][0] - rayInitVertexB = dists[1][0][1] - - return (rayInitVertexA, rayInitVertexB) - - - rayInitVertexB = getEdgeEndClosestToPoint(intersectionPoint, edge) - - return (rayInitVertexA, rayInitVertexB) - - -def buildInitialRayEndVertice(planeCenter: FreeCAD.Vector, rayInitVertex: FreeCAD.Vector, rayLength: float) -> FreeCAD.Vector: - """Builds and returns the vertice used to draw a line of length `rayLength` - starting at `planeCenter` and going through `rayInitVertex`. - """ - rayInitLength = planeCenter.distanceToPoint(rayInitVertex) - # @see https://stackoverflow.com/a/14883334 - rayInitVector = rayInitVertex.sub(planeCenter) - if (rayInitLength < rayLength): - # make the initial ray as long as rayLength - rayInitVector.multiply(rayLength / rayInitLength) - - return planeCenter.add(rayInitVector) - - -def solveRayAngleFaceB(planeA, planeB, planesAxis, rayInitVertexA, rayInitVertexB, angle): - """Depending on the faces orientation, the ray angle could be in the opposite - direction for the B face. - This fn returns either angle or angle * -1. - """ - aCenter = getPlaneCenter(planeA) - bCenter = getPlaneCenter(planeB) - projectionPlaneCenter = bCenter - projectionPlane = Part.Plane(projectionPlaneCenter, planesAxis) - - rayLength = 100.0 - aRay = Part.makeLine(aCenter, buildInitialRayEndVertice(aCenter, rayInitVertexA, rayLength)) - bRay = Part.makeLine(bCenter, buildInitialRayEndVertice(bCenter, rayInitVertexB, rayLength)) - bBisRay = Part.makeLine(bCenter, buildInitialRayEndVertice(bCenter, rayInitVertexB, rayLength)) - - aRotationAxis = FreeCAD.Vector(planeA.Axis) - bRotationAxis = FreeCAD.Vector(planeB.Axis) - - aRay.rotate(aCenter, aRotationAxis, angle) - bRay.rotate(bCenter, bRotationAxis, angle) - bBisRay.rotate(bCenter, bRotationAxis, angle * -1) - - aProjection = FreeCAD.Vector(aRay.Vertexes[1].Point) - aProjection.projectToPlane(projectionPlaneCenter, planesAxis) - - bProjection = FreeCAD.Vector(bRay.Vertexes[1].Point) - bProjection.projectToPlane(projectionPlaneCenter, planesAxis) - - bBisProjection = FreeCAD.Vector(bBisRay.Vertexes[1].Point) - bBisProjection.projectToPlane(projectionPlaneCenter, planesAxis) - - distAB = aProjection.distanceToPoint(bProjection) - distABBis = aProjection.distanceToPoint(bBisProjection) - - if (distAB < distABBis): - return angle - else: - return angle * -1 - +def arePointsIdentical(pointA, pointB): + return 0.0 == pointA.distanceToPoint(pointB) def getTotalLengthOfNonLinearEdges(edges: [Part.Edge]) -> float: @@ -254,7 +72,6 @@ def getFoldingPointsOnStraightEdge(edge: Part.Edge) -> [FoldingPoint]: def buildFoldingPointsOnWire(wire: Part.Wire, maxPointsCount: int) -> [FoldingPoint]: # order the edges in a geometrical way (the end of one edge matches the start of the next one) edges = Part.__sortEdges__(wire.Edges) - # edges = wire.Edges foldingPoints = dict() totalLengthOfNonLinearEdges = getTotalLengthOfNonLinearEdges(edges) @@ -290,154 +107,29 @@ def buildFoldingPointsOnWire(wire: Part.Wire, maxPointsCount: int) -> [FoldingPo # @see https://www.researchgate.net/publication/269052821_Convolutas_-_Piece-wise_Developable_Surfaces_using_two_Curved_Guidelines # @see https://arxiv.org/pdf/2212.06589.pdf def buildFoldingLines(pointsWireA: [FoldingPoint], pointsWireB: [FoldingPoint], tolerance: float) -> [FoldingLine]: - mostPoints: [FoldingPoint] = [] - leastPoints: [FoldingPoint] = [] foldingLines: [FoldingLine] = [] - if len(pointsWireB) > len(pointsWireA): - mostPoints = pointsWireB - leastPoints = pointsWireA - else: - mostPoints = pointsWireA - leastPoints = pointsWireB - - for mostPoint in mostPoints: - for leastPoint in leastPoints: + for pointWireA in pointsWireA: + for pointWireB in pointsWireB: # @see https://en.wikipedia.org/wiki/Coplanarity # @see https://en.wikipedia.org/wiki/Triple_product#Scalar_triple_product # @see https://github.com/Rentlau/WorkFeature/blob/eccc84fb1024a7394c158ba801772f858f1a4bc7/WorkFeature/WF.py#L11960 - a = mostPoint.getPoint().sub(leastPoint.getPoint()) - b = mostPoint.getPoint().sub(mostPoint.getTangent()) - c = leastPoint.getPoint().sub(leastPoint.getTangent()) + va = pointWireA.getPoint().sub(pointWireB.getPoint()) + vb = pointWireA.getPoint().sub(pointWireA.getTangent()) + vc = pointWireB.getPoint().sub(pointWireB.getTangent()) - a.normalize() - b.normalize() - c.normalize() + va.normalize() + vb.normalize() + vc.normalize() - res = abs(a.dot(b.cross(c))) + res = abs(va.dot(vb.cross(vc))) if (res < tolerance): - foldingLines.append(FoldingLine(mostPoint.getPoint(), leastPoint.getPoint())) - + foldingLines.append(FoldingLine(pointWireA.getPoint(), pointWireB.getPoint())) return foldingLines -# def build3DFoldingLines(pointsToProject, projectionFace, projectionAxis, pointsOnProjectionFace, isProjectionOnFaceB): -# foldingLines: [FoldingLine] = [] -# projectionPlaneCenter = getPlaneCenter(projectionFace) - -# for pointToProject in pointsToProject: -# projection = FreeCAD.Vector(pointToProject.x, pointToProject.y, pointToProject.z) -# projection.projectToPlane(projectionPlaneCenter, projectionAxis) - -# closestPointIndex = closest(projection, pointsOnProjectionFace) -# closestPoint = pointsOnProjectionFace[closestPointIndex] - -# if isProjectionOnFaceB: -# foldingLine = FoldingLine(pointToProject, closestPoint) -# else: -# foldingLine = FoldingLine(closestPoint, pointToProject) - -# foldingLines.append(foldingLine) - -# return foldingLines - - -# def findVectorHavingTheClosestDirectionToPlanesAxis(baseVertex: FreeCAD.Vector, vectorsPool: [FreeCAD.Vector], planesAxis: FreeCAD.Vector) -> FreeCAD.Vector: -# foundVector: FreeCAD.Vector = None -# foundDot = 0.0 -# normalizedPlanesAxis = FreeCAD.Vector(planesAxis.x, planesAxis.y, planesAxis.z) -# normalizedPlanesAxis.normalize() - -# for v in vectorsPool: -# baseVertexToTestedVectorAxis = getVectorBewteenTwoVertexes(baseVertex, v) -# baseVertexToTestedVectorAxis.normalize() -# dot = abs(baseVertexToTestedVectorAxis.dot(normalizedPlanesAxis)) - -# if dot > foundDot: -# foundDot = dot -# foundVector = v - -# return foundVector - - -# def buildFoldingLinesAtStraightEdgesEnds(wire: Part.Wire, otherFacePoints: [FreeCAD.Vector], planesAxis: FreeCAD.Vector) -> [FoldingLine]: -# foldingLines: [FoldingLine] = [] -# edges = wire.Edges - -# for edge in edges: -# if not isinstance(edge.Curve, Part.Line): -# continue - -# edgeEnds = getFoldingPointsOnStraightEdge(edge) -# edgeStart = edgeEnds[0] -# edgeEnd = edgeEnds[1] -# edgeMiddle = edge.valueAt(edge.LastParameter / 2) - -# relatedPoint = findVectorHavingTheClosestDirectionToPlanesAxis(edgeMiddle, otherFacePoints, planesAxis) - -# foldingLines.append(FoldingLine(relatedPoint, edgeStart)) -# foldingLines.append(FoldingLine(relatedPoint, edgeEnd)) - -# return foldingLines - - -# def buildFoldingLinesOnCurvedEdges(wire: Part.Wire, otherFacePoints: [FreeCAD.Vector], planesAxis: FreeCAD.Vector, maxPointsCount: int) -> [FoldingLine]: -# foldingLines: [FoldingLine] = [] -# edges = wire.Edges -# totalLengthOfNonLinearEdges = getTotalLengthOfNonLinearEdges(edges) - -# chunksOffset = 0 -# if len(edges) == 1: -# chunksOffset = 1 - -# for edge in edges: -# if isinstance(edge.Curve, Part.Line): -# continue - -# # compare the length of this non linear edge to the total length of -# # non linear edges to know how many folding points we should place on it -# pointsToPlaceCount = round((edge.Length * maxPointsCount) / totalLengthOfNonLinearEdges) - -# # pick the evenly spaced points on edge -# for pt in edge.discretize(Number=pointsToPlaceCount + chunksOffset): -# relatedPoint = findVectorHavingTheClosestDirectionToPlanesAxis(pt, otherFacePoints, planesAxis) - -# if not relatedPoint: -# continue - -# foldingLines.append(FoldingLine(relatedPoint, pt)) - -# return foldingLines - -def arePointsIdentical(pointA, pointB): - return 0.0 == pointA.distanceToPoint(pointB) - - -# def filterPoints(pointsA, pointsB): -# """When consecutive points are on the same emplacement, skip them. -# """ -# filteredPointsA = [] -# filteredPointsB = [] - -# for i in range(len(pointsA)): -# currentPointA = pointsA[i] -# currentPointB = pointsB[i] - -# if i > 0: -# if arePointsIdentical(currentPointA, pointsA[0]) and arePointsIdentical(currentPointB, pointsB[0]): -# continue - -# if arePointsIdentical(currentPointA, pointsA[i - 1]) and arePointsIdentical(currentPointB, pointsB[i - 1]): -# continue - -# filteredPointsA.append(currentPointA) -# filteredPointsB.append(currentPointB) - -# return (filteredPointsA, filteredPointsB) - - def showFoldingLinesOn3DShape(foldingLines: [FoldingLine]) -> None: """Draw the folding lines on the 3D shape. """ @@ -499,7 +191,7 @@ def flattenFoldingLines(foldingLines: [FoldingLine]) -> [FlatFoldingLine]: return flatFoldingLines -def buildEdges(points: [FreeCAD.Vector], edges = []) -> None: +def buildFlatEdges(points: [FreeCAD.Vector], edges = []) -> None: """Link the given points by making edges, and add the created edges to the given edges array parameter. """ @@ -530,8 +222,8 @@ def drawFlattenRepresentation(flatFoldingLines: [FlatFoldingLine]) -> None: flattenPointsB.append(flatFoldingLines[i].getPointFaceB()) # the flat edges of A & B wires - buildEdges(flattenPointsA, edges) - buildEdges(flattenPointsB, edges) + buildFlatEdges(flattenPointsA, edges) + buildFlatEdges(flattenPointsB, edges) i = 0 while (i < foldingLinesCount): @@ -562,7 +254,6 @@ def getWiresFromGuiSelection() -> (Part.Wire, Part.Wire): return (skA.Shape, skB.Shape) - def main(): # @TODO : GUI to set angle, show folding lines on 3D shape opt, tolerance angle = 10.0 @@ -571,54 +262,11 @@ def main(): wireFaceA, wireFaceB = getWiresFromGuiSelection() - planeA = wireFaceA.findPlane() - planeB = wireFaceB.findPlane() - planesAxis = getVectorBewteenTwoPlanes(planeA, planeB) - - # where to place the initial ray on each face - # rayInitVertexA, rayInitVertexB = solveRayInitVertexes(wireFaceA, wireFaceB, planeB) - - # rayAngleFaceA = angle - # could be in the opposite direction of rayAngleFaceA - # rayAngleFaceB = solveRayAngleFaceB(planeA, planeB, planesAxis, rayInitVertexA, rayInitVertexB, angle) - pointsWireA = buildFoldingPointsOnWire(wireFaceA, maxPointsCount) pointsWireB = buildFoldingPointsOnWire(wireFaceB, maxPointsCount) foldingLines = buildFoldingLines(pointsWireA, pointsWireB, tolerance) - - def dbgg(foldingLines): - lines = [] - for foldingLine in foldingLines: - lines.append(Part.makeLine(foldingLine.getPointFaceA(), foldingLine.getPointFaceB())) - - if (len(lines) > 0): - Part.show(Part.makeCompound(lines)) - - - dbgg(foldingLines) - # dbgg(buildFoldingLinesAtStraightEdgesEnds(wireFaceA, pointsWireB, planesAxis)) - # dbgg(buildFoldingLinesAtStraightEdgesEnds(wireFaceB, pointsWireA, planesAxis)) - # dbgg(buildFoldingLinesOnCurvedEdges(wireFaceA, pointsWireB, planesAxis, maxPointsCount)) - # dbgg(buildFoldingLinesOnCurvedEdges(wireFaceB, pointsWireA, planesAxis, maxPointsCount)) - - return - - # def dbgg(pts): - # for pt in pts: - # Part.show(Part.Vertex(pt.x, pt.y, pt.z)) - - # dbgg(pointsWireA) - # dbgg(pointsWireB) - # return - - # if len(pointsWireB) > len(pointsWireA): - # # if wireFaceB.Length > wireFaceA.Length: - # foldingLines = build3DFoldingLines(pointsWireB, planeA, planesAxis, pointsWireA, False) - # else: - # foldingLines = build3DFoldingLines(pointsWireA, planeB, planesAxis, pointsWireB, True) - showFoldingLinesOn3DShape(foldingLines) flatFoldingLines = flattenFoldingLines(foldingLines) From f07fd43c817db64d939a8108e47776d4f1d054ad Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Sat, 25 Mar 2023 01:32:29 +0100 Subject: [PATCH 09/13] handle flat representation case of a folding line when a folding point is used by multiple folding lines --- test_generatrices.FCMacro | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/test_generatrices.FCMacro b/test_generatrices.FCMacro index dd9df64..5899289 100644 --- a/test_generatrices.FCMacro +++ b/test_generatrices.FCMacro @@ -158,15 +158,21 @@ def flattenFoldingLine(foldingLines: [FoldingLine], previousFlatFoldingLine: Fla distAB = pointA.distanceToPoint(pointB) diagLength = previousPointA.distanceToPoint(pointB) - # use circles to find the position of the bFlat point - circleBStep = Part.makeCircle(bStepLength, previousBFlat) - circleDiag = Part.makeCircle(diagLength, previousAFlat) - bFlat = findIntersection(circleBStep, circleDiag)[0] - - # use circles to find the position of the aFlat point - circleAStep = Part.makeCircle(aStepLength, previousAFlat) - circleAFlatBFlat = Part.makeCircle(distAB, bFlat) - aFlat = findIntersection(circleAStep, circleAFlatBFlat)[-1] + if (0.0 == bStepLength): + bFlat = previousBFlat + else: + # use circles to find the position of the bFlat point + circleBStep = Part.makeCircle(bStepLength, previousBFlat) + circleDiag = Part.makeCircle(diagLength, previousAFlat) + bFlat = findIntersection(circleBStep, circleDiag)[0] + + if (0.0 == aStepLength): + aFlat = previousAFlat + else: + # use circles to find the position of the aFlat point + circleAStep = Part.makeCircle(aStepLength, previousAFlat) + circleAFlatBFlat = Part.makeCircle(distAB, bFlat) + aFlat = findIntersection(circleAStep, circleAFlatBFlat)[-1] return FlatFoldingLine(aFlat, bFlat) From de4dbfd408f7abf3fdf85c937bd4537445fad343 Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Sun, 26 Mar 2023 16:05:04 +0200 Subject: [PATCH 10/13] fix `BRepAdaptor_Curve::No geometry` error when drawing flatten representation --- test_generatrices.FCMacro | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/test_generatrices.FCMacro b/test_generatrices.FCMacro index 5899289..dc0e364 100644 --- a/test_generatrices.FCMacro +++ b/test_generatrices.FCMacro @@ -8,10 +8,6 @@ def serializeVector(v: FreeCAD.Vector) -> str: return "%f;%f;%f" % (v.x, v.y, v.z) -def arePointsIdentical(pointA, pointB): - return 0.0 == pointA.distanceToPoint(pointB) - - def getTotalLengthOfNonLinearEdges(edges: [Part.Edge]) -> float: totalLength = 0.0 @@ -208,7 +204,10 @@ def buildFlatEdges(points: [FreeCAD.Vector], edges = []) -> None: currentPoint = points[i] i += 1 - if (arePointsIdentical(previousPoint, currentPoint)): + if (previousPoint.distanceToPoint(currentPoint) < 1e-10): + # Consider points as identical, so can't make a line. + # If we try to make a line in this case, it would result into a + # `BRepAdaptor_Curve::No geometry` error. continue edges.append(Part.makeLine(previousPoint, currentPoint)) @@ -237,10 +236,6 @@ def drawFlattenRepresentation(flatFoldingLines: [FlatFoldingLine]) -> None: edges.append(Part.makeLine(flatFoldingLines[i].getPointFaceA(), flatFoldingLines[i].getPointFaceB())) i += 1 - # Sometimes there's a `BRepAdaptor_Curve::No geometry` error when trying to draw - # the flatten representation as a compound. If this occurs, you can still - # downgrade the shape into multiple edges using the Draft->downgrade button - # and place all the edges into a directory (i.e. a group). Part.show(Part.makeCompound(edges)) From 32516d03ac7132586b2b091b181af8f0af2f1680 Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Sun, 26 Mar 2023 18:21:04 +0200 Subject: [PATCH 11/13] add CosmeticFoldingLine notion Create cosmetic folding lines in the middle of straight edges. There is no folding on such CosmeticFoldingLine, it is only used as a guide. It would also be nice to draw them in a different color, but it seems that changing a line color requires a `Part::Feature` object added to the active document :/ cf https://forum.freecad.org/viewtopic.php?t=7335 --- test_generatrices.FCMacro | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/test_generatrices.FCMacro b/test_generatrices.FCMacro index dc0e364..c52284a 100644 --- a/test_generatrices.FCMacro +++ b/test_generatrices.FCMacro @@ -33,11 +33,22 @@ class FoldingLine(): return self.pointFaceB +class CosmeticFoldingLine(FoldingLine): + """A line for cosmetic (i.e. guide) purposes. There is actually no folding on + this line, it is only used as a guide.""" + pass + + class FlatFoldingLine(FoldingLine): """A flatten representation of a FoldingLine""" pass +class CosmeticFlatFoldingLine(FlatFoldingLine): + """A flatten representation of a CosmeticFoldingLine""" + pass + + class FoldingPoint(): """A point used at one extremity of a FoldingLine""" def __init__(self, point: FreeCAD.Vector, tangent: FreeCAD.Vector): @@ -52,15 +63,23 @@ class FoldingPoint(): return self.tangent +class CosmeticFoldingPoint(FoldingPoint): + """A point used at one extremity of a CosmeticFoldingLine.""" + pass + + def getFoldingPointsOnStraightEdge(edge: Part.Edge) -> [FoldingPoint]: firstPoint = edge.valueAt(edge.FirstParameter) + middlePoint = edge.valueAt(edge.FirstParameter + (edge.LastParameter - edge.FirstParameter) / 2) lastPoint = edge.valueAt(edge.LastParameter) firstTangent = edge.tangentAt(edge.FirstParameter) + middleTangent = edge.tangentAt(edge.FirstParameter + (edge.LastParameter - edge.FirstParameter) / 2) lastTangent = edge.tangentAt(edge.LastParameter) return [ FoldingPoint(firstPoint, firstTangent), + CosmeticFoldingPoint(middlePoint, middleTangent), FoldingPoint(lastPoint, lastTangent) ] @@ -121,7 +140,12 @@ def buildFoldingLines(pointsWireA: [FoldingPoint], pointsWireB: [FoldingPoint], res = abs(va.dot(vb.cross(vc))) if (res < tolerance): - foldingLines.append(FoldingLine(pointWireA.getPoint(), pointWireB.getPoint())) + if isinstance(pointWireA, CosmeticFoldingPoint) or isinstance(pointWireB, CosmeticFoldingPoint): + foldingLine = CosmeticFoldingLine(pointWireA.getPoint(), pointWireB.getPoint()) + else: + foldingLine = FoldingLine(pointWireA.getPoint(), pointWireB.getPoint()) + + foldingLines.append(foldingLine) return foldingLines @@ -142,8 +166,9 @@ def flattenFoldingLine(foldingLines: [FoldingLine], previousFlatFoldingLine: Fla if (i < 1): return - pointA = foldingLines[i].getPointFaceA() - pointB = foldingLines[i].getPointFaceB() + foldingLine = foldingLines[i] + pointA = foldingLine.getPointFaceA() + pointB = foldingLine.getPointFaceB() previousPointA = foldingLines[i - 1].getPointFaceA() previousPointB = foldingLines[i - 1].getPointFaceB() previousAFlat = previousFlatFoldingLine.getPointFaceA() @@ -170,7 +195,10 @@ def flattenFoldingLine(foldingLines: [FoldingLine], previousFlatFoldingLine: Fla circleAFlatBFlat = Part.makeCircle(distAB, bFlat) aFlat = findIntersection(circleAStep, circleAFlatBFlat)[-1] - return FlatFoldingLine(aFlat, bFlat) + if isinstance(foldingLine, CosmeticFoldingLine): + return CosmeticFlatFoldingLine(aFlat, bFlat) + else: + return FlatFoldingLine(aFlat, bFlat) def flattenFoldingLines(foldingLines: [FoldingLine]) -> [FlatFoldingLine]: From ac257e2723f6643b725540ae047b56e5acedc8df Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Sun, 26 Mar 2023 18:34:57 +0200 Subject: [PATCH 12/13] draw again the first flat folding line in order to easily wrap up the flatten representation --- test_generatrices.FCMacro | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test_generatrices.FCMacro b/test_generatrices.FCMacro index c52284a..21705e0 100644 --- a/test_generatrices.FCMacro +++ b/test_generatrices.FCMacro @@ -163,8 +163,6 @@ def showFoldingLinesOn3DShape(foldingLines: [FoldingLine]) -> None: def flattenFoldingLine(foldingLines: [FoldingLine], previousFlatFoldingLine: FlatFoldingLine, i: int) -> FlatFoldingLine: """Computes the flatten representation of a folding line.""" - if (i < 1): - return foldingLine = foldingLines[i] pointA = foldingLine.getPointFaceA() @@ -218,6 +216,12 @@ def flattenFoldingLines(foldingLines: [FoldingLine]) -> [FlatFoldingLine]: flatFoldingLines.append(previousFlatFoldingLine) i += 1 + # Add again the first folding line to be drawn in last position so the flatten + # representation can be wrapped up having the two drawings of the first folding line + # superposed. + previousFlatFoldingLine = flattenFoldingLine(foldingLines, previousFlatFoldingLine, 0) + flatFoldingLines.append(previousFlatFoldingLine) + return flatFoldingLines From d360c95c2fba05ff2cd257b412c00b87ea3abb46 Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Mon, 27 Mar 2023 21:28:47 +0200 Subject: [PATCH 13/13] only compute cosmetic folding points when there are curved edges i.e. not only straight edges --- test_generatrices.FCMacro | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/test_generatrices.FCMacro b/test_generatrices.FCMacro index 21705e0..614f901 100644 --- a/test_generatrices.FCMacro +++ b/test_generatrices.FCMacro @@ -68,20 +68,24 @@ class CosmeticFoldingPoint(FoldingPoint): pass -def getFoldingPointsOnStraightEdge(edge: Part.Edge) -> [FoldingPoint]: - firstPoint = edge.valueAt(edge.FirstParameter) - middlePoint = edge.valueAt(edge.FirstParameter + (edge.LastParameter - edge.FirstParameter) / 2) - lastPoint = edge.valueAt(edge.LastParameter) +def getFoldingPointsOnStraightEdge(edge: Part.Edge, computeCosmeticFoldingPoints: bool) -> [FoldingPoint]: + foldingPoints: [FoldingPoint] = [] + firstPoint = edge.valueAt(edge.FirstParameter) firstTangent = edge.tangentAt(edge.FirstParameter) - middleTangent = edge.tangentAt(edge.FirstParameter + (edge.LastParameter - edge.FirstParameter) / 2) + foldingPoints.append(FoldingPoint(firstPoint, firstTangent)) + + if computeCosmeticFoldingPoints: + at = edge.FirstParameter + (edge.LastParameter - edge.FirstParameter) / 2 + middlePoint = edge.valueAt(at) + middleTangent = edge.tangentAt(at) + foldingPoints.append(CosmeticFoldingPoint(middlePoint, middleTangent)) + + lastPoint = edge.valueAt(edge.LastParameter) lastTangent = edge.tangentAt(edge.LastParameter) + foldingPoints.append(FoldingPoint(lastPoint, lastTangent)) - return [ - FoldingPoint(firstPoint, firstTangent), - CosmeticFoldingPoint(middlePoint, middleTangent), - FoldingPoint(lastPoint, lastTangent) - ] + return foldingPoints def buildFoldingPointsOnWire(wire: Part.Wire, maxPointsCount: int) -> [FoldingPoint]: @@ -90,6 +94,10 @@ def buildFoldingPointsOnWire(wire: Part.Wire, maxPointsCount: int) -> [FoldingPo foldingPoints = dict() totalLengthOfNonLinearEdges = getTotalLengthOfNonLinearEdges(edges) + # only compute cosmetic folding points when there are curved edges (i.e. + # not only straight edges) + computeCosmeticFoldingPoints = 0.0 != totalLengthOfNonLinearEdges + chunksOffset = 0 if len(edges) == 1: chunksOffset = 1 @@ -97,7 +105,7 @@ def buildFoldingPointsOnWire(wire: Part.Wire, maxPointsCount: int) -> [FoldingPo for edge in edges: if isinstance(edge.Curve, Part.Line): # pick the start and end points of a straight line - for foldingPoint in getFoldingPointsOnStraightEdge(edge): + for foldingPoint in getFoldingPointsOnStraightEdge(edge, computeCosmeticFoldingPoints): foldingPoints[serializeVector(foldingPoint.getPoint())] = foldingPoint continue