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])) 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 @@ + + + +
+ + + +{{time}}
++ {{#status}}Pass{{/status}}{{^status}}Fail{{/status}} + Passed: {{total_successes}} / {{total}} ({{percentage}}%) +
+
+ {{#failed_entities}}
+ {{reason}} {{element}}
+ {{/failed_entities}}
+