Merge branch 'alafr-arch-structure' of https://github.com/alafr/FreeCAD

This commit is contained in:
Yorik van Havre
2021-09-02 14:36:43 +02:00
11 changed files with 785 additions and 92 deletions

View File

@@ -62,6 +62,7 @@ SET(Draft_tests
drafttests/test_dwg.py
drafttests/test_oca.py
drafttests/test_airfoildat.py
drafttests/test_draftgeomutils.py
drafttests/draft_test_objects.py
drafttests/README.md
)

View File

@@ -86,7 +86,8 @@ from draftgeoutils.edges import (findEdge,
is_line,
invert,
findMidpoint,
getTangent)
getTangent,
get_referenced_edges)
from draftgeoutils.faces import (concatenate,
getBoundary,
@@ -129,7 +130,9 @@ from draftgeoutils.wires import (findWires,
rebaseWire,
removeInterVertices,
cleanProjection,
tessellateProjection)
tessellateProjection,
get_placement_perpendicular_to_wire,
get_extended_wire)
# Needs wires functions
from draftgeoutils.fillets import (fillet,

View File

@@ -101,12 +101,15 @@ from drafttests.test_import import DraftImport as DraftTest01
from drafttests.test_creation import DraftCreation as DraftTest02
from drafttests.test_modification import DraftModification as DraftTest03
# Testing the utils module
from drafttests.test_draftgeomutils import TestDraftGeomUtils as DraftTest04
# Handling of file formats tests
from drafttests.test_svg import DraftSVG as DraftTest04
from drafttests.test_dxf import DraftDXF as DraftTest05
from drafttests.test_dwg import DraftDWG as DraftTest06
# from drafttests.test_oca import DraftOCA as DraftTest07
# from drafttests.test_airfoildat import DraftAirfoilDAT as DraftTest08
from drafttests.test_svg import DraftSVG as DraftTest05
from drafttests.test_dxf import DraftDXF as DraftTest06
from drafttests.test_dwg import DraftDWG as DraftTest07
# from drafttests.test_oca import DraftOCA as DraftTest08
# from drafttests.test_airfoildat import DraftAirfoilDAT as DraftTest09
# Use the modules so that code checkers don't complain (flake8)
True if DraftTest01 else False
@@ -115,5 +118,6 @@ True if DraftTest03 else False
True if DraftTest04 else False
True if DraftTest05 else False
True if DraftTest06 else False
# True if DraftTest07 else False
True if DraftTest07 else False
# True if DraftTest08 else False
# True if DraftTest09 else False

View File

@@ -221,8 +221,31 @@ def getTangent(edge, from_point=None):
return None
def get_referenced_edges(property_value):
"""Return the Edges referenced by the value of a App:PropertyLink, App::PropertyLinkList,
App::PropertyLinkSub or App::PropertyLinkSubList property."""
edges = []
if not isinstance(property_value, list):
property_value = [property_value]
for element in property_value:
if hasattr(element, "Shape") and element.Shape:
edges += shape.Edges
elif isinstance(element, tuple) and len(element) == 2:
object, subelement_names = element
if hasattr(object, "Shape") and object.Shape:
if len(subelement_names) == 1 and subelement_names[0] == "":
edges += object.Shape.Edges
else:
for subelement_name in subelement_names:
if subelement_name.startswith("Edge"):
edge_number = int(subelement_name.lstrip("Edge")) - 1
if edge_number < len(object.Shape.Edges):
edges.append(object.Shape.Edges[edge_number])
return edges
# compatibility layer
isLine = is_line
## @}
## @}

View File

@@ -59,11 +59,17 @@ def precision():
return precisionInt # return PARAMGRP.GetInt("precision", 6)
def vec(edge):
"""Return a vector from an edge or a Part.LineSegment."""
# if edge is not straight, you'll get strange results!
def vec(edge, use_orientation = False):
"""Return a vector from an edge or a Part.LineSegment.
If use_orientation is True, it takes into account the edges orientation.
If edge is not straight, you'll get strange results!
"""
if isinstance(edge, Part.Shape):
return edge.Vertexes[-1].Point.sub(edge.Vertexes[0].Point)
if use_orientation and isinstance(edge, Part.Edge) and edge.Orientation == "Reversed":
return edge.Vertexes[0].Point.sub(edge.Vertexes[-1].Point)
else:
return edge.Vertexes[-1].Point.sub(edge.Vertexes[0].Point)
elif isinstance(edge, Part.LineSegment):
return edge.EndPoint.sub(edge.StartPoint)
else:

View File

@@ -32,6 +32,7 @@ import lazy_loader.lazy_loader as lz
import FreeCAD as App
import DraftVecUtils
import WorkingPlane
import FreeCAD as App
from draftgeoutils.general import geomType, vec, precision
from draftgeoutils.geometry import get_normal
@@ -435,4 +436,95 @@ def tessellateProjection(shape, seglen):
return Part.makeCompound(newedges)
## @}
def get_placement_perpendicular_to_wire(wire):
"""Return the placement whose base is the wire's first vertex and it's z axis aligned to the wire's tangent."""
pl = App.Placement()
if wire.Length > 0.0:
pl.Base = wire.OrderedVertexes[0].Point
first_edge = wire.OrderedEdges[0]
if first_edge.Orientation == "Forward":
zaxis = -first_edge.tangentAt(first_edge.FirstParameter)
else:
zaxis = first_edge.tangentAt(first_edge.LastParameter)
pl.Rotation = App.Rotation(App.Vector(1, 0, 0), App.Vector(0, 0, 1), zaxis, "ZYX")
else:
App.Console.PrintError("debug: get_placement_perpendicular_to_wire called with a zero-length wire.\n")
return pl
def get_extended_wire(wire, offset_start, offset_end):
"""Return a wire trimmed (negative offset) or extended (positive offset) at its first vertex, last vertex or both ends.
get_extended_wire(wire, -100.0, 0.0) -> returns a copy of the wire with its first 100 mm removed
get_extended_wire(wire, 0.0, 100.0) -> returns a copy of the wire extended by 100 mm after it's last vertex
"""
if min(offset_start, offset_end, offset_start + offset_end) <= -wire.Length:
App.Console.PrintError("debug: get_extended_wire error, wire's length insufficient for trimming.\n")
return wire
if offset_start < 0: # Trim the wire from the first vertex
offset_start = -offset_start
out_edges = []
for edge in wire.OrderedEdges:
if offset_start >= edge.Length: # Remove entire edge
offset_start -= edge.Length
elif round(offset_start, precision()) > 0: # Split edge, to remove the required length
if edge.Orientation == "Forward":
new_edge = edge.split(edge.getParameterByLength(offset_start)).OrderedEdges[1]
else:
new_edge = edge.split(edge.getParameterByLength(edge.Length - offset_start)).OrderedEdges[0]
new_edge.Placement = edge.Placement # Strangely, edge.split discards the placement and orientation
new_edge.Orientation = edge.Orientation
out_edges.append(new_edge)
offset_start = 0
else: # Keep the remaining entire edges
out_edges.append(edge)
wire = Part.Wire(out_edges)
elif offset_start > 0: # Extend the first edge along its normal
first_edge = wire.OrderedEdges[0]
if first_edge.Orientation == "Forward":
start, end = first_edge.FirstParameter, first_edge.LastParameter
vec = first_edge.tangentAt(start).multiply(offset_start)
else:
start, end = first_edge.LastParameter, first_edge.FirstParameter
vec = -first_edge.tangentAt(start).multiply(offset_start)
if geomType(first_edge) == "Line": # Replace first edge with the extended new edge
new_edge = Part.LineSegment(first_edge.valueAt(start).sub(vec), first_edge.valueAt(end)).toShape()
wire = Part.Wire([new_edge] + wire.OrderedEdges[1:])
else: # Add a straight edge before the first vertex
new_edge = Part.LineSegment(first_edge.valueAt(start).sub(vec), first_edge.valueAt(start)).toShape()
wire = Part.Wire([new_edge] + wire.OrderedEdges)
if offset_end < 0: # Trim the wire from the last vertex
offset_end = -offset_end
out_edges = []
for edge in reversed(wire.OrderedEdges):
if offset_end >= edge.Length: # Remove entire edge
offset_end -= edge.Length
elif round(offset_end, precision()) > 0: # Split edge, to remove the required length
if edge.Orientation == "Forward":
new_edge = edge.split(edge.getParameterByLength(edge.Length - offset_end)).OrderedEdges[0]
else:
new_edge = edge.split(edge.getParameterByLength(offset_end)).OrderedEdges[1]
new_edge.Placement = edge.Placement # Strangely, edge.split discards the placement and orientation
new_edge.Orientation = edge.Orientation
out_edges.insert(0, new_edge)
offset_end = 0
else: # Keep the remaining entire edges
out_edges.insert(0, edge)
wire = Part.Wire(out_edges)
elif offset_end > 0: # Extend the last edge along its normal
last_edge = wire.OrderedEdges[-1]
if last_edge.Orientation == "Forward":
start, end = last_edge.FirstParameter, last_edge.LastParameter
vec = last_edge.tangentAt(end).multiply(offset_end)
else:
start, end = last_edge.LastParameter, last_edge.FirstParameter
vec = -last_edge.tangentAt(end).multiply(offset_end)
if geomType(last_edge) == "Line": # Replace last edge with the extended new edge
new_edge = Part.LineSegment(last_edge.valueAt(start), last_edge.valueAt(end).add(vec)).toShape()
wire = Part.Wire(wire.OrderedEdges[:-1] + [new_edge])
else: # Add a straight edge after the last vertex
new_edge = Part.LineSegment(last_edge.valueAt(end), last_edge.valueAt(end).add(vec)).toShape()
wire = Part.Wire(wire.OrderedEdges + [new_edge])
return wire
## @}

View File

@@ -0,0 +1,142 @@
# ***************************************************************************
# * Copyright (c) 2020 Antoine Lafr *
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with FreeCAD; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
"""Unit test for the DraftGeomUtils module."""
import unittest
import FreeCAD
import Part
import DraftGeomUtils
import drafttests.auxiliary as aux
from draftutils.messages import _msg
class TestDraftGeomUtils(unittest.TestCase):
"""Testing the functions in the file DraftGeomUtils.py"""
def setUp(self):
"""Prepare the test. Nothing to do here, DraftGeomUtils doesn't need a document."""
aux.draw_header()
def test_get_extended_wire(self):
"""Test the DraftGeomUtils.get_extended_wire function."""
operation = "DraftGeomUtils.get_extended_wire"
_msg(" Test '{}'".format(operation))
# Build wires made with straight edges and various combination of Orientation: the wires 1-4 are all equivalent
points = [FreeCAD.Vector(0.0, 0.0, 0.0),
FreeCAD.Vector(1500.0, 2000.0, 0.0),
FreeCAD.Vector(4500.0, 2000.0, 0.0),
FreeCAD.Vector(4500.0, 2000.0, 2500.0)]
edges = []
for start, end in zip(points[:-1], points[1:]):
edge = Part.makeLine(start, end)
edges.append(edge)
wire1 = Part.Wire(edges)
edges = []
for start, end in zip(points[:-1], points[1:]):
edge = Part.makeLine(end, start)
edge.Orientation = "Reversed"
edges.append(edge)
wire2 = Part.Wire(edges)
edges = []
for start, end in zip(points[:-1], points[1:]):
edge = Part.makeLine(start, end)
edge.Orientation = "Reversed"
edges.insert(0, edge)
wire3 = Part.Wire(edges)
wire3.Orientation = "Reversed"
edges = []
for start, end in zip(points[:-1], points[1:]):
edge = Part.makeLine(end, start)
edges.insert(0, edge)
wire4 = Part.Wire(edges)
wire4.Orientation = "Reversed"
# Build wires made with arcs and various combination of Orientation: the wires 5-8 are all equivalent
points = [FreeCAD.Vector(0.0, 0.0, 0.0),
FreeCAD.Vector(1000.0, 1000.0, 0.0),
FreeCAD.Vector(2000.0, 0.0, 0.0),
FreeCAD.Vector(3000.0, 0.0, 1000.0),
FreeCAD.Vector(4000.0, 0.0, 0.0)]
edges = []
for start, mid, end in zip(points[:-2], points[1:-1], points[2:]):
edge = Part.Arc(start, mid, end).toShape()
edges.append(edge)
wire5 = Part.Wire(edges)
edges = []
for start, mid, end in zip(points[:-2], points[1:-1], points[2:]):
edge = Part.Arc(end, mid, start).toShape()
edge.Orientation = "Reversed"
edges.append(edge)
wire6 = Part.Wire(edges)
edges = []
for start, mid, end in zip(points[:-2], points[1:-1], points[2:]):
edge = Part.Arc(start, mid, end).toShape()
edge.Orientation = "Reversed"
edges.insert(0, edge)
wire7 = Part.Wire(edges)
wire7.Orientation = "Reversed"
edges = []
for start, mid, end in zip(points[:-2], points[1:-1], points[2:]):
edge = Part.Arc(end, mid, start).toShape()
edges.insert(0, edge)
wire8 = Part.Wire(edges)
wire8.Orientation = "Reversed"
# Run "get_extended_wire" for all the wires with various offset_start, offset_end combinations
num_subtests = 0
offset_values = (2000.0, 0.0, -1000, -2000, -3000, -5500)
for i, wire in enumerate((wire1, wire2, wire3, wire4, wire5, wire6, wire7, wire8)):
_msg(" Running tests with wire{}".format(i + 1))
for offset_start in offset_values:
for offset_end in offset_values:
if offset_start + offset_end > -wire.Length:
subtest = "get_extended_wire(wire{0}, {1}, {2})".format(i + 1, offset_start, offset_end)
num_subtests += 1 # TODO: it should be "with self.subtest(subtest):" but then it doesn't report failures.
extended = DraftGeomUtils.get_extended_wire(wire, offset_start, offset_end)
# Test that the extended wire's length is correctly changed
self.assertAlmostEqual(extended.Length, wire.Length + offset_start + offset_end,
DraftGeomUtils.precision(), "'{0}.{1}' failed".format(operation, subtest))
if offset_start == 0.0:
# If offset_start is 0.0, check that the wire's start point is unchanged
self.assertAlmostEqual(extended.OrderedVertexes[0].Point.distanceToPoint(wire.OrderedVertexes[0].Point), 0.0,
DraftGeomUtils.precision(), "'{0}.{1}' failed".format(operation, subtest))
if offset_end == 0.0:
# If offset_end is 0.0, check that the wire's end point is unchanged
self.assertAlmostEqual(extended.OrderedVertexes[-1].Point.distanceToPoint(wire.OrderedVertexes[-1].Point), 0.0,
DraftGeomUtils.precision(), "'{0}.{1}' failed".format(operation, subtest))
_msg(" Test completed, {} subtests run".format(num_subtests))
def tearDown(self):
"""Finish the test. Nothing to do here, DraftGeomUtils doesn't need a document."""
pass
# suite = unittest.defaultTestLoader.loadTestsFromTestCase(TestDraftGeomUtils)
# unittest.TextTestRunner().run(suite)