Merge branch 'alafr-arch-structure' of https://github.com/alafr/FreeCAD
This commit is contained in:
@@ -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
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
## @}
|
||||
## @}
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
## @}
|
||||
142
src/Mod/Draft/drafttests/test_draftgeomutils.py
Normal file
142
src/Mod/Draft/drafttests/test_draftgeomutils.py
Normal 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)
|
||||
Reference in New Issue
Block a user