mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-13 19:07:57 +00:00
You can now audit and view basic results using the IfcTester webapp
This commit is contained in:
@@ -42,6 +42,7 @@ def cast_to_value(from_value, to_value):
|
||||
|
||||
class Facet:
|
||||
def __init__(self, *parameters):
|
||||
self.status = None
|
||||
self.failed_entities = []
|
||||
self.failed_reasons = []
|
||||
for i, name in enumerate(self.parameters):
|
||||
|
||||
@@ -217,7 +217,8 @@ class Specification:
|
||||
self.applicable_entities.append(element)
|
||||
for facet in self.requirements:
|
||||
result = facet(element)
|
||||
if not bool(result):
|
||||
facet.status = bool(result)
|
||||
if not facet.status:
|
||||
self.failed_entities.add(element)
|
||||
facet.failed_entities.append(element)
|
||||
facet.failed_reasons.append(str(result))
|
||||
@@ -227,5 +228,7 @@ class Specification:
|
||||
self.status = False
|
||||
elif self.minOccurs != 0 and not self.applicable_entities:
|
||||
self.status = False
|
||||
for facet in self.requirements:
|
||||
facet.status = False
|
||||
elif len(self.applicable_entities) > (self.maxOccurs or 1):
|
||||
self.status = False
|
||||
|
||||
@@ -28,10 +28,10 @@ class Reporter:
|
||||
def report(self, ids):
|
||||
pass
|
||||
|
||||
def to_string():
|
||||
def to_string(self):
|
||||
return ""
|
||||
|
||||
def write(filepath):
|
||||
def write(self, filepath):
|
||||
pass
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ class Json(Reporter):
|
||||
requirements.append(
|
||||
{
|
||||
"description": requirement.to_string("requirement"),
|
||||
"success": not requirement.failed_entities,
|
||||
"status": requirement.status,
|
||||
"failed_entities": [
|
||||
{"reason": requirement.failed_reasons[i], "element": str(e)}
|
||||
for i, e in enumerate(requirement.failed_entities[0:10])
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
# IfcTester - IDS based model auditing
|
||||
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcTester.
|
||||
#
|
||||
# IfcTester is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcTester 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcTester. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import os
|
||||
import time
|
||||
import ifctester
|
||||
import ifctester.reporter
|
||||
import ifcopenshell
|
||||
from flask import Flask, request, send_from_directory
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
class Ifc:
|
||||
ifc = None
|
||||
filepath = None
|
||||
|
||||
@classmethod
|
||||
def get(cls, filepath=None):
|
||||
if filepath is None or filepath == cls.filepath:
|
||||
return cls.ifc
|
||||
cls.filepath = filepath
|
||||
cls.ifc = ifcopenshell.open(filepath)
|
||||
return cls.ifc
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
with open("www/index.html") as template:
|
||||
return template.read()
|
||||
|
||||
|
||||
@app.route("/<path:asset>.<string:ext>")
|
||||
def get_asset(asset, ext):
|
||||
if ext in ("js", "css"):
|
||||
return send_from_directory("www", asset + "." + ext)
|
||||
|
||||
|
||||
@app.route("/audit", methods=["POST"])
|
||||
def audit():
|
||||
filename = ifcopenshell.guid.new()
|
||||
ids_filepath = os.path.join("uploads", filename + ".ids")
|
||||
ifc_filepath = os.path.join("uploads", filename + ".ifc")
|
||||
request.files.get("ids").save(ids_filepath)
|
||||
request.files.get("ifc").save(ifc_filepath)
|
||||
|
||||
start = time.time()
|
||||
specs = ifctester.open(ids_filepath)
|
||||
ifc = Ifc.get(ifc_filepath)
|
||||
print("Finished loading:", time.time() - start)
|
||||
start = time.time()
|
||||
specs.validate(ifc)
|
||||
print("Finished validating:", time.time() - start)
|
||||
start = time.time()
|
||||
|
||||
os.remove(ids_filepath)
|
||||
os.remove(ifc_filepath)
|
||||
|
||||
engine = ifctester.reporter.Json(specs)
|
||||
engine.report()
|
||||
return engine.to_string()
|
||||
@@ -10,6 +10,7 @@ class IDSContainer extends HTMLElement {
|
||||
this.filename = 'specifications.ids';
|
||||
this.ids = null;
|
||||
this.containerId = crypto.randomUUID();
|
||||
this.isEditing = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,6 +316,13 @@ class IDSFacets extends HTMLElement {
|
||||
}
|
||||
feather.replace();
|
||||
}
|
||||
|
||||
showResults(requirements) {
|
||||
var facetElements = this.getElementsByTagName('ids-facet');
|
||||
for (var i=0; i<facetElements.length; i++) {
|
||||
facetElements[i].showResults(requirements[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IDSFacetInstructions extends HTMLElement {
|
||||
@@ -401,6 +409,21 @@ class IDSFacet extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
showResults(requirement) {
|
||||
var idsResultElements = this.parentElement.getElementsByTagName('ids-result');
|
||||
for (var i=0; i<idsResultElements.length; i++) {
|
||||
if (! idsResultElements[i].classList.contains('hidden')) {
|
||||
idsResultElements[i].classList.add('hidden');
|
||||
}
|
||||
if (requirement.status == true && idsResultElements[i].attributes['name'].value == 'pass') {
|
||||
idsResultElements[i].classList.remove('hidden');
|
||||
} else if (requirement.status == false && idsResultElements[i].attributes['name'].value == 'fail') {
|
||||
idsResultElements[i].classList.remove('hidden');
|
||||
idsResultElements[i].getElementsByTagName('span')[0].textContent = requirement.failed_entities.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
renderTemplate(templates, parameters) {
|
||||
for (var i=0; i<templates.length; i++) {
|
||||
var hasKeys = true;
|
||||
@@ -747,6 +770,13 @@ class IDSSpecs extends HTMLElement {
|
||||
}
|
||||
feather.replace();
|
||||
}
|
||||
|
||||
showResults(specifications) {
|
||||
var specElements = this.getElementsByTagName('ids-spec');
|
||||
for (var i=0; i<specElements.length; i++) {
|
||||
specElements[i].showResults(specifications[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IDSSpec extends HTMLElement {
|
||||
@@ -786,6 +816,15 @@ class IDSSpec extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
showResults(specification) {
|
||||
var facetsElements = this.getElementsByTagName('ids-facets');
|
||||
for (var i=0; i<facetsElements.length; i++) {
|
||||
if (facetsElements[i].attributes['name'].value == "requirements") {
|
||||
facetsElements[i].showResults(specification.requirements);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dragover(e) {
|
||||
// The HTML draggable API is terrible.
|
||||
e.preventDefault();
|
||||
@@ -809,7 +848,7 @@ class IDSLoader extends HTMLElement {
|
||||
inputElement.accept = '.ids,.xml';
|
||||
inputElement.multiple = false;
|
||||
inputElement.addEventListener("change", this.loadFile)
|
||||
inputElement.dispatchEvent(new MouseEvent("click"));
|
||||
inputElement.dispatchEvent(new MouseEvent("click"));
|
||||
}
|
||||
|
||||
loadFile(e) {
|
||||
@@ -845,9 +884,8 @@ class IDSSave extends HTMLElement {
|
||||
}
|
||||
|
||||
click() {
|
||||
var xmlString = new XMLSerializer().serializeToString(this.closest('ids-container').ids);
|
||||
console.log(xmlString);
|
||||
var container = this.closest('ids-container')
|
||||
var xmlString = new XMLSerializer().serializeToString(container.ids);
|
||||
this.download(container.filename, xmlString);
|
||||
}
|
||||
|
||||
@@ -862,9 +900,53 @@ class IDSSave extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class IDSAudit extends HTMLElement {
|
||||
connectedCallback() {
|
||||
this.addEventListener('click', this.launchFileBrowser);
|
||||
}
|
||||
|
||||
launchFileBrowser(accept, callback) {
|
||||
var inputElement = document.createElement("input");
|
||||
inputElement.idsAudit = this;
|
||||
inputElement.type = "file";
|
||||
inputElement.accept = '.ifc';
|
||||
inputElement.multiple = false;
|
||||
inputElement.addEventListener("change", this.loadFile)
|
||||
inputElement.dispatchEvent(new MouseEvent("click"));
|
||||
}
|
||||
|
||||
loadFile(e) {
|
||||
var self = this.idsAudit;
|
||||
var container = self.closest('ids-container');
|
||||
var request = new XMLHttpRequest();
|
||||
request.onreadystatechange = function() { self.processResponse(request); };
|
||||
request.open("POST", "audit");
|
||||
var data = new FormData();
|
||||
data.append('ifc', this.files[0]);
|
||||
data.append('ids', new Blob([new XMLSerializer().serializeToString(container.ids)], {type:'text/plain'}));
|
||||
request.send(data);
|
||||
}
|
||||
|
||||
processResponse(request) {
|
||||
if (request.readyState != 4) {
|
||||
return;
|
||||
}
|
||||
var container = this.closest('ids-container');
|
||||
container.isEditing = false;
|
||||
var results = JSON.parse(request.responseText);
|
||||
var specsElements = container.getElementsByTagName('ids-specs');
|
||||
for (var i=0; i<specsElements.length; i++) {
|
||||
var specs = specsElements[i];
|
||||
specs.showResults(results.specifications);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('ids-container', IDSContainer);
|
||||
window.customElements.define('ids-loader', IDSLoader);
|
||||
window.customElements.define('ids-save', IDSSave);
|
||||
window.customElements.define('ids-audit', IDSAudit);
|
||||
window.customElements.define('ids-info', IDSInfo);
|
||||
window.customElements.define('ids-info-element', IDSInfoElement);
|
||||
window.customElements.define('ids-specs', IDSSpecs);
|
||||
|
||||
@@ -47,9 +47,11 @@
|
||||
<i data-feather="download"></i> SAVE
|
||||
</a>
|
||||
</ids-save>
|
||||
<a href="#">
|
||||
<i data-feather="play"></i> AUDIT MODEL
|
||||
</a>
|
||||
<ids-audit>
|
||||
<a href="#">
|
||||
<i data-feather="play"></i> AUDIT MODEL
|
||||
</a>
|
||||
</ids-audit>
|
||||
<span>
|
||||
<a href="#">
|
||||
<i data-feather="x"></i> CLOSE
|
||||
@@ -139,6 +141,13 @@
|
||||
<i data-feather="x"></i>
|
||||
</ids-facet-remove>
|
||||
</span>
|
||||
<ids-result name="fail" class="result-icon hidden">
|
||||
<i data-feather="x"></i>
|
||||
<span>32</span>
|
||||
</ids-result>
|
||||
<ids-result name="pass" class="result-icon hidden">
|
||||
<i data-feather="check"></i>
|
||||
</ids-result>
|
||||
<ids-facet type="requirement"></ids-facet>
|
||||
<ids-facet-instructions class="requirement-instructions" title="Instructions">
|
||||
Optionally write instructions about how to achieve this requirement.
|
||||
@@ -163,7 +172,7 @@
|
||||
</ids-spec-move>
|
||||
</span>
|
||||
</div>
|
||||
<ids-spec>
|
||||
</ids-spec>
|
||||
</template>
|
||||
</ids-specs>
|
||||
</div>
|
||||
|
||||
@@ -270,20 +270,30 @@ ids-spec ids-spec-handle .snippet {
|
||||
bottom: -5px;
|
||||
position: relative;
|
||||
}
|
||||
.result-icon {
|
||||
ids-result {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
margin-left: -30px;
|
||||
margin-top: -2px;
|
||||
margin-top: -3px;
|
||||
cursor: help;
|
||||
}
|
||||
.result-icon.pass {
|
||||
ids-result span {
|
||||
position: absolute;
|
||||
float: right;
|
||||
right: 30px;
|
||||
background-color: #333;
|
||||
color: white;
|
||||
font-family: 'Nova Mono';
|
||||
padding: 3px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
ids-result[name="pass"] {
|
||||
color: var(--green);
|
||||
}
|
||||
.result-icon.fail {
|
||||
ids-result[name="fail"] {
|
||||
color: var(--red);
|
||||
}
|
||||
.result-icon:hover {
|
||||
ids-result:hover {
|
||||
color: var(--blue);
|
||||
}
|
||||
.hidden {
|
||||
|
||||
Reference in New Issue
Block a user