From e5037dc41a7e062bfc87e56f61b0758c01338d0b Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Sun, 9 Jan 2022 20:52:48 +0100 Subject: [PATCH] #1960 test cases --- .../test/test_wall_opening.py | 174 ++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 src/ifcopenshell-python/test/test_wall_opening.py diff --git a/src/ifcopenshell-python/test/test_wall_opening.py b/src/ifcopenshell-python/test/test_wall_opening.py new file mode 100644 index 0000000000..2cd4540416 --- /dev/null +++ b/src/ifcopenshell-python/test/test_wall_opening.py @@ -0,0 +1,174 @@ +import os +import re +import glob +import json +import typing +import shutil +import subprocess + +from dataclasses import dataclass, field + +import pytest +import ifcopenshell +import ifcopenshell.template + +PERF = False + +@dataclass +class rect: + width : float + height : float + + def build(self, f): + return f.createIfcRectangleProfileDef("AREA", None, None, self.width, self.height) + +@dataclass +class circle: + radius : float + + def build(self, f): + return f.createIfcCircleProfileDef("AREA", None, None, self.radius) + +@dataclass +class opening: + x : float + z : float + shape : typing.Any + depth : float + direc : tuple = field(default_factory=lambda: (0.0, 0.0, -1.0)) + +O = 0., 0., 0. +X = 1., 0., 0. +Y = 0., 1., 0. +Z = 0., 0., 1. + +# Creates an IfcAxis2Placement3D from Location, Axis and RefDirection specified as Python tuples +def create_ifcaxis2placement(f, point=O, dir1=Z, dir2=X): + point = f.createIfcCartesianPoint(point) + dir1 = f.createIfcDirection(dir1) + dir2 = f.createIfcDirection(dir2) + axis2placement = f.createIfcAxis2Placement3D(point, dir1, dir2) + return axis2placement + +# Creates an IfcLocalPlacement from Location, Axis and RefDirection, specified as Python tuples, and relative placement +def create_ifclocalplacement(f, point=O, dir1=Z, dir2=X, relative_to=None): + axis2placement = create_ifcaxis2placement(f,point,dir1,dir2) + ifclocalplacement2 = f.createIfcLocalPlacement(relative_to,axis2placement) + return ifclocalplacement2 + +# Creates an IfcPolyLine from a list of points, specified as Python tuples +def create_ifcpolyline(f, point_list): + ifcpts = [] + for point in point_list: + point = f.createIfcCartesianPoint(point) + ifcpts.append(point) + polyline = f.createIfcPolyLine(ifcpts) + return polyline + +# Creates an IfcExtrudedAreaSolid from a list of points, specified as Python tuples +def create_ifcextrudedareasolid(f, point_list, ifcaxis2placement, extrude_dir, extrusion): + polyline = create_ifcpolyline(f, point_list) + ifcclosedprofile = f.createIfcArbitraryClosedProfileDef("AREA", None, polyline) + ifcdir = f.createIfcDirection(extrude_dir) + ifcextrudedareasolid = f.createIfcExtrudedAreaSolid(ifcclosedprofile, ifcaxis2placement, ifcdir, extrusion) + return ifcextrudedareasolid + +def create_case(fn, openings): + + f = ifcopenshell.template.create() + + owner_history = f.by_type("IfcOwnerHistory")[0] + project = f.by_type("IfcProject")[0] + context = f.by_type("IfcGeometricRepresentationContext")[0] + + wall_placement = create_ifclocalplacement(f, relative_to=None) + + extrusion_placement = create_ifcaxis2placement(f, (0.0, 0.0, 0.0), (0.0, 0.0, 1.0), (1.0, 0.0, 0.0)) + point_list_extrusion_area = [(0.0, -0.2, 0.0), (15.0, -0.2, 0.0), (15.0, 0.0, 0.0), (0.0, 0.0, 0.0), (0.0, -0.2, 0.0)] + solid = create_ifcextrudedareasolid(f, point_list_extrusion_area, extrusion_placement, (0.0, 0.0, 1.0), 4.0) + body_representation = f.createIfcShapeRepresentation(context, "Body", "SweptSolid", [solid]) + + product_shape = f.createIfcProductDefinitionShape(None, None, [body_representation]) + + wall = f.createIfcWallStandardCase(ifcopenshell.guid.new(), owner_history, "Wall", None, None, wall_placement, product_shape, None) + + for opening in openings: + + opening_placement = create_ifclocalplacement(f, (opening.x, 0.0, opening.z), (0.0, 1.0, 0.0), (1.0, 0.0, 0.0), wall_placement) + opening_solid = f.createIfcExtrudedAreaSolid(opening.shape.build(f), None, f.createIfcDirection(opening.direc), opening.depth) + opening_representation = f.createIfcShapeRepresentation(context, "Body", "SweptSolid", [opening_solid]) + opening_shape = f.createIfcProductDefinitionShape(None, None, [opening_representation]) + opening_element = f.createIfcOpeningElement(ifcopenshell.guid.new(), owner_history, "Opening", None, None, opening_placement, opening_shape, None) + f.createIfcRelVoidsElement(ifcopenshell.guid.new(), owner_history, None, None, wall, opening_element) + + f.write(fn) + +class TestWallOpenings: + def test_all(self): + + cases = [ + ("wall-openings-non-intersecting-rect-circle.ifc", [opening(i * 4.0 + 2.0,2.0,rect(1.0,1.0),0.2) for i in range(3)] + [opening(i * 4.0 + 4.0,2.0,circle(0.5),0.2) for i in range(3)]), + ("wall-openings-intersecting-inner-bounds.ifc", [opening(i * 0.8 + 2.0, i * 0.1 + 1.0,rect(1.0,1.0),0.2) for i in range(15)]), + ("wall-openings-intersecting-with-outer.ifc", [opening(i * 2.0, 4.0,rect(1.0,1.0),0.2) for i in range(15)]), + ("wall-openings-recesses.ifc", [opening(i * 4.0 + 2.0,2.0,rect(1.0,1.0),0.1) for i in range(3)] + [opening(i * 4.0 + 4.0,2.0,circle(0.5),0.1) for i in range(3)]), + ("wall-openings-non-orthogonal.ifc", [opening(i * 4.0 + 2.0,2.0,rect(1.0,1.0),0.3,direc=(1.0, 0.0, -1.0)) for i in range(6)]), + ("wall-openings-contained-in-other.ifc", [opening(2.0,2.0,rect(1.0,1.0),0.2), opening(2.0,2.0,rect(0.5, 0.5),0.2)]), + ("wall-openings-outside-of-outer.ifc", [opening(2.0,2.0,rect(1.0,1.0),0.2), opening(2.0,6.0,rect(1.0,1.0),0.2)]), + ("wall-openings-touching-outer.ifc", [opening(2.0,3.0,rect(2.0,2.0),0.2)]) + ] + + checks = [ + [(1, "Processed fully in 2D")], + [(1, "Intersecting boundaries")], + [(1, "Intersecting boundaries")], + [(1, "No second operands can be processed as 2D inner bounds"),(0, "Operand B creates a through hole")], + [(0, "Operand B 1/6 is an extrusion")], + [(1, "Subtraction operand contained in other"), (0, "Subtraction operand outside of outer bound")], + [], #[(1, "Subtraction operand outside of outer bound")], + [(1, "Intersecting boundaries")] + ] + + + for fn in glob.glob("*.log.json"): + os.unlink(fn) + + pat = re.compile(r'^([\w :]+?)\s*: (\d+\.\d+)') + + result = [] + + for ci, ((fn, ops), cs) in enumerate(zip(cases, checks), start=1): + create_case(fn, ops) + + result.append([fn]) + for i in range(2 if PERF else 1): + + args = [shutil.which("IfcConvert") or "IfcConvert", "-qyvvv", fn, fn+".dae", "--log-format", "json", "--log-file", fn+".log.json"] + if i: + args.append("--no-2d-boolean") + + ts = [] + for j in range(10 if PERF else 1): + subprocess.call(args, stdout=subprocess.PIPE) + + log = [json.loads(ln)['message'] for ln in open(fn+".log.json") if ln] + perf = dict(x.groups() for x in [re.match(pat, l) for l in log] if x) + + ts.append(float(perf["file geometry conversion"])) + + result[-1].append(sum(ts) / len(ts)) + + if i == 0 and j == 0: + for ln, st in cs: + assert len([l for l in log if l.startswith(st)]) == ln + + # breakpoint() + + try: + import tabulate + except: + return + + print(tabulate.tabulate(result, headers=["file", "", "--no-2d-boolean"], tablefmt="github")) + +if __name__ == "__main__": + TestWallOpenings().test_all() \ No newline at end of file