Compare commits

...

49 Commits

Author SHA1 Message Date
dependabot[bot] 78758771fd build(deps): bump actions/checkout from 6 to 7
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-25 00:48:40 +00:00
Dion Moult d808104725 ci: test the daily bonsai build against the branch that built it
The bare clone fetched the default branch, so a v0.9.0 daily would be
smoke-tested and pytested against v0.8.0 scripts and tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 10:25:42 +10:00
Dion Moult b71b217814 ci: trigger bonsai and ifcsverchok dailies from v0.9.0
Daily builds for user testing now come from the v0.9.0 branch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 10:25:10 +10:00
Dion Moult d9f218eacf Bump build e333c1c > ad113e1
First v0.9.0alpha0 binary set, so the version prefix moves with it.
The bump trackers had drifted (bonsai's OLD pointed at 3e7b739 while
ifcopenshell-python pinned e333c1c), so this was done by hand;
'make bump' works again from here.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 10:25:04 +10:00
Dion Moult 0faba0fdd8 bonsai: drop Intel macOS builds
Upstream binary builds no longer produce macos64 zips (build_osx builds
arm64 only since wgpu Qt), and Blender dropped Intel Mac support in 5.0,
so there is nothing left to package for that platform.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 10:24:53 +10:00
Dion Moult ad113e1283 ifcviewer: latch the motion contribution cull instead of flip-flopping
The coarse motion threshold (15 px vs the 3 px still floor) followed the
per-frame "did the camera move" test directly. During a slow drag on a
janky main thread — the 66-model web session at 20 fps, mouse events
coalesced — some frames see no camera change, so the cull alternated
between thresholds every few frames: 84% of the visible set vanishing
and reappearing (139k <-> 22k objects in the log), with a full
visible-set re-upload at each flip feeding the very jank that caused the
gaps. On screen it read as the model sporadically jumping and returning
while orbiting slowly, easing as streaming and caching settled — which
is exactly how it was reported.

The motion state now latches: any camera movement arms it, and it only
drops after 250 ms of stillness, with the render loop kept alive over
the hold so the fine-threshold re-cull actually runs in an on-demand
loop. A drag degrades once at its start and restores once shortly after
it ends. Measured with a deliberately gappy scripted drag: two
transitions for the whole drag where each 120 ms pause previously
flipped it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 07:42:13 +10:00
Dion Moult 4a761b51f5 ifcviewer: pack the cull-hot instance fields and skip unchanged culls
On the single-threaded web build the CPU cull WAS the frame: 52-60 ms
of a 60 ms frame at 640k instances (desktop hides the same cost across
cores via std::async, which web cannot use without COOP/COEP+pthreads).

Two changes, both also helping desktop:

- ModelGpuData::CullInstance packs the six AABB floats and three ids the
  cull reads into 40 contiguous bytes. InstanceInfo is 232 bytes with
  the AABB 200 bytes away from the ids, so the walk paid two or three
  cache lines per instance. Rebuilt by rebuildCullInstances at model
  apply and inside uploadInstanceRecords, which every recompose,
  transform and colour-override change already funnels through.
  Measured on web: 94 ns/instance -> 36 ns/instance during a continuous
  orbit (~2.6x).

- render() re-culls only when a cull input changed: the camera, a
  cull-relevant setting (contribution px, LOD px, x-ray, HiZ on/off), a
  fresh HiZ pyramid, or scene_epoch_ — bumped by chunk residency,
  visibility, colours, transforms, model add/remove/hide/unload. A
  frame requested for an overlay redraw, pick feedback, or a streaming
  tick where nothing landed draws from the buffers the last cull
  uploaded and skips the walk entirely. Benchmarks are exempt so bench
  numbers keep measuring the real cull.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 07:42:13 +10:00
Dion Moult 73f8e6aea7 ifcviewer-web: complete the OPFS cache in the background
Filling only from the viewer's reads meant the cache converged on the
bytes the camera had needed — a user had to orbit every model into view
(unloading others to get there) before an entry could finish. Now a
filling entry fetches its uncovered spans in order, 8 MB at a time,
whenever the viewer has been quiet for 1.5 s, yielding the moment real
reads resume so interactive streaming always wins. A 42 MB model that
levelled off at 85% viewed now completes seconds after load with no
interaction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 07:42:13 +10:00
Dion Moult e5072460cd ifcviewer-web: OPFS model cache behind addUrl(url, {cache: true})
Streaming a federation over the network re-downloads everything on every
visit: browsers do not populate their HTTP cache from ranged fetches
(measured at 0 of 78 range requests served from cache even with a strong
ETag). Host pages have started hand-rolling OPFS caches against the
library's own source seam — this is the second app to port the same ~350
lines — so the capability moves into the library.

The design keeps what those pages got right: the cache fills FROM THE
VIEWER'S OWN RANGED READS (no second download, and only bytes the camera
actually needed), entries are keyed by a hash of the URL and validated
by ETag (falling back to Last-Modified + size), and a byte-span ledger
guarantees a partial copy is never mistaken for a whole one. What it
fixes: writes go through a FileSystemSyncAccessHandle in an inline
worker — positional writes with no copy-on-open, where the pages'
createWritable({keepExistingData}) paid a whole-file copy per flush
(quadratic as the cache fills) and buffered up to 48 MB per model in JS
to compensate — the handle's exclusive lock makes a second tab fall back
to plain network instead of corrupting the entry; a complete copy now
opens when the server is unreachable (offline was dead before despite
the bytes being local); and entry names are hashes, where prefix-matched
sanitised names could delete a sibling model's cache.

viewer.cacheInfo() reports entries and the storage estimate;
viewer.clearCache(url?) drops one or all. Browsers without OPFS or sync
handles, servers without validators, and second tabs all degrade to
exactly today's network streaming.

Verified: the sample round-trips to zero range requests on reload, and a
42 MB model goes from 108 range requests to 5 on the second visit — the
85% the camera had viewed comes off disk, coverage honestly reports
incomplete for the bytes streaming never needed. The test server now
sends a content-hash ETag so the specs exercise real validation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 07:42:13 +10:00
Dion Moult 311b75a955 ifcviewer: carve the margin out of the cache on the first driver growth refusal
On web there is no device-memory query, so the budget sat at the wasm
heap cap while the pool grew until Chrome's GPU process refused
(observed at 1920 MB on a 66-model session). Nothing acted on that
refusal: the cache kept the last byte, and the next attachment
reallocation (orbit resize, 76 MB) had to fail first — a few frames of
invalid-TextureView errors — before pressure feedback carved out room.

The refusal IS the query-less platform's device report. render() now
answers the first one by lowering the budget by the required-tier
margin and shrinking the pool to it, so attachments and model buffers
find headroom without ever failing. Desktop gets the same fallback for
drivers GpuMemory cannot answer for.

Reproduced under Playwright with a native process squeezing the GPU:
Chrome refuses at 512 MB, the margin (256 MB) is released on the next
frame, and the session continues with zero uncaptured WebGPU errors —
previously the same squeeze produced invalid-view frames before
recovery.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 07:42:13 +10:00
Dion Moult 6f24133d35 ifcviewer-web: stream getObjects() per model instead of one scene-sized JSON
getObjects() is on every real host page's path — a click hands back an
object id, and resolving it to a GlobalId/name/type needs the element
tables (the JS layer also builds its GUID index from this call). The
implementation materialised a vector of ElementRef (three fresh
std::strings per element), serialised the entire scene into one JSON
string grown by +=, and UTF8ToString'd the whole thing — several
hundred MB simultaneously alive at ~600k elements. The wasm heap never
returns pages, so that transient became the session's permanent floor.

ViewportCore::visitModelElements hands out one model's elements as
slices into its string table (no per-element copies), and the export
serialises straight from those, one model per batch, reusing one string
whose capacity grows only to the largest model. The JS side accumulates
batches and resolves the same array as before — the page API is
unchanged. Peak is now one model's JSON instead of the scene's.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 07:42:13 +10:00
Dion Moult 091b4d4113 ifcviewer: bound the CPU triangle shadow and the cull scratch to residency
The wasm heap grew past 2 GB on a 66-model session (surfacing first as
the setBindGroup 2 GB TypeError, fixed separately) because CPU memory
attached to loaded geometry never shrank while the GPU pool did:

- mesh_triangles_cache — the dequantised positions + LOD0 indices the
  surface raycasts and measurement tools read — was filled once per mesh
  on first residency (gated on mesh_local_volumes == 0) and never
  released, converging over a session to the whole federation's geometry
  on the heap: 12 B/vertex + 4 B/index, 400 MB - 1 GB at this scale. And
  on web nothing reads it at all (no measurement tools yet).
- Every chunk's cull scratch was reserved at model load (20 B/instance
  scene-wide) and the scratch + uploaded mirrors survived eviction.
- Cull ran the HiZ test and emitted VisibleDrawGpu entries — then
  uploaded them — for non-resident chunks render() cannot draw.

Now the shadow follows GPU residency: a per-mesh resident-chunk refcount
(the spatial planner may duplicate a mesh into several chunks) is
counted up in applyStreamedChunk and down in unloadChunk, releasing the
mesh's entry at zero and refilling from the chunk bytes on the next
residency. mesh_local_volumes (8 B/mesh) is kept across eviction so the
Volume tool still covers evicted meshes. Hosts opt in via
ViewportHost::wantsCpuMeshTriangles(): Qt yes, web no until the tools
are ported — so on web the shadow costs nothing.

Cull stops at the streaming counters for non-resident chunks, the eager
scratch reserve is gone, and unloadChunk releases the scratch and
uploaded mirrors. Clearing the mirrors also fixes a real staleness bug
in unload/load: the model's cull buffers are recreated on load, and a
stale mirror would make the memcmp dirty-check skip the first upload
into the fresh (garbage) buffer.

The heartbeat log reports the shadow (cpuTris). Measured on a 3-model /
990 MB scene: shadow tracks residency (493 MB at a 530 MB resident set,
flat over minutes of streaming churn; previously monotonic), unload
drops it to zero, reload refills it (verified via readbackMeshTriangles
round trip).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 07:42:13 +10:00
Dion Moult 06d87e21a7 ifcviewer-web: never hand setBindGroup the whole wasm heap as dynamic offsets
Emscripten's generated WebGPU shim implements the dynamic-offset path of
wgpuRenderPassEncoderSetBindGroup as

    pass.setBindGroup(index, group, HEAPU32, ptr >>> 2, count);

where HEAPU32 is the view over the entire wasm linear memory. Browsers
validate the byte length of that whole backing buffer, not the slice
actually read, and refuse anything over 2 GB. This build lets the heap
grow to 4 GB because large federations need it, so on a big enough
session (66 models) every dynamic-offset draw — the axis gizmo, section
gizmo and overlay lines, all drawn every frame — throws

    TypeError: GPURenderPassEncoder.setBindGroup: Argument 3 can't be an
    ArrayBuffer or an ArrayBufferView larger than 2 GB

on every frame for the life of the page.

ifcviewer::setBindGroupDynamic copies the handful of offsets into a
small Uint32Array on web (HEAPU32.slice, not subarray, which would alias
the heap again) and forwards straight through natively. The five
dynamic-offset call sites route through it. A Playwright spec spies on
setBindGroup and asserts the largest buffer it is ever handed is the
offsets themselves (4 bytes), where the shim previously passed the full
268 MB heap 35 times in three seconds of idle rendering.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 07:42:13 +10:00
Dion Moult b7f1f1728f ifcviewer-web: expose frame stats and per-model unload/load to the page
The memory work (cache budget, pressure handling, unloadModel) lives in
ViewportCore and so already ran in the wasm, but the page could not see
or use any of it: the web host had no onFrameStats, and there were no
bindings for residency.

- WebViewportHost latches the last FrameStats; ifcv_get_frame_stats_c
  hands them to JS as doubles, and viewer.stats() returns {fps,
  frameTimeMs, objects, triangles, drawCalls, vram{used, capacity,
  budget}, workingSet{chunks, chunksMissing, missingBytes}} — the same
  figures BonsaiViewer's status bar shows. Device-wide VRAM is omitted:
  there is no query for it on web.
- viewer.unloadModel / loadModel / modelUnloaded / modelVramBytes, keyed
  by source id like the other per-model calls.
- The demo page shows a GPU memory line that turns into a "full: N of M
  visible chunks not loaded" notice once a shortfall persists for 3 s,
  and each model's MB with an Unload/Load button.
- memory.spec.mjs covers stats() and the unload/load round trip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 07:42:13 +10:00
Dion Moult 8074541057 Surface VRAM shortfall to the user and let them unload models
When the geometry in view needs more GPU memory than the cache can
hold, the viewer keeps the largest on-screen chunks resident and streams
the rest as the camera moves. That is the right degradation, but it was
invisible: nothing told the user the scene did not fit, and the only
lever was removing or hiding models, neither of which is "keep it in the
federation but stop spending GPU memory on it".

Viewer core:
- ModelGpuData::unloaded, with drawable() = !hidden && !unloaded now the
  test every cull / draw / pick / streaming pass uses. unloadModel evicts
  every chunk and releases the model's own buffers; loadModel recreates
  them from the CPU mirrors (no disk read) and lets chunks stream back.
  Recompose keeps the CPU instances current while a model is unloaded so
  a reload sees up-to-date transforms. The MeshGpu/InstanceGpu record
  builders are factored out so load and reload share them.
- FrameStats reports the camera's working set: chunks wanted, how many
  of those are not resident, and their bytes.
- modelVramBytes / isModelUnloaded accessors, forwarded by ViewportWindow.

BonsaiViewer:
- Models tree gains a memory column (name | MB | eye) refreshed once a
  second and on load-state changes; unloaded models read "unloaded" in
  italics. The viewport stays the single authority for the state;
  SessionState only carries the modelLoadStateChanged notification.
- Context menu: "Unload Model" / "Load Model", distinct from hide and
  remove, reporting the MB freed in the status bar.
- Status bar notice, independent of the perf-stats toggle, once the
  shortfall has persisted for 3 s (a moment of missing chunks after any
  camera move is normal): "GPU memory full: N of M visible chunks (X MB)
  not loaded", with a tooltip pointing at Unload. The perf label also
  shows "N/M chunks waiting".

Verified on the GPU: unloading a 497 MB model frees it immediately with
the others still rendering; reloading streams all 180 chunks back.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 07:42:13 +10:00
Dion Moult d755ca3a59 bonsaiviewer: show the pool's capacity in the VRAM readout, budget alongside
The readout showed used over budget, which reads as impossible once the
pool legitimately sits a sub-buffer above a lowered budget (releasing
it would undershoot). Show used/capacity, and the budget only when it
differs from capacity.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 07:42:13 +10:00
Dion Moult 777b728205 ifcviewer: move the live budget only on sustained device readings
A 66-model session oscillated with a ~4 s period — 298 releases in one
log: the pool grew to its ceiling, the next report read ~83 MB free, the
budget dropped and the pool shrank, the reading rebounded, the budget
rose and the pool re-grew, reloading the same chunks each time. Objects
flickered on and off continuously.

The report includes transients the viewer itself creates: the upload
staging behind a burst of chunk loads (~170 MB in that session) and a
released sub-buffer the driver has not yet reclaimed. A budget that
followed every reading fed those straight back into growth decisions.

GpuBudget::update now bounds the cache outright on the first device
report and afterwards moves only on sustained readings: lower when free
memory is below half the margin on two consecutive scheduled reports,
raise when it is above 1.5× the margin on two, and nothing in between.
Transients drain well within a poll interval, so a momentary low never
reaches the pool, while a process that really took memory still does a
second later. A refused allocation (onPressure) is never deferred.

Verified in the saturated regime (working set ~990 MB against a 683 MB
budget, continuous streaming): zero releases over 75 s.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 07:42:13 +10:00
Dion Moult 24616ed655 ifcviewer: stop the live budget from over-shrinking and from going stale
A 66-model session showed the pool reach a 1938 MB ceiling, the next
poll lower the budget to 1756, and the shrink drop 402 MB (73+73+256)
for a 182 MB excess, which the pool then spent seconds re-growing. Two
causes.

The release granularity is whole sub-buffers but the shrink ran "until
capacity ≤ target", so the last 36 MB of excess cost a 256 MB
sub-buffer. shrinkToCapacity now never undershoots — it releases only
while doing so keeps capacity ≥ target, leaving a sub-buffer's worth of
excess for the margin to absorb — and the pressure path uses a separate
releaseAtLeast(bytes), whose contract is the opposite: free at least
what the failed allocation needs, whatever the granularity. Resident
geometry is also only evicted once the pool is over budget by half the
margin (GpuBudget::shrinkTarget), so report jitter does not trigger a
shrink-and-reload.

The ceiling was a second old when the pool grew into it, and the upload
staging that rides on growth had pushed device free memory to ~74 MB —
below the driver's observed refusal point — before the next scheduled
poll. pollDeviceMemory now re-derives the budget immediately after any
sub-buffer is added, so the next growth decision sees the device as it
is.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 07:42:13 +10:00
Dion Moult 6201c4052b ifcviewer: make the geometry cache budget live, not fixed at startup
The budget was derived once at init as device free minus a reserve sized
for attachments at 4K plus margin. On a 1440p surface that idled ~550 MB
of VRAM the user's hardware could have spent on geometry, and it never
followed the device as other processes came and went.

Now, on the same once-a-second device poll that feeds the status bar,

    budget = cache capacity + device free - margin

is recomputed and applied: the pool's growth ceiling moves with it, and
the pool yields whole sub-buffers when the device has less to give than
the pool holds. The attachments are eager, so at any poll they are
already inside "used" at the actual surface size; a resize that no
longer fits is answered by the existing pressure path rather than by a
permanent reserve.

The margin is 256 MB for later required allocations plus a learned part:
drivers refuse while still reporting memory free (the original crash
refused 59 MB with 221 MB "free"), so a pressure event records how much
reported-free memory proved unusable and update() stops short of it from
then on, instead of growing straight back into the same refusal.

Web is unchanged: fixed heap cap plus pressure. On the test machine the
idle-device budget goes from 1609 MB to 2212 MB; with another process
holding 1 GB mid-session the budget follows it down and back up without
evicting geometry the device could still hold.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 07:42:13 +10:00
Dion Moult ab99024307 ifcviewer: budget the geometry cache and make required allocations fallible
Loading enough models drove the chunk pool to the driver's refusal point,
after which the first click aborted: the pick attachments are allocated
lazily, wgpu-native reported their OOM as a validation error nobody
observed, and the invalid views reached wgpuQueueSubmit, which panics
across the FFI boundary. Two policy defects compounding: the cache was
allowed to take the last byte, and nothing but the pool's own growth was
treated as fallible.

GPU memory is now two tiers. Required allocations (per-pixel attachments,
a model's metadata buffers, readback staging) are eager, deterministic
and fallible; the chunk pool is an elastic cache that grows only to a
budget and yields whenever a required allocation fails.

- GpuBudget (pure, unit-tested): desktop derives the budget from the
  driver's free-memory report minus a reserve for the attachments at 4K;
  web keeps the wasm-heap cap; either lowers it on pressure. The budget's
  source differs per platform, the mechanism does not.
- GpuAllocScope: the OOM/Validation error-scope dance in one place,
  synchronous on wgpu-native, provisional on Dawn-web. BufferPool's
  inline copy now uses it.
- BufferPool::shrinkToCapacity releases whole sub-buffers newest-first
  after the owner empties them; growth clamps to the budget instead of
  overshooting.
- ViewportCore::allocateRequired runs any required creation under a
  scope and, on failure, lowers the budget, evicts and releases cache
  sub-buffers, waits for the device to reclaim them, and retries until
  it fits or the cache is at its floor. Pick attachments are created with
  the other attachments in configureSurface; render() skips a frame
  rather than submit invalid views; a model whose buffers cannot fit is
  not loaded instead of aborting.

Verified on a 4 GB GeForce: the pool clamps itself at the derived budget
(256+256+67 MB for a 579 MB budget) and, in a standalone check against
the real device, a pool grown to the driver's refusal point observes a
failed required allocation, releases 320 MB and succeeds on retry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 07:42:12 +10:00
Dion Moult b7d2b2fa3a bonsaiviewer: show pool and device VRAM in the performance stats
FrameStats gains the geometry pool's used/capacity bytes and, on desktop,
the device-wide used/total reported by the driver (NVML via dlopen, or
amdgpu/i915 sysfs, matched to the wgpu adapter's vendor/device id so a
switchable-graphics laptop reports the card wgpu actually picked). The
device query is polled once a second, not per frame. Web has no VRAM
query, so the device figure is omitted there.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 07:42:12 +10:00
Andrej730 5fa14c3dea IfcAlignment examples: fix Wunknown-pragmas
gcc was complaining, because it couldn't recognize msvc pragmas
2026-08-24 19:18:00 +05:00
Andrej730 7fa8506fac build-all: test examples if they were built 2026-08-24 19:11:10 +05:00
Andrej730 9d68e7b9ca build-all: support --occt-shared 2026-08-24 19:11:10 +05:00
Andrej730 9ffbfe0dbb build-all: fix missing is_on_off
Update build-all.py
2026-08-24 19:10:28 +05:00
Andrej730 5b00c8b451 build-all: add --help and verify args 2026-08-24 18:50:23 +05:00
Andrej730 a5b6f83a3d Normalize whitespaces in cmake files 2026-08-24 18:50:23 +05:00
Andrej730 908d85a51a IfcAdvancedHouse: report an error in case serialization fails
Instead of a crash
2026-08-24 18:50:23 +05:00
Thomas Krijnen 9089a20ce3 Add branch filter for v0.9.0 in CI workflow #9336 2026-08-22 13:21:43 +02:00
Thomas Krijnen d4a5420851 Rename job to publish_ifctester_org 2026-08-22 13:18:20 +02:00
Thomas Krijnen 87bc6bfbab Add string decode/encode api 2026-08-22 13:12:36 +02:00
Petru Conduraru a5e94cf0d8 Version and SOVERSION for geometry_serializer 2026-08-22 13:04:57 +02:00
Dion Moult 2c1d445d5b ifcviewer-web: mint session model ids when a load is requested
A federated pick could be attributed to the wrong file. The model slot a
host sees — ElementRef::model_index, modelProgress's index — is a rank in
session_model_id order, and on web that id was minted at the END of the
sidecar read chain, after three network round trips. So the ranking was
the order the models' reads happened to finish in, not the order the host
added them. With ~40 similarly-sized models over HTTP, adjacent models
swapped and a click reported its neighbour's file; the host page then
asked for a GUID the file does not contain.

Mint the id at the top of loadSidecarMetadataWeb instead, which runs
synchronously from load_sidecar_from_source_c and therefore in the order
the host asked for its models. A load that fails partway just abandons
its id, and the ranks compact over the surviving models as before.

Positions are still positions, though: if one model fails to load, every
later index shifts down one and a host mapping index into its own list
silently drifts again. So also carry the source id — the handle the host
minted itself when it registered the file — through ElementRef into the
pick payload and getObjects rows, and document it as the way to attribute
an object to a file. ModelGpuData::web_source_id defaults to -1 now, since
0 is a real source id and cannot double as "none".

The test server grows a ?delay=<ms> knob so a test can force the losing
interleaving: georef-a is added first and served slowly, and its objects
must still come back as model 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 11:30:56 +10:00
Dion Moult d86f89090b ifcviewer-web: draw the axis indicator (corner gizmo + orbit pivot)
The desktop viewport draws an RGB triad in the bottom-left corner and a
second one at the orbit target while navigating; the web build drew
neither. Both lived in the Qt-coupled OverlayRenderer, which only
ViewportWindow drives — the web host no-ops the overlay hooks — so the
wasm build had no path to them at all.

Lift them into AxisIndicatorRenderer, a Qt-free renderer in
IfcViewerCore, and drive it from ViewportCore::render for desktop and
web alike. Same move SectionGizmoRenderer already made; the drawing code
is unchanged apart from swapping qDegreesToRadians for CameraMath's kPiF.

Pivot visibility moves to the core with it: it was a QTimer on
ViewportWindow, so the afterglow couldn't follow the gizmo across. It is
now a Stopwatch deadline next to the drawing, with render() requesting
frames until an armed afterglow expires. Hosts keep the same three
triggers (on for orbit/pan drags, off on release, 600 ms on wheel).

The web demo shell's log overlay sat exactly on top of the corner gizmo,
so it shifts right of the 110 px box.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 11:16:41 +10:00
Andrej730 973f61c6dc express.h: remove non-snake case aliases (schemas are regenerated now) 2026-08-19 20:17:51 +05:00
Andrej730 f47aa4d81a Manually update other schemas 2026-08-19 20:17:51 +05:00
Andrej730 fa9a3383aa Use snake case in cpp consistently 2026-08-19 20:17:51 +05:00
Andrej730 104591a80b Normalize whitespaces in the codebase 2026-08-19 20:17:50 +05:00
Andrej730 030e6e5bb4 Regen cpp schemas 2026-08-19 20:17:50 +05:00
Andrej730 a031310a66 express/run.bat: rewrite in Python 2026-08-19 20:17:50 +05:00
Andrej730 436e3f7b2a Header_section_schema-definitions.h: use IFC_SCHEMA_API 2026-08-19 20:17:50 +05:00
Andrej730 335d571854 gltf_serializer: refactor proj code into a separate call 2026-08-19 20:17:49 +05:00
Andrej730 f78b380b71 express/mapping: fix breaking generation after f23db9440f
It was failing to map `int64_t` to `Argument_INT` enum.
2026-08-19 19:38:30 +05:00
Andrej730 2cebc3f60b typing 2026-08-19 19:38:30 +05:00
Andrej730 0a8159505d pyproject: black to format all files by default 2026-08-19 19:38:30 +05:00
Andrej730 301fba5a8b Bump ty to 0.0.72 2026-08-19 19:38:30 +05:00
Andrej730 ba90cf220d black, ruff 2026-08-19 19:38:29 +05:00
Andrej730 6318892a97 Script to check whitespace issues in the codebase 2026-08-19 19:33:08 +05:00
Dion Moult 1a6336bd20 Output test audits to sqlite 2026-08-18 13:50:14 +10:00
243 changed files with 49180 additions and 45065 deletions
+382
View File
@@ -0,0 +1,382 @@
# /// script
# dependencies = [
# "pytest",
# ]
# ///
"""Check (and by default fix) whitespace issues in tracked source files:
- stray CR, e.g. 'hello\\rworld' -> 'helloworld'
- line ending mismatch, e.g. 'hello\\r\\n' -> 'hello\\n' (or vice versa)
- missing newline at end of file
- extra newline(s) at end of file
- trailing whitespace at end of line
"""
import argparse
import io
import os
import re
import subprocess
import sys
from collections.abc import Callable
from pathlib import Path
from typing import BinaryIO, Literal, cast
import pytest
class C:
RED = "\033[31m"
GREEN = "\033[32m"
YELLOW = "\033[33m"
RESET = "\033[0m"
CR = b"\r"
CRLF = b"\r\n"
LF = b"\n"
LineSeparator = Literal[b"\r\n", b"\n"]
SYSTEM_LINE_SEPARATOR = cast(LineSeparator, os.linesep.encode())
class Checker:
def __init__(self, newline: LineSeparator = SYSTEM_LINE_SEPARATOR) -> None:
self.newline = newline
self.issues = 0
def report(self, label: str, issue: str) -> None:
self.issues += 1
print(f"{label}: {C.RED}{issue}{C.RESET}")
def check_stray_cr(self, filepath: Path, check: bool) -> None:
with filepath.open("r+b") as f:
self._check_stray_cr(f, str(filepath), check)
def _check_stray_cr(self, f: BinaryIO, label: str, check: bool) -> None:
# a CR is "stray" if it isn't immediately followed by a LF, i.e. not part of a CRLF pair
# CRLF/CR mismatch will be reported separately.
stray_cr = re.compile(rb"\r(?!\n)")
content = f.read()
matches = list(stray_cr.finditer(content))
if not matches:
return
line_numbers = dict.fromkeys(content.count(b"\n", 0, m.start()) + 1 for m in matches)
for line_number in line_numbers:
self.report(f"{label}:{line_number}", "stray carriage return")
if check:
return
f.seek(0)
f.write(stray_cr.sub(b"", content))
f.truncate()
def check_line_endings_mismatch(self, filepath: Path, check: bool) -> None:
with filepath.open("r+b") as f:
self._check_line_endings_mismatch(f, str(filepath), check)
def _check_line_endings_mismatch(self, f: BinaryIO, label: str, check: bool) -> None:
NEWLINE = self.newline
def get_line_ending(line: bytes) -> LineSeparator | None:
if line.endswith(CRLF):
return CRLF
if line.endswith(LF):
return LF
# last line with no trailing newline at all; check_eof_newline handles that
return None
changed = False
fixed_lines = []
for line_number, line in enumerate(f, start=1):
found = get_line_ending(line)
if found in (NEWLINE, None):
fixed_lines.append(line)
continue
self.report(f"{label}:{line_number}", f"line ending mismatch (expected {NEWLINE!r}, found {found!r})")
changed = True
content = line[: -len(found)]
fixed_lines.append(content + NEWLINE)
if changed and not check:
f.seek(0)
f.write(b"".join(fixed_lines))
f.truncate()
def check_eof_newline(self, filepath: Path, check: bool) -> None:
with filepath.open("r+b") as f:
self._check_eof_newline(f, str(filepath), check)
def _check_eof_newline(self, f: BinaryIO, label: str, check: bool) -> None:
NEWLINE = self.newline
NEWLINE_SIZE = len(NEWLINE)
size = f.seek(0, os.SEEK_END)
if size == 0:
return
trailing_newlines = 0
while True:
pos = f.seek((-trailing_newlines - 1) * NEWLINE_SIZE, os.SEEK_END)
if f.read(NEWLINE_SIZE) != NEWLINE:
break
trailing_newlines += 1
if pos == 0:
break
if trailing_newlines == 0:
self.report(label, "missing newline at end of file")
if check:
return
f.seek(0, os.SEEK_END)
f.write(NEWLINE)
elif trailing_newlines > 1:
self.report(label, f"{trailing_newlines} trailing newlines at end of file")
if check:
return
f.truncate(size - (trailing_newlines - 1) * NEWLINE_SIZE)
def check_trailing_whitespaces(self, filepath: Path, check: bool) -> None:
with filepath.open("r+b") as f:
self._check_trailing_whitespaces(f, str(filepath), check)
def _check_trailing_whitespaces(self, f: BinaryIO, label: str, check: bool) -> None:
NEWLINE = self.newline
NEWLINE_SIZE = len(NEWLINE)
changed = False
fixed_lines = []
for line_number, line in enumerate(f, start=1):
has_newline = line.endswith(NEWLINE)
content = line[:-NEWLINE_SIZE] if has_newline else line
stripped = content.rstrip()
if stripped != content:
self.report(f"{label}:{line_number}", "trailing whitespace")
changed = True
fixed_lines.append(stripped + (NEWLINE if has_newline else b""))
if changed and not check:
f.seek(0)
f.write(b"".join(fixed_lines))
f.truncate()
CheckMethod = Callable[[Checker, BinaryIO, str, bool], None]
class TestChecker:
def _assert_check(
self,
method: CheckMethod,
content: bytes,
expected_issues: int,
fixed: bytes,
check: bool,
line_ending: LineSeparator,
*,
transform: bool = True,
) -> None:
checker = Checker(line_ending)
if line_ending == CRLF and transform:
content = content.replace(LF, CRLF)
fixed = fixed.replace(LF, CRLF)
buffer = io.BytesIO(content)
method(checker, buffer, "test", check)
assert buffer.getvalue() == (content if check else fixed)
assert checker.issues == expected_issues
@pytest.mark.parametrize(
("content", "expected_issues", "fixed"),
(
# OK
(b"", 0, b""),
(b"hello\n", 0, b"hello\n"),
(b"line1\r\nline2\n", 0, b"line1\r\nline2\n"),
# ERR
(b"hello\rworld\n", 1, b"helloworld\n"),
(b"a\rb\rc\n", 1, b"abc\n"),
(b"hello\r", 1, b"hello"),
),
)
@pytest.mark.parametrize("check", [False, True])
def test_check_stray_cr(self, content: bytes, expected_issues: int, fixed: bytes, check: bool) -> None:
# Don't parametrize by line endings, since in this case it doesn't matter.
self._assert_check(Checker._check_stray_cr, content, expected_issues, fixed, check, LF)
@pytest.mark.parametrize(
("content", "expected_issues", "fixed", "line_ending"),
(
# OK
(b"", 0, b"", LF),
(b"hello\n", 0, b"hello\n", LF),
(b"hello\r\n", 0, b"hello\r\n", CRLF),
# ERR
(b"hello\r\n", 1, b"hello\n", LF),
(b"a\nb\r\nc\n", 1, b"a\nb\nc\n", LF),
(b"a\r\nb\r\n", 2, b"a\nb\n", LF),
(b"hello\n", 1, b"hello\r\n", CRLF),
(b"a\r\nb\nc\r\n", 1, b"a\r\nb\r\nc\r\n", CRLF),
),
)
@pytest.mark.parametrize("check", [False, True])
def test_check_line_endings_mismatch(
self, content: bytes, expected_issues: int, fixed: bytes, line_ending: LineSeparator, check: bool
) -> None:
self._assert_check(
Checker._check_line_endings_mismatch, content, expected_issues, fixed, check, line_ending, transform=False
)
@pytest.mark.parametrize(
("content", "expected_issues", "fixed"),
(
# OK
(b"", 0, b""),
(b"hello\n", 0, b"hello\n"),
# ERR
(b"hello", 1, b"hello\n"),
(b"hello\n\n\n", 1, b"hello\n"),
(b"\n\n\n", 1, b"\n"),
),
)
@pytest.mark.parametrize("check", [False, True])
@pytest.mark.parametrize("line_ending", [LF, CRLF])
def test_check_eof_newline(
self, content: bytes, expected_issues: int, fixed: bytes, check: bool, line_ending: LineSeparator
) -> None:
self._assert_check(Checker._check_eof_newline, content, expected_issues, fixed, check, line_ending)
@pytest.mark.parametrize(
("content", "expected_issues", "fixed"),
(
# OK
(b"", 0, b""),
(b"hello\n", 0, b"hello\n"),
(b"hello", 0, b"hello"),
# ERR
(b" ", 1, b""),
(b"hello ", 1, b"hello"),
),
)
@pytest.mark.parametrize("check", [False, True])
@pytest.mark.parametrize("line_ending", [LF, CRLF])
def test_check_trailing_whitespaces(
self, content: bytes, expected_issues: int, fixed: bytes, check: bool, line_ending: LineSeparator
) -> None:
self._assert_check(Checker._check_trailing_whitespaces, content, expected_issues, fixed, check, line_ending)
@staticmethod
def run_tests(extra_args: list[str] | None = None) -> None:
pytest.main([__file__, *(extra_args or [])])
def existing_path(value: str) -> Path:
path = Path(value)
if not path.exists():
raise argparse.ArgumentTypeError(f"path not found: {value}")
return path
# Python files are covered by `black`.
PATTERNS = (
"*.cpp",
"*.h",
"*.i",
"*.cmake",
"*/CMakeLists.txt",
)
REPO_ROOT = Path(subprocess.check_output(["git", "rev-parse", "--show-toplevel"], text=True).strip())
# Generated files; formatted by the express codegen, not by this script.
IGNORED_DIRS = (
REPO_ROOT / "src/ifcparse/schemas",
REPO_ROOT / "win/patches",
)
def get_tracked_files(root: Path | None = None) -> list[Path]:
output = subprocess.check_output(
["git", "ls-files", "--others", "--cached", "--exclude-standard", *PATTERNS],
cwd=root,
text=True,
)
base = root if root is not None else Path()
filepaths = []
for line in output.splitlines():
filepath = base / line
if not any(filepath.resolve().is_relative_to(d) for d in IGNORED_DIRS):
filepaths.append(filepath)
return filepaths
def main() -> int:
# anything after "--" is forwarded to pytest, e.g. `--test -- --capture=no`
argv = sys.argv[1:]
if "--" in argv:
split = argv.index("--")
argv, extra_args = argv[:split], argv[split + 1 :]
else:
extra_args = []
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description=__doc__,
)
parser.add_argument("paths", type=existing_path, nargs="*", help="files or directories to check")
parser.add_argument(
"--check",
action="store_true",
help="only check for whitespace issues without applying fixes",
)
parser.add_argument(
"--test",
action="store_true",
help="run self-tests",
)
parser.add_argument(
"--verbose",
action="store_true",
help="print each checked path",
)
args = parser.parse_args(argv)
if args.test:
TestChecker.run_tests(extra_args)
return 0
if args.paths:
filepaths: list[Path] = []
for path in args.paths:
filepaths.extend(get_tracked_files(path) if path.is_dir() else [path])
else:
filepaths = get_tracked_files()
# dict.fromkeys() dedupes while preserving order, unlike set().
filepaths = list(dict.fromkeys(filepaths))
checker = Checker()
for filepath in filepaths:
if args.verbose:
print(f"checking {filepath}")
checker.check_stray_cr(filepath, args.check)
checker.check_line_endings_mismatch(filepath, args.check)
checker.check_eof_newline(filepath, args.check)
checker.check_trailing_whitespaces(filepath, args.check)
print(f"{len(filepaths)} file(s) checked.")
if not checker.issues:
color = C.GREEN
elif args.check:
color = C.RED
else:
color = C.YELLOW
outcome = "found" if args.check else "found and fixed"
print(f"{color}{checker.issues} issue(s) {outcome}.{C.RESET}")
return 1 if args.check and checker.issues else 0
if __name__ == "__main__":
sys.exit(main())
@@ -26,7 +26,7 @@ jobs:
working-directory: src/bonsaiviewer-autodesk
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
+2 -11
View File
@@ -16,7 +16,7 @@ on:
- 'src/ifc5d/ifc5d/**'
- 'src/ifccityjson/**'
branches:
- v0.8.0
- v0.9.0
workflow_dispatch:
jobs:
@@ -51,19 +51,10 @@ jobs:
name: "Linux Build",
short_name: linux,
}
- {
name: "MacOS Build",
short_name: macos,
}
- {
name: "MacOS ARM Build",
short_name: macosm1,
}
exclude:
# Python 3.13 is needed for Blender 5.1+ and Blender dropped Intel Mac support in 5.0.
- pyver: py313
config:
short_name: macos
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
@@ -128,7 +119,7 @@ jobs:
blender --command extension install-file -r user_default -e $bonsai_zip
blender --command extension list
git clone https://github.com/IfcOpenShell/IfcOpenShell.git IfcOpenShell
git clone --branch ${{ github.ref_name }} --single-branch https://github.com/IfcOpenShell/IfcOpenShell.git IfcOpenShell
# Reregister Bonsai.
# Note that running it in background might miss some errors
-9
View File
@@ -34,19 +34,10 @@ jobs:
name: "Linux Build",
short_name: linux,
}
- {
name: "MacOS Build",
short_name: macos,
}
- {
name: "MacOS ARM Build",
short_name: macosm1,
}
exclude:
# Python 3.13 is needed for Blender 5.1+ and Blender dropped Intel Mac support in 5.0.
- pyver: py313
config:
short_name: macos
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
+1 -1
View File
@@ -7,7 +7,7 @@ on:
- '.github/workflows/ci-ifcsverchok-build.yml'
- 'src/ifcsverchok/*'
branches:
- v0.8.0
- v0.9.0
jobs:
activate:
+3 -1
View File
@@ -3,11 +3,13 @@ name: ci-ifctester-org
on:
workflow_dispatch:
push:
branches:
- v0.9.0
paths:
- src/ifctester/**
jobs:
publish_website:
publish_ifctester_org:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v7
+227 -54
View File
@@ -32,20 +32,7 @@ Example usage:
python build-all.py IfcParse IfcOpenShell-Python
Available arguments:
``-py-313`` - build for specific Python version
(building for all supported Python version by default).
``-occt-xxx`` - use a specific OCCT version (e.g. ``-occt-7.8.1``) instead of the default
``-wasm`` - compile for wasm
``-without-xxx`` - do not build dependency ``xxx`` (e.g. ``--without-swig``)
``-mac-cross-compile-intel`` - cross compile for Intel Mac on Apple Silicon host
``-shared`` - build shared libraries. By default will build static.
``-ifcopenshell-shared`` - build only IfcOpenShell's own libraries as shared
(dependencies stay static). Redundant if ``-shared`` is also passed.
``-diskcleanup`` - clean up build directories after finishing building dependencies
``-build-examples`` - build IfcOpenShell examples
``-lto`` - enable link-time optimization (adds ``-flto`` to compiler flags)
``-v`` - enable verbose logs
Run with --help to see available arguments.
Used environment variables:
@@ -74,6 +61,8 @@ Used environment variables:
`ADD_COMMIT_SHA` and `VERSION_OVERRIDE` will be set to `ON` while configuring IfcOpenShell
- ``BUILD_BONSAIVIEWER`` - enable building BonsaiViewer, `off` by default.
- ``IFCOS_BUILD_PYTHON_WRAPPER`` - enable building the Python wrapper, `on` by default.
- ``PYTHON_USER_SITE`` - install the Python wrapper into the user's site-packages directory
instead of the interpreter's prefix, `off` by default.
# This script builds IfcOpenShell and its dependencies #
# #
@@ -118,6 +107,9 @@ Used environment variables:
"""
from __future__ import annotations
import argparse
import glob
import logging
import multiprocessing
@@ -129,12 +121,13 @@ import subprocess as sp
import sys
import sysconfig
import tarfile
import textwrap
import threading
import time
from collections.abc import Generator, Sequence
from datetime import datetime
from pathlib import Path
from typing import Literal
from typing import Literal, NamedTuple
from urllib.request import urlretrieve
from typing_extensions import assert_never
@@ -162,6 +155,7 @@ ADD_COMMIT_SHA = is_on_off(os.getenv("ADD_COMMIT_SHA"), default=False)
IFCOS_BUILD_PYTHON_WRAPPER = is_on_off(os.getenv("IFCOS_BUILD_PYTHON_WRAPPER"), default=True)
BUILD_BONSAIVIEWER = is_on_off(os.getenv("BUILD_BONSAIVIEWER"), default=False)
USE_OCCT = is_on_off(os.getenv("USE_OCCT"), default=True)
PYTHON_USER_SITE = is_on_off(os.getenv("PYTHON_USER_SITE"), default=False)
PYTHON_VERSIONS = ["3.10.3", "3.11.8", "3.12.1", "3.13.6", "3.14.0"]
JSON_VERSION = "3.11.3"
@@ -202,10 +196,142 @@ strip = "strip"
xz = "xz" # Used implicitly for `tar -xf *.tar.xz`.
brew = "brew"
explicit_targets = [s for s in sys.argv[1:] if not s.startswith("-")]
class Args(NamedTuple):
explicit_targets: list[str]
build_examples: bool
diskcleanup: bool
lto: bool
verbose: bool
shared: bool
ifcopenshell_shared: bool
occt_shared: bool
mac_cross_compile_intel: bool
wasm: bool
class DynamicArgs(NamedTuple):
without: set[str]
py_versions: set[str]
occt_version: str | None
@classmethod
def from_unknown_flags(cls, unknown_flags: list[str], arg_parser: argparse.ArgumentParser) -> DynamicArgs:
flags = set(s.lstrip("-") for s in unknown_flags if s.startswith("-"))
without: set[str] = set()
py_versions: set[str] = set()
occt_versions: set[str] = set()
leftover: set[str] = set()
for f in flags:
if f.startswith("without-"):
without.add(f.removeprefix("without-").lower())
elif f.startswith("py-"):
py_versions.add(f.removeprefix("py-"))
elif f.startswith("occt-"):
occt_versions.add(f.removeprefix("occt-"))
else:
leftover.add(f)
if leftover:
arg_parser.error(f"unrecognized arguments: {', '.join('-' + f for f in sorted(leftover))}")
if len(occt_versions) > 1:
arg_parser.error(f"more than one OCCT version provided: {', '.join(sorted(occt_versions))}")
occt_version = next(iter(occt_versions), None)
return cls(without=without, py_versions=py_versions, occt_version=occt_version)
def parse_args() -> tuple[Args, DynamicArgs]:
arg_parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=textwrap.dedent("""\
Additional dynamic -flags (not declared above):
-py-313 build for specific Python version
(building for all supported Python versions by default)
-occt-xxx use a specific OCCT version (e.g. -occt-7.8.1) instead of the default
-without-xxx do not build dependency `xxx` (e.g. --without-swig)"""),
)
arg_parser.add_argument("explicit_targets", nargs="*", help="Targets provided by CLI.")
arg_parser.add_argument(
"--build-examples",
action="store_true",
default=False,
help="Build IfcOpenShell examples.",
)
arg_parser.add_argument(
"-diskcleanup",
"--diskcleanup",
action="store_true",
default=False,
help="Clean up build directories after finishing building dependencies.",
)
arg_parser.add_argument(
"-lto",
"--lto",
action="store_true",
default=False,
help="Enable link-time optimization (adds -flto to compiler flags).",
)
arg_parser.add_argument(
"-v",
"--verbose",
action="store_true",
default=False,
help="Enable verbose logs.",
)
arg_parser.add_argument(
"-shared",
"--shared",
action="store_true",
default=False,
help="Build shared libraries. By default will build static.",
)
arg_parser.add_argument(
"-ifcopenshell-shared",
"--ifcopenshell-shared",
action="store_true",
default=False,
help="Build only IfcOpenShell's own libraries as shared (dependencies stay static). "
"Redundant if -shared is also passed.",
)
arg_parser.add_argument(
"--occt-shared",
action="store_true",
default=False,
help="Build OCCT as shared. Redundant if -shared is also passed.",
)
arg_parser.add_argument(
"-mac-cross-compile-intel",
"--mac-cross-compile-intel",
action="store_true",
default=False,
help="Cross compile for Intel Mac on Apple Silicon host.",
)
arg_parser.add_argument("-wasm", "--wasm", action="store_true", default=False, help="Compile for wasm.")
namespace, unknown_flags = arg_parser.parse_known_args()
args = Args(
explicit_targets=namespace.explicit_targets,
build_examples=namespace.build_examples,
diskcleanup=namespace.diskcleanup,
lto=namespace.lto,
verbose=namespace.verbose,
shared=namespace.shared,
ifcopenshell_shared=namespace.ifcopenshell_shared or namespace.shared,
occt_shared=namespace.occt_shared or namespace.shared,
mac_cross_compile_intel=namespace.mac_cross_compile_intel,
wasm=namespace.wasm,
)
dynamic_args = DynamicArgs.from_unknown_flags(unknown_flags, arg_parser)
return args, dynamic_args
ARGS, DYNAMIC_ARGS = parse_args()
explicit_targets: set[str] = set(ARGS.explicit_targets)
"""Targets provided by CLI."""
flags = set(s.lstrip("-") for s in sys.argv[1:] if s.startswith("-"))
"""CLI flags."""
# Helper function for coloured printing
@@ -224,17 +350,11 @@ def cecho(message, color=NO_COLOR):
logger.info(f"{color}{message}\033[0m")
# Flags.
BUILD_EXAMPLES = "build-examples" in flags
DISK_CLEANUP = "diskcleanup" in flags
LTO = "lto" in flags
VERBOSE = "v" in flags
APPLE = platform.system() == "Darwin"
MAC_CROSS_COMPILE_INTEL = "mac-cross-compile-intel" in flags
MAC_CROSS_COMPILE_INTEL = ARGS.mac_cross_compile_intel
assert platform.system() == "Darwin" or not MAC_CROSS_COMPILE_INTEL
WASM = "wasm" in flags
WASM = ARGS.wasm
"""Build WASM outside pyodide build environment."""
WASM_CMAKE_IS_USING_INIT_VARS = False
if WASM:
@@ -380,12 +500,12 @@ dependency_tree: dict[str, tuple[str, ...]] = {
def gather_dependencies(dep: str) -> Generator[str]:
yield dep
for d in dependency_tree[dep]:
if f"without-{d.lower()}" not in flags:
if d.lower() not in DYNAMIC_ARGS.without:
for x in gather_dependencies(d):
yield x
if VERBOSE:
if ARGS.verbose:
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
ch.setFormatter(formatter)
@@ -406,29 +526,26 @@ else:
MAC_CROSS_COMPILE_INTEL_AUTOCONF_HOST_ARGS = []
OFF_ON = ["OFF", "ON"]
BUILD_STATIC = "shared" not in flags
BUILD_STATIC = not ARGS.shared
"""Whether dependencies are built static."""
IFCOPENSHELL_STATIC = BUILD_STATIC and "ifcopenshell-shared" not in flags
"""Whether IfcOpenShell's own libraries are built static."""
ENABLE_FLAG = "--enable-static" if BUILD_STATIC else "--enable-shared"
DISABLE_FLAG = "--disable-shared" if BUILD_STATIC else "--disable-static"
LINK_TYPE = "static" if BUILD_STATIC else "shared"
LINK_TYPE_UCFIRST = LINK_TYPE.capitalize()
LIBRARY_EXT = "a" if BUILD_STATIC else "so"
PIC = "-fPIC" if BUILD_STATIC else ""
if any(f.startswith("py-") for f in flags):
PYTHON_VERSIONS = [pyv for pyv in PYTHON_VERSIONS if f"py-{''.join(pyv.split('.')[:2])}" in flags]
if DYNAMIC_ARGS.py_versions:
PYTHON_VERSIONS = [pyv for pyv in PYTHON_VERSIONS if "".join(pyv.split(".")[:2]) in DYNAMIC_ARGS.py_versions]
if any(f.startswith("occt-") for f in flags):
OCCT_VERSION = next(f.split("-", 1)[1] for f in flags if f.startswith("occt-"))
if DYNAMIC_ARGS.occt_version is not None:
OCCT_VERSION = DYNAMIC_ARGS.occt_version
if explicit_targets:
targets = {dep for target in explicit_targets for dep in gather_dependencies(target)}
else:
targets = set(dependency_tree.keys())
targets = set(t for t in targets if "without-%s" % t.lower() not in flags)
targets = set(t for t in targets if t.lower() not in DYNAMIC_ARGS.without)
if not explicit_targets and not BUILD_BONSAIVIEWER:
targets.difference_update({"BonsaiViewer", "qt6"})
if BUILD_BONSAIVIEWER:
@@ -511,7 +628,7 @@ def restore_env(var_name: str, old_value: str | None) -> None:
os.environ[var_name] = old_value
def run(cmds: Sequence[str], cwd: str | None = None, can_fail: bool = False) -> str:
def run(cmds: Sequence[str], cwd: str | None = None, can_fail: bool = False, env: dict[str, str] | None = None) -> str:
"""
Wraps `subprocess.Popen.communicate()` and logs the command being executed,
sets up logging `stderr` to `LOG_FILE` (in append mode) and returns stdout
@@ -535,7 +652,7 @@ def run(cmds: Sequence[str], cwd: str | None = None, can_fail: bool = False) ->
# Ensure both live logs available in the log file
# and the putput.
with open(LOG_FILE, "a", encoding="utf-8") as log_file_handle:
proc = sp.Popen(cmds, cwd=cwd, stdout=sp.PIPE, stderr=sp.PIPE, encoding="utf-8")
proc = sp.Popen(cmds, cwd=cwd, stdout=sp.PIPE, stderr=sp.PIPE, encoding="utf-8", env=env)
assert proc.stdout and proc.stderr
t_out = threading.Thread(target=stream_reader, args=(proc.stdout, stdout, log_file_handle))
@@ -821,7 +938,7 @@ def build_dependency(
)
logger.info(f"\rInstalled {name} \n")
if DISK_CLEANUP:
if ARGS.diskcleanup:
shutil.rmtree(build_dir, ignore_errors=True)
@@ -927,6 +1044,8 @@ ADDITIONAL_ARGS_STR = " ".join(ADDITIONAL_ARGS)
CXXFLAGS_MINIMAL = f"{CXXFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
CFLAGS_MINIMAL = f"{CFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
CXXFLAGS_SHARED = CXXFLAGS_MINIMAL
CFLAGS_SHARED = CFLAGS_MINIMAL
if WASM:
# WASM `SIDE_MODULE_` are absorbed by `emcmake` automatically.
CXXFLAGS = CXXFLAGS_MINIMAL
@@ -936,19 +1055,19 @@ elif sp.call([bash, "-c", "ld --gc-sections 2>&1 | grep -- --gc-sections &> /dev
CXXFLAGS = f"{CXXFLAGS} {PIC} -fdata-sections -ffunction-sections -fvisibility=hidden -fvisibility-inlines-hidden {ADDITIONAL_ARGS_STR}"
CFLAGS = f"{CFLAGS} {PIC} -fdata-sections -ffunction-sections -fvisibility=hidden {ADDITIONAL_ARGS_STR}"
else:
CXXFLAGS = CXXFLAGS_MINIMAL
CFLAGS = CFLAGS_MINIMAL
CXXFLAGS = CXXFLAGS_SHARED
CFLAGS = CFLAGS_SHARED
LDFLAGS = f"{LDFLAGS} -Wl,--gc-sections {ADDITIONAL_ARGS_STR}"
else:
if BUILD_STATIC:
CXXFLAGS = f"{CXXFLAGS} {PIC} -fvisibility=hidden -fvisibility-inlines-hidden {ADDITIONAL_ARGS_STR}"
CFLAGS = f"{CFLAGS} {PIC} -fvisibility=hidden -fvisibility-inlines-hidden {ADDITIONAL_ARGS_STR}"
else:
CXXFLAGS = CXXFLAGS_MINIMAL
CFLAGS = CFLAGS_MINIMAL
CXXFLAGS = CXXFLAGS_SHARED
CFLAGS = CFLAGS_SHARED
LDFLAGS = f"{LDFLAGS} {ADDITIONAL_ARGS_STR}"
if LTO:
if ARGS.lto:
for f in compiler_flags:
locals()[f] += f" -flto={IFCOS_NUM_BUILD_PROCS}"
@@ -1031,6 +1150,9 @@ if "swig" in targets:
if USE_OCCT and "occ" in targets:
occt_args: list[str] = []
patches: list[str] = []
occt_link_type = "Shared" if ARGS.occt_shared else "Static"
occt_name = f"occt-shared-{OCCT_VERSION}" if ARGS.occt_shared else f"occt-{OCCT_VERSION}"
OCCT_INSTALL_PATH = f"{DEPS_DIR}/install/{occt_name}"
if OCCT_VERSION < "7.4":
patches.append("./patches/occt/enable-exception-handling.patch")
@@ -1045,12 +1167,23 @@ if USE_OCCT and "occ" in targets:
if WASM:
patches.append("./patches/occt/no_em_js.patch")
if ARGS.occt_shared:
# Using static flags for shared builds break it
# (e.g. `-fvisibility=hidden` hides many symbols).
# So we temporarily override flags.
OLD_CPP_FLAGS = os.environ["CPPFLAGS"]
OLD_CXX_FLAGS = os.environ["CXXFLAGS"]
OLD_C_FLAGS = os.environ["CFLAGS"]
os.environ["CXXFLAGS"] = CXXFLAGS_SHARED
os.environ["CPPFLAGS"] = CXXFLAGS_SHARED
os.environ["CFLAGS"] = CFLAGS_SHARED
build_dependency(
name=f"occt-{OCCT_VERSION}",
name=occt_name,
mode="cmake",
build_tool_args=[
f"-DINSTALL_DIR={DEPS_DIR}/install/occt-{OCCT_VERSION}",
f"-DBUILD_LIBRARY_TYPE={LINK_TYPE_UCFIRST}",
f"-DINSTALL_DIR={OCCT_INSTALL_PATH}",
f"-DBUILD_LIBRARY_TYPE={occt_link_type}",
f"-DBUILD_MODULE_Draw=0",
f"-DBUILD_RELEASE_DISABLE_EXCEPTIONS=Off",
# Disable xlib explicitly, as it tries to use it on Desktop Ubuntu, adding unnecessary dependency.
@@ -1069,6 +1202,11 @@ if USE_OCCT and "occ" in targets:
patch=patches,
revision="V" + OCCT_VERSION.replace(".", "_"),
)
if ARGS.occt_shared:
restore_env("CPPFLAGS", OLD_CPP_FLAGS)
restore_env("CXXFLAGS", OLD_CXX_FLAGS)
restore_env("CFLAGS", OLD_C_FLAGS)
elif "occ" in targets:
build_dependency(
name=f"oce-{OCE_VERSION}",
@@ -1431,7 +1569,7 @@ if "qt6" in targets:
cecho("Building IfcOpenShell:", GREEN)
IFCOS_DIR = os.path.join(DEPS_DIR, "build", "ifcopenshell")
if os.environ.get("NO_CLEAN", "").lower() not in {"1", "on", "true"}:
if not is_on_off(os.getenv("NO_CLEAN"), default=False):
if os.path.exists(IFCOS_DIR):
shutil.rmtree(IFCOS_DIR)
os.makedirs(IFCOS_DIR, exist_ok=True)
@@ -1441,8 +1579,8 @@ os.makedirs(ifcos_build_dir, exist_ok=True)
cmake_args = [
"-DUSE_MMAP=OFF",
f"-DBUILD_EXAMPLES={OFF_ON[BUILD_EXAMPLES]}",
"-DBUILD_SHARED_LIBS=" + OFF_ON[not IFCOPENSHELL_STATIC],
f"-DBUILD_EXAMPLES={OFF_ON[ARGS.build_examples]}",
"-DBUILD_SHARED_LIBS=" + OFF_ON[ARGS.ifcopenshell_shared],
"-DGLTF_SUPPORT=ON",
"-DBoost_NO_BOOST_CMAKE=On",
"-DCREATE_BUNDLE=On",
@@ -1487,7 +1625,7 @@ if "cgal" in targets:
cmake_args.append(f"-DCGAL_WITH_GMPXX=Off")
if "occ" in targets and USE_OCCT:
cmake_args_prefix_path.append(f"{DEPS_DIR}/install/occt-{OCCT_VERSION}")
cmake_args_prefix_path.append(OCCT_INSTALL_PATH)
elif "occ" in targets:
# We don't support find_package for OCE.
@@ -1573,6 +1711,42 @@ if not WASM and (
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "VERBOSE=1"], cwd=ifcos_build_dir)
run([make, "install/strip" if BUILD_CFG == "Release" else "install"], cwd=ifcos_build_dir)
def test_examples() -> None:
cecho("Running examples...", GREEN)
examples_bin_dir = Path(DEPS_DIR) / "install" / "ifcopenshell" / "bin"
examples_env = os.environ.copy()
ld_library_paths = ["../lib"]
if ARGS.occt_shared:
ld_library_paths.append(f"{OCCT_INSTALL_PATH}/lib")
examples_env["LD_LIBRARY_PATH"] = os.pathsep.join(ld_library_paths)
examples: dict[tuple[str, ...], str | None] = {
("./IfcOpenHouse",): "IfcOpenHouse.ifc",
("./IfcParseExamples", "IfcOpenHouse.ifc"): None,
("./IfcAdvancedHouse",): "IfcAdvancedHouse.ifc",
}
# Only for ifc4x3 schema.
if (examples_bin_dir / "IfcAlignment").is_file():
examples[("./IfcAlignment",)] = "FHWA_Bridge_Geometry_Alignment_Example.ifc"
examples[("./IfcSimplifiedAlignment",)] = "FHWA_Bridge_Geometry_Alignment_Example_Simplified.ifc"
produced_files: set[str] = set()
try:
for cmd, expected_file in examples.items():
run(cmd, cwd=str(examples_bin_dir), env=examples_env)
if expected_file is None:
continue
if not (examples_bin_dir / expected_file).is_file():
raise RuntimeError(f"Example `{' '.join(cmd)}` did not produce expected file '{expected_file}'.")
produced_files.add(expected_file)
finally:
for produced_file in produced_files:
(examples_bin_dir / produced_file).unlink(missing_ok=True)
if ARGS.build_examples:
test_examples()
if "IfcOpenShell-Python" in targets:
wrapper_ldflags = ""
if platform.system() == "Darwin":
@@ -1624,8 +1798,7 @@ if "IfcOpenShell-Python" in targets:
*([f"-DPYTHON_MODULE_INSTALL_DIR={REPO_PATH}"] * WASM),
f"-DPYTHON_INCLUDE_DIR={python_include}",
f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/ifcopenshell/tmp",
"-DUSERSPACE_PYTHON_PREFIX="
+ ["Off", "On"][os.environ.get("PYTHON_USER_SITE", "").lower() in {"1", "on", "true"}],
"-DUSERSPACE_PYTHON_PREFIX=" + OFF_ON[PYTHON_USER_SITE],
],
cmake_dir=CMAKE_DIR,
cwd=ifcos_build_dir,
+1
View File
@@ -1,5 +1,6 @@
#!/usr/bin/env python3
"""Intended to be run after nix/build-all.py has finished the wasm build."""
import shutil
import subprocess
from pathlib import Path
+13 -5
View File
@@ -6,11 +6,6 @@ version = "0.0.0"
[tool.black]
line-length = 120
include = '''
src/.*.pyi?$
|nix/.*.pyi?$
|pyodide/.*.pyi?$
'''
extend-exclude = '''
src/ifcopenshell-python/ifcopenshell/express/rules/*
|src/ifcopenshell-python/ifcopenshell/express/express_parser.py
@@ -19,6 +14,15 @@ extend-exclude = '''
|src/ifc2ca/templates/*
|src/svgfill
|src/exterior-shell-extractor
|choco/bonsai/tools/enable_blenderbim_addon.py
|choco/bonsai/tools/disable_blenderbim_addon.py
|docs/conf.py
|docs/generate_docs.py
|aws/lambda/example_handler/__init__.py
|conda/update_version_init.py
|test/bpy.py
|test/tests.py
|test/run.py
'''
[tool.pyright]
@@ -112,7 +116,9 @@ invalid-assignment = "ignore"
invalid-parameter-default = "ignore"
missing-override-decorator = "ignore"
invalid-yield = "ignore"
unsound-yield = "ignore"
invalid-return-type = "ignore"
unsound-return-statement = "ignore"
non-callable-init-subclass = "ignore"
not-iterable = "ignore"
possibly-missing-attribute = "ignore"
@@ -181,6 +187,8 @@ dev-setup.help = "Install repo packages in editable mode"
ruff = "ruff check"
check-whitespace = "uv run .github/scripts/check-whitespace.py"
black = "black ."
ty.sequence = ["ty-bonsai", "ty-ios"]
+1 -1
View File
@@ -1,5 +1,5 @@
black==26.3.1
ruff==0.16.0
poethepoet
ty==0.0.63
ty==0.0.72
gersemi==0.28.0
+4 -23
View File
@@ -69,32 +69,17 @@ endif # def PYVERSION
IFCMERGE_VERSION:=2026-04-07
ifdef PLATFORM
SUPPORTED_PLATFORMS := linux macos macosm1 win
SUPPORTED_PLATFORMS := linux macosm1 win
ifeq ($(filter $(PLATFORM),$(SUPPORTED_PLATFORMS)),)
$(error Unsupported PLATFORM=$(PLATFORM). Must be one of $(SUPPORTED_PLATFORMS))
endif
ifeq ($(PLATFORM),macos)
ifeq ($(PYVERSION),py313)
$(error Blender 5.1 with Python 3.13 doesn't support intel macOS.)
endif
endif
ifeq ($(PLATFORM), linux)
PYPI_PLATFORM:=--platform manylinux_2_17_x86_64
BLENDER_PLATFORM:=linux-x64
endif
ifeq ($(PLATFORM), macos)
ifeq ($(PYVERSION), py311)
PYPI_PLATFORM:=--platform macosx_10_10_x86_64
else
PYPI_PLATFORM:=--platform macosx_10_13_x86_64
endif
BLENDER_PLATFORM:=macos-x64
endif
ifeq ($(PLATFORM), macosm1)
PYPI_PLATFORM:=--platform macosx_11_0_arm64
BLENDER_PLATFORM:=macos-arm64
@@ -108,7 +93,7 @@ endif
endif # def PLATFORM
# Current build commit hash.
OLD:=3e7b739
OLD:=ad113e1
.PHONY: bump
bump:
ifndef NEW
@@ -194,10 +179,8 @@ endif
# Provides networkx graph analysis for project dependency calculations
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download networkx --dest=./wheels
# Required by IFCDiff
# Pinned <9.1: deepdiff 9.1.0 adds cachebox<6,>=5.2 which only ships macOS x86_64
# wheels for macosx_10_12+ and is incompatible with our macos py311 --platform
# macosx_10_10_x86_64 target. Revisit once the macos py311 platform tag is bumped
# to 10_13 (matching py312/py313).
# Pinned <9.1: deepdiff 9.1.0 adds the compiled dependency cachebox<6,>=5.2,
# which this platformless download cannot provide for every target platform.
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download "deepdiff<9.1" --dest=./wheels
# Required by IFCCSV and ifcopenshell.util.selector
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download lark --dest=./wheels
@@ -215,8 +198,6 @@ endif
# pyradiance is using different platform versions than defaults in our makefile.
ifeq ($(PLATFORM), linux)
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download pyradiance --platform manylinux_2_28_x86_64 --python-version $(PYPI_VERSION) --implementation $(PYPI_IMP) --only-binary=:all: --dest=./wheels
else ifeq ($(PLATFORM), macos)
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download pyradiance --platform macosx_10_13_x86_64 --python-version $(PYPI_VERSION) --implementation $(PYPI_IMP) --only-binary=:all: --dest=./wheels
else
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download pyradiance $(PYPI_PLATFORM) --python-version $(PYPI_VERSION) --implementation $(PYPI_IMP) --only-binary=:all: --dest=./wheels
endif
+56 -4
View File
@@ -428,7 +428,8 @@ void MainWindow::setupPanels() {
spatial_panel_ = new modules::spatial_hierarchy::SpatialHierarchyPanel(this);
properties_panel_ = new modules::properties::PropertiesPanel(this);
models_view_ = new modules::models::ModelsPanelView(models_panel_, session_state_, this);
models_view_ = new modules::models::ModelsPanelView(
models_panel_, session_state_, viewport_widget_->viewport(), this);
spatial_view_ = new modules::spatial_hierarchy::SpatialHierarchyPanelView(spatial_panel_, session_state_, this);
properties_view_ = new modules::properties::PropertiesPanelView(properties_panel_, session_state_, this);
@@ -481,6 +482,13 @@ void MainWindow::setupStatus() {
status_mode_label_ = new QLabel("Ready", this);
status_selection_label_ = new QLabel("No selection", this);
status_perf_label_ = new QLabel(this);
status_memory_label_ = new QLabel(this);
status_memory_label_->setVisible(false);
status_memory_label_->setToolTip(
"The geometry in view needs more GPU memory than is available, so the "
"viewer keeps the largest on-screen parts resident and streams the rest "
"as you move. Right-click a model in the Models panel and choose "
"\"Unload Model\" to free its GPU memory for the others.");
status_progress_bar_ = new QProgressBar(this);
status_perf_label_->setVisible(AppSettings::instance().showStats());
status_progress_bar_->setMaximumWidth(200);
@@ -489,6 +497,7 @@ void MainWindow::setupStatus() {
statusBar()->setSizeGripEnabled(false);
statusBar()->addWidget(status_mode_label_);
statusBar()->addWidget(status_selection_label_, 1);
statusBar()->addPermanentWidget(status_memory_label_);
statusBar()->addPermanentWidget(status_perf_label_);
statusBar()->addPermanentWidget(status_progress_bar_);
@@ -554,16 +563,59 @@ void MainWindow::setupLoader() {
connect(viewport_widget_->viewport(), &ViewportWindow::frameStatsUpdated, this,
[this](const ViewportWindow::FrameStats& stats) {
const double mb = 1.0 / (1024.0 * 1024.0);
// Missing chunks are normal for a moment after every camera move
// while streaming catches up; only a shortfall that persists means
// the view does not fit, and only that is worth telling the user.
constexpr qint64 kShortfallNoticeMs = 3000;
if (stats.chunks_wanted_missing == 0) {
memory_shortfall_since_.invalidate();
status_memory_label_->setVisible(false);
} else {
if (!memory_shortfall_since_.isValid()) memory_shortfall_since_.start();
if (memory_shortfall_since_.elapsed() >= kShortfallNoticeMs) {
status_memory_label_->setText(
QString("GPU memory full: %1 of %2 visible chunks (%3 MB) not loaded")
.arg(stats.chunks_wanted_missing)
.arg(stats.chunks_wanted)
.arg(double(stats.wanted_missing_bytes) * mb, 0, 'f', 0));
status_memory_label_->setVisible(true);
}
}
if (!status_perf_label_->isVisible()) return;
status_perf_label_->setText(
QString("%1 fps | %2 ms | %3/%4 obj | %5/%6 tri | %7 draws")
QString text =
QString("%1 fps | %2 ms | %3/%4 obj | %5/%6 tri | %7 draws | VRAM %8/%9 MB")
.arg(stats.fps, 0, 'f', 1)
.arg(stats.frame_time_ms, 0, 'f', 1)
.arg(stats.visible_objects)
.arg(stats.total_objects)
.arg(stats.visible_triangles)
.arg(stats.total_triangles)
.arg(stats.gl_draw_calls));
.arg(stats.gl_draw_calls)
.arg(double(stats.vram_used_bytes) * mb, 0, 'f', 0)
.arg(double(stats.vram_capacity_bytes) * mb, 0, 'f', 0);
// The budget is where the pool may grow to; the pool can also sit
// a sub-buffer above it (a release would undershoot). Show it
// only when it tells the user something capacity does not.
if (stats.vram_budget_bytes > 0
&& stats.vram_budget_bytes != stats.vram_capacity_bytes) {
text += QString(" (budget %1)")
.arg(double(stats.vram_budget_bytes) * mb, 0, 'f', 0);
}
// Device total is only known when a driver backend answered.
if (stats.device_vram_total_bytes > 0) {
text += QString(" | Device %1/%2 MB")
.arg(double(stats.device_vram_used_bytes) * mb, 0, 'f', 0)
.arg(double(stats.device_vram_total_bytes) * mb, 0, 'f', 0);
}
if (stats.chunks_wanted_missing > 0) {
text += QString(" | %1/%2 chunks waiting")
.arg(stats.chunks_wanted_missing)
.arg(stats.chunks_wanted);
}
status_perf_label_->setText(text);
});
connect(viewport_widget_->viewport(), &ViewportWindow::objectPicked,
this, [this](uint32_t object_id) {
+5
View File
@@ -26,6 +26,7 @@
#include <QStringList>
class QLabel;
#include <QElapsedTimer>
class QDockWidget;
class QMenu;
class QProgressBar;
@@ -70,6 +71,10 @@ private:
QLabel* status_mode_label_ = nullptr;
QLabel* status_selection_label_ = nullptr;
QLabel* status_perf_label_ = nullptr;
// Shown while the visible geometry persistently exceeds what fits in
// GPU memory (see onFrameStats): the user's cue to unload models.
QLabel* status_memory_label_ = nullptr;
QElapsedTimer memory_shortfall_since_;
QProgressBar* status_progress_bar_ = nullptr;
bonsaiviewer::components::TabBar* ribbon_tabs_ = nullptr;
QStackedWidget* ribbon_pages_ = nullptr;
+4
View File
@@ -199,6 +199,10 @@ void SessionState::notifyModelGeometryReady(uint32_t session_model_id) {
emit modelGeometryReady(session_model_id);
}
void SessionState::notifyModelLoadStateChanged(const QString& model_id) {
emit modelLoadStateChanged(model_id);
}
void SessionState::notifyProjectOpened(const QString& path) {
emit projectOpened(path);
}
+5
View File
@@ -89,6 +89,7 @@ public:
void notifyFederationChanged();
void notifyVisibilityChanged();
void notifyModelGeometryReady(uint32_t session_model_id);
void notifyModelLoadStateChanged(const QString& model_id);
void notifyProjectOpened(const QString& path);
void notifyProjectSaved(const QString& path);
void notifyProjectReset();
@@ -107,6 +108,10 @@ signals:
// for both sidecar-cache and stream loads; subscribers that just need to
// re-derive view state (e.g. ViewportView::refresh) listen to this.
void modelGeometryReady(uint32_t session_model_id);
// Fires when a model was unloaded from, or loaded back onto, the GPU
// (commands::unloadModel / loadModel). The viewport is the authority
// for the state itself — ViewportWindow::isModelUnloaded.
void modelLoadStateChanged(const QString& model_id);
// Fires when a model's live IFC data source (the .ifc/.rdb, opened in the
// background after a sidecar-cache hit) becomes available for queries —
// e.g. so the spatial hierarchy can be built once the file is loaded.
@@ -262,6 +262,28 @@ void removeModel(SessionState& session, ViewportWindow& viewport, QWidget& host,
session.setStatusMessage("Models", "Model removed");
}
void unloadModel(SessionState& session, ViewportWindow& viewport, const QString& model_id) {
const uint32_t session_model_id = session.sessionModelIdForModelId(model_id);
if (session_model_id == 0) return;
if (session.loader()->isLoadingModel(session_model_id)) return;
const double freed_mb = double(viewport.modelVramBytes(session_model_id)) / (1024.0 * 1024.0);
viewport.unloadModel(session_model_id);
session.notifyModelLoadStateChanged(model_id);
session.setStatusMessage("Models", QString("Model unloaded (freed %1 MB of GPU memory)")
.arg(freed_mb, 0, 'f', 0));
}
void loadModel(SessionState& session, ViewportWindow& viewport, const QString& model_id) {
const uint32_t session_model_id = session.sessionModelIdForModelId(model_id);
if (session_model_id == 0) return;
if (!viewport.loadModel(session_model_id)) {
session.setStatusMessage("Models", "Not enough GPU memory to load this model");
return;
}
session.notifyModelLoadStateChanged(model_id);
session.setStatusMessage("Models", "Model loaded");
}
void viewModels(SessionState& session, ViewportWindow& viewport, const QStringList& model_ids) {
// Federation ids are the panel's currency; the viewport speaks session
// model ids. sessionModelIdForModelId returns 0 for a model the viewport
@@ -60,6 +60,12 @@ void moveGroup(SessionState& session, const QString& id, const QString& parent_g
void moveModels(SessionState& session, const QStringList& ids, const QString& parent_group_id);
void removeGroup(SessionState& session, QWidget& host, const QString& group_id);
void removeModel(SessionState& session, ViewportWindow& viewport, QWidget& host, const QString& model_id);
// GPU residency, distinct from visibility (hide) and from membership
// (remove): unloadModel frees everything the model holds on the device
// while it stays in the federation; loadModel brings it back. Both emit
// modelLoadStateChanged.
void unloadModel(SessionState& session, ViewportWindow& viewport, const QString& model_id);
void loadModel(SessionState& session, ViewportWindow& viewport, const QString& model_id);
// "View Selected Model" — frame the camera on just these models' geometry, the
// way View All frames the whole federation. Models that carry no loaded
// geometry (never loaded, or still streaming their metadata) contribute
@@ -25,16 +25,25 @@
#include "../../../ifcviewer/Federation.h"
#include <QBrush>
#include <QFont>
#include <QColor>
namespace bonsaiviewer::modules::models {
namespace {
QStandardItem* siblingVisibilityItem(QStandardItem* name_item) {
QStandardItem* siblingItem(QStandardItem* name_item, Column column) {
QStandardItem* parent = name_item->parent();
if (!parent) parent = name_item->model()->invisibleRootItem();
return parent->child(name_item->row(), 1);
return parent->child(name_item->row(), int(column));
}
QStandardItem* siblingVisibilityItem(QStandardItem* name_item) {
return siblingItem(name_item, VisibilityColumn);
}
QString formatMegabytes(quint64 bytes) {
return QString("%1 MB").arg(double(bytes) / (1024.0 * 1024.0), 0, 'f', 0);
}
template <typename F>
@@ -51,7 +60,7 @@ FederationItemModel::FederationItemModel(Federation* federation, QObject* parent
: QStandardItemModel(parent)
, federation_(federation)
{
setColumnCount(2);
setColumnCount(ColumnCount);
rebuildAll();
connect(federation_, &Federation::groupAdded, this, &FederationItemModel::onGroupAdded);
@@ -67,7 +76,7 @@ FederationItemModel::FederationItemModel(Federation* federation, QObject* parent
void FederationItemModel::rebuildAll() {
clear();
setColumnCount(2);
setColumnCount(ColumnCount);
id_to_name_item_.clear();
for (const auto& root_group : federation_->rootGroups()) {
@@ -121,6 +130,14 @@ QStandardItem* FederationItemModel::makeVisibilityItem(ItemKind kind, bool visib
return item;
}
QStandardItem* FederationItemModel::makeMemoryItem() const {
auto* item = new QStandardItem(QString());
item->setEditable(false);
item->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
item->setForeground(QBrush(QColor(bonsaiviewer::ViewerSettings::instance().color("disabled_text"))));
return item;
}
void FederationItemModel::styleRowVisibility(QStandardItem* name_item, bool visible) const {
QStandardItem* vis_item = siblingVisibilityItem(name_item);
if (visible) {
@@ -133,6 +150,22 @@ void FederationItemModel::styleRowVisibility(QStandardItem* name_item, bool visi
}
}
void FederationItemModel::setModelResidency(const QString& model_id, bool unloaded, quint64 vram_bytes) {
QStandardItem* name_item = findItem(model_id);
if (!name_item) return;
QStandardItem* memory_item = siblingItem(name_item, MemoryColumn);
if (!memory_item) return;
const QString text = unloaded ? QStringLiteral("unloaded")
: vram_bytes > 0 ? formatMegabytes(vram_bytes)
: QString();
if (memory_item->text() != text) memory_item->setText(text);
QFont font = name_item->font();
if (font.italic() != unloaded) {
font.setItalic(unloaded);
name_item->setFont(font);
}
}
QStandardItem* FederationItemModel::findItem(const QString& id) const {
return id_to_name_item_.value(id, nullptr);
}
@@ -148,7 +181,7 @@ void FederationItemModel::appendModelTo(QStandardItem* parent_item, const QStrin
if (!model) return;
auto* name_item = makeModelNameItem(model_id, model->display_name);
auto* vis_item = makeVisibilityItem(ItemKind::Model, federation_->isModelEffectivelyVisible(model_id));
parent_item->appendRow({name_item, vis_item});
parent_item->appendRow({name_item, makeMemoryItem(), vis_item});
id_to_name_item_.insert(model_id, name_item);
styleRowVisibility(name_item, federation_->isModelEffectivelyVisible(model_id));
}
@@ -158,7 +191,7 @@ void FederationItemModel::appendGroupSubtreeTo(QStandardItem* parent_item, const
if (!group) return;
auto* name_item = makeGroupNameItem(group_id, group->display_name);
auto* vis_item = makeVisibilityItem(ItemKind::Group, group->visible);
parent_item->appendRow({name_item, vis_item});
parent_item->appendRow({name_item, makeMemoryItem(), vis_item});
id_to_name_item_.insert(group_id, name_item);
styleRowVisibility(name_item, group->visible);
@@ -31,7 +31,7 @@ class Federation;
namespace bonsaiviewer::modules::models {
// QStandardItemModel that mirrors the Federation tree (groups + models in
// two columns: name + visibility icon). Subscribes directly to Federation's
// three columns: name, GPU memory, visibility icon). Subscribes directly to Federation's
// granular signals so each mutation only touches the affected rows — view
// state (expansion, selection, scroll) is preserved automatically.
//
@@ -57,6 +57,12 @@ public:
// previously- and newly-active model rows.
void setActiveModelId(const QString& model_id);
// GPU residency is viewport state, not Federation state, so it is pushed
// in by the owning View: the memory column shows `vram_bytes` for a
// loaded model and "unloaded" for one the user unloaded (which is also
// drawn in italics). Models the viewport knows nothing about show blank.
void setModelResidency(const QString& model_id, bool unloaded, quint64 vram_bytes);
private slots:
void onGroupAdded(const QString& group_id);
void onGroupRemoved(const QString& group_id);
@@ -72,6 +78,7 @@ private:
QStandardItem* makeGroupNameItem(const QString& group_id, const QString& display_name) const;
QStandardItem* makeModelNameItem(const QString& model_id, const QString& display_name) const;
QStandardItem* makeVisibilityItem(ItemKind kind, bool visible) const;
QStandardItem* makeMemoryItem() const;
void styleRowVisibility(QStandardItem* name_item, bool visible) const;
QStandardItem* findItem(const QString& id) const;
+28 -6
View File
@@ -28,6 +28,7 @@
#include "../../components/Section.h"
#include "../../components/SvgIcon.h"
#include "../../../ifcviewer/Federation.h"
#include "../../../ifcviewer/ViewportWindow.h"
#include <QDataStream>
#include <QDrag>
@@ -79,6 +80,7 @@ QStringList selectedModelIdsAt(QTreeView* tree, const QModelIndex& clicked_index
}
constexpr int kVisibilityColumnWidth = 28;
constexpr int kMemoryColumnWidth = 72; // "1234 MB" / "unloaded"
// QTreeView subclass that handles drag-and-drop. Drop logic dispatches
// through commands (not directly into the model) so notifications + status
@@ -250,7 +252,7 @@ ModelsPanel::ModelsPanel(bonsaiviewer::SessionState* session_state,
connect(tree_, &QTreeView::clicked, this, [this](const QModelIndex& index) {
if (!index.isValid()) return;
if (index.column() == 1) {
if (index.column() == VisibilityColumn) {
commands::toggleVisibility(*session_state_, kindOf(index), idOf(index));
return;
}
@@ -380,6 +382,24 @@ ModelsPanel::ModelsPanel(bonsaiviewer::SessionState* session_state,
commands::saveModelAsToCloud(*session_state_, *this, id);
});
// GPU residency. Unload keeps the model in the federation (and
// its visibility) but frees everything it holds on the GPU — the
// lever when the scene does not fit in VRAM. Load brings it back.
menu.addSeparator();
const uint32_t session_model_id = session_state_->sessionModelIdForModelId(id);
const bool unloaded = session_model_id != 0 && viewport_->isModelUnloaded(session_model_id);
QAction* residency = menu.addAction(
components::icons::makeSvgIcon(":/icons/cube.svg"),
unloaded ? "Load Model" : "Unload Model");
residency->setEnabled(session_model_id != 0);
residency->setToolTip(unloaded
? "Allocate GPU memory for this model again and stream its geometry back in."
: "Free this model's GPU memory while keeping it in the federation.");
connect(residency, &QAction::triggered, this, [this, id, unloaded]() {
if (unloaded) commands::loadModel(*session_state_, *viewport_, id);
else commands::unloadModel(*session_state_, *viewport_, id);
});
menu.addSeparator();
QAction* remove = menu.addAction(
components::icons::makeSvgIcon(":/icons/minus-square.svg"), "Remove Model");
@@ -409,14 +429,16 @@ void ModelsPanel::setModel(FederationItemModel* model) {
}
void ModelsPanel::applyColumnLayout() {
// Column 0 (name) stretches to fill; column 1 (visibility icon) is fixed.
// The name stretches to fill; memory and visibility are fixed.
QHeaderView* header = tree_->header();
if (header->count() < 2) return;
if (header->count() < ColumnCount) return;
header->setStretchLastSection(false);
header->setMinimumSectionSize(kVisibilityColumnWidth);
header->setSectionResizeMode(0, QHeaderView::Stretch);
header->setSectionResizeMode(1, QHeaderView::Fixed);
header->resizeSection(1, kVisibilityColumnWidth);
header->setSectionResizeMode(NameColumn, QHeaderView::Stretch);
header->setSectionResizeMode(MemoryColumn, QHeaderView::Fixed);
header->resizeSection(MemoryColumn, kMemoryColumnWidth);
header->setSectionResizeMode(VisibilityColumn, QHeaderView::Fixed);
header->resizeSection(VisibilityColumn, kVisibilityColumnWidth);
}
} // namespace bonsaiviewer::modules::models
+8
View File
@@ -31,6 +31,14 @@ enum class ItemKind {
Model,
};
// Columns of the models tree: name | GPU memory | visibility eye.
enum Column : int {
NameColumn = 0,
MemoryColumn = 1,
VisibilityColumn = 2,
ColumnCount = 3,
};
struct TreeNode {
QString id;
QString name;
+30 -1
View File
@@ -26,6 +26,9 @@
#include "../../ViewerSettings.h"
#include "../../SessionState.h"
#include "../../../ifcviewer/Federation.h"
#include "../../../ifcviewer/ViewportWindow.h"
#include <QTimer>
namespace bonsaiviewer::modules::models {
@@ -54,17 +57,19 @@ QList<GroupOption> validMoveTargets(const Federation& federation,
ModelsPanelView::ModelsPanelView(ModelsPanel* widget,
bonsaiviewer::SessionState* session_state,
ViewportWindow* viewport,
QObject* parent)
: QObject(parent)
, widget_(widget)
, session_state_(session_state)
, viewport_(viewport)
, model_(new FederationItemModel(session_state->federation(), this))
{
widget_->setModel(model_);
// Coarse signals: full rebuild + re-style. The granular Federation
// signals are handled inside FederationItemModel and don't reach here.
auto rebuild = [this]() { model_->rebuildAll(); };
auto rebuild = [this]() { model_->rebuildAll(); refreshResidency(); };
connect(session_state_, &SessionState::projectReset, this, rebuild);
connect(session_state_, &SessionState::projectOpened, this, rebuild);
connect(&bonsaiviewer::ViewerSettings::instance(),
@@ -73,6 +78,30 @@ ModelsPanelView::ModelsPanelView(ModelsPanel* widget,
connect(session_state_, &SessionState::activeModelChanged, this, [this](const QString& model_id) {
model_->setActiveModelId(model_id);
});
// Residency: immediately on the events that change it, and on a slow
// tick for the memory figures, which move as chunks stream.
auto refresh = [this]() { refreshResidency(); };
connect(session_state_, &SessionState::modelLoadStateChanged, this, refresh);
connect(session_state_, &SessionState::modelGeometryReady, this, refresh);
connect(session_state_, &SessionState::modelsChanged, this, refresh);
auto* tick = new QTimer(this);
tick->setInterval(1000);
connect(tick, &QTimer::timeout, this, refresh);
tick->start();
}
void ModelsPanelView::refreshResidency() {
for (const auto& model : session_state_->federation()->models()) {
const uint32_t session_model_id = session_state_->sessionModelIdForModelId(model.id);
if (session_model_id == 0) {
model_->setModelResidency(model.id, false, 0);
continue;
}
model_->setModelResidency(model.id,
viewport_->isModelUnloaded(session_model_id),
viewport_->modelVramBytes(session_model_id));
}
}
} // namespace bonsaiviewer::modules::models
+10
View File
@@ -26,6 +26,7 @@
#include <QObject>
class Federation;
class ViewportWindow;
namespace bonsaiviewer { class SessionState; }
namespace bonsaiviewer::modules::models {
@@ -45,16 +46,25 @@ QList<GroupOption> validMoveTargets(const Federation& federation,
// coarse session signals (project open/reset, theme change) — those are the
// "rebuild from scratch" cases the model itself doesn't subscribe to.
// Granular Federation events are handled inside the model.
//
// Also the bridge for the one thing the tree shows that is not Federation
// state: each model's GPU residency (memory column, unloaded styling). The
// viewport owns that state, so this view polls it once a second — the
// numbers move continuously while geometry streams — and pushes it in.
class ModelsPanelView : public QObject {
Q_OBJECT
public:
explicit ModelsPanelView(ModelsPanel* widget,
bonsaiviewer::SessionState* session_state,
ViewportWindow* viewport,
QObject* parent = nullptr);
private:
void refreshResidency();
ModelsPanel* widget_ = nullptr;
bonsaiviewer::SessionState* session_state_ = nullptr;
ViewportWindow* viewport_ = nullptr;
FederationItemModel* model_ = nullptr;
};
+12 -7
View File
@@ -76,12 +76,12 @@ int main() {
// By adding a building, a hierarchy has been automatically created that consists of the following
// structure: IfcProject > IfcSite > IfcBuilding
// Lateron changing the name of the IfcProject can be done by obtaining a reference to the
// Lateron changing the name of the IfcProject can be done by obtaining a reference to the
// project, which has been created automatically.
file.getSingle<IfcSchema::IfcProject>().setName("IfcAdvancedHouse"s);
// To demonstrate the ability to serialize arbitrary opencascade solids a building envelope is
// constructed by applying boolean operations. Naturally, in IFC, building elements should be
// constructed by applying boolean operations. Naturally, in IFC, building elements should be
// modeled separately, with rich parametric and relational semantics. Creating geometry in this
// way does not preserve any history and is merely a demonstration of technical capabilities.
TopoDS_Shape outer = BRepPrimAPI_MakeBox(gp_Pnt(-5000., -180., -2000.), gp_Pnt(5000., 5180., 3000.)).Shape();
@@ -101,8 +101,13 @@ int main() {
// IfcFacetedBRep. If it would not be a polyhedron, serialise() can only be successful when linked
// to the IFC4 model and with `advanced` set to `true` which introduces IfcAdvancedFace. It would
// return `0` otherwise.
auto building_shape = ifcopenshell::geom::serialise(file, building_shell, false).as<IfcSchema::IfcProductDefinitionShape>();
auto building_shape_result = ifcopenshell::geom::serialise(file, building_shell, false);
if (!building_shape_result) {
std::cerr << "Failed to serialize building shell." << std::endl;
return 1;
}
auto building_shape = building_shape_result.as<IfcSchema::IfcProductDefinitionShape>();
file.add_entity(building_shape);
auto building_representations = building_shape.Representations();
building_representations.front().setContextOfItems(file.getRepresentationContext("model"));
@@ -122,7 +127,7 @@ int main() {
ground_representation = ifcopenshell::geom::tesselate(file, shape, 100.);
}
file.getSingle<IfcSchema::IfcSite>().setRepresentation(ground_representation.as<IfcSchema::IfcProductDefinitionShape>());
auto ground_reps = file.getSingle<IfcSchema::IfcSite>().Representation().Representations();
for (auto& rep : ground_reps) {
rep.setContextOfItems(file.getRepresentationContext("Model"));
@@ -175,10 +180,10 @@ void createGroundShape(TopoDS_Shape& shape) {
cv.SetValue(4, 4, gp_Pnt( 10000, 10000, -8130));
TColStd_Array1OfReal knots(0, 1);
knots(0) = 0;
knots(1) = 1;
knots(1) = 1;
TColStd_Array1OfInteger mult(0, 1);
mult(0) = 5;
mult(1) = 5;
mult(1) = 5;
Handle(Geom_BSplineSurface) surf = new Geom_BSplineSurface(cv, knots, knots, mult, mult, 4, 4);
#if OCC_VERSION_HEX < 0x60502
shape = BRepBuilderAPI_MakeFace(surf);
+8 -6
View File
@@ -28,7 +28,9 @@
// alignment explicitly
// Disable warnings coming from IfcOpenShell
#if defined(_MSC_VER)
#pragma warning(disable : 4018 4267 4250 4984 4985)
#endif
#include "../ifcparse/schemas/Ifc4x3_add2.h"
#include "../ifcparse/hierarchy_helper.h"
@@ -70,7 +72,7 @@ Schema::IfcProject setup_project(hierarchy_helper<Schema>& file) {
dimensions.setThermodynamicTemperatureExponent(0);
dimensions.setAmountOfSubstanceExponent(0);
dimensions.setLuminousIntensityExponent(0);
auto conversion_factor = file.create<Schema::IfcMeasureWithUnit>();
auto length = file.create<Schema::IfcLengthMeasure>();
length.set_attribute_value(0, 304.80);
@@ -82,7 +84,7 @@ Schema::IfcProject setup_project(hierarchy_helper<Schema>& file) {
conversion_based_unit.setUnitType(Schema::IfcUnitEnum::IfcUnit_LENGTHUNIT);
conversion_based_unit.setName("FEET");
conversion_based_unit.setConversionFactor(conversion_factor);
units.erase(std::remove(units.begin(), units.end(), unit)); // remove the millimeter unit
units.push_back(conversion_based_unit); // add the feet unit
units_in_context.setUnits(units); // update the UnitsInContext
@@ -386,7 +388,7 @@ int main() {
nests_horizontal_segments.setName("Nests horizontal alignment segments with horizontal alignment");
nests_horizontal_segments.setRelatingObject(horizontal_alignment);
nests_horizontal_segments.setRelatedObjects(horizontal_segments);
//
// Create plan view footprint model representation for the horizontal alignment
//
@@ -403,7 +405,7 @@ int main() {
footprint_shape_representation.setRepresentationType("Curve2D");
// the composite curve is a representation item
footprint_shape_representation.setItems({composite_curve});
//
// Define vertical profile segments
//
@@ -539,7 +541,7 @@ int main() {
nests_alignment_layouts.setName("Nest horizontal and vertical alignment layouts with the alignment");
nests_alignment_layouts.setRelatingObject(alignment);
nests_alignment_layouts.setRelatedObjects({horizontal_alignment, vertical_profile});
// Define the relationship with the project
// IFC 4.1.4.1.1 "Every IfcAlignment must be related to IfcProject using the IfcRelAggregates relationship"
@@ -550,7 +552,7 @@ int main() {
aggregate_alignments_with_project.setName("Alignments in project");
aggregate_alignments_with_project.setRelatingObject(project);
aggregate_alignments_with_project.setRelatedObjects({alignment});
// Define the spatial structure of the alignment with respect to the site
// IFC 4.1.5.1 alignment is referenced in spatial structure of an IfcSpatialElement. In this case IfcSite is the highest level IfcSpatialElement
+20 -20
View File
@@ -55,7 +55,7 @@
using namespace std::string_literals;
// Some convenience typedefs and definitions.
// Some convenience typedefs and definitions.
typedef ifcopenshell::global_id guid;
typedef std::pair<double, double> XY;
#ifdef SCHEMA_HAS_IfcPresentationStyleAssignment
@@ -295,9 +295,9 @@ int main() {
west_void.setOwnerHistory(file.getSingle<IfcSchema::IfcOwnerHistory>());
west_void.setRelatingBuildingElement(west_wall);
west_void.setRelatedOpeningElement(west_opening_copy);
// Up until now we have only used simple extrusions for the creation of the geometry. For the
// ground mesh of the IfcSite we will use a Nurbs surface created in Open Cascade. The surface
// Up until now we have only used simple extrusions for the creation of the geometry. For the
// ground mesh of the IfcSite we will use a Nurbs surface created in Open Cascade. The surface
// will be tessellated using the deflection specified.
TopoDS_Shape shape;
createGroundShape(shape);
@@ -325,7 +325,7 @@ int main() {
site_prop.setOwnerHistory(file.getSingle<IfcSchema::IfcOwnerHistory>());
site_prop.setRelatedObjects({file.getSingle<IfcSchema::IfcSite>()});
site_prop.setRelatingPropertyDefinition(pset);
auto ground_reps = file.getSingle<IfcSchema::IfcSite>().Representation().Representations();
for (auto& rep : ground_reps) {
rep.setContextOfItems(file.getRepresentationContext("Model"));
@@ -334,11 +334,11 @@ int main() {
setSurfaceColour(file,ground_representation, 0.15, 0.25, 0.05);
// According to the Ifc2x3 schema an IfcWallStandardCase needs to have an IfcMaterialLayerSet
// assigned. Note that this material definition is independent of the surface styles we have
// been assigning to the walls already. The surface styles determine the colour in the
// assigned. Note that this material definition is independent of the surface styles we have
// been assigning to the walls already. The surface styles determine the colour in the
// '3D viewport' of most applications.
// Some BIM authoring applications, such as Autodesk Revit, ignore the geometrical representation
// by and large and construct native walls using the layer thickness and reference line offset
// by and large and construct native walls using the layer thickness and reference line offset
// provided here.
auto material = file.create<IfcSchema::IfcMaterial>();
material.setName("Brick");
@@ -422,7 +422,7 @@ int main() {
#endif
door.setRepresentation(file.addBox(80, 80, 2120, IfcSchema::IfcAxis2Placement2D{}, file.addPlacement3d(460, 0, 0)));
auto door_representations = door.Representation().Representations();
IfcSchema::IfcShapeRepresentation door_body;
for (auto& rep : door_representations) {
@@ -465,9 +465,9 @@ int main() {
#endif
// Surface styles are assigned to representation items, hence there is no real limitation to
// assign different colours within the same representation. However, some viewers have
// difficulties rendering products with representation items with different surface styles.
// Therefore we will construct the window as a decomposition of beams and a plate, in which
// assign different colours within the same representation. However, some viewers have
// difficulties rendering products with representation items with different surface styles.
// Therefore we will construct the window as a decomposition of beams and a plate, in which
// only the plate will have a transparent material assigned.
// The window frame will consists of four separate beams.
@@ -476,7 +476,7 @@ int main() {
// match the bounding box of the representation. Furthermore, the window placement needs
// to align with the lowerleft corner of the constituent parts.
std::vector<IfcSchema::IfcShapeRepresentation> frame_representations;
auto horizontal_bar = file.addEmptyRepresentation();
auto vertical_bar = file.addEmptyRepresentation();
file.addBox(horizontal_bar, 1860, 90, 90);
@@ -498,7 +498,7 @@ int main() {
// Because of the duplication the iterator is incremented twice
}
// This window will be placed at five locations within the building. A list of placements is
// This window will be placed at five locations within the building. A list of placements is
// created and is iterated over to create all window instances.
std::vector<IfcSchema::IfcLocalPlacement> window_placements;
window_placements.push_back(file.addLocalPlacement(storey_placement, 2*-1770-430-930, -45, 400));
@@ -506,7 +506,7 @@ int main() {
window_placements.push_back(file.addLocalPlacement(storey_placement, -430-930, -45, 400));
window_placements.push_back(file.addLocalPlacement(storey_placement, 3000-930, -45, 400));
window_placements.push_back(file.addLocalPlacement(storey_placement, -4855+45, 885-930, 400, 0, 0, 1, 0, 1, 0));
for (auto& place : window_placements) {
// Create the window at the current location
@@ -520,7 +520,7 @@ int main() {
window.setPredefinedType(IfcSchema::IfcWindowTypeEnum::IfcWindowType_WINDOW);
window.setPartitioningType(IfcSchema::IfcWindowTypePartitioningEnum::IfcWindowTypePartitioning_SINGLE_PANEL);
#endif
file.addBuildingProduct(window);
file.addBuildingProduct(window);
// Initialize a list of parts for the window to be composed of
std::vector<IfcSchema::IfcObjectDefinition> window_parts;
@@ -532,7 +532,7 @@ int main() {
frame_placements.push_back(file.addLocalPlacement(storey_placement, 930, 45, 1510));
frame_placements.push_back(file.addLocalPlacement(storey_placement, -885+930, 45, 90));
frame_placements.push_back(file.addLocalPlacement(storey_placement, 885+930, 45, 90));
// Now iterate over the placements and representations of the beam and add them to list of parts
std::vector<IfcSchema::IfcLocalPlacement>::const_iterator frame_placement;
std::vector<IfcSchema::IfcShapeRepresentation>::const_iterator frame_representation;
@@ -565,7 +565,7 @@ int main() {
window_parts.push_back(glass_part);
file.relatePlacements(window, glass_part);
setSurfaceColour(file, glass_part.Representation(), 0.6, 0.7, 0.75, 0.1);
// Now create a decomposition relation between the window and the parts. Most viewers and authoring
// tools will consider the window a single entity that can be selected as a whole.
{
@@ -612,10 +612,10 @@ void createGroundShape(TopoDS_Shape& shape) {
cv.SetValue(4, 4, gp_Pnt( 10000, 10000, -8130));
TColStd_Array1OfReal knots(0, 1);
knots(0) = 0;
knots(1) = 1;
knots(1) = 1;
TColStd_Array1OfInteger mult(0, 1);
mult(0) = 5;
mult(1) = 5;
mult(1) = 5;
Handle(Geom_BSplineSurface) surf = new Geom_BSplineSurface(cv, knots, knots, mult, mult, 4, 4);
#if OCC_VERSION_HEX < 0x60502
shape = BRepBuilderAPI_MakeFace(surf);
+2 -2
View File
@@ -30,7 +30,7 @@
#include INCLUDE_SCHEMA(ifcparse/schemas, IfcSchema)
#include INCLUDE_SCHEMA_DEFINITIONS(ifcparse/schemas, IfcSchema)
#ifdef _MSC_VER
#ifdef _MSC_VER
#define strcasecmp _stricmp
#endif
@@ -151,7 +151,7 @@ void process_pset(element_properties& props, const T& inst) {
template <typename Schema>
void get_psets_s(element_properties& props, const typename Schema::IfcObjectDefinition& inst) {
// Extracts the property definitions for an IFC instance.
// Extracts the property definitions for an IFC instance.
if (auto tyob = inst.template as<typename Schema::IfcTypeObject>()) {
if (tyob.HasPropertySets()) {
auto defs = *tyob.HasPropertySets();
+2
View File
@@ -28,7 +28,9 @@
// to simplify alignment construction
// Disable warnings coming from IfcOpenShell
#if defined(_MSC_VER)
#pragma warning(disable : 4018 4267 4250 4984 4985)
#endif
#include "../ifcparse/schemas/Ifc4x3_add2.h"
#include "../ifcparse/alignment_helper.h"
+28 -28
View File
@@ -336,7 +336,7 @@ int main(int argc, char** argv) {
std::string exterior_only_algo;
ifcopenshell::geom::settings settings;
po::options_description geom_options("Geometry options");
geom_options.add_options()
("kernel", po::value<std::string>(&geometry_kernel)->default_value(default_kernel),
@@ -389,7 +389,7 @@ int main(int argc, char** argv) {
("model", "Specifies whether to include surfaces and solids in the output result. "
"Typically these are representations of type Body or Facetation. ")
;
settings.define_options(geom_options);
std::string bounds;
@@ -466,7 +466,7 @@ int main(int argc, char** argv) {
num_threads = std::thread::hardware_concurrency();
logger.notice("SYS", 7, "Using " + std::to_string(num_threads) + " threads");
}
if (vmap.count("log-format") == 1) {
boost::to_lower(log_format);
if (log_format == "plain") {
@@ -479,7 +479,7 @@ int main(int argc, char** argv) {
return EXIT_FAILURE;
}
}
if (!filter_filename.empty()) {
size_t num_filters = read_filters_from_file(ifcopenshell::path::to_utf8(filter_filename), include_filter, include_traverse_filter, exclude_filter, exclude_traverse_filter);
if (num_filters) {
@@ -524,10 +524,10 @@ int main(int argc, char** argv) {
// If no output filename is specified a Wavefront OBJ file will be output
// to maintain backwards compatibility with the obsolete IfcObj executable.
const path_t output_filename = vmap.count("output-file") == 1
const path_t output_filename = vmap.count("output-file") == 1
? vmap["output-file"].as<path_t>()
: change_extension(input_filename, ifcopenshell::path::from_utf8(DEFAULT_EXTENSION));
if (output_filename.size() < 5) {
cerr_ << "[error] Invalid or unsupported output file '" << output_filename << "' given" << std::endl;
print_usage();
@@ -572,13 +572,13 @@ int main(int argc, char** argv) {
}
path_t output_temp_filename = output_filename + ifcopenshell::path::from_utf8(TEMP_FILE_EXTENSION);
std::vector<path_t> tokens;
split(tokens, output_filename, boost::is_any_of("."));
std::vector<path_t>::iterator tok_iter;
path_t ext = *(tokens.end() - 1);
path_t dot;
dot = '.';
dot = '.';
path_t output_extension = dot + ext;
boost::to_lower(output_extension);
@@ -785,7 +785,7 @@ int main(int argc, char** argv) {
time_t start,end;
time(&start);
// @nb last argument true -> bypass_properties which are not read by any of the geometry serializers
// Document serializers and IFC are already special-cased above
// SVG requires properties for IfcAnnotation/DRAWING properties
@@ -839,12 +839,12 @@ int main(int argc, char** argv) {
settings.get<ifcopenshell::geom::settings::ModelOffset>().value = offset;
}
if (is_tesselated && (center_model || center_model_geometry)) {
std::vector<double> offset(3);
ifcopenshell::geom::iterator tmp_context_iterator(ifcopenshell::geom::kernels::construct(ifc_file, geometry_kernel, settings, logger), settings, ifc_file, filter_funcs, num_threads, logger);
time_t bounds_start, bounds_end;
time(&bounds_start);
if (!quiet) logger.status("Computing bounds...");
@@ -860,7 +860,7 @@ int main(int argc, char** argv) {
return EXIT_FAILURE;
}
}
tmp_context_iterator.compute_bounds(center_model_geometry);
time(&bounds_end);
@@ -919,19 +919,19 @@ int main(int argc, char** argv) {
}
// The functions ifcopenshell::geom::iterator::get() and ifcopenshell::geom::iterator::next()
// wrap an iterator of all geometrical products in the Ifc file.
// wrap an iterator of all geometrical products in the Ifc file.
// ifcopenshell::geom::iterator::get() returns an ifcopenshell::geom::triangulation_element or
// -native_element pointer, based on current settings. (see iterator.h
// for definition) ifcopenshell::geom::iterator::next() is used to poll whether more
// geometrical entities are available. None of these functions throw
// exceptions, neither for parsing errors or geometrical errors. Upon
// calling next() the entity to be returned has already been processed, a
// non-null return value guarantees that a successfully processed product is
// available.
// geometrical entities are available. None of these functions throw
// exceptions, neither for parsing errors or geometrical errors. Upon
// calling next() the entity to be returned has already been processed, a
// non-null return value guarantees that a successfully processed product is
// available.
size_t num_created = 0;
while (true) {
auto geom_object = context_iterator->get();
if (is_tesselated)
@@ -967,7 +967,7 @@ int main(int argc, char** argv) {
if (!context_iterator->next()) {
break;
}
}
}
if (!no_progress && quiet) {
for (; old_progress < 100; ++old_progress) {
cout_ << ".";
@@ -1086,7 +1086,7 @@ bool init_input_file(const std::string& filename, ifcopenshell::file*& ifc_file,
ifc_file->bypass_type("IfcProfileProperties");
ifc_file->bypass_type("IfcPhysicalQuantity");
}
#ifdef USE_MMAP
if (mmap) {
ifc_file->initialize(filename, mmap);
@@ -1382,20 +1382,20 @@ void fix_quantities(ifcopenshell::file& f, bool no_progress, bool quiet, bool st
auto person = latebound_access::create(f, "IfcPerson");
latebound_access::set(person, "FamilyName", std::string("IfcOpenShell"));
latebound_access::set(person, "GivenName", std::string("IfcOpenShell"));
auto org = latebound_access::create(f, "IfcOrganization");
latebound_access::set(org, "Name", std::string("IfcOpenShell"));
auto pando = latebound_access::create(f, "IfcPersonAndOrganization");
latebound_access::set(pando, "ThePerson", person);
latebound_access::set(pando, "TheOrganization", org);
auto application = latebound_access::create(f, "IfcApplication");
latebound_access::set(application, "ApplicationDeveloper", org);
latebound_access::set(application, "Version", std::string(IFCOPENSHELL_VERSION));
latebound_access::set(application, "ApplicationFullName", std::string("IfcConvert"));
latebound_access::set(application, "ApplicationIdentifier", std::string("IfcConvert") + IFCOPENSHELL_VERSION);
auto ownerhist = latebound_access::create(f, "IfcOwnerHistory");
latebound_access::set(ownerhist, "OwningUser", pando);
latebound_access::set(ownerhist, "OwningApplication", application);
@@ -1440,7 +1440,7 @@ void fix_quantities(ifcopenshell::file& f, bool no_progress, bool quiet, bool st
latebound_access::set(quantity_area, "AreaValue", a);
quantities.push_back(quantity_area);
}
if (geom_object->geometry().calculate_volume(a)) {
auto quantity_volume = latebound_access::create(f, "IfcQuantityVolume");
latebound_access::set(quantity_volume, "Name", std::string("Volume"));
@@ -1461,13 +1461,13 @@ void fix_quantities(ifcopenshell::file& f, bool no_progress, bool quiet, bool st
std::vector<express::base> quantities_2;
for (auto& part : geom_object->geometry()) {
for (auto& part : geom_object->geometry()) {
auto quantity_count = latebound_access::create(f, "IfcQuantityCount");
latebound_access::set(quantity_count, "Name", std::string("Surface Genus"));
latebound_access::set(quantity_count, "Description", '#' + boost::lexical_cast<std::string>(part.ItemId()));
latebound_access::set(quantity_count, "CountValue", (int64_t) part.shape()->surface_genus());
quantities_2.push_back(quantity_count);
quantities_2.push_back(quantity_count);
}
latebound_access::set(quantity_complex, "HasQuantities", quantities_2);
@@ -41,7 +41,7 @@ void fix_storeycontainment(ifcopenshell::file& f, bool no_progress, bool quiet,
elem_to_storey[*it] = storey;
}
}
});
});
auto storeys = f.instances_by_type("IfcBuildingStorey");
std::vector<const ifcopenshell::IfcBaseClass*> storeys_sorted(storeys->begin(), storeys->end());
@@ -98,7 +98,7 @@ void fix_storeycontainment(ifcopenshell::file& f, bool no_progress, bool quiet,
std::wcout << "---" << std::endl;
}
*/
if (!context_iterator.initialize()) {
return;
}
@@ -22,7 +22,7 @@ void fix_wallconnectivity(ifcopenshell::file& f, bool no_progress, bool quiet, b
settings.get<ifcopenshell::geom::settings::DisableOpeningSubtractions>().value = true;
settings.get<ifcopenshell::geom::settings::OutputDimensionality>().value = ifcopenshell::geom::settings::CURVES;
ifcopenshell::geom::converter c(ifcopenshell::geom::kernels::construct(&f, "cgal", settings, logger), &f, settings, logger);
auto rels = f.instances_by_type("IfcRelConnectsPathElements");
@@ -54,7 +54,7 @@ void fix_wallconnectivity(ifcopenshell::file& f, bool no_progress, bool quiet, b
if (!a_is_relating) {
std::swap(a_type, b_type);
}
}
}
#if 0
auto a_poly = ifcopenshell::geom::utils::create_polyhedron(a.handle()->second);
@@ -123,7 +123,7 @@ void fix_wallconnectivity(ifcopenshell::file& f, bool no_progress, bool quiet, b
} else {
auto p0 = boost::get<taxonomy::point3::ptr>(first_vertex);
auto p1 = boost::get<taxonomy::point3::ptr>(last_vertex);
auto v0 = taxonomy::cast<taxonomy::geom_item>(item)->matrix->ccomponents() * p0->ccomponents().homogeneous();
auto v1 = taxonomy::cast<taxonomy::geom_item>(item)->matrix->ccomponents() * p1->ccomponents().homogeneous();
@@ -142,7 +142,7 @@ void fix_wallconnectivity(ifcopenshell::file& f, bool no_progress, bool quiet, b
auto pit = std::minmax_element(parameters.begin(), parameters.end());
return std::make_pair(len, std::make_pair(CGAL::to_double(*pit.first), CGAL::to_double(*pit.second)));
}
}
}
const auto& nan = std::numeric_limits<double>::quiet_NaN();
return std::make_pair(nan, std::make_pair(nan, nan));
+2 -2
View File
@@ -37,7 +37,7 @@ inline static bool ALMOST_THE_SAME(const T& a, const T& b, double tolerance = AL
return fabs(a - b) < tolerance;
}
namespace ifcopenshell {
namespace ifcopenshell {
#if defined(_MSC_VER)
#pragma warning(push)
@@ -70,7 +70,7 @@ namespace ifcopenshell {
public:
bool propagate_exceptions = false;
bool partial_success_is_success = true;
abstract_kernel(const std::string& geometry_library, const ifcopenshell::geom::settings& settings, ifcopenshell::logger& logger = ifcopenshell::logger::root())
: geometry_library_(geometry_library)
, settings_(settings)
+2 -2
View File
@@ -44,7 +44,7 @@ namespace geom {
/// should return true if the geometry for the product is wanted to be included in the output.
/// http://www.boost.org/doc/libs/1_62_0/doc/html/function/tutorial.html
typedef std::function<bool(const express::base&)> filter_function;
class IFC_GEOM_API abstract_mapping {
protected:
ifcopenshell::geom::settings settings_;
@@ -106,7 +106,7 @@ namespace geom {
IFC_GEOM_API mapping_factory_implementation& mapping_implementations();
}
}
}
+6 -6
View File
@@ -510,20 +510,20 @@ namespace ifcopenshell::geom {
virtual void triangulate(ifcopenshell::geom::settings settings, const ifcopenshell::geom::taxonomy::matrix4& place, triangulation* t, int item_id, int surface_style_id, ifcopenshell::logger& logger = ifcopenshell::logger::root()) const = 0;
ifcopenshell::geom::triangulation* triangulate(const ifcopenshell::geom::settings& settings, ifcopenshell::logger& logger = ifcopenshell::logger::root()) const;
virtual void serialize(const ifcopenshell::geom::taxonomy::matrix4& place, std::string&) const = 0;
virtual int surface_genus() const = 0;
virtual bool is_manifold() const = 0;
virtual int num_vertices() const = 0;
virtual int num_edges() const = 0;
virtual int num_faces() const = 0;
// @todo choose one prototype
virtual double bounding_box(void*&) const = 0;
// @todo this must be something with a virtual dtor so that we can delete it.
virtual std::pair<opaque_coordinate<3>, opaque_coordinate<3>> bounding_box() const = 0;
virtual void set_box(void* b) = 0;
virtual opaque_number length() = 0;
virtual opaque_number area() = 0;
virtual opaque_number volume() = 0;
@@ -550,11 +550,11 @@ namespace ifcopenshell::geom {
virtual std::size_t map(opaque_coordinate<4>& from, opaque_coordinate<4>& to) = 0;
virtual std::size_t map(const std::vector<opaque_coordinate<4>>& from, const std::vector<opaque_coordinate<4>>& to) = 0;
virtual conversion_result_shape* moved(ifcopenshell::geom::taxonomy::matrix4::ptr) const = 0;
virtual bool surface_area_along_direction(double tol, const ifcopenshell::geom::taxonomy::matrix4::ptr&, double& along_x, double& along_y, double& along_z) const = 0;
virtual ~conversion_result_shape() {}
};
class IFC_GEOM_API conversion_result {
+1 -1
View File
@@ -498,7 +498,7 @@ namespace ifcopenshell {
static constexpr const char* const description = "Slight variation of --model-offset where large offsets are applied by negating existing large offsets to retain maximum precision. Requires --no-parallel-mapping.";
};
}
namespace impl {
template <typename T>
struct readable_name {
+3 -3
View File
@@ -19,7 +19,7 @@ ifcopenshell::geom::converter::~converter() {
ifcopenshell::geom::native_element* ifcopenshell::geom::converter::create_brep_for_representation_and_product(taxonomy::ptr representation_node, const express::base product_, const taxonomy::matrix4::ptr& place_) {
auto product = product_.as<express::entity>();
std::stringstream representation_id_builder;
auto place = place_;
@@ -32,7 +32,7 @@ ifcopenshell::geom::native_element* ifcopenshell::geom::converter::create_brep_f
if (!kernel_->convert(representation_node, shapes)) {
return 0;
}
if (settings_.get<ifcopenshell::geom::settings::ApplyLayerSets>().get()) {
ifcopenshell::geom::layerset_information layerinfo;
std::vector<ifcopenshell::geom::endpoint_connection> neighbours;
@@ -55,7 +55,7 @@ ifcopenshell::geom::native_element* ifcopenshell::geom::converter::create_brep_f
/*
if (util::flatten_shape_list(shapes, merge, false, getValue(GV_PRECISION))) {
if (util::count(merge, TopAbs_FACE) > 0) {
if (convert_layerset(product, layers, styles, thickness)) {
IfcSchema::IfcRelAssociates::list::ptr associations = product->HasAssociations();
+1 -1
View File
@@ -27,7 +27,7 @@ namespace ifcopenshell { namespace geom {
ifcopenshell::geom::kernels::abstract_kernel* kernel() { return &*kernel_; }
converter(std::unique_ptr<ifcopenshell::geom::kernels::abstract_kernel>&& geometry_library, ifcopenshell::file* file, ifcopenshell::geom::settings& settings, ifcopenshell::logger& logger = ifcopenshell::logger::root());
~converter();
ifcopenshell::geom::abstract_mapping* mapping() const { return mapping_; }
+2 -2
View File
@@ -53,7 +53,7 @@ namespace ifcopenshell::geom {
}
}
const ifcopenshell::geom::taxonomy::matrix4::ptr& data() const {
if (matrix_orig_units_) {
if (matrix_orig_units_) {
return matrix_orig_units_;
}
if (matrix_) {
@@ -131,7 +131,7 @@ namespace ifcopenshell::geom {
const std::string& guid, const std::string& context, const ifcopenshell::geom::taxonomy::matrix4::ptr& trsf, const express::entity& product)
: _id(id), _parent_id(parent_id), _name(name), _type(type), _guid(guid), _context(context), _transformation(settings, trsf)
, product_(product)
{
{
std::ostringstream oss;
if (type == "IfcProject") {
+1 -1
View File
@@ -1,2 +1,2 @@
// A purposely empty file so that the unrolled loop
// can overflow into an existing empty include file.
// can overflow into an existing empty include file.
+1 -1
View File
@@ -135,7 +135,7 @@ struct cant_fn_evaluator : public fn_evaluator {
auto g = gradient_evaluator_.evaluate(u);
auto c = cant_evaluator_.evaluate(u);
// curvature is stored in row 3 - capture it and remove it from the xy and uz matrices
// so the matrix operations (ie multiplication) works correctly
auto gradient_curvature = g.row(3);
+2 -2
View File
@@ -13,8 +13,8 @@ IFC_GEOM_API std::vector<double> helmert_curve_point(double A0, double A1, doubl
/// This is intended to be used from python side. Polylines are mapped to a loop, but when
/// representing an alignment they need to be a function_item so the can be evaluated by function_item_evaluator.
/// On the C++ side, the dcast operator take care of this, but dcast is not accessible on the python side.
/// @param loop
/// @return
/// @param loop
/// @return
inline taxonomy::function_item::ptr convert_loop_to_function_item(taxonomy::loop::ptr loop) {
return ifcopenshell::geom::taxonomy::dcast<taxonomy::function_item>(loop);
}
+4 -4
View File
@@ -78,7 +78,7 @@ taxonomy::loft::ptr ifcopenshell::geom::make_loft(const ifcopenshell::geom::sett
while (dist_along > *(profile_index + 1)) {
profile_index++;
if (profile_index == longitudes.end()) {
// @todo handle this?
// @todo handle this?
}
}
@@ -158,7 +158,7 @@ taxonomy::loft::ptr ifcopenshell::geom::make_loft(const ifcopenshell::geom::sett
}
interpolated->matrix->components() = lerp(m4a, m4b, relative_dist_along);
}
auto interpolated_offset = lerp(offset_a, offset_b, relative_dist_along);
if (rotation_a == rotation_b && rotation_a) {
// @todo we don't support an overridden rotation on only one of the placements
@@ -215,7 +215,7 @@ taxonomy::loft::ptr ifcopenshell::geom::make_loft(const ifcopenshell::geom::sett
std::vector<taxonomy::point3::ptr> points;
std::vector<std::set<std::string>> tags;
std::vector<std::string>::const_iterator tag_it;
if (!loop->closed.value_or(false)) {
points = {std::get<taxonomy::point3::ptr>(loop->children[0]->start)};
if (input_tags) {
@@ -352,7 +352,7 @@ taxonomy::loft::ptr ifcopenshell::geom::make_loft(const ifcopenshell::geom::sett
for (auto& x : tags_for_this_point_on_subsequent_profile) {
points.push_back(taxonomy::make<taxonomy::point3>(p3));
common_tags_vec.push_back(x);
}
}
}
} else {
for (auto tmp__ : boost::combine(w1_points, w2_points)) {
+1 -1
View File
@@ -9,7 +9,7 @@
namespace ifcopenshell {
namespace geom {
struct IFC_GEOM_API cross_section {
double dist_along;
taxonomy::geom_item::ptr section_geometry;
+4 -4
View File
@@ -624,7 +624,7 @@ std::unique_ptr<ifcopenshell::geom::element> ifcopenshell::geom::iterator::get()
hasParent = false;
}
}
// Add the previously found parent to the vector
hasParent = hasParent && parent_object->parent_id() != -1;
}
@@ -688,13 +688,13 @@ express::base ifcopenshell::geom::iterator::create() {
}
ifcopenshell::geom::taxonomy::direction3::ptr ifcopenshell::geom::iterator::remove_offset_() {
using namespace ifcopenshell::geom::taxonomy;
if (!settings_.get<ifcopenshell::geom::settings::MaxOffset>().has()) {
return nullptr;
}
if (!settings_.get<ifcopenshell::geom::settings::NoParallelMapping>().get()) {
throw std::runtime_error("remove_offset() can only be called with defer-processing-first-element and no-parallel-mapping settings");
}
+2 -2
View File
@@ -128,7 +128,7 @@ namespace ifcopenshell::geom {
bool task_result_ptr_initialized = false;
bool task_result_ptr_exhausted = false;
size_t async_elements_returned_ = 0;
ifcopenshell::geom::settings settings_;
ifcopenshell::file* ifc_file;
std::vector<ifcopenshell::geom::filter_function> filters_;
@@ -138,7 +138,7 @@ namespace ifcopenshell::geom {
// When single-threaded
ifcopenshell::geom::converter* converter_;
// When multi-threaded
std::vector<ifcopenshell::geom::converter*> kernel_pool;
std::vector<std::unique_ptr<ifcopenshell::logger>> worker_loggers_;
@@ -296,7 +296,7 @@ ifcopenshell::geom::cgal_shape::cgal_shape(const cgal_polyhedron& shape, bool co
};
std::vector<CGAL::Point_2<kernel_>> ps;
for (auto& he1 : CGAL::halfedges_around_face(face->halfedge(), poly)) {
const auto& source = he1->vertex()->point();
ps.push_back(transform_point(source));
@@ -345,7 +345,7 @@ void ifcopenshell::geom::cgal_shape::to_poly() const {
CGAL::Polygon_mesh_processing::orient_to_bound_a_volume(poly);
}
shape_ = poly;
// nef_->convert_to_polyhedron(*shape_);
}
}
@@ -384,7 +384,7 @@ void ifcopenshell::geom::cgal_shape::triangulate(ifcopenshell::geom::settings se
}
const bool setting_use_original_edges = settings.get<ifcopenshell::geom::settings::CgalEmitOriginalEdges>().get();
std::set<std::set<kernel_::Point_3>> original_edges;
if (setting_use_original_edges) {
for (auto it = shape_to_use->edges_begin(); it != shape_to_use->edges_end(); ++it) {
@@ -457,7 +457,7 @@ void ifcopenshell::geom::cgal_shape::triangulate(ifcopenshell::geom::settings se
// std::map<cgal_vertex_descriptor, kernel_::Vector_3> vertex_normals;
// boost::associative_property_map<std::map<cgal_vertex_descriptor, kernel_::Vector_3>> vertex_normals_map(vertex_normals);
// triangulate the shape and compute the normals
std::map<facet_const_handle, kernel_::Vector_3> face_normals;
boost::associative_property_map<std::map<facet_const_handle, kernel_::Vector_3>> face_normals_map(face_normals);
@@ -556,7 +556,7 @@ void ifcopenshell::geom::cgal_shape::triangulate(ifcopenshell::geom::settings se
is_face_boundary[i] = setting_use_original_edges
? original_edges.find({ current_halfedge->vertex()->point(), current_halfedge->prev()->vertex()->point() }) != original_edges.end()
: facet_to_component[face] != facet_to_component[current_halfedge->opposite()->face()];
++i;
++num_vertices;
++current_halfedge;
@@ -764,7 +764,7 @@ opaque_coordinate<3> ifcopenshell::geom::cgal_shape::position()
for (auto it = shp.points_begin(); it != shp.points_end(); ++it) {
for (int i = 0; i < 3; ++i) {
p[i] += it->cartesian(i);
}
}
}
kernel_::FT N(static_cast<double>(std::distance(shp.points_begin(), shp.points_end())));
for (int i = 0; i < 3; ++i) {
+9 -9
View File
@@ -84,7 +84,7 @@ CGAL::Polyhedron_3<kernel_> ifcopenshell::geom::utils::create_polyhedron(std::li
// fresult.close();
return CGAL::Polyhedron_3<kernel_>();
}
// std::cout << "After: " << polyhedron.size_of_vertices() << " vertices and " << polyhedron.size_of_facets() << " facets" << std::endl;
return polyhedron;
@@ -223,7 +223,7 @@ bool cgal_kernel::convert(const taxonomy::shell::ptr l, cgal_polyhedron& shape)
} else {
logger().message(ifcopenshell::logger::LOG_ERROR, "Failed to convert face:", f->instance);
return false;
}
}
}
// std::cout << "Face in ConnectedFaceSet: " << std::endl;
@@ -673,9 +673,9 @@ namespace {
namespace {
void face_to_poly_with_holes(const cgal_face& face, CGAL::Polygon_with_holes_2<kernel_>& pwh, CGAL::Aff_transformation_3<kernel_>& place) {
// static
// static
kernel_::Vector_3 Z(0, 0, 1);
// static
// static
kernel_::Vector_3 X(1, 0, 0);
auto refz = newell(face.outer);
@@ -912,7 +912,7 @@ bool ifcopenshell::geom::kernels::cgal_kernel::convert_openings(const express::b
#else
CGAL::Nef_nary_union_3<CGAL::Nef_polyhedron_3<kernel_>> second_operand_collector;
size_t second_operand_collector_size = 0;
std::list<std::pair<express::base, std::list<cgal_polyhedron>>> operands;
std::list<express::base> second_operand_instances;
@@ -1483,7 +1483,7 @@ bool cgal_kernel::preprocess_boolean_operand(const express::base& log_reference,
for (auto& nef : first_operands_nef) {
// @todo eliminate this copy (= to remove const)
auto nef_copy = nef;
auto tree = build_halfspace_tree_decomposed(nef_copy, planes_fixed);
auto tree = build_halfspace_tree_decomposed(nef_copy, planes_fixed);
}
{
// @nb we snap internally as well...
@@ -1551,7 +1551,7 @@ bool cgal_kernel::preprocess_boolean_operand(const express::base& log_reference,
}
}
/*
{
@@ -2026,7 +2026,7 @@ bool cgal_kernel::convert_impl(const taxonomy::boolean_result::ptr br, std::vect
if (!convert(face, fs) || fs.size() != 1) {
return false;
}
auto& w = fs.front().outer;
CGAL::Polygon_2<kernel_> ps;
for (auto& wire_point : w) {
@@ -2037,7 +2037,7 @@ bool cgal_kernel::convert_impl(const taxonomy::boolean_result::ptr br, std::vect
continue;
}
// static
// static
auto z = taxonomy::make<taxonomy::direction3>(0, 0, 1);
cgal_polyhedron poly;
process_extrusion(fs.front(), z, 200, poly);
+1 -1
View File
@@ -74,7 +74,7 @@ namespace ifcopenshell {
class IFC_GEOMLIBRARY_API cgal_kernel : public abstract_kernel {
private:
#ifndef IFOPSH_SIMPLE_KERNEL
enum boolean_operand_preprocess {
enum boolean_operand_preprocess {
PP_MINKOWSKY_DILATE,
PP_SNAP_POINTS_TO_FIRST_OPERAND,
PP_SNAP_PLANES_TO_FIRST_OPERAND,
@@ -95,7 +95,7 @@ std::string dump_facet(typename CGAL::Nef_polyhedron_3<Kernel>::Halffacet_const_
const auto& p = h->plane();
oss << "Facet plane=" << p << std::endl;
auto fc = h->facet_cycles_begin();
auto se = shalfedge_const_handle(fc);
CGAL_assertion(se != 0);
@@ -187,7 +187,7 @@ plane_map<Kernel> snap_halfspaces(const std::list<CGAL::Plane_3<Kernel>>& planes
fuzzy_sphere fs(query, search_radius, 0.);
// std::cout << "q " << query << std::endl;
std::list<point_d> results_pos, results_neg;
kdtree.search(std::back_inserter(results_pos), fs);
@@ -801,7 +801,7 @@ void bfs(graph<Kernel>& g, size_t start_vertex, Fn& fn) {
for (boost::tie(ei, ei_end) = boost::out_edges(cur, g); ei != ei_end; ++ei) {
auto s = boost::source(*ei, g);
auto t = boost::target(*ei, g);
// @todo is this necessary?
if (cur == t) {
std::swap(s, t);
@@ -863,10 +863,10 @@ std::unique_ptr<halfspace_tree<TreeKernel>> build_halfspace_tree(graph<Kernel>&
int largest_component_idx = -1;
int num_components = 0;
// @nb we don't just randomly start from an arbitrary seed, but we sort planes by d / | abc |
// for (size_t i = 0; i < boost::num_vertices(sub_graph_0); ++i) {
std::vector<size_t> sorted_verts;
for (size_t i = 0; i < boost::num_vertices(sub_graph_0); ++i) {
sorted_verts.push_back(i);
@@ -1232,7 +1232,7 @@ std::unique_ptr<halfspace_tree<TreeKernel>> build_halfspace_tree_decomposed(cons
// directly, so for now we need to isolate the individual volumes.
CGAL::Polyhedron_3<Kernel> P;
poly.convert_inner_shell_to_polyhedron(ci->shells_begin(), P);
CGAL::Nef_polyhedron_3<Kernel> Pnef(P);
CGAL::Nef_polyhedron_3<Kernel> Pnef(P);
for (auto it = Pnef.halffacets_begin(); it != Pnef.halffacets_end(); ++it) {
if (it->incident_volume()->mark()) {
@@ -1315,7 +1315,7 @@ size_t edge_contract(graph<Kernel>& G) {
bool exists = boost::edge(srcid, tt, G).second;
if (!exists) {
boost::add_edge(srcid, tt, G);
}
}
}
}
++n;
@@ -19,7 +19,7 @@ struct IFC_GEOMLIBRARY_API manifold_part {
auto copy = s;
copy.CalculateNormals(3);
mesh = copy.GetMeshGL64();
solid = s;
solid = s;
}
manifold_part(const manifold::MeshGL64& s) : mesh(s) {}
@@ -114,10 +114,10 @@ namespace {
mesh_type build() const {
mesh_type mesh;
mesh.numProp = 3;
std::vector<size_t> vertex_use_count(vertices.size(), 0);
std::vector<Eigen::Vector3d> vertex_normals(vertices.size(), Eigen::Vector3d::Zero());
for (size_t i = 0; i < tri_verts.size(); i += 3) {
for (size_t j = 0; j < 3; ++j) {
vertex_use_count[tri_verts[i + j]]++;
@@ -1242,13 +1242,13 @@ namespace {
}
std::optional<part> part_from_halfspace_solid(halfspace_build_state& state, const taxonomy::solid::ptr& solid, const taxonomy::face::ptr& face,const manifold::Box& reference_box, double precision, double dilation) {
auto plane = taxonomy::cast<taxonomy::plane>(face->basis);
// @todo verify order
const auto transform = matrix_or_identity(solid->matrix) * matrix_or_identity(plane->matrix);
const auto extrusion_dir = matrix_or_identity(solid->matrix).col(2).head<3>().eval();
Eigen::Vector3d x = transform.col(0).head<3>();
Eigen::Vector3d y = transform.col(1).head<3>();
Eigen::Vector3d normal = transform.col(2).head<3>();
@@ -1281,7 +1281,7 @@ namespace {
const auto delta = corner - origin;
const auto u = delta.dot(x);
const auto v = delta.dot(y);
u_min = std::min(u_min, u);
u_max = std::max(u_max, u);
v_min = std::min(v_min, v);
@@ -58,7 +58,7 @@ double ifcopenshell::geom::util::min_edge_length(const TopoDS_Shape & a) {
TopExp_Explorer exp(a, TopAbs_EDGE);
for (; exp.More(); exp.Next()) {
const TopoDS_Edge& e = TopoDS::Edge(exp.Current());
TopoDS_Vertex v0, v1;
TopExp::Vertices(e, v0, v1);
if (!v0.IsNull() && !v1.IsNull() && v0.IsSame(v1)) {
+10 -10
View File
@@ -43,8 +43,8 @@ bool is_intersect_ray_box(const struct ray *ray, const struct box *box) {
// https://github.com/NVIDIA-Omniverse/PhysX/blob/main/physx/source/geomutils/src/intersection/GuIntersectionRayTriangle.h
// With minor modifications to use gp_Vec type.
// More reading: https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm
bool intersectRayTriangle( const gp_Vec& orig, const gp_Vec& dir,
const gp_Vec& vert0, const gp_Vec& vert1, const gp_Vec& vert2,
bool intersectRayTriangle( const gp_Vec& orig, const gp_Vec& dir,
const gp_Vec& vert0, const gp_Vec& vert1, const gp_Vec& vert2,
double& at, double& au, double& av,
bool cull, float enlarge) {
// Find vectors for two edges sharing vert0
@@ -147,7 +147,7 @@ void edgeEdgeDist(gp_Vec& x, gp_Vec& y, // closest points
const double Denom = ADotA*BDotB - ADotB*ADotB;
double t; // We will clamp result so t is on the segment (p, a)
if(Denom!=0.0)
if(Denom!=0.0)
t = ios_clamp((ADotT*BDotB - BDotT*ADotB) / Denom, 0.0, 1.0);
else
t = 0.0;
@@ -268,7 +268,7 @@ double distanceTriangleTriangleSquared(gp_Vec& cp, gp_Vec& cq, const std::array<
if(Tp[2]>Tp[index]) index = 2;
}
if(index >= 0)
if(index >= 0)
{
shown_disjoint = true;
@@ -297,7 +297,7 @@ double distanceTriangleTriangleSquared(gp_Vec& cp, gp_Vec& cq, const std::array<
gp_Vec Tn = Tv[0].Crossed(Tv[1]);
double Tnl = Tn.Dot(Tn);
if(Tnl>1e-15f)
{
const std::array<double, 3> Sp = {(q[0] - p[0]).Dot(Tn),
@@ -317,7 +317,7 @@ double distanceTriangleTriangleSquared(gp_Vec& cp, gp_Vec& cq, const std::array<
}
if(index >= 0)
{
{
shown_disjoint = true;
const gp_Vec& pIndex = p[index];
@@ -525,11 +525,11 @@ bool trianglesIntersectCoplanar(const gp_Vec& p1_n, const gp_Vec& a1, const gp_V
const double third = (1.0 / 3.0);
//A bit of the computations done inside the following functions could be shared but it's kept simple since the
//A bit of the computations done inside the following functions could be shared but it's kept simple since the
//difference is not very big and the coplanar case is not expected to be the most common case
if (linesIntersect(a1, b1, a2, b2, x, y) || linesIntersect(a1, b1, b2, c2, x, y) || linesIntersect(a1, b1, c2, a2, x, y) ||
linesIntersect(b1, c1, a2, b2, x, y) || linesIntersect(b1, c1, b2, c2, x, y) || linesIntersect(b1, c1, c2, a2, x, y) ||
linesIntersect(c1, a1, a2, b2, x, y) || linesIntersect(c1, a1, b2, c2, x, y) || linesIntersect(c1, a1, c2, a2, x, y) ||
linesIntersect(c1, a1, a2, b2, x, y) || linesIntersect(c1, a1, b2, c2, x, y) || linesIntersect(c1, a1, c2, a2, x, y) ||
pointInTriangle(a1, b1, c1, third * (a2 + b2 + c2), x, y) || pointInTriangle(a2, b2, c2, third * (a1 + b1 + c1), x, y))
return true;
@@ -557,7 +557,7 @@ bool trianglesIntersect(const gp_Vec& a1, const gp_Vec& b1, const gp_Vec& c1, co
if ((p1ToA > 0) == (p1ToB > 0) && (p1ToA > 0) == (p1ToC > 0))
return false; //All points of triangle 2 on same side of triangle 1 -> no intersection
gp_Dir p2_n((b2 - a2).Crossed(c2 - a2).Normalized());
double p2_d = -a2.Dot(p2_n);
// const PxPlane p2(a2, b2, c2);
@@ -566,7 +566,7 @@ bool trianglesIntersect(const gp_Vec& a1, const gp_Vec& b1, const gp_Vec& c1, co
const double p2ToC = c1.Dot(p2_n) + p2_d;
if ((p2ToA > 0) == (p2ToB > 0) && (p2ToA > 0) == (p2ToC > 0))
return false; //All points of triangle 1 on same side of triangle 2 -> no intersection
return false; //All points of triangle 1 on same side of triangle 2 -> no intersection
gp_Vec intersectionDirection = p1_n.Crossed(p2_n);
const double l2 = intersectionDirection.SquareMagnitude();
@@ -20,7 +20,7 @@ struct IFC_GEOMLIBRARY_API box {
IFC_GEOMLIBRARY_API bool is_intersect_ray_box(const struct ray *ray, const struct box *box);
IFC_GEOMLIBRARY_API bool intersectRayTriangle( const gp_Vec& orig, const gp_Vec& dir,
const gp_Vec& vert0, const gp_Vec& vert1, const gp_Vec& vert2,
const gp_Vec& vert0, const gp_Vec& vert1, const gp_Vec& vert2,
double& at, double& au, double& av,
bool cull, float enlarge=0.0f);
@@ -35,7 +35,7 @@ bool open_cascade_kernel::convert(const taxonomy::extrusion::ptr extrusion, Topo
if (face.ShapeType() == TopAbs_COMPOUND) {
// For compounds (most likely the result of a IfcCompositeProfileDef)
// For compounds (most likely the result of a IfcCompositeProfileDef)
// create a compound solid shape.
TopExp_Explorer exp(face, TopAbs_FACE);
+1 -1
View File
@@ -247,7 +247,7 @@ namespace {
auto crv = get_curve(e->basis);
result = Handle(Geom_Surface)(new Geom_SurfaceOfRevolution(
crv, ax
crv, ax
));
result->Transform(tr);
@@ -70,7 +70,7 @@ namespace ifcopenshell::geom {
std::pair<wire_it, wire_it> inner_wires() const {
return { wires_.begin() + 1, wires_.end() };
}
};
};
}
}
+5 -5
View File
@@ -163,7 +163,7 @@ bool open_cascade_kernel::convert(const taxonomy::loft::ptr loft, TopoDS_Shape&
return false;
}
}
NCollection_List<TopoDS_Shape> faces;
TopoDS_Compound comp;
BRep_Builder BB;
@@ -330,7 +330,7 @@ bool open_cascade_kernel::convert(const taxonomy::loft::ptr loft, TopoDS_Shape&
std::array<std::vector<std::vector<std::set<std::string>>>::const_iterator, 2> tag_pairs = {
all_tags.begin() + std::distance(shps.begin(), it),
all_tags.begin() + std::distance(shps.begin(), jt)};
for (size_t i = 0; i < 2; ++i) {
NCollection_IndexedDataMap<TopoDS_Shape, NCollection_List<TopoDS_Shape>, TopTools_ShapeMapHasher> ancestors;
const auto& wire = wp[i];
@@ -357,7 +357,7 @@ bool open_cascade_kernel::convert(const taxonomy::loft::ptr loft, TopoDS_Shape&
for (NCollection_List<TopoDS_Shape>::Iterator edge_it(incidentEdges); edge_it.More(); edge_it.Next()) {
const TopoDS_Edge& e = TopoDS::Edge(edge_it.Value());
TopoDS_Vertex ev0, ev1;
TopExp::Vertices(e, ev0, ev1);
@@ -405,11 +405,11 @@ bool open_cascade_kernel::convert(const taxonomy::loft::ptr loft, TopoDS_Shape&
++d;
} else {
throw std::runtime_error("Unable to construct surface");
}
}
}
continue;
}
}
for (auto& wp : ws) {
BRepTools_WireExplorer a(wp[0]);
+1 -1
View File
@@ -204,7 +204,7 @@ namespace {
BRep_Tool::Pnt(v1).DumpJson(oss);
auto osss = oss.str();
std::wcout << osss.c_str() << std::endl;
#endif
#endif
BRep_Builder B;
TopoDS_Wire W;
@@ -75,7 +75,7 @@ void ifcopenshell::geom::open_cascade_shape::triangulate(ifcopenshell::geom::set
// A 3x3 matrix to rotate the vertex normals
std::optional<gp_Mat> rotation_matrix;
if (place.components_) {
const auto& m = *place.components_;
rotation_matrix.emplace(
@@ -84,7 +84,7 @@ void ifcopenshell::geom::open_cascade_shape::triangulate(ifcopenshell::geom::set
m(2, 0), m(2, 1), m(2, 2)
);
}
// When welding vertices, vertex coords will be shared among faces so we need to per-shape set
// to keep track of which edges were already emitted.
std::set<std::pair<int, int>> emitted_edges;
@@ -119,9 +119,9 @@ void ifcopenshell::geom::open_cascade_shape::triangulate(ifcopenshell::geom::set
for (exp.Init(shape_, TopAbs_FACE); exp.More(); exp.Next(), ++num_faces) {
TopoDS_Face face = TopoDS::Face(exp.Current());
size_t num_bounds = 0;
size_t num_bounds = 0;
for (TopoDS_Iterator it(face); it.More(); it.Next(), ++num_bounds) {}
const bool is_planar = BRep_Tool::Surface(face) && BRep_Tool::Surface(face)->DynamicType() == STANDARD_TYPE(Geom_Plane);
const bool has_inner_bounds = num_bounds > 1;
@@ -314,7 +314,7 @@ void ifcopenshell::geom::open_cascade_shape::triangulate(ifcopenshell::geom::set
} else {
p = tessellater.Value(i).XYZ();
}
auto p_local = p;
taxonomy_transform(place.components_, p);
@@ -582,7 +582,7 @@ conversion_result_shape* ifcopenshell::geom::open_cascade_shape::concat(conversi
{
TopoDS_Compound compound;
BRep_Builder builder;
auto& left = shape_;
auto& right = ((ifcopenshell::geom::open_cascade_shape*)other)->shape_;
@@ -594,7 +594,7 @@ conversion_result_shape* ifcopenshell::geom::open_cascade_shape::concat(conversi
builder.MakeCompound(compound);
builder.Add(compound, left);
}
builder.Add(compound, right);
return new open_cascade_shape(std::move(compound));
@@ -84,7 +84,7 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_openings(const express::ba
// opening_trsf = relative;
std::vector<ifcopenshell::geom::conversion_result> opening_shapes;
// @todo
abstract_kernel::convert(op.first, opening_shapes);
@@ -309,13 +309,13 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// IfcSchema::IfcRelVoidsElement::list::ptr ifcopenshell::geom::Kernel::find_openings(IfcSchema::IfcProduct* product) {
// std::vector<IfcSchema::IfcRelVoidsElement*> rs;
//
//
// if (product->declaration().is(IfcSchema::IfcElement::Class()) && !product->declaration().is(IfcSchema::IfcOpeningElement::Class())) {
// IfcSchema::IfcElement* element = (IfcSchema::IfcElement*)product;
// auto rels = element->HasOpenings();
// rs.insert(rs.end(), rels->begin(), rels->end());
// }
//
//
// // Is the IfcElement a decomposition of an IfcElement with any IfcOpeningElements?
// IfcSchema::IfcObjectDefinition* obdef = product->as<IfcSchema::IfcObjectDefinition>();
// for (;;) {
@@ -327,10 +327,10 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// auto rels = element->HasOpenings();
// rs.insert(rs.end(), rels->begin(), rels->end());
// }
//
//
// obdef = rel_obdef;
// }
//
//
// // Filter openings in Reference view, solely marked as Reference.
// IfcSchema::IfcRelVoidsElement::list::ptr openings(new IfcSchema::IfcRelVoidsElement::list);
// std::for_each(rs.begin(), rs.end(), [&openings](IfcSchema::IfcRelVoidsElement* rel) {
@@ -341,17 +341,17 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// }
// }
// });
//
//
// return openings;
// }
//
//
// const IfcSchema::IfcMaterial* ifcopenshell::geom::Kernel::get_single_material_association(const IfcSchema::IfcProduct* product) {
// IfcSchema::IfcMaterial* single_material = 0;
// IfcSchema::IfcRelAssociatesMaterial::list::ptr associated_materials = product->HasAssociations()->as<IfcSchema::IfcRelAssociatesMaterial>();
// if (associated_materials->size() == 1) {
// IfcSchema::IfcMaterialSelect* associated_material = (*associated_materials->begin())->RelatingMaterial();
// single_material = associated_material->as<IfcSchema::IfcMaterial>();
//
//
// // NB: IfcMaterialLayerSets are also considered, regardless of --enable-layerset-slicing. Picking
// // the first material (in accordance with other viewers) when layerset-slicing is disabled.
// if (!single_material && associated_material->as<IfcSchema::IfcMaterialLayerSetUsage>()) {
@@ -366,21 +366,21 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// }
// return single_material;
// }
//
//
// ifcopenshell::geom::native_element* ifcopenshell::geom::Kernel::create_brep_for_representation_and_product(
// const IteratorSettings& settings, IfcSchema::IfcRepresentation* representation, IfcSchema::IfcProduct* product)
// {
// std::stringstream representation_id_builder;
//
//
// representation_id_builder << representation->data().id();
//
//
// ifcopenshell::geom::native* shape;
// std::vector<ifcopenshell::geom::conversion_result> shapes, shapes2;
//
//
// if (!convert_shapes(representation, shapes)) {
// return 0;
// }
//
//
// if (settings.get(IteratorSettings::APPLY_LAYERSETS)) {
// TopoDS_Shape merge;
// if (util::flatten_shape_list(shapes, merge, false, getValue(GV_PRECISION))) {
@@ -390,7 +390,7 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// std::vector< std::vector<Handle_Geom_Surface> > folded_layers;
// std::vector<std::shared_ptr<const SurfaceStyle>> styles;
// if (convert_layerset(product, layers, styles, thickness)) {
//
//
// IfcSchema::IfcRelAssociates::list::ptr associations = product->HasAssociations();
// for (IfcSchema::IfcRelAssociates::list::it it = associations->begin(); it != associations->end(); ++it) {
// IfcSchema::IfcRelAssociatesMaterial* associates_material = (**it).as<IfcSchema::IfcRelAssociatesMaterial>();
@@ -400,7 +400,7 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// break;
// }
// }
//
//
// if (styles.size() > 1) {
// // If there's only a single layer there is no need to manipulate geometries.
// bool success = true;
@@ -415,7 +415,7 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// success = true;
// }
// }
//
//
// if (!success) {
// ifcopenshell::logger::root().error("Failed processing layerset");
// }
@@ -424,9 +424,9 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// }
// }
// }
//
//
// bool material_style_applied = false;
//
//
// const IfcSchema::IfcMaterial* single_material = get_single_material_association(product);
// if (single_material) {
// auto s = get_style(single_material);
@@ -448,11 +448,11 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// ifcopenshell::logger::root().warning("No material and surface styles for:", product);
// }
// }
//
//
// if (material_style_applied) {
// representation_id_builder << "-material-" << single_material->data().id();
// }
//
//
// if (settings.force_space_transparency() >= 0. && product->declaration().is("IfcSpace")) {
// for (auto& s : shapes) {
// if (s.hasStyle()) {
@@ -464,7 +464,7 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// }
// }
// }
//
//
// int parent_id = -1;
// try {
// express::entity* parent_object = get_decomposing_entity(product);
@@ -474,10 +474,10 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// } catch (const std::exception& e) {
// ifcopenshell::logger::root().error(e);
// }
//
//
// const std::string name = product->Name().value_or("");
// const std::string guid = product->GlobalId();
//
//
// gp_Trsf trsf;
// try {
// if (product->ObjectPlacement()) {
@@ -488,20 +488,20 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// } catch (...) {
// ifcopenshell::logger::root().error("Failed to construct placement");
// }
//
//
// // Does the IfcElement have any IfcOpenings?
// // Note that openings for IfcOpeningElements are not processed
// IfcSchema::IfcRelVoidsElement::list::ptr openings = find_openings(product);
//
//
// const std::string product_type = product->declaration().name();
// ElementSettings element_settings(settings, getValue(GV_LENGTH_UNIT), product_type);
//
//
// if (!settings.get(ifcopenshell::geom::IteratorSettings::DISABLE_OPENING_SUBTRACTIONS) && openings && openings->size()) {
// representation_id_builder << "-openings";
// for (IfcSchema::IfcRelVoidsElement::list::it it = openings->begin(); it != openings->end(); ++it) {
// representation_id_builder << "-" << (*it)->data().id();
// }
//
//
// std::vector<ifcopenshell::geom::conversion_result> opened_shapes;
// bool caught_error = false;
// try {
@@ -512,11 +512,11 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// } catch (...) {
// ifcopenshell::logger::root().message(ifcopenshell::logger::LOG_ERROR, "error processing openings for:", product);
// }
//
//
// if (caught_error && opened_shapes.size() < shapes.size()) {
// opened_shapes = shapes;
// }
//
//
// if (settings.get(IteratorSettings::USE_WORLD_COORDS)) {
// for (std::vector<ifcopenshell::geom::conversion_result>::iterator it = opened_shapes.begin(); it != opened_shapes.end(); ++it) {
// it->prepend(trsf);
@@ -535,14 +535,14 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// } else {
// shape = new ifcopenshell::geom::native(element_settings, representation_id_builder.str(), shapes);
// }
//
//
// std::string context_string = "";
// if (representation->RepresentationIdentifier()) {
// context_string = *representation->RepresentationIdentifier();
// } else if (representation->ContextOfItems()->ContextType()) {
// context_string = *representation->ContextOfItems()->ContextType();
// }
//
//
// auto elem = new native_element(
// product->data().id(),
// parent_id,
@@ -554,7 +554,7 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// std::shared_ptr<ifcopenshell::geom::native>(shape),
// product
// );
//
//
// if (settings.get(IteratorSettings::VALIDATE_QUANTITIES)) {
// auto rels = product->IsDefinedBy();
// for (auto& rel : *rels) {
@@ -623,10 +623,10 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// }
// }
// }
//
//
// return elem;
// }
//
//
// IfcSchema::IfcRepresentation* ifcopenshell::geom::Kernel::representation_mapped_to(const IfcSchema::IfcRepresentation* representation) {
// IfcSchema::IfcRepresentation* representation_mapped_to = 0;
// try {
@@ -651,36 +651,36 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// }
// return representation_mapped_to;
// }
//
//
// IfcSchema::IfcProduct::list::ptr ifcopenshell::geom::Kernel::products_represented_by(const IfcSchema::IfcRepresentation* representation) {
// IfcSchema::IfcProduct::list::ptr products(new IfcSchema::IfcProduct::list);
//
//
// IfcSchema::IfcProductRepresentation::list::ptr prodreps = representation->OfProductRepresentation();
//
//
// for (IfcSchema::IfcProductRepresentation::list::it it = prodreps->begin(); it != prodreps->end(); ++it) {
// // http://buildingsmart-tech.org/ifc/IFC2x3/TC1/html/ifcrepresentationresource/lexical/ifcproductrepresentation.htm
// // IFC2x Edition 3 NOTE Users should not instantiate the entity IfcProductRepresentation from IFC2x Edition 3 onwards.
// // It will be changed into an ABSTRACT supertype in future releases of IFC.
//
//
// // IfcProductRepresentation also lacks the INVERSE relation to IfcProduct
// // Let's find the IfcProducts that reference the IfcProductRepresentation anyway
// products->push((*it)->data().get_inverse((&IfcSchema::IfcProduct::Class()), -1)->as<IfcSchema::IfcProduct>());
// }
//
//
// IfcSchema::IfcRepresentationMap::list::ptr maps = representation->RepresentationMap();
//
//
// if (products->size() && maps->size()) {
// ifcopenshell::logger::root().warning("Representation used by IfcRepresentationMap and IfcProductDefinitionShape", representation);
// }
//
//
// if (prodreps->size() > 1) {
// ifcopenshell::logger::root().warning("Multiple IfcProductDefinitionShapes for representation", representation);
// }
//
//
// if (maps->size() > 1) {
// ifcopenshell::logger::root().warning("Multiple IfcRepresentationMaps for representation", representation);
// }
//
//
// if (maps->size() == 1) {
// IfcSchema::IfcRepresentationMap* map = *maps->begin();
// if (is_identity_transform(map->MappingOrigin())) {
@@ -688,11 +688,11 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// for (IfcSchema::IfcMappedItem::list::it it = items->begin(); it != items->end(); ++it) {
// IfcSchema::IfcMappedptr item = *it;
// if (item->StyledByItem()->size() != 0) continue;
//
//
// if (!is_identity_transform(item->MappingTarget())) {
// continue;
// }
//
//
// IfcSchema::IfcRepresentation::list::ptr reps = item->data().get_inverse((&IfcSchema::IfcRepresentation::Class()), -1)->as<IfcSchema::IfcRepresentation>();
// for (IfcSchema::IfcRepresentation::list::it jt = reps->begin(); jt != reps->end(); ++jt) {
// IfcSchema::IfcRepresentation* rep = *jt;
@@ -706,10 +706,10 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// }
// }
// }
//
//
// return products;
// }
//
//
// ifcopenshell::geom::native_element* ifcopenshell::geom::Kernel::create_brep_for_processed_representation(
// const IteratorSettings& /*settings*/, IfcSchema::IfcRepresentation* representation, IfcSchema::IfcProduct* product,
// ifcopenshell::geom::native_element* brep)
@@ -723,10 +723,10 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// } catch (const std::exception& e) {
// ifcopenshell::logger::root().error(e);
// }
//
//
// const std::string name = product->Name().value_or("");
// const std::string guid = product->GlobalId();
//
//
// gp_Trsf trsf;
// try {
// if (product->ObjectPlacement()) {
@@ -737,16 +737,16 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// } catch (...) {
// ifcopenshell::logger::root().error("Failed to construct placement");
// }
//
//
// std::string context_string = "";
// if (representation->RepresentationIdentifier()) {
// context_string = *representation->RepresentationIdentifier();
// } else if (representation->ContextOfItems()->ContextType()) {
// context_string = *representation->ContextOfItems()->ContextType();
// }
//
//
// const std::string product_type = product->declaration().name();
//
//
// return new native_element(
// product->data().id(),
// parent_id,
@@ -759,24 +759,24 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// product
// );
// }
//
//
// bool ifcopenshell::geom::Kernel::convert_layerset(const IfcSchema::IfcProduct* product, std::vector<Handle_Geom_Surface>& surfaces, std::vector<std::shared_ptr<const SurfaceStyle>>& styles, std::vector<double>& thicknesses) {
//
//
// }
//
//
// bool ifcopenshell::geom::Kernel::find_wall_end_points(const IfcSchema::IfcWall* wall, gp_Pnt& start, gp_Pnt& end) {
// IfcSchema::IfcRepresentation* axis_representation = find_representation(wall, "Axis");
// if (!axis_representation) {
// return false;
// }
//
//
// std::vector<conversion_result> items;
// {
// Kernel temp = *this;
// temp.setValue(GV_DIMENSIONALITY, -1.);
// temp.convert_shapes(axis_representation, items);
// }
//
//
// TopoDS_Vertex a, b;
// for (std::vector<conversion_result>::const_iterator it = items.begin(); it != items.end(); ++it) {
// TopExp_Explorer exp(it->shape(), TopAbs_VERTEX);
@@ -787,36 +787,36 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// }
// }
// }
//
//
// if (a.IsNull() || b.IsNull()) {
// return false;
// }
//
//
// start = BRep_Tool::Pnt(a);
// end = BRep_Tool::Pnt(b);
//
//
// return true;
// }
//
//
// bool ifcopenshell::geom::Kernel::fold_layers(const IfcSchema::IfcWall* wall, const std::vector<conversion_result>& items, const std::vector<Handle_Geom_Surface>& surfaces, const std::vector<double>& thicknesses, std::vector< std::vector<Handle_Geom_Surface> >& result) {
// /*
// * @todo isn't it easier to do this based on the non-folded surfaces of
// * the connected walls and fold both pairs of layersets simultaneously?
// */
//
//
// bool folds_made = false;
//
//
// IfcSchema::IfcRelConnectsPathElements::list::ptr connections(new IfcSchema::IfcRelConnectsPathElements::list);
// connections->push(wall->ConnectedFrom()->as<IfcSchema::IfcRelConnectsPathElements>());
// connections->push(wall->ConnectedTo()->as<IfcSchema::IfcRelConnectsPathElements>());
//
//
// typedef std::vector<Handle_Geom_Surface> surfaces_t;
// typedef std::pair<Handle_Geom_Surface, Handle_Geom_Curve> curve_on_surface;
// typedef std::vector<curve_on_surface> curves_on_surfaces_t;
// typedef std::vector< std::pair< std::pair<IfcSchema::IfcConnectionTypeEnum::Value, IfcSchema::IfcConnectionTypeEnum::Value>, const IfcSchema::IfcProduct*> > endpoint_connections_t;
// typedef std::vector< std::vector<Handle_Geom_Surface> > result_t;
// endpoint_connections_t endpoint_connections;
//
//
// // Find the semantic connections to other wall elements when they are not connected 'AT_PATH' because
// // in that latter case no folds need to be made.
// for (IfcSchema::IfcRelConnectsPathElements::list::it it = connections->begin(); it != connections->end(); ++it) {
@@ -838,18 +838,18 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// }
// }
// }
//
//
// if (endpoint_connections.size() == 0) {
// return false;
// }
//
//
// // Count how many connections are made AT_START and AT_END respectively
// int connection_type_count[2] = { 0,0 };
// for (endpoint_connections_t::const_iterator it = endpoint_connections.begin(); it != endpoint_connections.end(); ++it) {
// const int idx = it->first.first == IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATSTART;
// connection_type_count[idx] ++;
// }
//
//
// gp_Trsf local;
// if (wall->ObjectPlacement()) {
// if (!convert(wall->ObjectPlacement(), local)) {
@@ -857,7 +857,7 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// }
// }
// local.Invert();
//
//
// {
// // Copy the unfolded surfaces
// result.resize(surfaces.size());
@@ -867,25 +867,25 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// result_it->push_back(*input_it);
// }
// }
//
//
// const double total_thickness = std::accumulate(thicknesses.begin(), thicknesses.end(), 0.);
//
//
// gp_Pnt own_axis_start, own_axis_end;
// find_wall_end_points(wall, own_axis_start, own_axis_end);
//
//
// // Sometimes duplicate IfcRelConnectsPathElements exist. These are detected
// // and the counts of connections are decremented accordingly.
// for (int idx = 0; idx < 2; ++idx) {
// if (connection_type_count[idx] <= 1) {
// continue;
// }
//
//
// /*
// IfcSchema::IfcConnectionTypeEnum::Value connection_type = idx == 1
// ? IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATSTART
// : IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATEND;
// */
//
//
// std::set<const IfcSchema::IfcProduct*> others;
// endpoint_connections_t::iterator it = endpoint_connections.begin();
// while (it != endpoint_connections.end()) {
@@ -899,38 +899,38 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// }
// }
// }
//
//
// // Check whether the end points are of the wall are really ~1 LayerThickness away from each other
// /*
// for (endpoint_connections_t::const_iterator it = endpoint_connections.begin(); it != endpoint_connections.end(); ++it) {
// IfcSchema::IfcConnectionTypeEnum::Value own_type = it->first.first;
// IfcSchema::IfcConnectionTypeEnum::Value other_type = it->first.second;
//
//
// gp_Pnt other_axis_start, other_axis_end;
// find_wall_end_points(it->second->as<IfcSchema::IfcWall>(), other_axis_start, other_axis_end);
//
//
// gp_Trsf other;
// if (!convert(it->second->ObjectPlacement(), other)) {
// continue;
// }
//
//
// other.Transforms(other_axis_start.ChangeCoord());
// local.Transforms(other_axis_start.ChangeCoord());
// other.Transforms(other_axis_end.ChangeCoord());
// local.Transforms(other_axis_end.ChangeCoord());
//
//
// const gp_Pnt& a = own_type == IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATSTART
// ? own_axis_start
// : own_axis_end;
//
//
// const gp_Pnt& b = other_type == IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATSTART
// ? other_axis_start
// : other_axis_end;
//
//
// const double d = a.Distance(b);
// }
// */
//
//
// const double length_required = endpoint_connections.size() * total_thickness;
// // @todo this is not precisely the distance in case of curved walls. Also, it's safer
// // to first reproject the body onto the axis to get the precise curve parametrization
@@ -940,20 +940,20 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// ifcopenshell::logger::root().warning("The wall axis is not long enough to accommodate the fold points");
// return false;
// }
//
//
// for (endpoint_connections_t::const_iterator it = endpoint_connections.begin(); it != endpoint_connections.end(); ++it) {
// IfcSchema::IfcConnectionTypeEnum::Value connection_type = it->first.first;
//
//
// // If more than one wall connects to this start/end -point assume layers do not need to be folded
// const int idx = connection_type == IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATSTART;
// if (connection_type_count[idx] > 1) continue;
//
//
// // Pick the corresponding point from the axis
// const gp_Pnt& own_end_point = connection_type == IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATEND
// ? own_axis_end
// : own_axis_start;
// const IfcSchema::IfcProduct* other_wall = it->second;
//
//
// gp_Trsf other;
// if (other_wall->ObjectPlacement()) {
// if (!convert(other_wall->ObjectPlacement(), other)) {
@@ -961,32 +961,32 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// continue;
// }
// }
//
//
// IfcSchema::IfcRepresentation* axis_representation = find_representation(other_wall, "Axis");
//
//
// if (!axis_representation) {
// ifcopenshell::logger::root().warning("Joined wall has no axis representation", other_wall);
// continue;
// }
//
//
// std::vector<conversion_result> axis_items;
// {
// Kernel temp = *this;
// temp.setValue(GV_DIMENSIONALITY, -1.);
// temp.convert_shapes(axis_representation, axis_items);
// }
//
//
// TopoDS_Shape axis_shape;
// util::flatten_shape_list(axis_items, axis_shape, false, getValue(GV_PRECISION));
//
//
// // local and other are IfcLocalPlacements and therefore have a unit
// // scale factor that can be applied by means of TopoDS_Shape::Move()
// axis_shape.Move(other);
// axis_shape.Move(local);
//
//
// TopoDS_Shape body_shape;
// util::flatten_shape_list(items, body_shape, false, getValue(GV_PRECISION));
//
//
// // Create a single paremetric range over a single curve
// // that represents the entire 1d domain of the other wall
// // Sometimes there are multiple edges in the Axis shape
@@ -998,19 +998,19 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// if (!exp.More()) {
// return false;
// }
//
//
// TopoDS_Edge axis_edge = TopoDS::Edge(exp.Current());
// other_axis_curve = BRep_Tool::Curve(axis_edge, axis_u1, axis_u2);
//
//
// gp_Pnt other_a_1, other_a_2;
// other_axis_curve->D0(axis_u1, other_a_1);
// other_axis_curve->D0(axis_u2, other_a_2);
//
//
// if (axis_u2 < axis_u1) {
// std::swap(axis_u1, axis_u2);
// }
// exp.Next();
//
//
// for (; exp.More(); exp.Next()) {
// TopoDS_Edge axis_edge2 = TopoDS::Edge(exp.Current());
// TopExp_Explorer exp2(axis_edge2, TopAbs_VERTEX);
@@ -1025,22 +1025,22 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// }
// }
// }
//
//
// double layer_offset = 0;
//
//
// std::vector<double>::const_iterator thickness = thicknesses.begin();
// result_t::iterator result_vector = result.begin() + 1;
//
//
// // nb The first layer is never folded, because it corresponds
// // to one of the longitudinal faces of the wall. Hence the +1
// for (surfaces_t::const_iterator jt = surfaces.begin() + 1; jt != surfaces.end() - 1; ++jt, ++result_vector) {
// layer_offset += *thickness++;
//
//
// bool found_intersection = false, parallel = false;
// std::optional<gp_Pnt> point_outside_param_range;
//
//
// const Handle_Geom_Surface& surface = *jt;
//
//
// // Find the intersection point between the layerset surface
// // and the other axis curve. If it's within the parametric
// // range of the other wall it means the walls are connected
@@ -1048,16 +1048,16 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// GeomAPI_IntCS intersections(other_axis_curve, surface);
// if (intersections.IsDone() && intersections.NbPoints() == 1) {
// const gp_Pnt& p = intersections.Point(1);
//
//
// double u, v, w;
// intersections.Parameters(1, u, v, w);
//
//
// gp_Pnt Pc, Ps;
// gp_Vec Vc, Vs1, Vs2;
// other_axis_curve->D1(w, Pc, Vc);
// surface->D1(u, v, Ps, Vs1, Vs2);
// Vs1.Cross(Vs2);
//
//
// if (Vs1.IsNormal(Vc, 1.e-5)) {
// ifcopenshell::logger::root().warning("Connected walls are parallel");
// parallel = true;
@@ -1069,9 +1069,9 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// break;
// }
// }
//
//
// if (!parallel && !found_intersection && point_outside_param_range) {
//
//
// /*
// Is there a bug in Open Cascade related to the intersection
// of offset surfaces constructed from linear extrusions?
@@ -1083,13 +1083,13 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// Handle_Geom_Surface yz2 = new Geom_OffsetSurface(yz, 1.);
// intersect(xy, yz2);
// */
//
//
// Handle_Geom_Surface plane = new Geom_Plane(*point_outside_param_range, gp::DZ());
//
//
// // vertical edges at wall end point face.
// curves_on_surfaces_t layer_ends;
// util::intersect(surface, body_shape, layer_ends);
//
//
// Handle_Geom_Curve layer_body_intersection;
// Handle_Geom_Surface body_surface;
// double mind = std::numeric_limits<double>::infinity();
@@ -1111,9 +1111,9 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// if (d < total_thickness * 3 && d < mind) {
// GeomAdaptor_Curve GAC(other_axis_curve);
// GeomAdaptor_Surface GAS(kt->first);
//
//
// Extrema_ExtCS x(GAC, GAS, getValue(GV_PRECISION), getValue(GV_PRECISION));
//
//
// if (x.IsParallel()) {
// body_surface = kt->first;
// layer_body_intersection = kt->second;
@@ -1122,16 +1122,16 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// }
// }
// }
//
//
// if (body_surface.IsNull()) {
// continue;
// }
//
//
// // Intersect vertical edge with ground plane for point.
// GeomAPI_IntCS intersection2(layer_body_intersection, plane);
// if (intersection2.IsDone() && intersection2.NbPoints() == 1) {
// const gp_Pnt& layer_end_point = intersection2.Point(1);
//
//
// // Intersect layerset surface with ground plane
// GeomAPI_IntSS intersection3(surface, plane, 1.e-7);
// if (intersection3.IsDone() && intersection3.NbLines() == 1) {
@@ -1140,14 +1140,14 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// ShapeAnalysis_Curve sac;
// gp_Pnt layer_end_point_projected; double layer_end_point_param;
// sac.Project(layer_line, layer_end_point, 1e-3, layer_end_point_projected, layer_end_point_param, false);
//
//
// // Move point inwards by distance from other layerset
// GCPnts_AbscissaPoint dst(layer_line_adaptor, layer_offset, layer_end_point_param);
// if (dst.IsDone()) {
// // Convert parameter to point
// gp_Pnt layer_fold_point;
// layer_line->D0(dst.Parameter(), layer_fold_point);
//
//
// GeomAPI_IntSS intersection4(body_surface, plane, 1.e-7);
// if (intersection4.IsDone() && intersection4.NbLines() == 1) {
// Handle_Geom_Curve body_trim_curve = intersection4.Line(1);
@@ -1155,7 +1155,7 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// gp_Pnt layer_fold_point_projected; double layer_fold_point_param;
// sac2.Project(body_trim_curve, layer_fold_point, 1.e-7, layer_fold_point_projected, layer_fold_point_param, false);
// Handle_Geom_Curve fold_curve = new Geom_OffsetCurve(body_trim_curve->Reversed(), layer_fold_point_projected.Distance(layer_fold_point), gp::DZ());
//
//
// Handle_Geom_Surface fold_surface = new Geom_SurfaceOfLinearExtrusion(fold_curve, gp::DZ());
// result_vector->push_back(fold_surface);
// folds_made = true;
@@ -1163,15 +1163,15 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// }
// }
// }
//
//
// }
//
//
// }
// }
//
//
// return folds_made;
// }
//
//
// IfcSchema::IfcRepresentation* ifcopenshell::geom::Kernel::find_representation(const IfcSchema::IfcProduct* product, const std::string& identifier) {
// if (!product->Representation()) return 0;
// IfcSchema::IfcProductRepresentation* prod_rep = product->Representation();
@@ -1183,12 +1183,12 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// }
// return 0;
// }
//
//
// const IfcSchema::IfcRepresentationptr ifcopenshell::geom::Kernel::find_item_carrying_style(const IfcSchema::IfcRepresentationptr item) {
// if (item->StyledByItem()->size()) {
// return item;
// }
//
//
// while (item->declaration().is(IfcSchema::IfcBooleanResult::Class())) {
// // All instantiations of IfcBooleanOperand (type of FirstOperand) are subtypes of
// // IfcGeometricRepresentationItem
@@ -1197,24 +1197,24 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// return item;
// }
// }
//
//
// // TODO: Ideally this would be done for other entities (such as IfcCsgSolid) as well.
// // But neither are these very prevalent, nor does the current IfcOpenShell style
// // mechanism enable to conveniently style subshapes, which would be necessary for
// // distinctly styled union operands.
//
//
// return item;
// }
//
//
// bool ifcopenshell::geom::Kernel::is_identity_transform(ifcopenshell::IfcBaseInterface* l) {
// IfcSchema::IfcAxis2Placement2D* ax2d;
// IfcSchema::IfcAxis2Placement3D* ax3d;
//
//
// IfcSchema::IfcCartesianTransformationOperator2D* op2d;
// IfcSchema::IfcCartesianTransformationOperator3D* op3d;
// IfcSchema::IfcCartesianTransformationOperator2DnonUniform* op2dnonu;
// IfcSchema::IfcCartesianTransformationOperator3DnonUniform* op3dnonu;
//
//
// if ((op2dnonu = l->as<IfcSchema::IfcCartesianTransformationOperator2DnonUniform>()) != 0) {
// gp_GTrsf2d gtrsf2d;
// convert(op2dnonu, gtrsf2d);
@@ -1243,18 +1243,18 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// throw ifcopenshell::exception("Invalid valuation for IfcAxis2Placement / IfcCartesianTransformationOperator");
// }
// }
//
//
// void ifcopenshell::geom::Kernel::set_conversion_placement_rel_to_type(const ifcopenshell::declaration* type) {
// placement_rel_to_type_ = type;
// }
//
//
// void ifcopenshell::geom::Kernel::set_conversion_placement_rel_to_instance(const express::entity* instance) {
// placement_rel_to_instance_ = instance;
// }
//
//
//
//
// namespace {
//
//
// bool process_colour(IfcSchema::IfcColourRgb* colour, double* rgb) {
// if (colour != 0) {
// rgb[0] = colour->Red();
@@ -1263,7 +1263,7 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// }
// return colour != 0;
// }
//
//
// bool process_colour(IfcSchema::IfcNormalisedRatioMeasure* factor, double* rgb) {
// if (factor != 0) {
// const double f = *factor;
@@ -1271,7 +1271,7 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// }
// return factor != 0;
// }
//
//
// bool process_colour(IfcSchema::IfcColourOrFactor* colour_or_factor, double* rgb) {
// if (colour_or_factor == 0) {
// return false;
@@ -1283,11 +1283,11 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// return false;
// }
// }
//
//
// }
//
//
// #define Kernel POSTFIX_SCHEMA(Kernel)
//
//
// std::shared_ptr<const ifcopenshell::geom::SurfaceStyle> ifcopenshell::geom::Kernel::internalize_surface_style(const std::pair<express::base, express::base>& shading_styles) {
// if (shading_styles.second == 0) {
// return 0;
@@ -1297,22 +1297,22 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// if (it != style_cache.end()) {
// return it->second;
// }
//
//
//
//
// IfcSchema::IfcSurfaceStyle* style = shading_styles.first->as<IfcSchema::IfcSurfaceStyle>();
// IfcSchema::IfcSurfaceStyleShading* shading = shading_styles.second->as<IfcSchema::IfcSurfaceStyleShading>();
//
//
// std::shared_ptr<SurfaceStyle> surface_style_ptr;
//
//
// if (style->Name()) {
// surface_style_ptr.reset(new SurfaceStyle(surface_style_id, *style->Name()));
// } else {
// surface_style_ptr.reset(new SurfaceStyle(surface_style_id));
// }
//
//
// std::shared_ptr<const SurfaceStyle> surface_style_ptr_const = std::const_pointer_cast<const SurfaceStyle>(surface_style_ptr);
// SurfaceStyle& surface_style = *surface_style_ptr;
//
//
// double rgb[3];
// if (process_colour(shading->SurfaceColour(), rgb)) {
// surface_style.Diffuse().reset(SurfaceStyle::ColorComponent(rgb[0], rgb[1], rgb[2]));
@@ -1353,11 +1353,11 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// }
// return style_cache[surface_style_id] = surface_style_ptr_const;
// }
//
//
// std::shared_ptr<const ifcopenshell::geom::SurfaceStyle> ifcopenshell::geom::Kernel::get_style(const IfcSchema::IfcRepresentationptr item) {
// return internalize_surface_style(get_surface_style<IfcSchema::IfcSurfaceStyleShading>(item));
// }
//
//
// std::shared_ptr<const ifcopenshell::geom::SurfaceStyle> ifcopenshell::geom::Kernel::get_style(const IfcSchema::IfcMaterial* material) {
// IfcSchema::IfcMaterialDefinitionRepresentation::list::ptr defs = material->HasRepresentation();
// for (IfcSchema::IfcMaterialDefinitionRepresentation::list::it jt = defs->begin(); jt != defs->end(); ++jt) {
@@ -1376,14 +1376,14 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// auto material_style = std::make_shared<ifcopenshell::geom::SurfaceStyle>(material->data().id(), material->Name());
// return style_cache[material->data().id()] = material_style;
// }
//
//
// void ifcopenshell::geom::Kernel::apply_layerset(std::vector<ifcopenshell::geom::conversion_result>& r, const ifcopenshell::geom::layerset_information& info) {
// convert(info.layers);
//
//
// if (info.layers.empty()) {
// return;
// }
//
//
// if (axis_curve->DynamicType() == STANDARD_TYPE(Geom_Line)) {
// Handle_Geom_Line axis_line = Handle_Geom_Line::DownCast(axis_curve);
// // @todo note that this creates an offset into the wrong order, the cross product arguments should be
@@ -1397,7 +1397,7 @@ bool ifcopenshell::geom::open_cascade_kernel::convert_impl(const taxonomy::revol
// ifcopenshell::logger::root().message(ifcopenshell::logger::LOG_ERROR, "Unsupported underlying curve of Axis representation:", product);
// return false;
// }
//
//
// std::vector<ifcopenshell::geom::conversion_result> r2;
// if (ifcopenshell::geom::util::apply_layerset(r, const std::vector<ifcopenshell::geom::taxonomy::style>&, std::vector<conversion_result>& r2, double tol)) {
// std::swap(r, r2)
@@ -90,7 +90,7 @@ private:
std::map<std::pair<int, int>, TopoDS_Edge> edges_;
double eps_;
bool non_manifold_;
void loop_(const ifcopenshell::geom::taxonomy::loop::ptr ps, const std::function<void(int, int, bool)>& callback);
public:
faceset_helper(open_cascade_kernel* kernel, const ifcopenshell::geom::taxonomy::shell::ptr l);
@@ -99,7 +99,7 @@ private:
bool non_manifold() const { return non_manifold_; }
bool& non_manifold() { return non_manifold_; }
double epsilon() const { return eps_; }
bool edge(int A, int B, TopoDS_Edge& e);
bool wire(const ifcopenshell::geom::taxonomy::loop::ptr loop, TopoDS_Wire& wire);
+2 -2
View File
@@ -64,8 +64,8 @@ bool open_cascade_kernel::convert(const taxonomy::solid::ptr solid, TopoDS_Shape
result = halfspace;
return true;
} else if (solid->children.size() == 1
&& solid->children[0]->children.size() == 1
} else if (solid->children.size() == 1
&& solid->children[0]->children.size() == 1
&& solid->children[0]->children[0]->basis
&& solid->children[0]->children[0]->basis->kind() == taxonomy::SPHERE)
{
@@ -126,7 +126,7 @@ bool open_cascade_kernel::convert(const taxonomy::sweep_along_curve::ptr scs, To
}
}
}
// Build the wire from curve, which is the directrix offset toward the origin
// when applied_temporary_offset is set. Using scs->curve here left the wire
// far from the origin yet still translated the result back by +mean, which
@@ -155,11 +155,11 @@ bool open_cascade_kernel::convert(const taxonomy::sweep_along_curve::ptr scs, To
} else {
return false;
}
Handle(Geom_Surface) surface;
if (scs->surface) {
surface = convert_surface(scs->surface);
}
}
gp_Trsf directrix;
TopoDS_Wire wire = std::get<TopoDS_Wire>(w);
+18 -18
View File
@@ -201,12 +201,12 @@ namespace ifcopenshell::geom {
box.corners[1][1] = static_cast<float>(max_point[1] + 1e-5);
box.corners[1][2] = static_cast<float>(max_point[2] + 1e-5);
/*
std::cout << "Ray "
std::cout << "Ray "
<< v_ray.origin[0] << " "
<< v_ray.origin[1] << " "
<< v_ray.origin[2] << " "
<< std::endl;
std::cout << "Box "
std::cout << "Box "
<< min_point[0] << " "
<< min_point[1] << " "
<< min_point[2] << " "
@@ -864,7 +864,7 @@ namespace ifcopenshell::geom {
if (dss.Value() <= extend) {
distances_.push_back(dss.Value());
protrusion_distances_.push_back(max_distance_inside(B, A));
}
}
return dss.Value() <= extend;
}
} else {
@@ -925,8 +925,8 @@ namespace ifcopenshell::geom {
// Gap is assumed to be positive throughout the codebase,
// but at least for IsOut() in the selector a negative
// Gap should work as well.
b.SetGap(b.GetGap() + extend);
b.SetGap(b.GetGap() + extend);
return select_box(b, completely_within);
}
@@ -956,7 +956,7 @@ namespace ifcopenshell::geom {
double gap = B.GetGap();
gp_Pnt p1(x1 - gap, y1 - gap, z1 - gap);
gp_Pnt p2(x2 + gap, y2 + gap, z2 + gap);
if (!b.IsOut(p1) && !b.IsOut(p2)) {
ts_filtered.push_back(*it);
}
@@ -1389,7 +1389,7 @@ namespace ifcopenshell::geom {
if (extend > 0.0) {
BRepExtrema_DistShapeShape dss(v, B);
if (dss.Perform() && dss.NbSolution() >= 1 && dss.Value() <= extend) {
distances_.push_back(dss.Value());
distances_.push_back(dss.Value());
protrusion_distances_.push_back(max_distance_inside(B, v));
ts_filtered.push_back(*it);
@@ -1416,9 +1416,9 @@ namespace ifcopenshell::geom {
spatial_tree tree_;
shape_map shapes_;
std::map<T, Bnd_Box> aabbs_;
std::map<T, Bnd_OBB> obbs_;
std::map<T, double> max_protrusions_;
std::map<T, opencascade::handle<BVH_Tree<double, 3, BVH_BinaryTree>>> bvhs_;
std::map<T, Bnd_OBB> obbs_;
std::map<T, double> max_protrusions_;
std::map<T, opencascade::handle<BVH_Tree<double, 3, BVH_BinaryTree>>> bvhs_;
std::unordered_map<T, bool> is_manifold_;
std::unordered_map<T, std::vector<std::array<int, 3>>> tris_;
std::unordered_map<T, std::vector<gp_Pnt>> verts_;
@@ -1433,7 +1433,7 @@ namespace ifcopenshell::geom {
std::map<std::string, std::vector<int>> local_faces_;
std::map<std::string, std::vector<ifcopenshell::geom::taxonomy::style::ptr>> local_materials_;
std::map<std::string, std::vector<int>> local_material_ids_;
bool enable_face_styles_ = false;
class selector : public spatial_tree::Selector
@@ -1480,7 +1480,7 @@ namespace ifcopenshell::geom {
opencascade_tree(ifcopenshell::geom::iterator& it) {
add_file(it);
}
}
void add_file(ifcopenshell::file& f, ifcopenshell::geom::settings settings) {
ifcopenshell::geom::settings settings_ = settings;
@@ -1509,7 +1509,7 @@ namespace ifcopenshell::geom {
result.reserve(flat_list.size());
for (size_t i = 0; i < flat_list.size(); i += 3) {
vin <<
vin <<
flat_list[i],
flat_list[i + 1],
flat_list[i + 2];
@@ -1576,7 +1576,7 @@ namespace ifcopenshell::geom {
vs_transformed.push_back(p.Transformed(tr));
aabb.Add(vs_transformed.back());
}
std::unordered_map<std::tuple<int, int, int>, std::vector<size_t>, boost::hash<std::tuple<int, int, int>>> quantized_normal_counts;
std::vector<double> tri_areas;
@@ -1631,7 +1631,7 @@ namespace ifcopenshell::geom {
gp_Ax3 ax3;
gp_Trsf trsf2;
for (size_t attempt = 0; attempt < 2; ++attempt) {
if (candidates.empty() || attempt == 1) {
@@ -1673,7 +1673,7 @@ namespace ifcopenshell::geom {
obb.SetZComponent(ax3.Direction(), halfsize.Z());
obb.SetCenter(cent.Transformed(trsf2.Inverted()));
}
const auto& t = elem->product();
const auto& matrix = elem->transformation().data();
const std::vector<double>& elem_verts_local = elem->geometry().verts();
@@ -1744,7 +1744,7 @@ namespace ifcopenshell::geom {
obbs_[t] = obb;
max_protrusions_[t] = std::min(std::min(obb.XHSize(), obb.YHSize()), obb.ZHSize()) * 2;
}
void add_element(ifcopenshell::geom::native_element* elem) {
if (!elem) {
return;
@@ -1753,7 +1753,7 @@ namespace ifcopenshell::geom {
auto compound_generic = (ifcopenshell::geom::open_cascade_shape*)elem->geometry().as_compound();
TopoDS_Shape compound(std::move(compound_generic->shape()));
delete compound_generic;
const auto& m = elem->transformation().data()->ccomponents();
gp_Trsf tr;
tr.SetValues(
@@ -381,7 +381,7 @@ namespace {
bool ifcopenshell::geom::util::wire_intersections(const TopoDS_Wire& wire, NCollection_List<TopoDS_Shape>& wires, const wire_tolerance_settings& settings) {
double eps = get_wire_intersection_tolerance(settings, wire);
double eps_real = settings.precision;
if (!wire.Closed()) {
wires.Append(wire);
return false;
@@ -576,7 +576,7 @@ void ifcopenshell::geom::util::select_largest(const NCollection_List<TopoDS_Shap
BRepBndLib::AddClose(it.Value(), bb);
double xyz_min[3], xyz_max[3];
bb.Get(xyz_min[0], xyz_min[1], xyz_min[2], xyz_max[0], xyz_max[1], xyz_max[2]);
// @todo hard coded precision.
// @todo this is a really strange measure for wire size. Why not use newell's
// method to project to plane and then calculate size of the 2d bbox?
@@ -45,7 +45,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcArbitraryClosedProfileDef& i
}
}
}
return face;
} else {
return nullptr;
+1 -1
View File
@@ -35,7 +35,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcAxis1Placement& inst) {
taxonomy::direction3::ptr v = taxonomy::cast<taxonomy::direction3>(map(inst.Axis()));
axis = *v->components_;
}
// @todo not sure what to do with ref, we're probably never reading it,
// because we just created an Axis1 again from it in the kernel, but
// to this constructor we need to supply something valid.
@@ -27,12 +27,12 @@ using namespace ifcopenshell::geom;
#ifdef SCHEMA_HAS_IfcBSplineCurveWithKnots
taxonomy::ptr mapping::map_impl(const IfcSchema::IfcBSplineCurveWithKnots& inst) {
auto bc = taxonomy::make<taxonomy::bspline_curve>();
const std::vector<IfcSchema::IfcCartesianPoint> cps = inst.ControlPointsList();
std::vector<taxonomy::point3::ptr> points;
std::transform(cps.begin(), cps.end(), std::back_inserter(points), [this](const IfcSchema::IfcCartesianPoint& cp) { return taxonomy::cast<taxonomy::point3>(map(cp)); });
bc->control_points = points;
auto knot_multiplicities = inst.KnotMultiplicities();
bc->multiplicities.assign(knot_multiplicities.begin(), knot_multiplicities.end());
bc->knots = inst.Knots();
@@ -73,7 +73,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcCenterLineProfileDef&) {
mw.Add(BRepBuilderAPI_MakeEdge(c1a, c2a));
mw.Add(BRepBuilderAPI_MakeEdge(c2));
mw.Add(BRepBuilderAPI_MakeEdge(c2b, c1b));
face = BRepBuilderAPI_MakeFace(mw.Wire());
} else {
BRepOffsetAPI_MakeOffset offset(BRepBuilderAPI_MakeFace(gp_Pln(gp::Origin(), gp::DZ())));
+1 -1
View File
@@ -23,7 +23,7 @@ using namespace ifcopenshell::geom;
taxonomy::ptr mapping::map_impl(const IfcSchema::IfcCircle& inst) {
const double r = inst.Radius() * length_unit_;
if (r < settings_.get<settings::Precision>().get()) {
if (r < settings_.get<settings::Precision>().get()) {
logger_.message(ifcopenshell::logger::LOG_ERROR, "GEO", 237, "Radius not greater than zero for:", inst);
return nullptr;
}
+1 -1
View File
@@ -31,7 +31,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcCompositeCurve& inst) {
#else
std::vector<IfcSchema::IfcCompositeCurveSegment> segments = inst.Segments();
#endif
for (auto& segment : segments) {
if (segment.as<IfcSchema::IfcCompositeCurveSegment>() && segment.as<IfcSchema::IfcCompositeCurveSegment>().ParentCurve().as<IfcSchema::IfcLine>()) {
logger_.notice("GEO", 238, "Infinite IfcLine used as ParentCurve of segment, treating as a segment", segment);
+1 -1
View File
@@ -25,7 +25,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcCurveBoundedPlane& inst) {
taxonomy::plane::ptr pl = taxonomy::cast<taxonomy::plane>(map(inst.BasisSurface()));
auto f = taxonomy::make<taxonomy::face>();
f->children.push_back(taxonomy::cast<taxonomy::loop>(map(inst.OuterBoundary())));
std::vector<IfcSchema::IfcCurve> boundaries = inst.InnerBoundaries();
for (auto& b : boundaries) {
+10 -10
View File
@@ -133,7 +133,7 @@ struct spiral_parent_curve : public parent_curve_function {
// this is the piecewise curve segment function for horizontal and vertical
struct curve_segment_function {
curve_segment_function(const Eigen::Matrix4d& curve_segment_placement, const Eigen::Matrix4d& parent_curve_normalization, std::shared_ptr<parent_curve_function> parent_curve_fn) :
curve_segment_function(const Eigen::Matrix4d& curve_segment_placement, const Eigen::Matrix4d& parent_curve_normalization, std::shared_ptr<parent_curve_function> parent_curve_fn) :
curve_segment_placement_(curve_segment_placement),
parent_curve_normalization_(parent_curve_normalization),
parent_curve_fn_(parent_curve_fn) {
@@ -153,7 +153,7 @@ struct curve_segment_function {
// this is the piecewise curve segment function for cant
struct cant_curve_segment_function {
cant_curve_segment_function(const Eigen::Matrix4d& curve_segment_placement, const Eigen::Matrix4d& parent_curve_start_point, std::shared_ptr<parent_curve_function> parent_curve_fn) :
cant_curve_segment_function(const Eigen::Matrix4d& curve_segment_placement, const Eigen::Matrix4d& parent_curve_start_point, std::shared_ptr<parent_curve_function> parent_curve_fn) :
curve_segment_placement_(curve_segment_placement),
parent_curve_start_point_(parent_curve_start_point),
parent_curve_fn_(parent_curve_fn) {
@@ -354,7 +354,7 @@ class curve_segment_evaluator {
} else {
// The parent curve function returns the 4x4 matrix for the parent curve.
// Normalize the parent curve so that the trim start point and tangent direction at the start point
// are aligned with the origin. This is accomplished with a normalization matrix that subtracts the
// are aligned with the origin. This is accomplished with a normalization matrix that subtracts the
// incremental parent curve start point and applies a rotation. Apply the incremental
// translation and rotation to the curve_segment_placement to get the curve_segment_point
@@ -623,7 +623,7 @@ class curve_segment_evaluator {
} else if (segment_type_ == ST_CANT) {
std::optional<std::function<double(double)>> super, slope;
std::tie(super, slope) = get_superelevation_functions();
auto cant = [constant_term, cosine_term, L](double t) -> double {
auto a0 = constant_term.has_value() ? 1 / constant_term.value() : 0.0;
auto a1 = (1 / cosine_term) * cos(PI * t / L);
@@ -690,7 +690,7 @@ class curve_segment_evaluator {
} else if (segment_type_ == ST_CANT) {
std::optional<std::function<double(double)>> super, slope;
std::tie(super, slope) = get_superelevation_functions();
auto cant = [constant_term, linear_term, sine_term, L](double t) -> double {
auto a0 = constant_term.has_value() ? 1 / constant_term.value() : 0.0;
auto a1 = linear_term.has_value() ? (linear_term.value()/fabs(linear_term.value())) * pow(1 / linear_term.value(), 2.0) * t : 0.0;
@@ -825,7 +825,7 @@ class curve_segment_evaluator {
#else
A3 = c.QubicTerm();
#endif
if (segment_type_ == ST_CANT) {
polynomial_cant_spiral(A0, A1, A2, A3, A4, A5, A6, A7);
} else {
@@ -983,7 +983,7 @@ class curve_segment_evaluator {
[](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); });
}
}
void operator()(const IfcSchema::IfcLine& l) {
projected_length_ = length_;
@@ -995,8 +995,8 @@ class curve_segment_evaluator {
// 8.9.3.30 IfcDirection https://standards.buildingsmart.org/IFC/RELEASE/IFC4_3/HTML/lexical/IfcDirection.htm
// "The IfcDirection does not imply a vector length, and the direction ratios does not have to be normalized."
//
// Therefore, the direction ratios need to be normalized to compute points on the line.
//
// Therefore, the direction ratios need to be normalized to compute points on the line.
//
// Magnitude is not used because it relates to the parameterization of the line, which isn't currently done for IfcCurveSegment
// @todo - parameterization was recently added so Magnitude needs to be taking into consideration
auto dr = l.Dir().Orientation().DirectionRatios();
@@ -1030,7 +1030,7 @@ class curve_segment_evaluator {
auto pcDZy = curve_segment_placement_ ? (*curve_segment_placement_)(1, 2) : 0.;
auto pcDZz = curve_segment_placement_ ? (*curve_segment_placement_)(2, 2) : 1.;
parent_curve_fn_ = std::make_shared<line_parent_curve>(
[segment_type = segment_type_,pcX, pcY, pcDXx, pcDXy, pcDZy, pcDZz, convert_u](double u)->Eigen::Matrix4d {
u = convert_u(u);
@@ -56,7 +56,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcExtrudedAreaSolidTapered& in
if (has_position) {
matrix = taxonomy::cast<taxonomy::matrix4>(map(inst.Position()));
}
loft->matrix = matrix;
return loft;
+2 -2
View File
@@ -24,7 +24,7 @@ using namespace ifcopenshell::geom;
#ifdef SCHEMA_HAS_IfcIndexedPolyCurve
taxonomy::ptr mapping::map_impl(const IfcSchema::IfcIndexedPolyCurve& inst) {
auto point_list = inst.Points();
std::vector< std::vector<double> > coordinates;
if (point_list.as<IfcSchema::IfcCartesianPointList2D>()) {
@@ -100,7 +100,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcIndexedPolyCurve& inst) {
previous = current;
}
}
return loop;
}
+1 -1
View File
@@ -32,7 +32,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcLShapeProfileDef& inst) {
const double x = inst.Width().value_or(inst.Depth()) / 2.0f * length_unit_;
const double d = inst.Thickness() * length_unit_;
const double slope = inst.LegSlope().value_or(0.) * angle_unit_;
double f1 = 0.0f;
double f2 = 0.0f;
if (doFillet) {
@@ -84,11 +84,11 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcOffsetCurveByDistances& inst
double pz = first_offset_value.OffsetVertical().value_or(0.0);
py *= length_unit_;
pz *= length_unit_;
auto fn = [py, pz](double /*u*/) -> Eigen::Matrix4d {
Eigen::Matrix4d m = Eigen::Matrix4d::Identity();
m.col(3)(1) = py;
m.col(3)(2) = pz;
auto fn = [py, pz](double /*u*/) -> Eigen::Matrix4d {
Eigen::Matrix4d m = Eigen::Matrix4d::Identity();
m.col(3)(1) = py;
m.col(3)(2) = pz;
return m; };
offset_spans.emplace_back(taxonomy::make<taxonomy::functor_item>(first_distance, fn));
}
@@ -146,7 +146,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcOffsetCurveByDistances& inst
zn = zn_at_end;
}
auto fn = [yp, yn, zp, zn, l](double u) -> Eigen::Matrix4d {
Eigen::Matrix4d m = Eigen::Matrix4d::Identity();
m.col(3)(1) = (l == 0.0 ? yp : (yp + (yn - yp) * u / l));
@@ -171,10 +171,10 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcOffsetCurveByDistances& inst
py *= length_unit_;
pz *= length_unit_;
double l = basis_curve_length - last_distance;
auto fn = [py, pz](double /*u*/) -> Eigen::Matrix4d {
Eigen::Matrix4d m = Eigen::Matrix4d::Identity();
m.col(3)(1) = py;
m.col(3)(2) = pz;
auto fn = [py, pz](double /*u*/) -> Eigen::Matrix4d {
Eigen::Matrix4d m = Eigen::Matrix4d::Identity();
m.col(3)(1) = py;
m.col(3)(2) = pz;
return m; };
offset_spans.emplace_back(taxonomy::make<taxonomy::functor_item>(l, fn));
@@ -87,7 +87,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcOpenCrossProfileDef& inst) {
auto r = taxonomy::loop::ptr((taxonomy::loop*)mapped->clone_());
r->closed = false;
return r;
}*/
}*/
mapped->closed = false;
mapped->tags = tags;
+1 -1
View File
@@ -24,5 +24,5 @@ using namespace ifcopenshell::geom;
taxonomy::ptr mapping::map_impl(const IfcSchema::IfcPlane& inst) {
auto p = taxonomy::make<taxonomy::plane>();
p->matrix = taxonomy::cast<taxonomy::matrix4>(map(inst.Position()));
return p;
return p;
}
@@ -45,7 +45,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcPointByDistanceExpression& i
if (inst.OffsetLateral().has_value()) {
auto offset_lateral = inst.OffsetLateral().value() * length_unit_;
auto y = Eigen::Vector3d(m.col(1)(0), m.col(1)(1), m.col(1)(2));
auto y = Eigen::Vector3d(m.col(1)(0), m.col(1)(1), m.col(1)(2));
o += offset_lateral * y;
}
+1 -1
View File
@@ -50,7 +50,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcPolyLoop& inst) {
int count = polygon.size();
if (original_count - count != 0) {
std::stringstream ss; ss << (original_count - count) << " edges removed for:";
std::stringstream ss; ss << (original_count - count) << " edges removed for:";
logger_.message(ifcopenshell::logger::LOG_WARNING, "GEO", 280, ss.str(), inst);
}
+1 -1
View File
@@ -61,7 +61,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcPolygonalFaceSet& inst) {
for (auto& f : polygonal_faces) {
auto fa = taxonomy::make<taxonomy::face>();
shell->children.push_back(fa);
{
auto loop = taxonomy::make<taxonomy::loop>();
fa->children = { loop };
+2 -2
View File
@@ -44,7 +44,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcProduct& inst) {
}
if (openings->size() && !settings_.get(IteratorSettings::DISABLE_OPENING_SUBTRACTIONS) && use_body) {
Eigen::Matrix4d ci;
if (c->matrix.components_) {
ci = c->matrix.components_->inverse();
@@ -69,7 +69,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcProduct& inst) {
c->children = { child };
} else {
delete c;
return nullptr;
return nullptr;
}
}
@@ -33,7 +33,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcRectangleHollowProfileDef& i
const double r1 = fr1 ? (*inst.OuterFilletRadius()) * length_unit_ : 0.;
const double r2 = fr2 ? (*inst.InnerFilletRadius()) * length_unit_ : 0.;
const double tol = settings_.get<settings::Precision>().get();
if (x < tol || y < tol) {
@@ -42,7 +42,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcRectangleProfileDef& inst) {
if (has_position) {
m4 = taxonomy::cast<taxonomy::matrix4>(map(inst.Position()));
}
return profile_helper(m4, {
{{-x,-y}},
{{x,-y}},
+1 -1
View File
@@ -27,7 +27,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcRevolvedAreaSolid& inst) {
const double ang = inst.Angle() * angle_unit_;
taxonomy::cast<taxonomy::face>(map(inst.SweptArea()));
std::optional<double> angle;
taxonomy::matrix4::ptr matrix;
@@ -32,7 +32,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcRightCircularCylinder&) {
BRepPrimAPI_MakeCylinder builder(r, h);
gp_Trsf trsf;
ifcopenshell::geom::Kernel::convert(inst.Position(),trsf);
// IfcCsgPrimitive3D.Position has unit scale factor
shape = builder.Solid().Moved(trsf);
@@ -38,7 +38,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcSectionedSolidHorizontal& in
return nullptr;
}
{
{
auto css = inst.CrossSections();
auto csps = inst.CrossSectionPositions();
std::vector<taxonomy::face::ptr> faces;
@@ -47,7 +47,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcSectionedSolidHorizontal& in
// reference frame along a certain curve location (b) the longitude.
// The longitudes determine the range of the sweep and the offsets are interpolated in between
// sweep segments.
// sweep segments.
std::vector<Eigen::Vector3d> profile_offsets;
std::vector<std::optional<Eigen::Matrix3d>> profile_rotations;
std::vector<double> longitudes;
+2 -2
View File
@@ -39,7 +39,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcSectionedSurface& inst) {
}
{
{
auto css = inst.CrossSections();
auto csps = inst.CrossSectionPositions();
std::vector<taxonomy::geom_item::ptr> faces;
@@ -48,7 +48,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcSectionedSurface& inst) {
// reference frame along a certain curve location (b) the longitude.
// The longitudes determine the range of the sweep and the offsets are interpolated in between
// sweep segments.
// sweep segments.
std::vector<Eigen::Vector3d> profile_offsets;
std::vector<std::optional<Eigen::Matrix3d>> profile_rotations;
std::vector<double> longitudes;
@@ -28,7 +28,7 @@ using namespace ifcopenshell::geom;
taxonomy::ptr mapping::map_impl(const IfcSchema::IfcSegmentedReferenceCurve& inst) {
if (!inst.BaseCurve().as<IfcSchema::IfcGradientCurve>())
logger_.warning("GEO", 291, "Expected IfcSegmentedReferenceCurve.BaseCurve to be IfcGradient", inst); // CT 4.1.7.1.1.3
auto segments = inst.Segments();
taxonomy::piecewise_function::span_list spans;
+6 -6
View File
@@ -97,9 +97,9 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcSweptDiskSolid& inst) {
return taxonomy::make<taxonomy::sweep_along_curve>(taxonomy::make<taxonomy::matrix4>(), f, nullptr, loop);
/*
TopoDS_Wire wire, section1, section2;
bool hasInnerRadius = !!inst.InnerRadius();
@@ -107,7 +107,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcSweptDiskSolid& inst) {
if (!convert_wire(inst.Directrix(), wire)) {
return false;
}
if (util::count(wire, TopAbs_EDGE) == 1 && sp && ep) {
@@ -119,7 +119,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcSweptDiskSolid& inst) {
double a, b;
auto crv = BRep_Tool::Curve(e, a, b);
if ((crv->DynamicType() == STANDARD_TYPE(Geom_Circle)) ||
(crv->DynamicType() == STANDARD_TYPE(Geom_Ellipse)))
(crv->DynamicType() == STANDARD_TYPE(Geom_Ellipse)))
{
BRepBuilderAPI_MakeEdge me(crv, *sp, *ep);
if (me.IsDone()) {
@@ -227,7 +227,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcSweptDiskSolid& inst) {
j += 1;
} else {
ifcopenshell::logger::root().error("Unexpected amount of fillet edges generated");
}
}
} else {
ifcopenshell::logger::root().error("Unable to build fillet, probably edge too short");
}
@@ -258,7 +258,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcSweptDiskSolid& inst) {
// NB: Note that StartParam and EndParam param are ignored and the assumption is
// made that the parametric range over which to be swept matches the IfcCurve in
// its entirety.
util::process_sweep(wire, inst.Radius() * length_unit_, shape);
if (shape.IsNull()) {
+1 -1
View File
@@ -43,7 +43,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcTShapeProfileDef& inst) {
logger_.message(ifcopenshell::logger::LOG_NOTICE, "GEO", 296, "Skipping zero sized profile:", inst);
return nullptr;
}
double dy1 = 0.0f;
double dy2 = 0.0f;
double dx1 = 0.0f;
+4 -4
View File
@@ -29,21 +29,21 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcTrimmedCurve& inst) {
auto basis_curve = inst.BasisCurve();
bool isConic = basis_curve.declaration().is(IfcSchema::IfcConic::Class());
double parameterFactor = isConic ? angle_unit_ : length_unit_;
auto tc = taxonomy::make<taxonomy::edge>();
tc->basis = map(inst.BasisCurve());
bool trim_cartesian = inst.MasterRepresentation() != IfcSchema::IfcTrimmingPreference::IfcTrimmingPreference_PARAMETER;
auto trims1 = inst.Trim1();
auto trims2 = inst.Trim2();
// reversed orientation handling happens in geometry kernel
unsigned sense_agreement = 0;
double flts[2];
taxonomy::point3::ptr pnts[2];
bool has_flts[2] = {false,false};
bool has_pnts[2] = {false,false};
tc->curve_sense = inst.SenseAgreement();
for (auto it = trims1.begin(); it != trims1.end(); it ++) {
+1 -1
View File
@@ -33,7 +33,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcUShapeProfileDef& inst) {
const double d1 = inst.WebThickness() * length_unit_;
const double d2 = inst.FlangeThickness() * length_unit_;
const double slope = inst.FlangeSlope().value_or(0.) * angle_unit_;
double dy1 = 0.0f;
double dy2 = 0.0f;
double f1 = 0.0f;
+1 -1
View File
@@ -31,7 +31,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcZShapeProfileDef& inst) {
bool doFillet = !!inst.FilletRadius();
bool doEdgeFillet = !!inst.EdgeRadius();
double f1 = 0.;
double f2 = 0.;
+8 -8
View File
@@ -69,7 +69,7 @@ std::vector<IfcSchema::IfcProduct> mapping::products_represented_by(const IfcSch
auto invs = prodrep.file()->get_inverse(prodrep.id(), &IfcSchema::IfcProduct::Class(), -1);
for (auto& inv : invs) {
products.push_back(inv.as<IfcSchema::IfcProduct>());
}
}
}
if (only_direct) {
@@ -169,7 +169,7 @@ bool mapping::reuse_ok_(const std::vector<IfcSchema::IfcProduct>& products) {
std::vector<express::base> mapping::find_openings(const express::base& inst) {
std::vector<express::base> openings;
if (auto rep = inst.as<IfcSchema::IfcRepresentation>()) {
// @todo this is essentially only for hybrid kernel trying to guess
// when not to use a simple kernel.
@@ -246,11 +246,11 @@ void mapping::get_representations(std::vector<geometry_conversion_task>& tasks,
int task_index = 0;
std::set<IfcSchema::IfcProduct> products_seen;
for (auto representation : representations) {
IfcSchema::IfcRepresentationMap rmap;
std::vector<IfcSchema::IfcProduct> ifcproducts = filter_products(products_represented_by(representation, rmap, false), filters);
if (ifcproducts.empty()) {
continue;
}
@@ -838,7 +838,7 @@ express::base mapping::get_decomposing_entity(const express::base& inst, bool in
for (auto it = parents.begin(); it != parents.end(); ++it) {
IfcSchema::IfcRelDecomposes decompose = (*it).as<IfcSchema::IfcRelDecomposes>();
express::base ifc_objectdef;
ifc_objectdef = get_RelatingObject(decompose);
if (!ifc_objectdef || product == ifc_objectdef) continue;
@@ -871,7 +871,7 @@ void mapping::initialize_units_() {
length_unit_ = 1.;
angle_unit_ = -1.;
length_unit_name_ = "METER";
#ifdef SCHEMA_HAS_IfcContext
auto projects = file_->instances_by_type<IfcSchema::IfcContext>();
#else
@@ -1133,7 +1133,7 @@ bool mapping::get_layerset_information(const express::base& p, layerset_informat
std::vector<IfcSchema::IfcExtrudedAreaSolid> extrusions;
for (auto& r : resources) {
if (auto ex = r.as<IfcSchema::IfcExtrudedAreaSolid>()) {
extrusions.push_back(ex);
extrusions.push_back(ex);
}
}
@@ -1209,7 +1209,7 @@ bool mapping::get_layerset_information(const express::base& p, layerset_informat
}
return true;
}
+3 -3
View File
@@ -16,7 +16,7 @@
namespace ifcopenshell {
namespace geom {
class POSTFIX_SCHEMA(mapping) : public abstract_mapping {
private:
ifcopenshell::file* file_;
@@ -30,7 +30,7 @@ namespace geom {
const express::base placement_rel_to_instance_;
Eigen::Matrix4d offset_and_rotation_ = Eigen::Matrix4d::Identity();
void initialize_units_();
void addRepresentationsFromContextIds(std::vector<IfcSchema::IfcRepresentation>&);
void addRepresentationsFromPriorities(std::vector<IfcSchema::IfcRepresentation>&);
@@ -136,7 +136,7 @@ namespace geom {
template <>
struct element_type<taxonomy::solid> {
typedef taxonomy::shell type;
};
};
template <typename U = taxonomy::collection, typename T>
typename U::ptr map_to_collection(POSTFIX_SCHEMA(mapping)* m, const T& ts) {
+1 -1
View File
@@ -182,4 +182,4 @@ BIND(IfcSurfaceStyle); // -> style
#ifdef SCHEMA_HAS_IfcCurveSegment
BIND(IfcCurveSegment);
#endif
#endif
+3 -3
View File
@@ -7,8 +7,8 @@ taxonomy::loop::ptr ifcopenshell::geom::fillet_loop(taxonomy::loop::ptr loop, do
const auto child_count = static_cast<int>(loop->children.size());
for (int b = 0; b < child_count; ++b) {
int c = (b + child_count - 1) % child_count;
pps[b] = {
std::get<taxonomy::point3::ptr>(loop->children[c]->start)->ccomponents(),
pps[b] = {
std::get<taxonomy::point3::ptr>(loop->children[c]->start)->ccomponents(),
radius, loop->children[c], loop->children[b]
};
}
@@ -227,7 +227,7 @@ std::pair<std::vector<taxonomy::point3::ptr>, std::vector<std::set<std::string>>
if (equal) {
// do not remove the first or last point to
// maintain connectivity with other wires
/*
// Only removing direct equality so does not impact connectivity
if ((closed && j == 0) || (!closed && j == (n - 1))) {

Some files were not shown because too many files have changed in this diff Show More