mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-23 13:16:25 +00:00
black .
This commit is contained in:
@@ -1453,7 +1453,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# --- tolerance check ---
|
# --- tolerance check ---
|
||||||
tolerance = 1e-5 #to provide a little wiggle room
|
tolerance = 1e-5 # to provide a little wiggle room
|
||||||
if layer2_bases and (max(layer2_bases) - min(layer2_bases)) > tolerance:
|
if layer2_bases and (max(layer2_bases) - min(layer2_bases)) > tolerance:
|
||||||
min_base = min(layer2_bases)
|
min_base = min(layer2_bases)
|
||||||
max_base = max(layer2_bases)
|
max_base = max(layer2_bases)
|
||||||
|
|||||||
@@ -55,26 +55,26 @@ class IfcTesterWebSocketServer:
|
|||||||
self.site = None
|
self.site = None
|
||||||
self.loop = None
|
self.loop = None
|
||||||
self.shutdown_event = None
|
self.shutdown_event = None
|
||||||
|
|
||||||
# Register namespace
|
# Register namespace
|
||||||
self.sio.register_namespace(IfcTesterNamespace("/ifctester"))
|
self.sio.register_namespace(IfcTesterNamespace("/ifctester"))
|
||||||
|
|
||||||
# Add health check route
|
# Add health check route
|
||||||
self.app.router.add_get("/health", self.health_check)
|
self.app.router.add_get("/health", self.health_check)
|
||||||
|
|
||||||
async def health_check(self, request):
|
async def health_check(self, request):
|
||||||
return web.Response(text="OK", content_type="text/plain")
|
return web.Response(text="OK", content_type="text/plain")
|
||||||
|
|
||||||
async def start_server(self):
|
async def start_server(self):
|
||||||
self.loop = asyncio.get_event_loop()
|
self.loop = asyncio.get_event_loop()
|
||||||
self.shutdown_event = asyncio.Event()
|
self.shutdown_event = asyncio.Event()
|
||||||
|
|
||||||
self.runner = web.AppRunner(self.app)
|
self.runner = web.AppRunner(self.app)
|
||||||
await self.runner.setup()
|
await self.runner.setup()
|
||||||
self.site = web.TCPSite(self.runner, "127.0.0.1", self.port)
|
self.site = web.TCPSite(self.runner, "127.0.0.1", self.port)
|
||||||
await self.site.start()
|
await self.site.start()
|
||||||
print(f"IfcTester WebSocket server started on 127.0.0.1:{self.port}")
|
print(f"IfcTester WebSocket server started on 127.0.0.1:{self.port}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Wait for shutdown signal
|
# Wait for shutdown signal
|
||||||
await self.shutdown_event.wait()
|
await self.shutdown_event.wait()
|
||||||
@@ -82,35 +82,35 @@ class IfcTesterWebSocketServer:
|
|||||||
print("WebSocket server received cancellation")
|
print("WebSocket server received cancellation")
|
||||||
finally:
|
finally:
|
||||||
await self._cleanup()
|
await self._cleanup()
|
||||||
|
|
||||||
async def _cleanup(self):
|
async def _cleanup(self):
|
||||||
try:
|
try:
|
||||||
# Disconnect all clients
|
# Disconnect all clients
|
||||||
print("Shutting down SocketIO...")
|
print("Shutting down SocketIO...")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.sio.shutdown()
|
await self.sio.shutdown()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error shutting down socketio: {e}")
|
print(f"Error shutting down socketio: {e}")
|
||||||
|
|
||||||
# Stop the web server
|
# Stop the web server
|
||||||
if self.site:
|
if self.site:
|
||||||
print("Stopping web server...")
|
print("Stopping web server...")
|
||||||
await asyncio.wait_for(self.site.stop(), timeout=2.0)
|
await asyncio.wait_for(self.site.stop(), timeout=2.0)
|
||||||
self.site = None
|
self.site = None
|
||||||
|
|
||||||
# Clean up the runner
|
# Clean up the runner
|
||||||
if self.runner:
|
if self.runner:
|
||||||
print("Cleaning up runner...")
|
print("Cleaning up runner...")
|
||||||
await asyncio.wait_for(self.runner.cleanup(), timeout=2.0)
|
await asyncio.wait_for(self.runner.cleanup(), timeout=2.0)
|
||||||
self.runner = None
|
self.runner = None
|
||||||
|
|
||||||
print("IfcTester WebSocket server stopped")
|
print("IfcTester WebSocket server stopped")
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
print("Websocket server cleanup timed out, forcing shutdown")
|
print("Websocket server cleanup timed out, forcing shutdown")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error during websocket cleanup: {e}")
|
print(f"Error during websocket cleanup: {e}")
|
||||||
|
|
||||||
def stop_server(self):
|
def stop_server(self):
|
||||||
if self.loop and self.shutdown_event and not self.shutdown_event.is_set():
|
if self.loop and self.shutdown_event and not self.shutdown_event.is_set():
|
||||||
try:
|
try:
|
||||||
@@ -135,18 +135,13 @@ class IfcTesterNamespace(socketio.AsyncNamespace):
|
|||||||
try:
|
try:
|
||||||
request_id = data.get("id")
|
request_id = data.get("id")
|
||||||
ids_string = data.get("ids")
|
ids_string = data.get("ids")
|
||||||
|
|
||||||
if not request_id:
|
if not request_id:
|
||||||
await self.emit("error", {
|
await self.emit("error", {"error": "No request ID provided"}, room=sid)
|
||||||
"error": "No request ID provided"
|
|
||||||
}, room=sid)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if not ids_string:
|
if not ids_string:
|
||||||
await self.emit("error", {
|
await self.emit("error", {"id": request_id, "error": "No IDS XML string provided"}, room=sid)
|
||||||
"id": request_id,
|
|
||||||
"error": "No IDS XML string provided"
|
|
||||||
}, room=sid)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
print(f"Processing IDS audit request {request_id}")
|
print(f"Processing IDS audit request {request_id}")
|
||||||
@@ -154,30 +149,23 @@ class IfcTesterNamespace(socketio.AsyncNamespace):
|
|||||||
# Check if IFC is loaded in Bonsai
|
# Check if IFC is loaded in Bonsai
|
||||||
ifc = tool.Ifc.get()
|
ifc = tool.Ifc.get()
|
||||||
if not ifc:
|
if not ifc:
|
||||||
await self.emit("error", {
|
await self.emit(
|
||||||
"id": request_id,
|
"error", {"id": request_id, "error": "No IFC model is currently loaded in Bonsai"}, room=sid
|
||||||
"error": "No IFC model is currently loaded in Bonsai"
|
)
|
||||||
}, room=sid)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Parse IDS from string
|
# Parse IDS from string
|
||||||
try:
|
try:
|
||||||
ids = ifctester.ids.from_string(ids_string)
|
ids = ifctester.ids.from_string(ids_string)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await self.emit("error", {
|
await self.emit("error", {"id": request_id, "error": f"Failed to parse IDS XML: {str(e)}"}, room=sid)
|
||||||
"id": request_id,
|
|
||||||
"error": f"Failed to parse IDS XML: {str(e)}"
|
|
||||||
}, room=sid)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Validate IFC against IDS
|
# Validate IFC against IDS
|
||||||
try:
|
try:
|
||||||
ids.validate(ifc)
|
ids.validate(ifc)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await self.emit("error", {
|
await self.emit("error", {"id": request_id, "error": f"Validation failed: {str(e)}"}, room=sid)
|
||||||
"id": request_id,
|
|
||||||
"error": f"Validation failed: {str(e)}"
|
|
||||||
}, room=sid)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Generate reports
|
# Generate reports
|
||||||
@@ -193,28 +181,21 @@ class IfcTesterNamespace(socketio.AsyncNamespace):
|
|||||||
html_report = html_reporter.to_string()
|
html_report = html_reporter.to_string()
|
||||||
|
|
||||||
# Send results back
|
# Send results back
|
||||||
await self.emit("audit_result", {
|
await self.emit(
|
||||||
"id": request_id,
|
"audit_result", {"id": request_id, "json_report": json_report, "html_report": html_report}, room=sid
|
||||||
"json_report": json_report,
|
)
|
||||||
"html_report": html_report
|
|
||||||
}, room=sid)
|
|
||||||
|
|
||||||
print(f"Successfully processed IDS audit request {request_id}")
|
print(f"Successfully processed IDS audit request {request_id}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await self.emit("error", {
|
await self.emit("error", {"id": request_id, "error": f"Failed to generate reports: {str(e)}"}, room=sid)
|
||||||
"id": request_id,
|
|
||||||
"error": f"Failed to generate reports: {str(e)}"
|
|
||||||
}, room=sid)
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error processing audit request: {str(e)}")
|
print(f"Error processing audit request: {str(e)}")
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
print(traceback.format_exc())
|
print(traceback.format_exc())
|
||||||
await self.emit("error", {
|
await self.emit("error", {"id": request_id, "error": f"Internal server error: {str(e)}"}, room=sid)
|
||||||
"id": request_id,
|
|
||||||
"error": f"Internal server error: {str(e)}"
|
|
||||||
}, room=sid)
|
|
||||||
|
|
||||||
async def on_ping(self, sid, data):
|
async def on_ping(self, sid, data):
|
||||||
await self.emit("pong", {"timestamp": data.get("timestamp")}, room=sid)
|
await self.emit("pong", {"timestamp": data.get("timestamp")}, room=sid)
|
||||||
@@ -318,9 +299,9 @@ class StartIfcTesterWebapp(bpy.types.Operator):
|
|||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
global webapp_process, websocket_server_thread, websocket_app
|
global webapp_process, websocket_server_thread, websocket_app
|
||||||
|
|
||||||
props = tool.Tester.get_tester_props()
|
props = tool.Tester.get_tester_props()
|
||||||
|
|
||||||
if webapp_process is not None or websocket_server_thread is not None:
|
if webapp_process is not None or websocket_server_thread is not None:
|
||||||
self.report({"WARNING"}, "IfcTester webapp is already running")
|
self.report({"WARNING"}, "IfcTester webapp is already running")
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
@@ -328,19 +309,21 @@ class StartIfcTesterWebapp(bpy.types.Operator):
|
|||||||
try:
|
try:
|
||||||
import ifctester.webapp.serve
|
import ifctester.webapp.serve
|
||||||
except ImportError:
|
except ImportError:
|
||||||
self.report({"ERROR"}, "IfcTester webapp not available. Please ensure the latest version of ifctester is installed.")
|
self.report(
|
||||||
|
{"ERROR"}, "IfcTester webapp not available. Please ensure the latest version of ifctester is installed."
|
||||||
|
)
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
webapp_port = self.find_free_port()
|
webapp_port = self.find_free_port()
|
||||||
websocket_port = self.find_free_port()
|
websocket_port = self.find_free_port()
|
||||||
|
|
||||||
# Get the path to the serve.py module
|
# Get the path to the serve.py module
|
||||||
webapp_serve_path = ifctester.webapp.serve.__file__
|
webapp_serve_path = ifctester.webapp.serve.__file__
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Start the websocket server in a thread
|
# Start the websocket server in a thread
|
||||||
websocket_app = IfcTesterWebSocketServer(websocket_port)
|
websocket_app = IfcTesterWebSocketServer(websocket_port)
|
||||||
|
|
||||||
def run_websocket_server():
|
def run_websocket_server():
|
||||||
loop = asyncio.new_event_loop()
|
loop = asyncio.new_event_loop()
|
||||||
asyncio.set_event_loop(loop)
|
asyncio.set_event_loop(loop)
|
||||||
@@ -350,34 +333,35 @@ class StartIfcTesterWebapp(bpy.types.Operator):
|
|||||||
print(f"WebSocket server error: {e}")
|
print(f"WebSocket server error: {e}")
|
||||||
finally:
|
finally:
|
||||||
loop.close()
|
loop.close()
|
||||||
|
|
||||||
websocket_server_thread = threading.Thread(target=run_websocket_server, daemon=True)
|
websocket_server_thread = threading.Thread(target=run_websocket_server, daemon=True)
|
||||||
websocket_server_thread.start()
|
websocket_server_thread.start()
|
||||||
|
|
||||||
# Start the Flask server as subprocess
|
# Start the Flask server as subprocess
|
||||||
webapp_process = subprocess.Popen([
|
webapp_process = subprocess.Popen(
|
||||||
sys.executable, webapp_serve_path,
|
[sys.executable, webapp_serve_path, "--host", "127.0.0.1", "--port", str(webapp_port)]
|
||||||
"--host", "127.0.0.1",
|
)
|
||||||
"--port", str(webapp_port)
|
|
||||||
])
|
|
||||||
|
|
||||||
# Update properties
|
# Update properties
|
||||||
props.webapp_server_port = webapp_port
|
props.webapp_server_port = webapp_port
|
||||||
props.websocket_server_port = websocket_port
|
props.websocket_server_port = websocket_port
|
||||||
props.webapp_is_running = True
|
props.webapp_is_running = True
|
||||||
|
|
||||||
# Wait a moment for servers to start, then open browser
|
# Wait a moment for servers to start, then open browser
|
||||||
def delayed_open_browser():
|
def delayed_open_browser():
|
||||||
import time
|
import time
|
||||||
|
|
||||||
time.sleep(1.5)
|
time.sleep(1.5)
|
||||||
webbrowser.open(f"http://127.0.0.1:{webapp_port}?bonsai_server={websocket_port}")
|
webbrowser.open(f"http://127.0.0.1:{webapp_port}?bonsai_server={websocket_port}")
|
||||||
|
|
||||||
browser_thread = threading.Thread(target=delayed_open_browser, daemon=True)
|
browser_thread = threading.Thread(target=delayed_open_browser, daemon=True)
|
||||||
browser_thread.start()
|
browser_thread.start()
|
||||||
|
|
||||||
self.report({"INFO"}, f"IfcTester webapp started at http://127.0.0.1:{webapp_port} (Websocket: {websocket_port})")
|
self.report(
|
||||||
|
{"INFO"}, f"IfcTester webapp started at http://127.0.0.1:{webapp_port} (Websocket: {websocket_port})"
|
||||||
|
)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Clean up on error
|
# Clean up on error
|
||||||
if webapp_process:
|
if webapp_process:
|
||||||
@@ -387,13 +371,13 @@ class StartIfcTesterWebapp(bpy.types.Operator):
|
|||||||
# The websocket server will be cleaned up when the thread ends
|
# The websocket server will be cleaned up when the thread ends
|
||||||
websocket_server_thread = None
|
websocket_server_thread = None
|
||||||
websocket_app = None
|
websocket_app = None
|
||||||
|
|
||||||
self.report({"ERROR"}, f"Failed to start servers: {str(e)}")
|
self.report({"ERROR"}, f"Failed to start servers: {str(e)}")
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
def find_free_port(self):
|
def find_free_port(self):
|
||||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||||
s.bind(('', 0))
|
s.bind(("", 0))
|
||||||
s.listen(1)
|
s.listen(1)
|
||||||
port = s.getsockname()[1]
|
port = s.getsockname()[1]
|
||||||
return port
|
return port
|
||||||
@@ -406,15 +390,15 @@ class StopIfcTesterWebapp(bpy.types.Operator):
|
|||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
global webapp_process, websocket_server_thread, websocket_app
|
global webapp_process, websocket_server_thread, websocket_app
|
||||||
|
|
||||||
props = tool.Tester.get_tester_props()
|
props = tool.Tester.get_tester_props()
|
||||||
|
|
||||||
if webapp_process is None and websocket_server_thread is None:
|
if webapp_process is None and websocket_server_thread is None:
|
||||||
self.report({"WARNING"}, "No IfcTester servers are running")
|
self.report({"WARNING"}, "No IfcTester servers are running")
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
errors = []
|
errors = []
|
||||||
|
|
||||||
# Stop webapp server
|
# Stop webapp server
|
||||||
if webapp_process:
|
if webapp_process:
|
||||||
try:
|
try:
|
||||||
@@ -426,34 +410,34 @@ class StopIfcTesterWebapp(bpy.types.Operator):
|
|||||||
errors.append(f"Error stopping webapp server: {str(e)}")
|
errors.append(f"Error stopping webapp server: {str(e)}")
|
||||||
finally:
|
finally:
|
||||||
webapp_process = None
|
webapp_process = None
|
||||||
|
|
||||||
# Stop websocket server
|
# Stop websocket server
|
||||||
if websocket_app and websocket_server_thread:
|
if websocket_app and websocket_server_thread:
|
||||||
try:
|
try:
|
||||||
print("Stopping WebSocket server...")
|
print("Stopping WebSocket server...")
|
||||||
|
|
||||||
# Signal shutdown using thread-safe method
|
# Signal shutdown using thread-safe method
|
||||||
websocket_app.stop_server()
|
websocket_app.stop_server()
|
||||||
|
|
||||||
# Wait for the websocket thread to finish
|
# Wait for the websocket thread to finish
|
||||||
websocket_server_thread.join(timeout=5)
|
websocket_server_thread.join(timeout=5)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
errors.append(f"Error during websocket shutdown: {str(e)}")
|
errors.append(f"Error during websocket shutdown: {str(e)}")
|
||||||
finally:
|
finally:
|
||||||
websocket_app = None
|
websocket_app = None
|
||||||
websocket_server_thread = None
|
websocket_server_thread = None
|
||||||
|
|
||||||
# Update properties
|
# Update properties
|
||||||
props.webapp_server_port = 0
|
props.webapp_server_port = 0
|
||||||
props.websocket_server_port = 0
|
props.websocket_server_port = 0
|
||||||
props.webapp_is_running = False
|
props.webapp_is_running = False
|
||||||
|
|
||||||
if errors:
|
if errors:
|
||||||
self.report({"WARNING"}, f"IfcTester webapp and server stopped with errors: {'; '.join(errors)}")
|
self.report({"WARNING"}, f"IfcTester webapp and server stopped with errors: {'; '.join(errors)}")
|
||||||
else:
|
else:
|
||||||
self.report({"INFO"}, "IfcTester webapp and server stopped")
|
self.report({"INFO"}, "IfcTester webapp and server stopped")
|
||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@@ -464,11 +448,11 @@ class OpenIfcTesterWebapp(bpy.types.Operator):
|
|||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
props = tool.Tester.get_tester_props()
|
props = tool.Tester.get_tester_props()
|
||||||
|
|
||||||
if not props.webapp_is_running:
|
if not props.webapp_is_running:
|
||||||
self.report({"ERROR"}, "IfcTester webapp is not running. Please start it first.")
|
self.report({"ERROR"}, "IfcTester webapp is not running. Please start it first.")
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
webbrowser.open(f"http://127.0.0.1:{props.webapp_server_port}?bonsai_server={props.websocket_server_port}")
|
webbrowser.open(f"http://127.0.0.1:{props.webapp_server_port}?bonsai_server={props.websocket_server_port}")
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|||||||
@@ -70,14 +70,14 @@ class BIM_PT_tester(Panel):
|
|||||||
|
|
||||||
row = self.layout.row()
|
row = self.layout.row()
|
||||||
row.operator("bim.execute_ifc_tester")
|
row.operator("bim.execute_ifc_tester")
|
||||||
|
|
||||||
self.layout.separator()
|
self.layout.separator()
|
||||||
|
|
||||||
# IfcTester Webapp controls
|
# IfcTester Webapp controls
|
||||||
if props.webapp_is_running:
|
if props.webapp_is_running:
|
||||||
row = self.layout.row()
|
row = self.layout.row()
|
||||||
row.label(text=f"Webapp: {props.webapp_server_port} | Server: {props.websocket_server_port}")
|
row.label(text=f"Webapp: {props.webapp_server_port} | Server: {props.websocket_server_port}")
|
||||||
|
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
row.operator("bim.stop_ifc_tester_webapp")
|
row.operator("bim.stop_ifc_tester_webapp")
|
||||||
row.operator("bim.open_ifc_tester_webapp", icon="URL", text="")
|
row.operator("bim.open_ifc_tester_webapp", icon="URL", text="")
|
||||||
|
|||||||
@@ -1059,7 +1059,7 @@ class BIM_OT_enum_property_search(bpy.types.Operator):
|
|||||||
# though it would fit perfectly.
|
# though it would fit perfectly.
|
||||||
if self.dummy_name:
|
if self.dummy_name:
|
||||||
self.layout.label(text=f"Current: {self.dummy_name}")
|
self.layout.label(text=f"Current: {self.dummy_name}")
|
||||||
|
|
||||||
self.layout.prop_search(self, "dummy_name", self, "collection_names", text=self.prop_name)
|
self.layout.prop_search(self, "dummy_name", self, "collection_names", text=self.prop_name)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
from .serve import app
|
from .serve import app
|
||||||
|
|
||||||
__all__ = ['app']
|
__all__ = ["app"]
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from ifctester.ids import Ids, IdsXmlValidationError, get_schema
|
|||||||
# https://github.com/buildingSMART/IDS/blob/9914d568c7ac037acd97e58a0d16e9f93c3e3416/Schema/ids.xsd#L232
|
# https://github.com/buildingSMART/IDS/blob/9914d568c7ac037acd97e58a0d16e9f93c3e3416/Schema/ids.xsd#L232
|
||||||
ifc_schemas = ["IFC2X3", "IFC4", "IFC4X3_ADD2"]
|
ifc_schemas = ["IFC2X3", "IFC4", "IFC4X3_ADD2"]
|
||||||
|
|
||||||
|
|
||||||
def get_predefined_types_for_entity(schema_name, entity_name):
|
def get_predefined_types_for_entity(schema_name, entity_name):
|
||||||
"""Get a list of predefined types for a given entity."""
|
"""Get a list of predefined types for a given entity."""
|
||||||
|
|
||||||
@@ -18,50 +19,57 @@ def get_predefined_types_for_entity(schema_name, entity_name):
|
|||||||
entity = schema.declaration_by_name(entity_name)
|
entity = schema.declaration_by_name(entity_name)
|
||||||
except:
|
except:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
if not entity or not entity.as_entity():
|
if not entity or not entity.as_entity():
|
||||||
print(f"Entity {entity_name} not found")
|
print(f"Entity {entity_name} not found")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
entity = entity.as_entity()
|
entity = entity.as_entity()
|
||||||
predefined_type_attr = None
|
predefined_type_attr = None
|
||||||
|
|
||||||
# Check all attributes for "PredefinedType"
|
# Check all attributes for "PredefinedType"
|
||||||
for attr in entity.all_attributes():
|
for attr in entity.all_attributes():
|
||||||
if attr.name() == "PredefinedType":
|
if attr.name() == "PredefinedType":
|
||||||
predefined_type_attr = attr
|
predefined_type_attr = attr
|
||||||
break
|
break
|
||||||
|
|
||||||
if not predefined_type_attr:
|
if not predefined_type_attr:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
param_type = predefined_type_attr.type_of_attribute()
|
param_type = predefined_type_attr.type_of_attribute()
|
||||||
|
|
||||||
if param_type.as_named_type():
|
if param_type.as_named_type():
|
||||||
enum_decl = param_type.as_named_type().declared_type()
|
enum_decl = param_type.as_named_type().declared_type()
|
||||||
if enum_decl.as_enumeration_type():
|
if enum_decl.as_enumeration_type():
|
||||||
return enum_decl.as_enumeration_type().enumeration_items()
|
return enum_decl.as_enumeration_type().enumeration_items()
|
||||||
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def get_all_entity_classes(schema_name):
|
def get_all_entity_classes(schema_name):
|
||||||
"""Get all IFC entity classes in the given schema."""
|
"""Get all IFC entity classes in the given schema."""
|
||||||
|
|
||||||
schema = ifcopenshell.schema_by_name(schema_name)
|
schema = ifcopenshell.schema_by_name(schema_name)
|
||||||
entities = []
|
entities = []
|
||||||
|
|
||||||
for entity in schema.entities():
|
for entity in schema.entities():
|
||||||
entities.append(entity.name())
|
entities.append(entity.name())
|
||||||
|
|
||||||
# Sort alphabetically
|
# Sort alphabetically
|
||||||
entities.sort()
|
entities.sort()
|
||||||
return entities
|
return entities
|
||||||
|
|
||||||
|
|
||||||
def get_all_data_types(schema_name):
|
def get_all_data_types(schema_name):
|
||||||
"""Get all data types in the given schema."""
|
"""Get all data types in the given schema."""
|
||||||
|
|
||||||
schema = ifcopenshell.schema_by_name(schema_name)
|
schema = ifcopenshell.schema_by_name(schema_name)
|
||||||
return {d.name(): ifcopenshell.util.attribute.get_primitive_type(d) for d in schema.declarations() if d.as_type_declaration()}
|
return {
|
||||||
|
d.name(): ifcopenshell.util.attribute.get_primitive_type(d)
|
||||||
|
for d in schema.declarations()
|
||||||
|
if d.as_type_declaration()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_entity_attributes(schema_name, entity_name):
|
def get_entity_attributes(schema_name, entity_name):
|
||||||
"""Get all attributes for a given entity."""
|
"""Get all attributes for a given entity."""
|
||||||
@@ -72,40 +80,44 @@ def get_entity_attributes(schema_name, entity_name):
|
|||||||
entity = schema.declaration_by_name(entity_name)
|
entity = schema.declaration_by_name(entity_name)
|
||||||
except:
|
except:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
if not entity or not entity.as_entity():
|
if not entity or not entity.as_entity():
|
||||||
print(f"Entity {entity_name} not found")
|
print(f"Entity {entity_name} not found")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
entity = entity.as_entity()
|
entity = entity.as_entity()
|
||||||
attributes = []
|
attributes = []
|
||||||
for attr in entity.all_attributes():
|
for attr in entity.all_attributes():
|
||||||
attributes.append({
|
attributes.append(
|
||||||
"name": attr.name(),
|
{
|
||||||
# "type": attr.type_of_attribute() # TODO Types of attribute
|
"name": attr.name(),
|
||||||
})
|
# "type": attr.type_of_attribute() # TODO Types of attribute
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return attributes
|
return attributes
|
||||||
|
|
||||||
def get_applicable_psets(schema_name, entity_name, predefined_type = ""):
|
|
||||||
|
def get_applicable_psets(schema_name, entity_name, predefined_type=""):
|
||||||
"""Get all applicable property and quantity sets for a given entity."""
|
"""Get all applicable property and quantity sets for a given entity."""
|
||||||
|
|
||||||
pset_qto = ifcopenshell.util.pset.PsetQto(schema_name)
|
pset_qto = ifcopenshell.util.pset.PsetQto(schema_name)
|
||||||
pset_names = pset_qto.get_applicable_names(entity_name, predefined_type)
|
pset_names = pset_qto.get_applicable_names(entity_name, predefined_type)
|
||||||
|
|
||||||
return pset_names
|
return pset_names
|
||||||
|
|
||||||
|
|
||||||
def get_all_psets(schema_name):
|
def get_all_psets(schema_name):
|
||||||
"""Get all property sets and quantity sets defined in an IFC schema"""
|
"""Get all property sets and quantity sets defined in an IFC schema"""
|
||||||
|
|
||||||
pset_qto = ifcopenshell.util.pset.PsetQto(schema_name)
|
pset_qto = ifcopenshell.util.pset.PsetQto(schema_name)
|
||||||
result = {}
|
result = {}
|
||||||
|
|
||||||
for template_file in pset_qto.templates:
|
for template_file in pset_qto.templates:
|
||||||
for pset_template in template_file.by_type("IfcPropertySetTemplate"):
|
for pset_template in template_file.by_type("IfcPropertySetTemplate"):
|
||||||
pset_name = pset_template.Name
|
pset_name = pset_template.Name
|
||||||
properties = []
|
properties = []
|
||||||
|
|
||||||
# Get property templates for this pset
|
# Get property templates for this pset
|
||||||
if pset_template.HasPropertyTemplates:
|
if pset_template.HasPropertyTemplates:
|
||||||
for prop_template in pset_template.HasPropertyTemplates:
|
for prop_template in pset_template.HasPropertyTemplates:
|
||||||
@@ -113,7 +125,7 @@ def get_all_psets(schema_name):
|
|||||||
"name": prop_template.Name,
|
"name": prop_template.Name,
|
||||||
# "description": prop_template.Description
|
# "description": prop_template.Description
|
||||||
}
|
}
|
||||||
|
|
||||||
# Extract type information
|
# Extract type information
|
||||||
if prop_template.is_a("IfcSimplePropertyTemplate"):
|
if prop_template.is_a("IfcSimplePropertyTemplate"):
|
||||||
if prop_template.PrimaryMeasureType:
|
if prop_template.PrimaryMeasureType:
|
||||||
@@ -121,61 +133,62 @@ def get_all_psets(schema_name):
|
|||||||
else:
|
else:
|
||||||
prop_info["type"] = str(prop_template.TemplateType)
|
prop_info["type"] = str(prop_template.TemplateType)
|
||||||
elif prop_template.is_a("IfcComplexPropertyTemplate"):
|
elif prop_template.is_a("IfcComplexPropertyTemplate"):
|
||||||
prop_info["type"] = None # Complex properties are not supported
|
prop_info["type"] = None # Complex properties are not supported
|
||||||
else:
|
else:
|
||||||
prop_info["type"] = None
|
prop_info["type"] = None
|
||||||
|
|
||||||
properties.append(prop_info)
|
properties.append(prop_info)
|
||||||
result[pset_name] = properties
|
result[pset_name] = properties
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def get_material_categories():
|
def get_material_categories():
|
||||||
return ['concrete', 'steel', 'aluminium', 'block', 'brick', 'stone', 'wood', 'glass', 'gypsum', 'plastic', 'earth']
|
return ["concrete", "steel", "aluminium", "block", "brick", "stone", "wood", "glass", "gypsum", "plastic", "earth"]
|
||||||
|
|
||||||
|
|
||||||
def get_standard_classification_systems():
|
def get_standard_classification_systems():
|
||||||
return {
|
return {
|
||||||
'BB/SfB (3/4 cijfers)': {'source': 'Regie der Gebouwen', 'tokens': ['.']},
|
"BB/SfB (3/4 cijfers)": {"source": "Regie der Gebouwen", "tokens": ["."]},
|
||||||
'BIMTypeCode': {'source': 'BIMStockholm', 'tokens': None},
|
"BIMTypeCode": {"source": "BIMStockholm", "tokens": None},
|
||||||
'Common Arrangement of Work Sections (CAWS)': {'source': 'NBS', 'tokens': ['/']},
|
"Common Arrangement of Work Sections (CAWS)": {"source": "NBS", "tokens": ["/"]},
|
||||||
'CBI Classification - Level 2': {'source': 'Masterspec', 'tokens': None},
|
"CBI Classification - Level 2": {"source": "Masterspec", "tokens": None},
|
||||||
'CBI Classification - Level 4': {'source': 'Masterspec', 'tokens': None},
|
"CBI Classification - Level 4": {"source": "Masterspec", "tokens": None},
|
||||||
'Rumsfunktionskoder CC001 - 001': {'source': 'BIMAlliance', 'tokens': ['-']},
|
"Rumsfunktionskoder CC001 - 001": {"source": "BIMAlliance", "tokens": ["-"]},
|
||||||
'CCS': {'source': 'Molio', 'tokens': None},
|
"CCS": {"source": "Molio", "tokens": None},
|
||||||
'CCTB': {'source': 'CCT-Bâtiments', 'tokens': ['.']},
|
"CCTB": {"source": "CCT-Bâtiments", "tokens": ["."]},
|
||||||
'Funktionskoder Regionservice CD001 - 001': {'source': 'BIMAlliance', 'tokens': None},
|
"Funktionskoder Regionservice CD001 - 001": {"source": "BIMAlliance", "tokens": None},
|
||||||
'Rumsfunktion Blekinge CD002 - 001': {'source': 'BIMAlliance', 'tokens': None},
|
"Rumsfunktion Blekinge CD002 - 001": {"source": "BIMAlliance", "tokens": None},
|
||||||
'EcoQuaestor Codetabel': {'source': 'EcoQuaestor', 'tokens': ['.', '-']},
|
"EcoQuaestor Codetabel": {"source": "EcoQuaestor", "tokens": [".", "-"]},
|
||||||
'GuBIMclass CA': {'source': 'GuBIMClass', 'tokens': ['.']},
|
"GuBIMclass CA": {"source": "GuBIMClass", "tokens": ["."]},
|
||||||
'GuBIMclass ES': {'source': 'GuBIMClass', 'tokens': ['.']},
|
"GuBIMclass ES": {"source": "GuBIMClass", "tokens": ["."]},
|
||||||
'MasterFormat': {'source': 'CSI', 'tokens': [' ', '.']},
|
"MasterFormat": {"source": "CSI", "tokens": [" ", "."]},
|
||||||
'NATSPEC Worksections': {'source': 'NATSPEC', 'tokens': None},
|
"NATSPEC Worksections": {"source": "NATSPEC", "tokens": None},
|
||||||
'NBS Create': {'source': 'NBS', 'tokens': ['_', '/']},
|
"NBS Create": {"source": "NBS", "tokens": ["_", "/"]},
|
||||||
'NL/SfB (4 cijfers)': {'source': 'BIMLoket', 'tokens': ['.']},
|
"NL/SfB (4 cijfers)": {"source": "BIMLoket", "tokens": ["."]},
|
||||||
'NS 3451 - Bygningsdelstabell': {'source': 'Standard Norge', 'tokens': None},
|
"NS 3451 - Bygningsdelstabell": {"source": "Standard Norge", "tokens": None},
|
||||||
'OmniClass': {'source': 'OmniClass', 'tokens': ['-', ' ']},
|
"OmniClass": {"source": "OmniClass", "tokens": ["-", " "]},
|
||||||
'ÖNORM 6241-2': {'source': 'freeBIM 2', 'tokens': None},
|
"ÖNORM 6241-2": {"source": "freeBIM 2", "tokens": None},
|
||||||
'RICS NRM1': {'source': 'RICS', 'tokens': ['.']},
|
"RICS NRM1": {"source": "RICS", "tokens": ["."]},
|
||||||
'RICS NRM3': {'source': 'RICS', 'tokens': ['.']},
|
"RICS NRM3": {"source": "RICS", "tokens": ["."]},
|
||||||
'SFG20': {'source': 'SFG20', 'tokens': ['-']},
|
"SFG20": {"source": "SFG20", "tokens": ["-"]},
|
||||||
'SINAPI': {'source': 'Caixa', 'tokens': ['/']},
|
"SINAPI": {"source": "Caixa", "tokens": ["/"]},
|
||||||
'STABU-Element': {'source': 'STABU', 'tokens': ['.']},
|
"STABU-Element": {"source": "STABU", "tokens": ["."]},
|
||||||
'TALO 2000 Building Component Classification': {'source': 'Rakennustieto', 'tokens': ['.']},
|
"TALO 2000 Building Component Classification": {"source": "Rakennustieto", "tokens": ["."]},
|
||||||
'TALO 2000 Hankenimikkeistö': {'source': 'Rakennustieto', 'tokens': ['.']},
|
"TALO 2000 Hankenimikkeistö": {"source": "Rakennustieto", "tokens": ["."]},
|
||||||
'Uniclass': {'source': 'RIBA Enterprises Ltd', 'tokens': ['_']},
|
"Uniclass": {"source": "RIBA Enterprises Ltd", "tokens": ["_"]},
|
||||||
'Uniclass 2015': {'source': 'RIBA Enterprises Ltd', 'tokens': ['_']},
|
"Uniclass 2015": {"source": "RIBA Enterprises Ltd", "tokens": ["_"]},
|
||||||
'UniFormat': {'source': 'UniFormat', 'tokens': ['.']},
|
"UniFormat": {"source": "UniFormat", "tokens": ["."]},
|
||||||
'Uniformat': {'source': 'UniFormat', 'tokens': ['.']},
|
"Uniformat": {"source": "UniFormat", "tokens": ["."]},
|
||||||
'VMSW': {'source': 'VMSW', 'tokens': ['.']}
|
"VMSW": {"source": "VMSW", "tokens": ["."]},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def ids_from_xml_string(xml: str, validate: bool = False) -> Ids:
|
def ids_from_xml_string(xml: str, validate: bool = False) -> Ids:
|
||||||
try:
|
try:
|
||||||
decode = get_schema().decode(
|
decode = get_schema().decode(
|
||||||
xml, strip_namespaces=True, namespaces={
|
xml, strip_namespaces=True, namespaces={"": "http://standards.buildingsmart.org/IDS"}
|
||||||
"": "http://standards.buildingsmart.org/IDS"
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
except XMLSchemaValidationError as e:
|
except XMLSchemaValidationError as e:
|
||||||
raise IdsXmlValidationError(e, "Provided XML appears to be invalid. See details above.")
|
raise IdsXmlValidationError(e, "Provided XML appears to be invalid. See details above.")
|
||||||
return Ids().parse(decode)
|
return Ids().parse(decode)
|
||||||
|
|||||||
@@ -22,11 +22,12 @@ from flask import Flask, send_from_directory, send_file
|
|||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_static_folder():
|
def get_static_folder():
|
||||||
base_dir = os.path.dirname(__file__)
|
base_dir = os.path.dirname(__file__)
|
||||||
dist_dir = os.path.join(base_dir, 'dist')
|
dist_dir = os.path.join(base_dir, "dist")
|
||||||
www_dir = os.path.join(base_dir, 'www')
|
www_dir = os.path.join(base_dir, "www")
|
||||||
|
|
||||||
if os.path.exists(dist_dir) and os.path.isdir(dist_dir):
|
if os.path.exists(dist_dir) and os.path.isdir(dist_dir):
|
||||||
return dist_dir
|
return dist_dir
|
||||||
elif os.path.exists(www_dir) and os.path.isdir(www_dir):
|
elif os.path.exists(www_dir) and os.path.isdir(www_dir):
|
||||||
@@ -34,36 +35,42 @@ def get_static_folder():
|
|||||||
else:
|
else:
|
||||||
return dist_dir
|
return dist_dir
|
||||||
|
|
||||||
|
|
||||||
STATIC_FOLDER = get_static_folder()
|
STATIC_FOLDER = get_static_folder()
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
def index():
|
|
||||||
return send_file(os.path.join(STATIC_FOLDER, 'index.html'))
|
|
||||||
|
|
||||||
@app.route('/<path:filename>')
|
@app.route("/")
|
||||||
|
def index():
|
||||||
|
return send_file(os.path.join(STATIC_FOLDER, "index.html"))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/<path:filename>")
|
||||||
def static_files(filename):
|
def static_files(filename):
|
||||||
return send_from_directory(STATIC_FOLDER, filename)
|
return send_from_directory(STATIC_FOLDER, filename)
|
||||||
|
|
||||||
@app.route('/assets/<path:filename>')
|
|
||||||
|
@app.route("/assets/<path:filename>")
|
||||||
def assets(filename):
|
def assets(filename):
|
||||||
return send_from_directory(os.path.join(STATIC_FOLDER, 'assets'), filename)
|
return send_from_directory(os.path.join(STATIC_FOLDER, "assets"), filename)
|
||||||
|
|
||||||
|
|
||||||
@app.errorhandler(404)
|
@app.errorhandler(404)
|
||||||
def not_found(error):
|
def not_found(error):
|
||||||
return send_file(os.path.join(STATIC_FOLDER, 'index.html'))
|
return send_file(os.path.join(STATIC_FOLDER, "index.html"))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser(description='Start IfcTester webapp')
|
parser = argparse.ArgumentParser(description="Start IfcTester webapp")
|
||||||
parser.add_argument('--host', default='127.0.0.1', help='Host to bind to (default: 127.0.0.1)')
|
parser.add_argument("--host", default="127.0.0.1", help="Host to bind to (default: 127.0.0.1)")
|
||||||
parser.add_argument('--port', type=int, default=5000, help='Port to bind to (default: 5000)')
|
parser.add_argument("--port", type=int, default=5000, help="Port to bind to (default: 5000)")
|
||||||
parser.add_argument('--debug', action='store_true', help='Enable debug mode')
|
parser.add_argument("--debug", action="store_true", help="Enable debug mode")
|
||||||
parser.add_argument('--dist-dir', default=STATIC_FOLDER, help='Directory containing built files')
|
parser.add_argument("--dist-dir", default=STATIC_FOLDER, help="Directory containing built files")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
STATIC_FOLDER = args.dist_dir
|
STATIC_FOLDER = args.dist_dir
|
||||||
|
|
||||||
print(f"Serving IfcTester webapp from: {STATIC_FOLDER}")
|
print(f"Serving IfcTester webapp from: {STATIC_FOLDER}")
|
||||||
print(f"Server running at: http://{args.host}:{args.port}")
|
print(f"Server running at: http://{args.host}:{args.port}")
|
||||||
|
|
||||||
app.run(host=args.host, port=args.port, debug=args.debug)
|
app.run(host=args.host, port=args.port, debug=args.debug)
|
||||||
|
|||||||
Reference in New Issue
Block a user