Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 42 additions & 8 deletions public/locales/de/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@
"name": "Name",
"description": "Beschreibung",
"warning": "Warnung",
"specNotApplyToVersion": "Spezifikation gilt nicht für diese IFC-Version",
"failureReason": "Fehlergrund",
"globalId": "Globale ID",
"tag": "Kennzeichnung",
"reportBy": "Bericht von",
Expand Down Expand Up @@ -156,32 +158,64 @@
"elementsPassedPrefix": "Elemente erfüllt",
"applicability": "Anwendbarkeit",
"all": "Alle",
"data": "Daten"
"data": "Daten",
"allEntityData": "Alle {{entity}} Daten"
},
"phrases": {
"moreOfSameType": "... {{count}} weitere vom gleichen Elementtyp ({{type}} mit Kennzeichnung {{tag}} und Globaler ID {{id}}) nicht angezeigt ...",
"moreElementsNotShown": "... {{count}} weitere {{type}} Elemente nicht angezeigt von insgesamt {{total}} ..."
},
"ids": {
"section": {
"loadBearing": "Tragende Struktur"
"sections": {
"projectDesignation": "Projektbezeichnung",
"projectPerimeter": "Projektperimeter",
"buildingDesignation": "Gebäudebezeichnung",
"storeyDesignation": "Geschossbezeichnung"
},
"description": {
"shouldHaveLoadBearing": "Alle Structure Elements sollte haben eine tragende Struktur"
"entities": {
"IfcProject": "Projekt",
"IfcSite": "Grundstück",
"IfcBuilding": "Gebäude",
"IfcBuildingStorey": "Geschoss"
},
"pattern": {
"nameShallBe": "Der Name muss {{value}} sein",
"fields": {
"name": "Name",
"description": "Beschreibung",
"phase": "Phase"
},
"propertySets": {
"Pset_PropertyAgreement": "Eigenschaftsvereinbarung",
"Cust_Site": "Standort-Eigenschaften"
},
"patterns": {
"nameShallBe": "{{field}} muss {{value}} sein",
"descriptionRequired": "Die Beschreibung muss angegeben werden",
"propertyRequired": "{{property}} muss im Eigenschaftssatz {{propertySet}} angegeben werden",
"propertyValue": "{{property}} muss {{value}} im Eigenschaftssatz {{propertySet}} sein",
"enumRequired": "Eine der Aufzählungswerte {{values}} muss im Eigenschaftssatz {{propertySet}} angegeben werden"
"dataRequired": "{{property}} Daten müssen {{value}} sein und im Eigenschaftssatz {{propertySet}}",
"enumRequired": "{{field}} muss einer der folgenden Werte sein: {{values}}",
"propertyInSet": "Eigenschaft {{property}} im Satz {{propertySet}}",
"propertyWithValue": "Eigenschaft {{property}} im Satz {{propertySet}} mit Wert {{value}}"
},
"enumerations": {
"51_0": "51.0",
"52_0": "52.0",
"53_0": "53.0",
"untergeschoss": "Untergeschoss",
"erdgeschoss": "Erdgeschoss",
"obergeschoss": "Obergeschoss",
"zwischengeschoss": "Zwischengeschoss",
"dach": "Dach",
"u01": "U01",
"da": "DA"
}
}
},
"console": {
"loading": {
"pyodide": "Pyodide wird geladen...",
"pyodideSuccess": "Pyodide erfolgreich geladen",
"idsFile": "IDS-Datei wird gelesen...",
"translations": "Übersetzungen werden geladen...",
"packages": "Erforderliche Pakete werden installiert...",
"micropipPatch": "Micropip wird für Kompatibilitätsprüfungen angepasst...",
Expand Down
3 changes: 3 additions & 0 deletions public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@
"name": "Name",
"description": "Description",
"warning": "Warning",
"specNotApplyToVersion": "specification does not apply to this IFC version",
"failureReason": "Failure Reason",
"globalId": "GlobalId",
"tag": "Tag",
"reportBy": "Report by",
Expand Down Expand Up @@ -190,6 +192,7 @@
"loading": {
"pyodide": "Loading Pyodide...",
"pyodideSuccess": "Pyodide loaded successfully",
"idsFile": "Reading IDS file content...",
"translations": "Loading translations...",
"packages": "Installing required packages...",
"micropipPatch": "Patching micropip for compatibility check bypass...",
Expand Down
5 changes: 5 additions & 0 deletions public/locales/fr/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@
"all": "Tous",
"data": "Données"
},
"specificationsPassedPrefix": "Spécifications réussies",
"requirementsPassedPrefix": "Exigences respectées",
"applicability": "Applicabilité",
"specNotApplyToVersion": "la spécification ne s'applique pas à cette version IFC",
"failureReason": "Raison de l'échec",
"phrases": {
"moreOfSameType": "... {{count}} autres du même type d'élément ({{type}} avec l'étiquette {{tag}} et l'ID global {{id}}) non affichés ...",
"moreElementsNotShown": "... {{count}} autres éléments {{type}} non affichés sur un total de {{total}} ..."
Expand Down
5 changes: 5 additions & 0 deletions public/locales/it/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@
"all": "Tutti",
"data": "Dati"
},
"specificationsPassedPrefix": "Specifiche superate",
"requirementsPassedPrefix": "Requisiti soddisfatti",
"applicability": "Applicabilità",
"specNotApplyToVersion": "la specifica non si applica a questa versione IFC",
"failureReason": "Motivo del fallimento",
"phrases": {
"moreOfSameType": "... {{count}} altri dello stesso tipo di elemento ({{type}} con Tag {{tag}} e ID Globale {{id}}) non mostrati ...",
"moreElementsNotShown": "... {{count}} altri elementi {{type}} non mostrati su un totale di {{total}} ..."
Expand Down
5 changes: 5 additions & 0 deletions public/locales/rm/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@
"all": "Tut",
"data": "Datas"
},
"specificationsPassedPrefix": "Specificaziuns reussidas",
"requirementsPassedPrefix": "Pretensiuns satisfatgas",
"applicability": "Applicabilitad",
"specNotApplyToVersion": "la specificaziun na s'applitgescha betg a questa versiun IFC",
"failureReason": "Motiv dal falliment",
"phrases": {
"moreOfSameType": "... {{count}} dapli dal medem tip d'element ({{type}} cun Tag {{tag}} ed ID global {{id}}) betg mussads ...",
"moreElementsNotShown": "... {{count}} ulteriurs elements {{type}} betg mussads ord in total da {{total}} ..."
Expand Down
254 changes: 254 additions & 0 deletions public/pyodideWorkerClean.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
/* global importScripts */
importScripts('https://cdn.jsdelivr.net/pyodide/v0.23.4/full/pyodide.js')

let pyodide = null

// Simple console message function
function getConsoleMessage(key, defaultMessage) {
return defaultMessage
}

// Load Pyodide once
async function initializePyodide() {
if (pyodide !== null) {
return pyodide
}

try {
self.postMessage({
type: 'progress',
message: getConsoleMessage('console.loading.pyodide', 'Loading Pyodide...'),
})

pyodide = await self.loadPyodide({
indexURL: 'https://cdn.jsdelivr.net/pyodide/v0.23.4/full/',
})

self.postMessage({
type: 'progress',
message: getConsoleMessage('console.loading.pyodideSuccess', 'Pyodide loaded successfully'),
})

return pyodide
} catch (error) {
self.postMessage({
type: 'error',
message: getConsoleMessage('console.error.pyodideLoad', `Failed to load Pyodide: ${error.message}`),
})
throw error
}
}

const WORKER_VERSION = '3.0.0-clean'
console.log('pyodideWorkerClean version:', WORKER_VERSION)

self.onmessage = async function (e) {
const { arrayBuffer, idsContent, fileName, language, generateBcf, idsFilename } = e.data

try {
// Load Pyodide
await initializePyodide()

// Install required packages
self.postMessage({
type: 'progress',
message: getConsoleMessage('console.loading.packages', 'Installing required packages...'),
})

await pyodide.loadPackage(['micropip', 'python-dateutil', 'six', 'numpy', 'setuptools'])

// Bypass the Emscripten version compatibility check for wheels
self.postMessage({
type: 'progress',
message: getConsoleMessage('console.loading.micropipPatch', 'Patching micropip for compatibility...'),
})

await pyodide.runPythonAsync(`
import micropip
from micropip._micropip import WheelInfo
WheelInfo.check_compatible = lambda self: None
`)

// Install IfcOpenShell and IfcTester
self.postMessage({
type: 'progress',
message: getConsoleMessage('console.loading.ifcOpenShell', 'Installing IfcOpenShell...'),
})

await pyodide.runPythonAsync(`
import micropip
await micropip.install('https://cdn.jsdelivr.net/gh/IfcOpenShell/wasm-wheels@33b437e5fd5425e606f34aff602c42034ff5e6dc/ifcopenshell-0.8.1+latest-cp312-cp312-emscripten_3_1_58_wasm32.whl')
`)

self.postMessage({
type: 'progress',
message: getConsoleMessage('console.loading.dependencies', 'Installing additional dependencies...'),
})

await pyodide.runPythonAsync(`
import micropip
await micropip.install(['lark', 'ifctester==0.8.1', 'bcf-client==0.8.1', 'pystache'], keep_going=True)
`)

// Write files to Pyodide filesystem
self.postMessage({
type: 'progress',
message: getConsoleMessage('console.loading.inputFiles', 'Processing input files...'),
})

pyodide.FS.writeFile('/input.ifc', new Uint8Array(arrayBuffer))
if (idsContent) {
pyodide.FS.writeFile('/input.ids', idsContent)
}

// Run validation using native IfcTester
self.postMessage({
type: 'progress',
message: getConsoleMessage('console.loading.validation', 'Running IFC validation...'),
})

const validationResult = await pyodide.runPythonAsync(`
import json
import os
import ifcopenshell
from ifctester import ids, reporter
from datetime import datetime

# Open IFC file
print("Opening IFC file: /input.ifc")
ifc = ifcopenshell.open('/input.ifc')

# Initialize basic results structure
results = {
"title": "IDS Validation Report",
"filename": "${fileName}",
"date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"language_code": "${language}",
"status": True,
"validation_status": "success"
}

# Check if IDS file exists and validate
if os.path.exists('/input.ids'):
print("Loading IDS specification: /input.ids")
ids_spec = ids.open('/input.ids')

if ids_spec and ids_spec.specifications:
print("Validating IFC against IDS...")
ids_spec.validate(ifc)

# Use IfcTester's native JSON reporter and transform to our frontend structure
try:
json_reporter = reporter.Json(ids_spec)
native_results = json_reporter.report()

# Transform IfcTester's structure to match our frontend expectations
results.update(native_results)
results["filename"] = "${fileName}"
results["language_code"] = "${language}"
results["validation_status"] = "success" if results.get("status", False) else "failed"

# Transform specifications to add missing fields our frontend expects
for spec in results.get("specifications", []):
# Add fields that our frontend expects but IfcTester doesn't provide
if "total_applicable" not in spec:
spec["total_applicable"] = 0
if "total_applicable_pass" not in spec:
spec["total_applicable_pass"] = 0
if "total_applicable_fail" not in spec:
spec["total_applicable_fail"] = 0

# Transform requirements to add missing fields
for req in spec.get("requirements", []):
# Map IfcTester's structure to our frontend structure
if "total_checks" not in req:
req["total_checks"] = req.get("total_applicable", 0)
if "cardinality" not in req:
req["cardinality"] = "required"
if "has_omitted_passes" not in req:
req["has_omitted_passes"] = False
if "has_omitted_failures" not in req:
req["has_omitted_failures"] = False

# Transform entity data to match our frontend expectations
for entity_list in ["passed_entities", "failed_entities"]:
if entity_list in req:
transformed_entities = []
for entity in req[entity_list]:
transformed_entity = {
"class": entity.get("class", "Unknown"),
"predefined_type": entity.get("predefined_type") or "None",
"name": entity.get("name") or "None",
"description": entity.get("description") or "None",
"global_id": entity.get("global_id") or "None",
"tag": entity.get("tag") or "None",
"reason": entity.get("reason", "")
}
transformed_entities.append(transformed_entity)
req[entity_list] = transformed_entities

print("Successfully transformed IfcTester JSON results to frontend format")

except Exception as e:
print(f"IfcTester JSON reporter error: {e}")
results["specifications"] = []
results["status"] = False
results["validation_status"] = "failed"
else:
print("No IDS file provided")
results["specifications"] = []

# Return JSON string - let frontend handle all translations and enhancements
json.dumps(results, default=str, ensure_ascii=False)
`)

// Parse the JSON result
const results = JSON.parse(validationResult)

// Check for errors
if (results.error) {
throw new Error(results.error)
}

// Add metadata for frontend
results.ui_language = language
results.available_languages = ['en', 'de', 'fr', 'it', 'rm']
results.filename = fileName
results.validation_status = results.status ? 'success' : 'failed'

// Attach idsFilename for tab title building (if provided)
if (idsFilename) {
results.ids_filename = idsFilename
}

// Send results back to frontend
self.postMessage({
type: 'complete',
results: results,
message: getConsoleMessage('console.success.processingComplete', 'Your files have been processed.'),
})

} catch (error) {
console.error('Worker error:', error)

// Check for out of memory error
const errorStr = error.toString().toLowerCase()
if (errorStr.includes('out of memory') || errorStr.includes('internalerror: out of memory')) {
self.postMessage({
type: 'error',
errorType: 'out_of_memory',
message: getConsoleMessage(
'console.error.outOfMemory',
'Pyodide ran out of memory. The page will reload automatically to free resources.',
),
stack: error.stack,
})
} else {
self.postMessage({
type: 'error',
message: getConsoleMessage('console.error.generic', `An error occurred: ${error.message}`),
stack: error.stack,
})
}
}
}
Loading