From 81fdfbacd04a8bed6e9d359614d8353aba726040 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 8 Aug 2022 14:34:06 +1000 Subject: [PATCH 1/2] OffsetObjectPlacements patch now allows 3D rotations, such as to fix wrong Z axis coming from software like Solidworks --- .../recipes/OffsetObjectPlacements.py | 41 +++++++++++++++---- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/src/ifcpatch/ifcpatch/recipes/OffsetObjectPlacements.py b/src/ifcpatch/ifcpatch/recipes/OffsetObjectPlacements.py index 0f8afd40d6..bb6c422aea 100644 --- a/src/ifcpatch/ifcpatch/recipes/OffsetObjectPlacements.py +++ b/src/ifcpatch/ifcpatch/recipes/OffsetObjectPlacements.py @@ -39,8 +39,15 @@ class Patcher: absolute_placements.append(absolute_placement) absolute_placements = set(absolute_placements) - angle = float(self.args[3]) - transformation = self.z_rotation_matrix(math.radians(angle)) if angle else np.eye(4) + transformation = self.identity_matrix() + if len(self.args) == 4: + angle = self.args[3] + if angle: + transformation = self.z_rotation_matrix(math.radians(angle), transformation) + elif len(self.args) == 6: + for arg in (("x", self.args[3]), ("y", self.args[4]), ("z", self.args[5])): + if arg[1]: + transformation = getattr(self, f"{arg[0]}_rotation_matrix")(math.radians(arg[1]), transformation) transformation[0][3] += float(self.args[0]) transformation[1][3] += float(self.args[1]) transformation[2][3] += float(self.args[2]) @@ -55,13 +62,29 @@ class Patcher: return self.get_absolute_placement(object_placement.PlacementRelTo) return object_placement - def z_rotation_matrix(self, angle): - return [ - [math.cos(angle), -math.sin(angle), 0., 0.], - [math.sin(angle), math.cos(angle), 0., 0.], - [0., 0., 1., 0.], - [0., 0., 0., 1.], - ] + def identity_matrix(self): + return np.eye(4) + + def x_rotation_matrix(self, angle, transformation): + transformation[1][1] = math.cos(angle) + transformation[1][2] = -math.sin(angle) + transformation[2][1] = math.sin(angle) + transformation[2][2] = math.cos(angle) + return transformation + + def y_rotation_matrix(self, angle, transformation): + transformation[0][0] = math.cos(angle) + transformation[0][2] = math.sin(angle) + transformation[2][0] = -math.sin(angle) + transformation[2][2] = math.cos(angle) + return transformation + + def z_rotation_matrix(self, angle, transformation): + transformation[0][0] = math.cos(angle) + transformation[0][1] = -math.sin(angle) + transformation[1][0] = math.sin(angle) + transformation[1][1] = math.cos(angle) + return transformation def get_relative_placement(self, m): x = np.array((m[0][0], m[1][0], m[2][0])) From e4016313c49e85389ed6e5f2042fda0e5a72c9c1 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 9 Aug 2022 17:04:12 +1000 Subject: [PATCH 2/2] IfcTester now supports HTML reports --- src/ifctester/ifctester/__main__.py | 2 + src/ifctester/ifctester/reporter.py | 34 +++++++- src/ifctester/ifctester/templates/report.html | 84 +++++++++++++++++++ 3 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 src/ifctester/ifctester/templates/report.html diff --git a/src/ifctester/ifctester/__main__.py b/src/ifctester/ifctester/__main__.py index 5f741bef53..ce81a3e1e7 100644 --- a/src/ifctester/ifctester/__main__.py +++ b/src/ifctester/ifctester/__main__.py @@ -51,6 +51,8 @@ if args.reporter == "Console": engine = reporter.Console(specs, use_colour=not args.no_color) elif args.reporter == "Json": engine = reporter.Json(specs) +elif args.reporter == "Html": + engine = reporter.Html(specs) engine.report() diff --git a/src/ifctester/ifctester/reporter.py b/src/ifctester/ifctester/reporter.py index 6a98ecc0c0..8a341b6f28 100644 --- a/src/ifctester/ifctester/reporter.py +++ b/src/ifctester/ifctester/reporter.py @@ -18,8 +18,11 @@ import os import sys +import math import logging +import datetime +cwd = os.path.dirname(os.path.realpath(__file__)) class Reporter: def __init__(self, ids): @@ -150,11 +153,14 @@ class Json(Reporter): } ) total = len(specification.applicable_entities) + total_successes = total - len(specification.failed_entities) + percentage = math.floor((total_successes / total) * 100) if total else "N/A" return { "name": specification.name, "status": specification.status, - "total_successes": total - len(specification.failed_entities), + "total_successes": total_successes, "total": total, + "percentage": percentage, "required": specification.minOccurs != 0, "requirements": requirements, } @@ -171,6 +177,32 @@ class Json(Reporter): return json.dump(self.results, outfile) +class Html(Json): + def __init__(self, ids): + super().__init__(ids) + self.results = {} + + def report(self): + self.results["title"] = self.ids.info.get("title", "Untitled IDS") + self.results["time"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + self.results["specifications"] = [] + for specification in self.ids.specifications: + self.results["specifications"].append(self.report_specification(specification)) + return self.results + + def to_string(self): + import pystache + + with open(os.path.join(cwd, "templates", "report.html"), "r") as file: + return pystache.render(file.read(), self.results) + + def to_file(self, filepath): + import pystache + + with open(os.path.join(cwd, "templates", "report.html"), "r") as file: + with open(filepath, "w") as outfile: + return outfile.write(pystache.render(file.read(), self.results)) + class BcfHandler(logging.StreamHandler): """Logging handler for creation of BCF report files. diff --git a/src/ifctester/ifctester/templates/report.html b/src/ifctester/ifctester/templates/report.html new file mode 100644 index 0000000000..9e0dd0e64b --- /dev/null +++ b/src/ifctester/ifctester/templates/report.html @@ -0,0 +1,84 @@ + + + + + + + + {{name}} + + + + +
+

{{title}}

+

{{time}}

+
+ {{#specifications}} +
+

{{name}}

+

+ {{#status}}Pass{{/status}}{{^status}}Fail{{/status}} + Passed: {{total_successes}} / {{total}} ({{percentage}}%) +

+
    + {{#requirements}} +
  1. + {{description}} + {{^status}}{{#total}} +

    + {{#failed_entities}} + {{reason}} {{element}}
    + {{/failed_entities}} +

    + {{/total}}{{/status}} +
  2. + {{/requirements}} +
+
+ {{/specifications}} +
+ + +