mirror of
https://github.com/brunopostle/ifcurl.git
synced 2026-07-12 02:08:13 +00:00
Simplify /clash: single selector, detect collisions within selected set
Drop selector_b — the endpoint now takes the URL's selector= and tests all matched elements against each other, consistent with the spec's model. Self-pairs and duplicates are filtered; requires at least 2 matched elements. https://claude.ai/code/session_01NBsytCd6L7UQPiKbcLn4kn
This commit is contained in:
parent
ccf692b168
commit
6ffc4a2b29
2 changed files with 19 additions and 45 deletions
|
|
@ -106,10 +106,8 @@ def _sandboxed_select(ifc_bytes: bytes, selector: str) -> list[str]:
|
|||
return [e.GlobalId for e in matched if hasattr(e, "GlobalId") and e.GlobalId]
|
||||
|
||||
|
||||
def _sandboxed_clash(
|
||||
ifc_bytes: bytes, selector_a: str, selector_b: str | None
|
||||
) -> list[list[str]]:
|
||||
"""Detect geometric clashes between two sets of IFC elements.
|
||||
def _sandboxed_clash(ifc_bytes: bytes, selector: str) -> list[list[str]]:
|
||||
"""Detect geometric clashes within the elements matched by selector.
|
||||
|
||||
Uses ifcopenshell.geom.tree.clash_collision_many for mesh-level
|
||||
interpenetration detection (hard clashes only, touching excluded).
|
||||
|
|
@ -124,32 +122,21 @@ def _sandboxed_clash(
|
|||
settings = ifcopenshell.geom.settings()
|
||||
settings.set("use-world-coords", True)
|
||||
|
||||
elems_a = list(ifcopenshell.util.selector.filter_elements(model, selector_a))
|
||||
ids_a = {e.id() for e in elems_a}
|
||||
|
||||
if selector_b:
|
||||
elems_b = list(ifcopenshell.util.selector.filter_elements(model, selector_b))
|
||||
else:
|
||||
elems_b = [
|
||||
e
|
||||
for e in model.by_type("IfcProduct")
|
||||
if e.id() not in ids_a and getattr(e, "GlobalId", None)
|
||||
]
|
||||
|
||||
if not elems_a or not elems_b:
|
||||
elems = list(ifcopenshell.util.selector.filter_elements(model, selector))
|
||||
if len(elems) < 2:
|
||||
return []
|
||||
|
||||
tree = ifcopenshell.geom.tree()
|
||||
tree.add_file(model, settings)
|
||||
|
||||
clashes = tree.clash_collision_many(elems_a, elems_b, allow_touching=False)
|
||||
clashes = tree.clash_collision_many(elems, elems, allow_touching=False)
|
||||
|
||||
pairs: list[list[str]] = []
|
||||
seen: set[frozenset[str]] = set()
|
||||
for c in clashes:
|
||||
guid_a = getattr(c.a, "GlobalId", None)
|
||||
guid_b = getattr(c.b, "GlobalId", None)
|
||||
if not guid_a or not guid_b:
|
||||
if not guid_a or not guid_b or guid_a == guid_b:
|
||||
continue
|
||||
key = frozenset((guid_a, guid_b))
|
||||
if key not in seen:
|
||||
|
|
@ -321,20 +308,16 @@ async def select_endpoint(
|
|||
@app.post("/clash")
|
||||
async def clash_endpoint(
|
||||
ifc: UploadFile = File(...),
|
||||
selector_a: str = Form(...),
|
||||
selector_b: str = Form(""),
|
||||
selector: str = Form(...),
|
||||
) -> Response:
|
||||
"""Detect clashing element pairs.
|
||||
"""Detect clashing element pairs within the selected set.
|
||||
|
||||
Returns JSON ``{"pairs": [["guidA", "guidB"], ...]}``.
|
||||
An empty ``selector_b`` means test set A against all other model elements.
|
||||
"""
|
||||
ifc_bytes = await ifc.read()
|
||||
|
||||
try:
|
||||
pairs = run_sandboxed(
|
||||
_sandboxed_clash, ifc_bytes, selector_a, selector_b or None
|
||||
)
|
||||
pairs = run_sandboxed(_sandboxed_clash, ifc_bytes, selector)
|
||||
except SandboxCrashError as exc:
|
||||
raise HTTPException(status_code=422, detail=f"IFC clash detection crashed: {exc}") from exc
|
||||
except SandboxTimeoutError as exc:
|
||||
|
|
|
|||
|
|
@ -126,9 +126,7 @@ def _select_via_socket(ifc_bytes: bytes, selector: str) -> list[str]:
|
|||
return resp.json()["guids"]
|
||||
|
||||
|
||||
def _clash_via_socket(
|
||||
ifc_bytes: bytes, selector_a: str, selector_b: str | None
|
||||
) -> list[list[str]]:
|
||||
def _clash_via_socket(ifc_bytes: bytes, selector: str) -> list[list[str]]:
|
||||
"""Delegate clash detection to the render service over the Unix socket."""
|
||||
import httpx
|
||||
|
||||
|
|
@ -137,7 +135,7 @@ def _clash_via_socket(
|
|||
resp = client.post(
|
||||
"http://render/clash",
|
||||
files={"ifc": ifc_bytes},
|
||||
data={"selector_a": selector_a, "selector_b": selector_b or ""},
|
||||
data={"selector": selector},
|
||||
timeout=_RENDER_TIMEOUT,
|
||||
)
|
||||
except httpx.TransportError as exc:
|
||||
|
|
@ -450,8 +448,6 @@ class QueryRequest(BaseModel):
|
|||
|
||||
class ClashRequest(BaseModel):
|
||||
url: str
|
||||
selector_b: str | None = None
|
||||
"""Second element set to test against. When omitted, set A is tested against all other model elements."""
|
||||
token: str | None = None
|
||||
|
||||
|
||||
|
|
@ -829,21 +825,18 @@ def query(request: QueryRequest) -> JSONResponse:
|
|||
|
||||
|
||||
@app.get("/clash")
|
||||
def clash_get(url: str, selector_b: str | None = None, token: str | None = None) -> JSONResponse:
|
||||
def clash_get(url: str, token: str | None = None) -> JSONResponse:
|
||||
"""GET variant of POST /clash."""
|
||||
return clash(ClashRequest(url=url, selector_b=selector_b, token=token))
|
||||
return clash(ClashRequest(url=url, token=token))
|
||||
|
||||
|
||||
@app.post("/clash")
|
||||
def clash(request: ClashRequest) -> JSONResponse:
|
||||
"""Detect geometric clashes between two sets of elements.
|
||||
"""Detect geometric clashes within the elements matched by the selector.
|
||||
|
||||
Set A is defined by the URL's ``selector=`` parameter. Set B is defined
|
||||
by ``selector_b``; when omitted, set A is tested against all other
|
||||
model elements.
|
||||
|
||||
Uses ifcopenshell hard-clash (interpenetration) detection. Returns
|
||||
``{"pairs": [["<guidA>", "<guidB>"], …]}``.
|
||||
The URL's ``selector=`` parameter defines the set of elements to test
|
||||
against each other. Uses ifcopenshell hard-clash (interpenetration)
|
||||
detection. Returns ``{"pairs": [["<guidA>", "<guidB>"], …]}``.
|
||||
"""
|
||||
try:
|
||||
ifc_url = IfcUrl.parse(request.url)
|
||||
|
|
@ -877,11 +870,9 @@ def clash(request: ClashRequest) -> JSONResponse:
|
|||
|
||||
try:
|
||||
if _RENDER_SOCKET:
|
||||
pairs = _clash_via_socket(ifc_bytes, ifc_url.selector, request.selector_b)
|
||||
pairs = _clash_via_socket(ifc_bytes, ifc_url.selector)
|
||||
else:
|
||||
pairs = run_sandboxed(
|
||||
_sandboxed_clash, ifc_bytes, ifc_url.selector, request.selector_b
|
||||
)
|
||||
pairs = run_sandboxed(_sandboxed_clash, ifc_bytes, ifc_url.selector)
|
||||
except SandboxCrashError as exc:
|
||||
raise HTTPException(
|
||||
status_code=422, detail=f"IFC clash detection crashed: {exc}"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue