#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (c) 2007 Jürgen Riegel # LGPL import os import sys import io import FreeCAD, unittest, Mesh from FreeCAD import Base import time, tempfile, math # http://python-kurs.eu/threads.php try: import _thread as thread except Exception: import thread from os.path import join #--------------------------------------------------------------------------- # define the functions to test the FreeCAD mesh module #--------------------------------------------------------------------------- class MeshTopoTestCases(unittest.TestCase): def setUp(self): # set up a planar face with 18 triangles self.planarMesh = [] for x in range(3): for y in range(3): self.planarMesh.append( [0.0 + x, 0.0 + y,0.0000] ) self.planarMesh.append( [1.0 + x, 1.0 + y,0.0000] ) self.planarMesh.append( [0.0 + x, 1.0 + y,0.0000] ) self.planarMesh.append( [0.0 + x, 0.0 + y,0.0000] ) self.planarMesh.append( [1.0 + x, 0.0 + y,0.0000] ) self.planarMesh.append( [1.0 + x, 1.0 + y,0.0000] ) def testCollapseFacetsSingle(self): for i in range(18): planarMeshObject = Mesh.Mesh(self.planarMesh) planarMeshObject.collapseFacets([i]) def testCollapseFacetsMultible(self): planarMeshObject = Mesh.Mesh(self.planarMesh) planarMeshObject.collapseFacets(range(7)) def testCollapseFacetsAll(self): planarMeshObject = Mesh.Mesh(self.planarMesh) planarMeshObject.collapseFacets(range(18)) def testCorruptedFacet(self): v = FreeCAD.Vector mesh = Mesh.Mesh() mesh.addFacet( v(1.0e1, -1.0e1, 1.0e1), v(1.0e1, +1.0e1, 1.0e1), v(0.0e0, 0.0e0, 1.0e1)) mesh.addFacet( v(-1.0e1, -1.0e1, 1.0e1), v(-1.0e1, +1.0e1, 1.0e1), v(0e0, 0.0e0, 1.0e1)) mesh.addFacet( v(+1.0e1, +1.0e1, 1.0e1), v(-1.0e1, +1.0e1, 1.0e1), v(.0e0, 0.0e0, 1.0e1)) mesh.addFacet( v(+1.0e1, -1.0e1, 1.0e1), v(-1.0e1, -1.0e1, 1.0e1), v(.0e0, 0.0e0, 1.0e1)) mesh.addFacet( v(-1.0e1, +1.0e1, 1.0e1), v(+1.0e1, +1.0e1, 1.0e1), v(+1.0e1, +1.0e1, 1.0e1)) mesh.addFacet( v(+1.0e1, +1.0e1, 1.0e1), v(+1.0e1, 00.0e1, 1.0e1), v(+1.0e1, -1.0e1, 1.0e1)) self.assertEqual(mesh.CountFacets, 6) mesh.fixIndices() self.assertEqual(mesh.CountFacets, 5) class MeshSplitTestCases(unittest.TestCase): def setUp(self): self.mesh = Mesh.createBox(1.0, 1.0, 1.0) def testSplitFacetOnOneEdge(self): p1 = self.mesh.Points[0].Vector p2 = self.mesh.Points[1].Vector p3 = self.mesh.Points[2].Vector self.mesh.splitFacet(0, p1, (p2 + p3) / 2) self.assertFalse(self.mesh.hasNonManifolds()) self.assertFalse(self.mesh.hasInvalidNeighbourhood()) self.assertFalse(self.mesh.hasPointsOutOfRange()) self.assertFalse(self.mesh.hasFacetsOutOfRange()) self.assertFalse(self.mesh.hasCorruptedFacets()) self.assertTrue(self.mesh.isSolid()) def testSplitFacetOnTwoEdges_21(self): p1 = self.mesh.Points[0].Vector p2 = self.mesh.Points[1].Vector p3 = self.mesh.Points[2].Vector self.mesh.splitFacet(0, (p1 + p3) / 2, (p2 + p3) / 2) self.assertFalse(self.mesh.hasNonManifolds()) self.assertFalse(self.mesh.hasInvalidNeighbourhood()) self.assertFalse(self.mesh.hasPointsOutOfRange()) self.assertFalse(self.mesh.hasFacetsOutOfRange()) self.assertFalse(self.mesh.hasCorruptedFacets()) self.assertTrue(self.mesh.isSolid()) def testSplitFacetOnTwoEdges_12(self): p1 = self.mesh.Points[0].Vector p2 = self.mesh.Points[1].Vector p3 = self.mesh.Points[2].Vector self.mesh.splitFacet(0, (p2 + p3) / 2, (p1 + p3) / 2) self.assertFalse(self.mesh.hasNonManifolds()) self.assertFalse(self.mesh.hasInvalidNeighbourhood()) self.assertFalse(self.mesh.hasPointsOutOfRange()) self.assertFalse(self.mesh.hasFacetsOutOfRange()) self.assertFalse(self.mesh.hasCorruptedFacets()) self.assertTrue(self.mesh.isSolid()) def testSplitFacetOnTwoEdges_01(self): p1 = self.mesh.Points[0].Vector p2 = self.mesh.Points[1].Vector p3 = self.mesh.Points[2].Vector self.mesh.splitFacet(0, (p1 + p2) / 2, (p2 + p3) / 2) self.assertFalse(self.mesh.hasNonManifolds()) self.assertFalse(self.mesh.hasInvalidNeighbourhood()) self.assertFalse(self.mesh.hasPointsOutOfRange()) self.assertFalse(self.mesh.hasFacetsOutOfRange()) self.assertFalse(self.mesh.hasCorruptedFacets()) self.assertTrue(self.mesh.isSolid()) def testSplitFacetOnTwoEdges_10(self): p1 = self.mesh.Points[0].Vector p2 = self.mesh.Points[1].Vector p3 = self.mesh.Points[2].Vector self.mesh.splitFacet(0, (p2 + p3) / 2, (p1 + p2) / 2) self.assertFalse(self.mesh.hasNonManifolds()) self.assertFalse(self.mesh.hasInvalidNeighbourhood()) self.assertFalse(self.mesh.hasPointsOutOfRange()) self.assertFalse(self.mesh.hasFacetsOutOfRange()) self.assertFalse(self.mesh.hasCorruptedFacets()) self.assertTrue(self.mesh.isSolid()) def testSplitFacetOnTwoEdges_02(self): p1 = self.mesh.Points[0].Vector p2 = self.mesh.Points[1].Vector p3 = self.mesh.Points[2].Vector self.mesh.splitFacet(0, (p1 + p2) / 2, (p1 + p3) / 2) self.assertFalse(self.mesh.hasNonManifolds()) self.assertFalse(self.mesh.hasInvalidNeighbourhood()) self.assertFalse(self.mesh.hasPointsOutOfRange()) self.assertFalse(self.mesh.hasFacetsOutOfRange()) self.assertFalse(self.mesh.hasCorruptedFacets()) self.assertTrue(self.mesh.isSolid()) def testSplitFacetOnTwoEdges_20(self): p1 = self.mesh.Points[0].Vector p2 = self.mesh.Points[1].Vector p3 = self.mesh.Points[2].Vector self.mesh.splitFacet(0, (p1 + p3) / 2, (p1 + p2) / 2) self.assertFalse(self.mesh.hasNonManifolds()) self.assertFalse(self.mesh.hasInvalidNeighbourhood()) self.assertFalse(self.mesh.hasPointsOutOfRange()) self.assertFalse(self.mesh.hasFacetsOutOfRange()) self.assertFalse(self.mesh.hasCorruptedFacets()) self.assertTrue(self.mesh.isSolid()) def testSplitFacetOnTwoEdges_5teps(self): Vec3d = FreeCAD.Vector for i in range(5): f = self.mesh.Facets[0] p1 = Vec3d(f.Points[0]) p2 = Vec3d(f.Points[1]) p3 = Vec3d(f.Points[2]) self.mesh.splitFacet(0, (p1 + p3) / 2, (p2 + p3) / 2) self.assertFalse(self.mesh.hasNonManifolds()) self.assertFalse(self.mesh.hasInvalidNeighbourhood()) self.assertFalse(self.mesh.hasPointsOutOfRange()) self.assertFalse(self.mesh.hasFacetsOutOfRange()) self.assertFalse(self.mesh.hasCorruptedFacets()) self.assertTrue(self.mesh.isSolid()) class MeshGeoTestCases(unittest.TestCase): def setUp(self): # set up a planar face with 2 triangles self.planarMesh = [] def testIntersection(self): self.planarMesh.append( [0.9961,1.5413,4.3943] ) self.planarMesh.append( [9.4796,10.024,-3.0937] ) self.planarMesh.append( [1.4308,11.3841,2.6829] ) self.planarMesh.append( [2.6493,2.2536,3.0679] ) self.planarMesh.append( [13.1126,0.4857,-4.4417] ) self.planarMesh.append( [10.2410,8.9040,-3.5002] ) planarMeshObject = Mesh.Mesh(self.planarMesh) f1 = planarMeshObject.Facets[0] f2 = planarMeshObject.Facets[1] res=f1.intersect(f2) self.assertTrue(len(res) == 0) def testIntersection2(self): self.planarMesh.append( [-16.097176,-29.891157,15.987688] ) self.planarMesh.append( [-16.176304,-29.859991,15.947966] ) self.planarMesh.append( [-16.071451,-29.900553,15.912505] ) self.planarMesh.append( [-16.092241,-29.893408,16.020439] ) self.planarMesh.append( [-16.007210,-29.926180,15.967641] ) self.planarMesh.append( [-16.064457,-29.904951,16.090832] ) planarMeshObject = Mesh.Mesh(self.planarMesh) f1 = planarMeshObject.Facets[0] f2 = planarMeshObject.Facets[1] # does definitely NOT intersect res=f1.intersect(f2) self.assertTrue(len(res) == 0) def testIntersectionCoplanar(self): self.planarMesh.append( [0.,10.,10.] ) self.planarMesh.append( [10.,0.,10.] ) self.planarMesh.append( [10.,10.,10.] ) self.planarMesh.append( [6.,8.,10.] ) self.planarMesh.append( [16.,8.,10.] ) self.planarMesh.append( [6.,18.,10.] ) planarMeshObject = Mesh.Mesh(self.planarMesh) f1 = planarMeshObject.Facets[0] f2 = planarMeshObject.Facets[1] res = f1.intersect(f2) self.assertTrue(len(res) == 2) def testIntersectionOverlap(self): self.planarMesh.append( [0.,0.,0.] ) self.planarMesh.append( [5.,0.,0.] ) self.planarMesh.append( [8.,5.,0.] ) self.planarMesh.append( [4.,0.,0.] ) self.planarMesh.append( [10.,0.,0.] ) self.planarMesh.append( [9.,5.,0.] ) planarMeshObject = Mesh.Mesh(self.planarMesh) f1 = planarMeshObject.Facets[0] f2 = planarMeshObject.Facets[1] res = f1.intersect(f2) self.assertTrue(len(res) == 2) def testIntersectionOfIntersectingEdges(self): self.planarMesh.append( [0.,10.,10.] ) self.planarMesh.append( [10.,0.,10.] ) self.planarMesh.append( [10.,10.,10.] ) self.planarMesh.append( [6.,8.,10.] ) self.planarMesh.append( [16.,8.,10.] ) self.planarMesh.append( [6.,18.,10.] ) planarMeshObject = Mesh.Mesh(self.planarMesh) edge1 = planarMeshObject.Facets[0].getEdge(2) edge2 = planarMeshObject.Facets[1].getEdge(2) res = edge1.intersectWithEdge(edge2) self.assertTrue(len(res) == 1) self.assertEqual(res[0][0], 6.0) self.assertEqual(res[0][1], 10.0) self.assertEqual(res[0][2], 10.0) def testIntersectionOfParallelEdges(self): self.planarMesh.append( [0.,10.,10.] ) self.planarMesh.append( [10.,0.,10.] ) self.planarMesh.append( [10.,10.,10.] ) self.planarMesh.append( [6.,8.,10.] ) self.planarMesh.append( [16.,8.,10.] ) self.planarMesh.append( [6.,18.,10.] ) planarMeshObject = Mesh.Mesh(self.planarMesh) edge1 = planarMeshObject.Facets[0].getEdge(2) edge2 = planarMeshObject.Facets[1].getEdge(0) res = edge1.intersectWithEdge(edge2) self.assertTrue(len(res) == 0) def testIntersectionOfCollinearEdges(self): self.planarMesh.append( [0.,0.,0.] ) self.planarMesh.append( [6.,0.,0.] ) self.planarMesh.append( [3.,4.,0.] ) self.planarMesh.append( [7.,0.,0.] ) self.planarMesh.append( [13.,0.,0.] ) self.planarMesh.append( [10.,4.,0.] ) planarMeshObject = Mesh.Mesh(self.planarMesh) edge1 = planarMeshObject.Facets[0].getEdge(0) edge2 = planarMeshObject.Facets[1].getEdge(0) res = edge1.intersectWithEdge(edge2) self.assertTrue(len(res) == 0) def testIntersectionOfWarpedEdges(self): self.planarMesh.append( [0.,0.,0.] ) self.planarMesh.append( [6.,0.,0.] ) self.planarMesh.append( [3.,4.,0.] ) self.planarMesh.append( [2.,2.,1.] ) self.planarMesh.append( [8.,2.,1.] ) self.planarMesh.append( [5.,6.,1.] ) planarMeshObject = Mesh.Mesh(self.planarMesh) edge1 = planarMeshObject.Facets[0].getEdge(1) edge2 = planarMeshObject.Facets[1].getEdge(0) res = edge1.intersectWithEdge(edge2) self.assertTrue(len(res) == 0) def testSelfIntersection(self): s = b"""solid Simple facet normal 0.0e0 0.0e0 1.0e1 outer loop vertex 0.0e1 0.0e1 1.0e1 vertex 0.0e1 +1.0e1 1.0e1 vertex +1.0e1 0.0e1 1.0e1 endloop endfacet facet normal 0.0e0 0.0e0 1.0e1 outer loop vertex 0.0e1 +1.0e1 1.0e1 vertex +1.0e1 0.0e1 1.0e1 vertex 1.0e1 1.0e1 1.0e1 endloop endfacet facet normal 0.0e0 0.0e0 1.0e1 outer loop vertex 0.0e1 0.0e1 1.0e1 vertex 0.0e1 +1.0e1 1.0e1 vertex -1.0e1 1.0e1 1.0e1 endloop endfacet facet normal 0.0e0 0.0e0 1.0e1 outer loop vertex 0.0e1 0.0e1 1.0e1 vertex +1.0e1 0.0e1 1.0e1 vertex +1.0e1 -1.0e1 1.0e1 endloop endfacet facet normal 0.0e0 0.0e0 1.0e1 outer loop vertex 0.6e1 0.8e1 1.0e1 vertex +1.6e1 0.8e1 1.0e1 vertex +0.6e1 1.8e1 1.0e1 endloop endfacet endsolid Simple""" mesh = Mesh.Mesh() data = io.BytesIO(s) mesh.read(Stream=data, Format="AST") self.assertTrue(mesh.hasSelfIntersections()) class PivyTestCases(unittest.TestCase): def setUp(self): # set up a planar face with 2 triangles self.planarMesh = [] FreeCAD.newDocument("MeshTest") def testRayPick(self): if not FreeCAD.GuiUp: return self.planarMesh.append( [-16.097176,-29.891157,15.987688] ) self.planarMesh.append( [-16.176304,-29.859991,15.947966] ) self.planarMesh.append( [-16.071451,-29.900553,15.912505] ) self.planarMesh.append( [-16.092241,-29.893408,16.020439] ) self.planarMesh.append( [-16.007210,-29.926180,15.967641] ) self.planarMesh.append( [-16.064457,-29.904951,16.090832] ) planarMeshObject = Mesh.Mesh(self.planarMesh) from pivy import coin; import FreeCADGui Mesh.show(planarMeshObject) view=FreeCADGui.ActiveDocument.ActiveView.getViewer() rp=coin.SoRayPickAction(view.getSoRenderManager().getViewportRegion()) rp.setRay(coin.SbVec3f(-16.05,16.0,16.0),coin.SbVec3f(0,-1,0)) rp.apply(view.getSoRenderManager().getSceneGraph()) pp=rp.getPickedPoint() self.assertTrue(pp != None) det=pp.getDetail() self.assertTrue(det.getTypeId() == coin.SoFaceDetail.getClassTypeId()) det=coin.cast(det,str(det.getTypeId().getName())) self.assertTrue(det.getFaceIndex() == 1) def testPrimitiveCount(self): if not FreeCAD.GuiUp: return self.planarMesh.append( [-16.097176,-29.891157,15.987688] ) self.planarMesh.append( [-16.176304,-29.859991,15.947966] ) self.planarMesh.append( [-16.071451,-29.900553,15.912505] ) self.planarMesh.append( [-16.092241,-29.893408,16.020439] ) self.planarMesh.append( [-16.007210,-29.926180,15.967641] ) self.planarMesh.append( [-16.064457,-29.904951,16.090832] ) planarMeshObject = Mesh.Mesh(self.planarMesh) from pivy import coin; import FreeCADGui Mesh.show(planarMeshObject) view=FreeCADGui.ActiveDocument.ActiveView view.setAxisCross(False) pc=coin.SoGetPrimitiveCountAction() pc.apply(view.getSceneGraph()) self.assertTrue(pc.getTriangleCount() == 2) #self.assertTrue(pc.getPointCount() == 6) def tearDown(self): #closing doc FreeCAD.closeDocument("MeshTest") # Threads def loadFile(name): #lock.acquire() mesh=Mesh.Mesh() #FreeCAD.Console.PrintMessage("Create mesh instance\n") #lock.release() mesh.read(name) #FreeCAD.Console.PrintMessage("Mesh loaded successfully.\n") def createMesh(r,s): #FreeCAD.Console.PrintMessage("Create sphere (%s,%s)...\n"%(r,s)) mesh=Mesh.createSphere(r,s) #FreeCAD.Console.PrintMessage("... destroy sphere\n") class LoadMeshInThreadsCases(unittest.TestCase): def setUp(self): pass def testSphereMesh(self): for i in range(6,8): thread.start_new(createMesh,(10.0,(i+1)*20)) time.sleep(10) def testLoadMesh(self): mesh=Mesh.createSphere(10.0,100) # a fine sphere name=tempfile.gettempdir() + os.sep + "mesh.stl" mesh.write(name) #FreeCAD.Console.PrintMessage("Write mesh to %s\n"%(name)) #lock=thread.allocate_lock() for i in range(2): thread.start_new(loadFile,(name,)) time.sleep(1) def tearDown(self): pass class PolynomialFitCases(unittest.TestCase): def setUp(self): pass def testFitGood(self): # symmetric v=[] v.append(FreeCAD.Vector(0,0,0.0)) v.append(FreeCAD.Vector(1,0,0.5)) v.append(FreeCAD.Vector(2,0,0.0)) v.append(FreeCAD.Vector(0,1,0.5)) v.append(FreeCAD.Vector(1,1,1.0)) v.append(FreeCAD.Vector(2,1,0.5)) v.append(FreeCAD.Vector(0,2,0.0)) v.append(FreeCAD.Vector(1,2,0.5)) v.append(FreeCAD.Vector(2,2,0.0)) d = Mesh.polynomialFit(v) c = d["Coefficients"] #print ("Polynomial: f(x,y)=%f*x^2%+f*y^2%+f*x*y%+f*x%+f*y%+f" % (c[0],c[1],c[2],c[3],c[4],c[5])) for i in d["Residuals"]: self.assertTrue(math.fabs(i) < 0.0001, "Too high residual %f" % math.fabs(i)) def testFitExact(self): # symmetric v=[] v.append(FreeCAD.Vector(0,0,0.0)) v.append(FreeCAD.Vector(1,0,0.0)) v.append(FreeCAD.Vector(2,0,0.0)) v.append(FreeCAD.Vector(0,1,0.0)) v.append(FreeCAD.Vector(1,1,1.0)) v.append(FreeCAD.Vector(2,1,0.0)) d = Mesh.polynomialFit(v) c = d["Coefficients"] #print ("Polynomial: f(x,y)=%f*x^2%+f*y^2%+f*x*y%+f*x%+f*y%+f" % (c[0],c[1],c[2],c[3],c[4],c[5])) for i in d["Residuals"]: self.assertTrue(math.fabs(i) < 0.0001, "Too high residual %f" % math.fabs(i)) def testFitBad(self): # symmetric v=[] v.append(FreeCAD.Vector(0,0,0.0)) v.append(FreeCAD.Vector(1,0,0.0)) v.append(FreeCAD.Vector(2,0,0.0)) v.append(FreeCAD.Vector(0,1,0.0)) v.append(FreeCAD.Vector(1,1,1.0)) v.append(FreeCAD.Vector(2,1,0.0)) v.append(FreeCAD.Vector(0,2,0.0)) v.append(FreeCAD.Vector(1,2,0.0)) v.append(FreeCAD.Vector(2,2,0.0)) d = Mesh.polynomialFit(v) c = d["Coefficients"] #print ("Polynomial: f(x,y)=%f*x^2%+f*y^2%+f*x*y%+f*x%+f*y%+f" % (c[0],c[1],c[2],c[3],c[4],c[5])) for i in d["Residuals"]: self.assertFalse(math.fabs(i) < 0.0001, "Residual %f must be higher" % math.fabs(i)) def tearDown(self): pass class NastranReader(unittest.TestCase): def setUp(self): self.test_dir = join(FreeCAD.getHomePath(), "Mod", "Mesh", "App", "TestData") def testEightCharGRIDElement(self): m = Mesh.read(f"{self.test_dir}/NASTRAN_Test_GRID_CQUAD4.bdf") self.assertEqual(m.CountPoints,10) self.assertEqual(m.CountFacets,8) # Quads split into two triangles def testDelimitedGRIDElement(self): m = Mesh.read(f"{self.test_dir}/NASTRAN_Test_Delimited_GRID_CQUAD4.bdf") self.assertEqual(m.CountPoints,10) self.assertEqual(m.CountFacets,8) # Quads split into two triangles def testSixteenCharGRIDElement(self): m = Mesh.read(f"{self.test_dir}/NASTRAN_Test_GRIDSTAR_CQUAD4.bdf") self.assertEqual(m.CountPoints,4) self.assertEqual(m.CountFacets,2) # Quads split into two triangles def testCTRIA3Element(self): m = Mesh.read(f"{self.test_dir}/NASTRAN_Test_GRID_CTRIA3.bdf") self.assertEqual(m.CountPoints,3) self.assertEqual(m.CountFacets,1) def tearDown(self): pass class MeshSubElement(unittest.TestCase): def setUp(self): self.mesh = Mesh.createBox(1.0, 1.0, 1.0) def testCenterOfGravity(self): c = self.mesh.CenterOfGravity self.assertEqual(c, Base.Vector(0.0, 0.0, 0.0)) def testSubElements(self): types = self.mesh.getElementTypes() self.assertIn("Mesh", types) self.assertIn("Segment", types) def testCountSubElements(self): self.assertEqual(self.mesh.countSubElements("Mesh"), 1) self.assertEqual(self.mesh.countSubElements("Segment"), 0) def testFacesFromSubElement(self): element = self.mesh.getFacesFromSubElement("Mesh", 0) self.assertIsInstance(element, tuple) self.assertEqual(len(element), 2) self.assertEqual(len(element[0]), 8) self.assertEqual(len(element[1]), 12) def testSegmentSubElement(self): self.mesh.addSegment([0, 2, 4, 6, 8]) self.assertEqual(self.mesh.countSegments(), 1) self.assertEqual(self.mesh.countSubElements("Segment"), 1) element = self.mesh.getFacesFromSubElement("Segment", 0) self.assertIsInstance(element, tuple) self.assertEqual(len(element), 2) self.assertEqual(len(element[0]), 7) self.assertEqual(len(element[1]), 5) segment = self.mesh.meshFromSegment(self.mesh.getSegment(0)) self.assertEqual(segment.CountPoints, 7) self.assertEqual(segment.CountFacets, 5) def tearDown(self): pass