mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-06 16:01:36 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9fd119bf95 | |||
| 94ba41d9ea | |||
| cd5897d10d | |||
| e0b97c574b | |||
| 429cab8b1b | |||
| 328ca6d387 | |||
| 12ecdf2aba | |||
| 8f9164bf72 | |||
| 403308a923 | |||
| 236da3c75a | |||
| cf58c675db | |||
| 5ea11817ad | |||
| 6d90048acd | |||
| cdb594b5c2 | |||
| 028e593939 | |||
| 42a05cf976 | |||
| a97276b8b1 |
@@ -127,7 +127,6 @@ src/ifcopenshell-python/ifcopenshell/express/*.exp.cache.dat
|
||||
|
||||
# temp files from AI coding tools
|
||||
*.claude
|
||||
CLAUDE.local.md
|
||||
*.py.tmp*
|
||||
*.json.tmp*
|
||||
|
||||
|
||||
@@ -314,12 +314,8 @@ if(WASM_BUILD)
|
||||
else()
|
||||
# @todo review this, shouldn't this be all possible header-only now?
|
||||
# ... or rewritten using C++17 features?
|
||||
# Boost.System has been header-only since 1.69 and its compiled stub library
|
||||
# was dropped in newer Boost, so requesting it as a component makes
|
||||
# find_package fail on Boost 1.70 and up (for example Boost 1.90). It is
|
||||
# still pulled in transitively by thread / iostreams where needed, so do not
|
||||
# request it explicitly.
|
||||
set(BOOST_COMPONENTS
|
||||
system
|
||||
program_options
|
||||
regex
|
||||
thread
|
||||
|
||||
@@ -0,0 +1,400 @@
|
||||
<!-- This file was generated with the assistance of an AI coding tool. -->
|
||||
|
||||
# Linked file features — queries, styles, transforms, and multi-linking for linked IFC models
|
||||
|
||||
> **Living dev note** for the `Linked_File_Features` branch/PR. Read before working
|
||||
> on the feature; append decisions and findings as the PR is refined. This is *not* user
|
||||
> documentation — at merge it is removed or its durable parts promoted to code comments.
|
||||
> See [README.md](README.md) for the convention (introduced on the
|
||||
> `opening-template-on-type` branch; not yet on this branch's base).
|
||||
|
||||
## Problem
|
||||
|
||||
Linked IFC models (`bim.link_ifc`) had several gaps that made them hard to use as a
|
||||
"reference in other trades' models" workflow:
|
||||
|
||||
- One shared `.ifc.cache.blend` per IFC file meant the **same file could not be linked
|
||||
twice with different selector queries** — both links showed whichever query was cached
|
||||
first in-session, and whichever was cached last after reopening (Blender reuses one
|
||||
library datablock per path).
|
||||
- The selector query was not durably stored anywhere in the host IFC, so save → reopen
|
||||
lost or cross-wired the filter; a scripted `bpy.ops.bim.reload_link()` also wiped it.
|
||||
- Linked geometry got **flat diffuse-only materials** — external `.blend` styles
|
||||
(`IfcExternallyDefinedSurfaceStyle`) and per-layer materials (layerset slicing) that
|
||||
the normal import applies were ignored.
|
||||
- Moving a linked model required an explicit enable-edit → move → save dance on the
|
||||
active link only, with save/cancel buttons in the panel header.
|
||||
- The Explore tool's highlight broke (GPU type errors), drew at the link's *original*
|
||||
location when the link had been moved, and `bim.append_inspected_linked_element`
|
||||
placed appended elements at the original location too.
|
||||
|
||||
## Key facts established
|
||||
|
||||
- **Cache architecture**: `LoadLink.link_ifc` generates a Python script and runs a
|
||||
background Blender subprocess that executes `bim.load_linked_project` and saves a
|
||||
`.ifc.cache.blend`. The host session then *links* (not appends) the `IfcProject/...`
|
||||
collection from that blend and instances it via an empty (the link "handle").
|
||||
Georeferencing metadata lives in a sidecar `.cache.json`; extracted properties in
|
||||
`.cache.sqlite` (whole file, query-independent — deliberately shared across queries).
|
||||
- **Blender reuses an in-session library per path.** Loading the same blend path twice
|
||||
yields the same library/collection. This is what broke multi-query linking with a
|
||||
shared cache filename, and why per-query *filenames* (not cache invalidation) are the
|
||||
fix.
|
||||
- **Last-used operator properties** are reused on the next *interactive* invocation
|
||||
(UI button), while scripted `bpy.ops` calls always start from defaults. LoadLink's
|
||||
internal `self.query = link.query` fallback assignment was remembered by Blender and
|
||||
leaked into the next button click (`operator_query='IfcWindow'` for the door link).
|
||||
Any `is_property_set()`-based logic is corrupted the same way. Fix: `SKIP_SAVE` on
|
||||
volatile props. **A GUI-only bug like this is invisible to scripted repro** — both
|
||||
headless and windowed `--python` test runs passed while the manual flow failed.
|
||||
- **`IfcDocumentReference`** per link: attribute index 1 (`Identification`) already
|
||||
stores the link's 4×4 transformation (existing Bonsai convention). `Description`
|
||||
(IFC4+; **absent in IFC2X3**) now stores the selector query. One
|
||||
`IfcDocumentInformation` (Scope `LINKED_MODEL`) per file, one reference per link.
|
||||
- **Geometry iterator materials**: `material.instance_id()` is the STEP id of the
|
||||
`IfcSurfaceStyle` — or of an `IfcMaterial` when the item has a material but no style,
|
||||
hence the `is_a("IfcSurfaceStyle")` guard when resolving external styles.
|
||||
- **External styles**: `IfcExternallyDefinedSurfaceStyle.Location` (`.blend`, relative
|
||||
paths resolve against the *linked* IFC, not the host) + `Identification` in
|
||||
`data_block_type/name` form (e.g. `materials/Brick`), same convention as
|
||||
`bim.activate_external_style`.
|
||||
- **Chunk pipeline dedups materials by RGBA color** (`np.unique` on a color array), so
|
||||
style identity must ride along as an extra column to survive — added only for styles
|
||||
that actually resolve to an external material, so plain colored styles dedupe exactly
|
||||
as before.
|
||||
- **`slice_layerset_mesh` needs a local-space, per-element mesh** (bisect planes are in
|
||||
object space), which the chunk path can't provide (world-space, many elements per
|
||||
mesh) — hence routing multi-layer elements through the instanced path. Its
|
||||
`dissolve_limit` produces **ngons**, which broke the Explore highlight's
|
||||
triangles-from-`polygon.vertices` assumption downstream.
|
||||
- **ID properties round-trip as `IDPropertyArray`**, not plain lists (verified in
|
||||
4.5.7: empty list → flat `IDPropertyArray`; nested lists → list of `IDPropertyArray`
|
||||
items), and `GPUIndexBuf` rejects them — selection geometry must be converted to
|
||||
plain tuples on read.
|
||||
- **`scene.ray_cast` returns the hit instance's world matrix** (link empty matrix
|
||||
included). For instanced occurrence objects the object's own local matrix is *not*
|
||||
identity, so resolving the instancing empty must compare against
|
||||
`empty.matrix_world @ obj.matrix_world`, not the empty's matrix alone.
|
||||
- **Link matrix math**: the handle empty's matrix is `inv(L) @ T @ G` (L = host local
|
||||
matrix from georef props, T = stored transformation, G = linked model's global
|
||||
matrix from the cache json). The world-space displacement of a moved link is
|
||||
therefore `inv(L) @ T @ L` — no json read needed (`calculate_link_delta_matrix`).
|
||||
- **Undo consistency of auto-saved moves**: Blender undo of a handle move fires another
|
||||
depsgraph update, so the handler re-saves the reverted matrix — stored state stays
|
||||
consistent without transactions (a handler can't open one).
|
||||
|
||||
## Design
|
||||
|
||||
### Per-query caches + query persistence (multi-linking)
|
||||
|
||||
`tool.Project.get_link_cache_paths(filepath, query)` appends `.md5(query)[:8]` to the
|
||||
cache blend/json names; the empty query keeps the legacy un-suffixed names so existing
|
||||
caches stay valid. Every cache-path consumer goes through it — `link_ifc` build and
|
||||
invalidation, the subprocess json write, model-origin/georef indicator reads,
|
||||
`calculate_link_matrix`, `save_link_transformation`, and the per-link
|
||||
selectability/wireframe/visibility toggles (which match collections *by library
|
||||
filepath* and would otherwise affect every link of the file at once).
|
||||
|
||||
The query persists on each link's `IfcDocumentReference.Description` (written by
|
||||
`LinkIfc` and `ReloadLink`); `load_linked_models_from_ifc` restores from it, with a
|
||||
legacy-JSON fallback that only applies when the file has a **single** link (with
|
||||
several links the shared JSON can't say which link it belonged to). IFC2X3 hosts have
|
||||
no `Description` — custom queries are not restorable there (accepted).
|
||||
|
||||
`LoadLink`/`ReloadLink` volatile properties are `SKIP_SAVE` (see key facts). Cache
|
||||
clearing tolerates a missing blend (a reload with a brand-new query points at a
|
||||
not-yet-existing filename).
|
||||
|
||||
### Include/Exclude filter pair
|
||||
|
||||
The selector grammar's only cross-group combiner is `+` (union) and the `parent`
|
||||
facet cannot express "not under X" (its `!=`/regex paths also match by GlobalId, so
|
||||
negation removes everything with any parent), which makes set differences like
|
||||
"group members minus the slabs under aggregate X" structurally inexpressible in one
|
||||
query string. Links therefore carry an **Exclude** query beside the include —
|
||||
mirroring `EPset_Drawing`'s Include/Exclude pattern: final set = include (or the
|
||||
default set when empty) − exclude, applied in `LoadLinkedProject` and per link in
|
||||
`create_drawing`.
|
||||
|
||||
- **Cache key**: `get_link_cache_paths` hashes `md5(query + "\0" + exclude)` when an
|
||||
exclude exists; include-only filters keep the pre-exclude `md5(query)` so existing
|
||||
caches stay valid; empty filter keeps legacy un-suffixed names. Keying on query
|
||||
alone would let same-include/different-exclude links silently serve each other's
|
||||
geometry.
|
||||
- **Persistence**: `encode_link_filter`/`decode_link_filter` — a plain include is
|
||||
stored in `Description` as-is (backwards compatible); an exclude, a `loaded`
|
||||
state or a custom display name promotes the value to
|
||||
`{"include": …, "exclude": …, "loaded": …, "name": …}` JSON. Decode treats
|
||||
non-JSON as a legacy include string. The display name (`Link.display_name`,
|
||||
double-click the list row to rename; file path shows as placeholder while
|
||||
unset) exists to tell apart several links of the same file.
|
||||
- Exclude applies on top of the **default** element set too, so
|
||||
"everything except X" needs no explicit include.
|
||||
- UI labels are **Include**/**Exclude** (matching the drawing pattern), but the
|
||||
property identifier stays `query` for script (`bpy.ops.bim.link_ifc(query=…)`)
|
||||
and persistence compatibility.
|
||||
- Verified headless: `query=""`/`exclude="IfcDoor"` loads only the window;
|
||||
same file with a different filter gets its own cache; both filters survive
|
||||
save → reopen → reload.
|
||||
|
||||
### Auto-load on open
|
||||
|
||||
Links that were **loaded and visible** at IFC save time auto-load when the project
|
||||
is reopened. `ExportIFC` calls `tool.Project.update_linked_models_state()`, which
|
||||
rewrites each reference's `Description` with a `loaded` flag
|
||||
(`is_loaded and not is_hidden`); `load_linked_models_from_ifc` replays flagged
|
||||
links via `load_link` after restoring the list (missing files warn and skip so
|
||||
they can't break project open). The flag extends the same JSON blob as the
|
||||
exclude — plain legacy strings decode as no-autoload. Trade-off: project open
|
||||
pays the link-load cost up front (fast on cache hit; a missing cache rebuilds in
|
||||
a background Blender, same as clicking Load). Verified headless: loaded+visible
|
||||
auto-loads; unloaded and loaded-but-hidden links stay unloaded.
|
||||
|
||||
### Long-term serialization target: STEP Part 21 Edition 3
|
||||
|
||||
STEP p21e3 defines the standards-track version of this feature's persistence:
|
||||
`ANCHOR`/`REFERENCE` sections (clauses 9–10) let one file import entities from
|
||||
another via URI + fragment, and **anchor tags** (`{tagname: value}`) are the
|
||||
designated slot for out-of-schema metadata — a cleaner home than the
|
||||
`Description` JSON blob (see the review-round discussion). ifcopenshell does not
|
||||
implement these sections yet ([#668](https://github.com/IfcOpenShell/IfcOpenShell/issues/668),
|
||||
open, unassigned); if it ever does, the migration path is: link →
|
||||
`REFERENCE` to the linked file's project anchor, filter/transform/loaded
|
||||
metadata → anchor tags. Keeping the blob behind
|
||||
`encode_link_filter`/`decode_link_filter` makes that a two-function change.
|
||||
|
||||
Two p21e3 design points this branch already conforms to:
|
||||
|
||||
- **Identity**: p21e3 distinguishes volatile file-scoped entity numbers
|
||||
(`#100` fragments) from durable anchors/UUIDs — the same lesson behind our
|
||||
STEP-id collision fixes (GUID-based matching, `element.file` guards). Raw
|
||||
STEP ids must never cross a file boundary; IFC GlobalIds map 1:1 onto
|
||||
p21e3 UUID anchors.
|
||||
- **Transport** (clause A.4): exchange structures plus referenced resources
|
||||
can ship as one ZIP archive with references resolving inside it. Our posix,
|
||||
optionally relative `Location`s resolved via `resolve_uri` are exactly the
|
||||
invariants a future "package project with links" export would need.
|
||||
|
||||
Even full p21e3 support would not cover per-link transforms, filters, or load
|
||||
state — a `REFERENCE` imports entities, it does not place a model — so the
|
||||
app-level metadata remains; only its container would change.
|
||||
|
||||
### External styles + layerset slicing in the linked loader
|
||||
|
||||
`LoadLinkedProject.get_external_material(style_id)` resolves a style id → appended
|
||||
Blender material from the external `.blend`, cached two ways (per style id; per
|
||||
appended data-block, so styles sharing one material don't append duplicates). Appended
|
||||
materials get their stale `ifc_definition_id` cleared (the source `.blend` may have
|
||||
been authored in a Bonsai session; the id would be misread in the linked file *and*
|
||||
in the host once the cache links in). Applied in both loading paths — instanced
|
||||
occurrences directly, chunks via the style-id column.
|
||||
|
||||
Multi-layer elements (`IfcMaterialLayerSetUsage`, >1 layer) route through the
|
||||
instanced path and get `slice_layerset_mesh`, which gained a pluggable
|
||||
`style_to_material` resolver (defaults to the old `tool.Ifc.get_object` for the normal
|
||||
import) — the linked resolver prefers the external material, falling back to a flat
|
||||
diffuse from the style's shading colour. Also fixed there: newly appended layer
|
||||
materials are registered in the dedup dict (two layers sharing one style used to
|
||||
append it twice).
|
||||
|
||||
Trade-off: layered walls become individual instanced objects instead of chunk members;
|
||||
meshes shared between elements (same geometry id) bake the slice from the first
|
||||
element's layerset usage — same behaviour as the normal importer.
|
||||
|
||||
### Reload Link dialog
|
||||
|
||||
`bim.reload_link` now exposes File Path (+ browse button), Use Relative Path
|
||||
(defaulting to the stored path form), Use Cache (default off = old always-rebuild
|
||||
behaviour), the False Origin Mode project props, and Query. A file browser can't open
|
||||
from inside a props dialog, so the browse button runs `bim.select_link_filepath`
|
||||
(fileselect) which *reopens* the reload dialog with the chosen path, carrying the
|
||||
in-progress dialog state through the round trip (op props are baked at draw time).
|
||||
Path changes update `link.name`/`filepath` and, with a host IFC, the reference
|
||||
`Location` + document name — which is why `ReloadLink` became a `tool.Ifc.Operator`.
|
||||
Script calls without arguments preserve all stored link values via `is_property_set`.
|
||||
|
||||
`bim.reload_all_links` (refresh button beside Link IFC in the panel header) reloads
|
||||
every *loaded* link via argument-less `reload_link` calls — each link's stored
|
||||
path/query/exclude replay and its cache rebuilds from disk. Unloaded links are left
|
||||
alone. Deliberately expensive: one background cache rebuild per link.
|
||||
|
||||
### Per-row lock toggle + auto-saved transforms
|
||||
|
||||
Link editing moved from the panel header into each list row as a lock/unlock icon:
|
||||
unlock (`bim.enable_editing_link`) frees the handle; **any movement is persisted
|
||||
immediately** by a `depsgraph_update_post` handler (lazy — ticks without transform
|
||||
updates cost ~nothing); lock (`bim.disable_editing_link`) saves and locks.
|
||||
`bim.edit_link` and the explicit save step are **removed**; cancel/restore semantics
|
||||
no longer exist (undo or move it back). The save math lives in
|
||||
`tool.Project.save_link_transformation`. Enable/disable take a `link_index`
|
||||
(default −1 = active link) so several links can be edited at once and script calls
|
||||
stay compatible.
|
||||
|
||||
### Explore tool + append fixes for moved links
|
||||
|
||||
- Highlight triangles come from `mesh.calc_loop_triangles()` filtered to the queried
|
||||
element's polygon range (ngon-safe); edges keep `polygon.edge_keys` (no diagonals).
|
||||
- `get_selected_geometry` converts the ID-prop round trip to plain tuples (GPU
|
||||
rejects `IDPropertyArray`); TRIS drawing gated on its own data.
|
||||
- `QueryLinkedElement` passes the ray-cast instance matrix through;
|
||||
`find_obj_root` compares it against `empty @ obj_local` and falls back to the
|
||||
collection's only instance when no matrix is available (select-by-GUID flow).
|
||||
- `bim.append_inspected_linked_element` pre-multiplies the imported object's matrix by
|
||||
`calculate_link_delta_matrix(link)`, matching the link by the queried instance's
|
||||
root empty first (filepath alone is ambiguous with several links per file). The
|
||||
element's IFC placement syncs to the moved location on save — intended.
|
||||
|
||||
### Drawings (`create_drawing`) — moved links and per-link queries
|
||||
|
||||
- The linework serializer opened linked IFCs raw, so a moved link's elements were
|
||||
drawn at their *original* coordinates (usually outside the drawing extents —
|
||||
"linked objects disappear from prints after moving the link").
|
||||
- The stored link transformation is already the **model-space** delta (that is how
|
||||
`save_link_transformation` derives it), which is exactly the space the serializer
|
||||
works in — so it can be baked straight into the geometry iterator via the existing
|
||||
`model-offset`/`model-rotation` settings. The mapping composes
|
||||
`Trans(model-offset) @ Rot(model-rotation)` (see `mapping.cpp`), matching the
|
||||
`Trans(t) @ Rot(R)` decomposition of the rigid link matrix; `model-rotation` is a
|
||||
quaternion passed as `(x, y, z, w)`. The pre-existing 2mm plan-view Z-offset simply
|
||||
adds onto the translation (translations commute).
|
||||
- The serialization loop previously collected files in a dict keyed by filepath, which
|
||||
**collapsed same-file links into one pass** (one transform — the last link's — and
|
||||
no query awareness): with two links of one file, only one showed in the drawing.
|
||||
It now iterates one entry per link (`(path, file, transform, query)` tuples), and
|
||||
intersects each link's drawing elements with
|
||||
`ifcopenshell.util.selector.filter_elements(ifc, link.query)` so the drawing shows
|
||||
what that link actually displays in the viewport.
|
||||
- `tool.Project.get_link_transformation_matrix(link)` is the shared accessor for the
|
||||
stored 4×4 (None when identity/absent).
|
||||
- Verified headless with the window/door kit: moved window offset in the SVG by
|
||||
exactly 5m × scale; unmoved door at its native position; both links present.
|
||||
|
||||
### Drawings — `.cut` styling for linked models (BISECT cut mode)
|
||||
|
||||
- The default **BISECT** cut mode deletes the OpenCASCADE serializer's cut linework
|
||||
(`remove_cut_linework`) and regenerates cuts by bisecting **Blender mesh objects**
|
||||
(`generate_bisect_linework` over `context.visible_objects`). Linked models are
|
||||
instanced collections with no mesh objects, so their cuts were deleted and never
|
||||
regenerated — linked elements only ever appeared as `projection`, and the `.cut`
|
||||
CSS rule never applied to them. Long-standing gap, unrelated to moved links
|
||||
(A/B-tested against pre-branch code: identical).
|
||||
- Fix: `remove_cut_linework` only removes cut groups whose guid resolves in the
|
||||
**host** file — linked elements keep the serializer's cut geometry, which the
|
||||
merge step then classes as `cut`.
|
||||
- **Cross-file STEP-id collision**: `tool.Ifc.get_object(linked_entity)` resolves the
|
||||
entity's STEP id against the *host* session's id map and can return an arbitrary
|
||||
host object (in the test project: the drawing camera, crashing
|
||||
`generate_material_layers` with "expected 'Mesh' found 'Camera'"). Guarded via
|
||||
`element.file is tool.Ifc.get()` in `generate_material_layers` and the merge step.
|
||||
- **Paint order**: the projection-under-cut convention was enforced only in
|
||||
OPENCASCADE mode (`move_projection_to_bottom`); BISECT appends its own cut paths
|
||||
last so it never needed it — but the retained serializer cuts of linked models are
|
||||
emitted *before* the projections. BISECT now runs the same pass; `BringToFront`
|
||||
(`move_elements_to_top`) still gets the final say.
|
||||
- Known limitation: linked cut paths are raw serializer output — they skip the
|
||||
shapely path-closing/merging and the material-layer hatching pass (both need host
|
||||
Blender objects). Stroke + fill from `.cut` CSS apply; layered hatching inside
|
||||
linked cuts is a candidate follow-up.
|
||||
- Debugging note: merged cut groups carry member guids as CSS *classes*, not as the
|
||||
`ifcopenshell:guid` attribute — inspect both when checking cut output.
|
||||
|
||||
## Deferred refactors (deliberate)
|
||||
|
||||
- **Upstream `exclude=` on `filter_elements`** — the include−exclude set difference
|
||||
is hand-rolled twice (links, drawings) because the selector grammar has no
|
||||
difference operator and `parent` negation is broken by design (its `!=`/regex
|
||||
paths also match GlobalIds, so negation strips everything that has a parent).
|
||||
The right home is an `exclude=` parameter on
|
||||
`ifcopenshell.util.selector.filter_elements`, documented in
|
||||
`selector_syntax.rst` together with the `parent`-negation limitation. Deferred
|
||||
to a separate ifcopenshell-python PR (different review audience; would widen
|
||||
this PR mid-review). Once it lands, both Bonsai call sites collapse.
|
||||
- **Core/tool ceremony skipped** — the new `tool.Project` methods have no
|
||||
`core/tool.py` interface declarations and no `bonsai/core` orchestration
|
||||
functions, matching the pre-existing linked-model code (which bypasses the
|
||||
core layer wholesale; `LoadLinkedProject` is flagged "prototyping" upstream).
|
||||
Interfaces nobody calls through wouldn't add testability — the pure helpers
|
||||
(`encode_link_filter`/`decode_link_filter`, `get_link_cache_paths`) are
|
||||
covered directly in `test/tool/test_project.py` instead. Revisit if the
|
||||
linked-model subsystem is ever promoted out of prototype status.
|
||||
|
||||
## Review round 1 (PR #8242, falken10vdl) — decisions
|
||||
|
||||
- **Path-form mismatch → duplicate documents (confirmed bug, fixed).**
|
||||
`get_linked_models_documents()` keyed documents by the *stored* `Location`, so
|
||||
linking the same file first relative then absolute (or vice versa) created a second
|
||||
`IfcDocumentInformation`. Both sides of the lookup now normalize through
|
||||
`tool.Ifc.resolve_uri()` before matching.
|
||||
- **`Description` for the query — kept.** It is implementation metadata in an IFC
|
||||
attribute, but consistent with the existing convention on these same references
|
||||
(`Identification` stores the 4×4 transformation, a bigger stretch). References are
|
||||
Bonsai-managed (`Scope="LINKED_MODEL"`), so user-description collisions are unlikely.
|
||||
A cleaner consolidated convention (query + transform + options in one serialized
|
||||
attribute) is a candidate follow-up, deliberately out of scope here.
|
||||
- **`md5(query)[:8]` — kept.** 32 bits ≈ birthday collision at ~65k distinct queries
|
||||
*per file*; and a collision is not silent: the cache JSON stores the full query and
|
||||
`should_clear_cache()` compares it, so a colliding cache is detected and rebuilt
|
||||
(self-healing).
|
||||
- **Depsgraph autosave vs save-on-lock — autosave kept.** Save-on-lock alone loses the
|
||||
"what you see is what's saved" guarantee (move + save project without locking =
|
||||
silently dropped move) and loses undo tracking (undo fires a depsgraph update that
|
||||
re-saves the reverted transform). The handler early-outs when no links exist and only
|
||||
works on ticks containing an object-transform update while a link is unlocked.
|
||||
|
||||
## Status — implemented (verified in Blender, incl. headless + GUI repro runs)
|
||||
|
||||
Six commits on `Linked_File_Features`:
|
||||
|
||||
- `0096c0f6a2` reload_link without a query preserves the stored one.
|
||||
- `40db55e52d` external styles + layerset slicing for linked models
|
||||
(`project/operator.py`, `tool/loader.py`).
|
||||
- `d210d4c814` full Reload Link dialog + `bim.select_link_filepath`.
|
||||
- `3dc161f0f2` per-row lock toggle, auto-save handler, `edit_link` removed
|
||||
(`project/operator.py`, `project/ui.py`, `project/__init__.py`, `tool/project.py`).
|
||||
- `0571d22855` Explore highlight (ngons, IDPropertyArray), moved-link highlight,
|
||||
append placement (`tool/project.py`, `project/operator.py`, `project/decorator.py`).
|
||||
- `c14592ec0a` per-query caches, Description persistence, SKIP_SAVE.
|
||||
|
||||
Plus:
|
||||
|
||||
- `ee43ed5526` review-round path normalization in `get_linked_models_documents` /
|
||||
`LinkIfc` (see Review round 1).
|
||||
- `1669cbcd43` drawing support for moved links and per-link queries in
|
||||
`create_drawing` (`drawing/operator.py`, `tool/project.py`).
|
||||
- `.cut` styling for linked models in BISECT cut mode + STEP-id collision guards +
|
||||
paint order (`drawing/operator.py`) — committed together with this note update.
|
||||
|
||||
End-to-end verified with a two-links-one-file kit (window/door, distinct queries):
|
||||
correct visuals on load, after save → reopen → reload, in both headless and windowed
|
||||
Blender.
|
||||
|
||||
## Things to test / verify
|
||||
|
||||
- **IFC2X3 host**: `Description` doesn't exist — link queries silently not restored on
|
||||
reopen (legacy fallback only for single-link files). Acceptable? Warn?
|
||||
- **Relative-path links** (`use_relative_path`) through the whole cycle: cache paths,
|
||||
reference `Location`, reload path change, query restore. The duplicate-document case
|
||||
(same file linked relative then absolute) is fixed — verify one document with two
|
||||
references via `IfcDocumentInformation.HasDocumentReferences`.
|
||||
- Same file linked twice, **both moved differently**: Explore highlight and append
|
||||
placement per instance (root-empty matching), per-link visibility toggles.
|
||||
- External styles with **image textures**: paths relative to the style's source
|
||||
`.blend` may not resolve from the cache blend's location (shared limitation with the
|
||||
normal import path).
|
||||
- Stale cache orphans: per-query filenames accumulate one blend+json pair per distinct
|
||||
query next to the IFC; nothing auto-deletes them. Cleanup on unlink? Document?
|
||||
- Mid-drag auto-save writes the IFC reference outside Bonsai's transaction system —
|
||||
confirm no undo-stack weirdness in longer editing sessions.
|
||||
- Layerset slicing on meshes shared by elements with *different* usages (offset/sense)
|
||||
bakes the first element's slice — same as normal import, but worth a look with types.
|
||||
- `bim.select_link_filepath` round trip when the reload dialog was opened for a
|
||||
non-active link, and dialog-state carry-over after editing the query *then* browsing.
|
||||
- **Drawing SVG guid cache vs moved links**: `create_drawing` skips elements whose
|
||||
guids already exist in the drawing's SVG (`cached_linework`, invalidated only for
|
||||
*edited host objects*). Moving a link does not invalidate its elements, so a
|
||||
regenerated drawing keeps their old positions until the SVG is deleted. Candidate
|
||||
fix: subtract a moved link's guids from `cached_linework` (compare stored transform
|
||||
against the one recorded at last generation).
|
||||
- Same element appearing in two links of one file (overlapping queries) serializes
|
||||
twice with different transforms; the SVG guid cache keeps whichever came first on
|
||||
regeneration. Degenerate case — probably fine to ignore, but note it.
|
||||
@@ -320,11 +320,9 @@ def loadIfcStore(scene: bpy.types.Scene) -> None:
|
||||
IfcStore.purge()
|
||||
refresh_ui_data()
|
||||
if not tool.Ifc.get():
|
||||
tool.Autosave.cancel_timer()
|
||||
return
|
||||
tool.Ifc.schema()
|
||||
IfcStore.relink_all_objects()
|
||||
tool.Autosave.reset_timer()
|
||||
|
||||
|
||||
@persistent
|
||||
|
||||
@@ -82,15 +82,7 @@ import math
|
||||
from collections.abc import Callable, Iterator
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
ClassVar,
|
||||
Literal,
|
||||
Optional,
|
||||
Protocol,
|
||||
runtime_checkable,
|
||||
)
|
||||
from typing import Any, ClassVar, Literal, Optional, Protocol, runtime_checkable
|
||||
|
||||
import blf
|
||||
import bpy
|
||||
@@ -113,9 +105,6 @@ from mathutils.kdtree import KDTree
|
||||
import bonsai.tool as tool
|
||||
from bonsai.bim.module.drawing.shaders import ExtrusionGuidesShader
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import bmesh
|
||||
|
||||
SNAP_POINT_SIZE = 10.0
|
||||
SNAP_POINT_COLOR = (1.0, 0.5, 0.0, 1.0)
|
||||
SNAP_MAX_RADIUS = 50.0
|
||||
@@ -2046,9 +2035,7 @@ class TexturedQuadGizmoMixin(StaticTrisGizmoMixin):
|
||||
|
||||
def setup(self) -> None:
|
||||
super().setup()
|
||||
from bonsai.bim.module.drawing import (
|
||||
gizmo_textures, # ty: ignore[unresolved-import]
|
||||
)
|
||||
from bonsai.bim.module.drawing import gizmo_textures
|
||||
|
||||
self._quad_batch = batch_for_shader(
|
||||
gizmo_textures.get_shader(),
|
||||
@@ -2057,9 +2044,7 @@ class TexturedQuadGizmoMixin(StaticTrisGizmoMixin):
|
||||
)
|
||||
|
||||
def draw(self, context: bpy.types.Context) -> None:
|
||||
from bonsai.bim.module.drawing import (
|
||||
gizmo_textures, # ty: ignore[unresolved-import]
|
||||
)
|
||||
from bonsai.bim.module.drawing import gizmo_textures
|
||||
|
||||
texture = gizmo_textures.get_icon_texture(self.icon_name)
|
||||
if texture is None:
|
||||
|
||||
@@ -602,7 +602,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
context_type: Literal["body", "annotation"],
|
||||
drawing_elements: set[ifcopenshell.entity_instance],
|
||||
target_view: str,
|
||||
link_matrix: Optional[Matrix] = None,
|
||||
link_transform: Optional[np.ndarray] = None,
|
||||
) -> None:
|
||||
drawing_elements = drawing_elements.copy()
|
||||
contexts_: list[list[int]] = getattr(contexts, context_type)
|
||||
@@ -614,19 +614,22 @@ class CreateDrawing(bpy.types.Operator):
|
||||
geom_settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS)
|
||||
geom_settings.set("iterator-output", ifcopenshell.ifcopenshell_wrapper.NATIVE)
|
||||
|
||||
is_plan = ifc.by_id(context[0]).ContextType == "Plan" and "PLAN_VIEW" in target_view
|
||||
z_offset = (0.002 if target_view == "PLAN_VIEW" else -0.002) if is_plan else 0.0
|
||||
|
||||
if link_matrix is not None:
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc)
|
||||
t = link_matrix.to_translation()
|
||||
offset = (t.x / unit_scale, t.y / unit_scale, t.z / unit_scale + z_offset)
|
||||
geom_settings.set("model-offset", offset)
|
||||
q = link_matrix.to_quaternion()
|
||||
geom_settings.set("model-rotation", (q.x, q.y, q.z, q.w))
|
||||
elif z_offset:
|
||||
offset = np.zeros(3)
|
||||
if ifc.by_id(context[0]).ContextType == "Plan" and "PLAN_VIEW" in target_view:
|
||||
# A 2mm Z offset to combat Z-fighting in plan or RCPs
|
||||
geom_settings.set("model-offset", (0.0, 0.0, z_offset))
|
||||
offset[2] = 0.002 if target_view == "PLAN_VIEW" else -0.002
|
||||
if link_transform is not None:
|
||||
# Bake a moved link's transformation into the geometry. The
|
||||
# mapping composes Trans(model-offset) @ Rot(model-rotation),
|
||||
# matching the Trans(t) @ Rot(R) decomposition of the rigid
|
||||
# link matrix, so the Z offset above simply adds on.
|
||||
offset += link_transform[:3, 3]
|
||||
quaternion = Matrix(link_transform.tolist()).to_quaternion()
|
||||
geom_settings.set(
|
||||
"model-rotation", (quaternion.x, quaternion.y, quaternion.z, quaternion.w)
|
||||
)
|
||||
if offset.any():
|
||||
geom_settings.set("model-offset", tuple(float(o) for o in offset))
|
||||
|
||||
geom_settings.set("context-ids", context)
|
||||
it = ifcopenshell.geom.iterator(
|
||||
@@ -679,6 +682,10 @@ class CreateDrawing(bpy.types.Operator):
|
||||
if "projection" in el.get("class", "").split():
|
||||
continue
|
||||
element = self.get_element_by_guid(el.get("{http://www.ifcopenshell.org/ns}guid"))
|
||||
if element is None or element.file is not tool.Ifc.get():
|
||||
# Linked model element - no Blender object to bisect, and its
|
||||
# STEP id must not be resolved against the host session.
|
||||
continue
|
||||
if not (obj := tool.Ifc.get_object(element)):
|
||||
continue
|
||||
if not (material := ifcopenshell.util.element.get_material(element)):
|
||||
@@ -934,16 +941,25 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
bim_props = tool.Blender.get_bim_props()
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
# Map ifc_path → (ifc_file, link_matrix); main file has no link_matrix (None)
|
||||
files: dict[str, tuple[ifcopenshell.file, Optional[Matrix]]] = {bim_props.ifc_file: (tool.Ifc.get(), None)}
|
||||
|
||||
props = tool.Project.get_project_props()
|
||||
# One entry per file *and* per link - the same file can be linked
|
||||
# several times with different queries and transformations, so links
|
||||
# cannot be collapsed into a dict keyed by filepath.
|
||||
# Each entry is (path, file, link transformation or None, link query, link exclude).
|
||||
file_entries: list[tuple[str, ifcopenshell.file, Optional[np.ndarray], str, str]] = [
|
||||
(bim_props.ifc_file, tool.Ifc.get(), None, "", "")
|
||||
]
|
||||
for link in props.get_loaded_links_for_drawings():
|
||||
try:
|
||||
link_matrix = tool.Project.calculate_link_matrix(link)
|
||||
except Exception:
|
||||
link_matrix = None
|
||||
files[link.filepath] = (self.get_linked_file(link), link_matrix)
|
||||
file_entries.append(
|
||||
(
|
||||
link.filepath,
|
||||
self.get_linked_file(link),
|
||||
tool.Project.get_link_transformation_matrix(link),
|
||||
link.query,
|
||||
link.exclude,
|
||||
)
|
||||
)
|
||||
|
||||
target_view = ifcopenshell.util.element.get_psets(self.camera_element)["EPset_Drawing"]["TargetView"]
|
||||
self.setup_serialiser(target_view)
|
||||
@@ -957,7 +973,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
raycast_objs = set()
|
||||
elements_with_faces = set()
|
||||
|
||||
for ifc_path, (ifc, link_matrix) in files.items():
|
||||
for ifc_path, ifc, link_transform, link_query, link_exclude in file_entries:
|
||||
# Don't use draw.main() just whilst we're prototyping and experimenting
|
||||
# TODO: hash paths are never used
|
||||
ifc_hash = hashlib.md5(ifc_path.encode("utf-8")).hexdigest()
|
||||
@@ -965,6 +981,11 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
self.serialiser.setFile(ifc)
|
||||
drawing_elements = tool.Drawing.get_drawing_elements(self.camera_element, ifc_file=ifc)
|
||||
if link_query:
|
||||
# Draw only what the link's selector filter loaded in the viewport.
|
||||
drawing_elements &= ifcopenshell.util.selector.filter_elements(ifc, link_query)
|
||||
if link_exclude:
|
||||
drawing_elements -= ifcopenshell.util.selector.filter_elements(ifc, link_exclude)
|
||||
|
||||
if self.cprops.fill_mode == "SHAPELY":
|
||||
for element in drawing_elements.copy():
|
||||
@@ -980,9 +1001,11 @@ class CreateDrawing(bpy.types.Operator):
|
||||
# A drawing prioritises a target view context first, followed by a model view context as a fallback.
|
||||
# Specifically for PLAN_VIEW and REFLECTED_PLAN_VIEW, any Plan context is also prioritised.
|
||||
contexts = self.get_linework_contexts(ifc, target_view)
|
||||
self.serialize_contexts_elements(ifc, tree, contexts, "body", drawing_elements, target_view, link_matrix)
|
||||
self.serialize_contexts_elements(
|
||||
ifc, tree, contexts, "annotation", drawing_elements, target_view, link_matrix
|
||||
ifc, tree, contexts, "body", drawing_elements, target_view, link_transform
|
||||
)
|
||||
self.serialize_contexts_elements(
|
||||
ifc, tree, contexts, "annotation", drawing_elements, target_view, link_transform
|
||||
)
|
||||
|
||||
if tool.Ifc.get() == ifc and self.camera_element not in drawing_elements:
|
||||
@@ -1038,6 +1061,10 @@ class CreateDrawing(bpy.types.Operator):
|
||||
if self.cprops.generate_material_layers:
|
||||
self.generate_material_layers(context, root)
|
||||
self.merge_linework_and_add_metadata(root)
|
||||
# Bisect cut linework is appended after the projections, but the
|
||||
# retained serializer cuts of linked models precede them - enforce
|
||||
# the projection-under-cut convention like OPENCASCADE mode does.
|
||||
self.move_projection_to_bottom(root)
|
||||
self.move_elements_to_top(root)
|
||||
elif self.cprops.cut_mode == "OPENCASCADE":
|
||||
self.move_projection_to_bottom(root)
|
||||
@@ -1433,9 +1460,21 @@ class CreateDrawing(bpy.types.Operator):
|
||||
continue
|
||||
|
||||
def remove_cut_linework(self, root):
|
||||
"""Remove host elements' cut linework so bisecting can regenerate it.
|
||||
|
||||
Linked model elements keep the serializer's cut geometry - bisect
|
||||
linework is generated from Blender mesh objects, and linked models
|
||||
are instanced collections without any.
|
||||
"""
|
||||
ifc_file = tool.Ifc.get()
|
||||
for el in root.findall(".//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}guid]"):
|
||||
if "projection" not in el.get("class", "").split():
|
||||
el.getparent().remove(el)
|
||||
if "projection" in el.get("class", "").split():
|
||||
continue
|
||||
try:
|
||||
ifc_file.by_guid(el.get("{http://www.ifcopenshell.org/ns}guid"))
|
||||
except RuntimeError:
|
||||
continue # Linked model element.
|
||||
el.getparent().remove(el)
|
||||
|
||||
def merge_linework_and_add_metadata(self, root):
|
||||
join_criteria = ifcopenshell.util.element.get_pset(self.camera_element, "EPset_Drawing", "JoinCriteria")
|
||||
@@ -1470,7 +1509,9 @@ class CreateDrawing(bpy.types.Operator):
|
||||
classes.append("cut")
|
||||
el.set("class", " ".join(classes))
|
||||
|
||||
obj = tool.Ifc.get_object(element)
|
||||
# Resolving a linked element's STEP id against the host session
|
||||
# would return an arbitrary host object.
|
||||
obj = tool.Ifc.get_object(element) if element is not None and element.file is tool.Ifc.get() else None
|
||||
|
||||
if not obj: # This is a linked model object. For now, do nothing.
|
||||
continue
|
||||
@@ -2343,9 +2384,7 @@ class ActivateDrawingBase(tool.Ifc.Operator):
|
||||
"Activates the selected drawing view.\n\n"
|
||||
+ "ALT+CLICK to keep the viewport position.\n\n"
|
||||
+ "SHIFT+CLICK to load a quick preview of the drawing view.\n\n"
|
||||
+ "SHIFT+CTRL+CLICK to load the annotations of all selected drawings without switching views, "
|
||||
+ "then select their cameras (the first selected drawing's camera becomes active).\n\n"
|
||||
+ "SHIFT+CTRL+ALT+CLICK to do the same but also select the annotations, not just the cameras"
|
||||
+ "SHIFT+CTRL+CLICK to load the annotations of all selected drawings without switching views"
|
||||
)
|
||||
|
||||
drawing: bpy.props.IntProperty()
|
||||
@@ -2367,25 +2406,16 @@ class ActivateDrawingBase(tool.Ifc.Operator):
|
||||
default=False,
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
include_annotations_in_selection: bpy.props.BoolProperty(
|
||||
name="Include Annotations In Selection",
|
||||
description="Also select the loaded annotation objects, not just the drawing cameras.",
|
||||
default=False,
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
drawing: int
|
||||
should_view_from_camera: bool
|
||||
use_quick_preview: bool
|
||||
load_selected_annotations: bool
|
||||
include_annotations_in_selection: bool
|
||||
|
||||
def invoke(self, context, event) -> set["rna_enums.OperatorReturnItems"]:
|
||||
if event.type == "LEFTMOUSE" and event.shift and event.ctrl:
|
||||
self.load_selected_annotations = True
|
||||
if event.alt:
|
||||
self.include_annotations_in_selection = True
|
||||
return self.execute(context)
|
||||
if event.type == "LEFTMOUSE" and event.alt:
|
||||
self.should_view_from_camera = False
|
||||
@@ -2400,34 +2430,15 @@ class ActivateDrawingBase(tool.Ifc.Operator):
|
||||
bpy.ops.bim.load_drawings()
|
||||
|
||||
if self.load_selected_annotations:
|
||||
objs_to_select = []
|
||||
active_camera = None
|
||||
for d in props.drawings:
|
||||
if not (d.is_drawing and d.is_selected):
|
||||
continue
|
||||
selected_drawing = tool.Ifc.get().by_id(d.ifc_definition_id)
|
||||
# Importing the camera (if missing) ensures the drawing's
|
||||
# collection exists so the annotations get collected into it.
|
||||
if not (camera := tool.Ifc.get_object(selected_drawing)):
|
||||
camera = tool.Drawing.import_drawing(selected_drawing)
|
||||
group = tool.Drawing.get_drawing_group(selected_drawing)
|
||||
tool.Drawing.import_annotations_in_group(group)
|
||||
|
||||
if active_camera is None:
|
||||
active_camera = camera
|
||||
objs_to_select.append(camera)
|
||||
if self.include_annotations_in_selection:
|
||||
for element in tool.Drawing.get_group_elements(group) or []:
|
||||
if element.is_a("IfcAnnotation") and element.ObjectType != "DRAWING":
|
||||
if annotation_obj := tool.Ifc.get_object(element):
|
||||
objs_to_select.append(annotation_obj)
|
||||
|
||||
# Select the checked drawings' objects, with the first drawing's camera as active.
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
for obj in objs_to_select:
|
||||
obj.select_set(True)
|
||||
if active_camera is not None:
|
||||
context.view_layer.objects.active = active_camera
|
||||
if not tool.Ifc.get_object(selected_drawing):
|
||||
tool.Drawing.import_drawing(selected_drawing)
|
||||
tool.Drawing.import_annotations_in_group(tool.Drawing.get_drawing_group(selected_drawing))
|
||||
return {"FINISHED"}
|
||||
|
||||
drawing = tool.Ifc.get().by_id(self.drawing)
|
||||
@@ -2516,9 +2527,7 @@ class ActivateDrawing(bpy.types.Operator, ActivateDrawingBase):
|
||||
"Activates the selected drawing view.\n\n"
|
||||
+ "ALT+CLICK to keep the viewport position.\n\n"
|
||||
+ "SHIFT+CLICK to load a quick preview of the drawing view.\n\n"
|
||||
+ "SHIFT+CTRL+CLICK to load the annotations of all selected drawings without switching views, "
|
||||
+ "then select their cameras (the first selected drawing's camera becomes active).\n\n"
|
||||
+ "SHIFT+CTRL+ALT+CLICK to do the same but also select the annotations, not just the cameras"
|
||||
+ "SHIFT+CTRL+CLICK to load the annotations of all selected drawings without switching views"
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ import bonsai.tool as tool
|
||||
from . import (
|
||||
array,
|
||||
covering,
|
||||
decorator,
|
||||
door,
|
||||
external,
|
||||
grid,
|
||||
|
||||
@@ -38,7 +38,6 @@ import numpy as np
|
||||
from ifcopenshell.util.shape_builder import ShapeBuilder
|
||||
from mathutils import Matrix, Vector
|
||||
|
||||
import bonsai.core.geometry
|
||||
import bonsai.core.root
|
||||
import bonsai.tool as tool
|
||||
from bonsai.bim.module.drawing import gizmos as gizmo
|
||||
|
||||
@@ -138,7 +138,7 @@ def update_bbim_railing_pset(element: ifcopenshell.entity_instance, railing_data
|
||||
|
||||
def generate_wall_mounted_handrail_preview(
|
||||
obj: bpy.types.Object,
|
||||
props: "prop.BIMRailingProperties",
|
||||
props: "BIMRailingProperties",
|
||||
path_data: dict[str, Any],
|
||||
si_conversion: float,
|
||||
) -> None:
|
||||
@@ -860,9 +860,7 @@ class GizmoRailingSchematic(bpy.types.GizmoGroup, gizmo.BaseSchematicGizmoGroup)
|
||||
terminal_world = anchor + billboard_rot @ view_rotation @ terminal_local
|
||||
self.terminal_gizmo.matrix_basis = gizmo.billboarded_at(terminal_world, billboard_rot, 0.18)
|
||||
|
||||
def update_editing_gizmos(
|
||||
self, context: bpy.types.Context, mw: "Matrix", props: "prop.BIMRailingProperties"
|
||||
) -> None:
|
||||
def update_editing_gizmos(self, context: bpy.types.Context, mw: "Matrix", props: "BIMRailingProperties") -> None:
|
||||
"""Hide the pen gizmo while polyline path-edit is active; reposition the cycle icon.
|
||||
|
||||
The base class shows the pen gizmo whenever ``is_editing`` is False,
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
|
||||
import bpy
|
||||
|
||||
import bonsai.tool as tool
|
||||
|
||||
from . import decorator, gizmo, operator, prop, ui, workspace
|
||||
|
||||
classes = (
|
||||
@@ -47,7 +45,6 @@ classes = (
|
||||
operator.DisableEditingHeader,
|
||||
operator.DisableEditingLink,
|
||||
operator.EditHeader,
|
||||
operator.EditLink,
|
||||
operator.EditProjectLibrary,
|
||||
operator.EnableCulling,
|
||||
operator.EnableEditingHeader,
|
||||
@@ -60,8 +57,6 @@ classes = (
|
||||
operator.LinkIfc,
|
||||
operator.LoadBlendMetadataAndIFC,
|
||||
operator.LoadLink,
|
||||
operator.AutosavePrompt,
|
||||
operator.LoadAutosavedRecoveryPopup,
|
||||
operator.LoadLinkedProject,
|
||||
operator.LoadProject,
|
||||
operator.LoadProjectElements,
|
||||
@@ -71,6 +66,7 @@ classes = (
|
||||
operator.QueryLinkedElement,
|
||||
operator.RefreshClippingPlanes,
|
||||
operator.RefreshLibrary,
|
||||
operator.ReloadAllLinks,
|
||||
operator.ReloadLink,
|
||||
operator.RemoveProjectLibrary,
|
||||
operator.RevertProject,
|
||||
@@ -78,6 +74,7 @@ classes = (
|
||||
operator.SaveLibraryFile,
|
||||
operator.SelectLibraryFile,
|
||||
operator.SelectLinkedModelElement,
|
||||
operator.SelectLinkFilepath,
|
||||
operator.SelectLinkHandle,
|
||||
operator.ToggleFilterCategories,
|
||||
operator.ToggleLinkSelectability,
|
||||
@@ -113,12 +110,45 @@ classes = (
|
||||
addon_keymaps = []
|
||||
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def _autosave_link_transforms(scene, depsgraph):
|
||||
"""Persist link transformations whenever an editing link's handle is moved.
|
||||
|
||||
Deliberate exemption from the transaction rule in
|
||||
docs/guides/development/undo_system.rst: a handler cannot run inside
|
||||
execute_ifc_operator, so this IFC write is not undo-tracked. It stays
|
||||
consistent anyway because undoing the move fires another depsgraph
|
||||
update, which re-saves the reverted matrix.
|
||||
"""
|
||||
import bonsai.tool as tool
|
||||
|
||||
props = tool.Project.get_project_props()
|
||||
if not props.links:
|
||||
return
|
||||
handles = None
|
||||
for update in depsgraph.updates:
|
||||
if not update.is_updated_transform or not isinstance(update.id, bpy.types.Object):
|
||||
continue
|
||||
if handles is None:
|
||||
# Built lazily so ticks without transform updates stay cheap.
|
||||
handles = {}
|
||||
for link in props.links:
|
||||
if link.is_loaded and link.is_editing and (handle := tool.Project.get_link_empty_handle(link)):
|
||||
handles[handle] = link
|
||||
if not handles:
|
||||
return
|
||||
if link := handles.get(update.id.original):
|
||||
tool.Project.save_link_transformation(link)
|
||||
|
||||
|
||||
def register():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.register_tool(workspace.ExploreTool, after={"builtin.transform"}, separator=True, group=False)
|
||||
bpy.types.Scene.BIMProjectProperties = bpy.props.PointerProperty(type=prop.BIMProjectProperties)
|
||||
bpy.types.Scene.MeasureToolSettings = bpy.props.PointerProperty(type=prop.MeasureToolSettings)
|
||||
bpy.app.handlers.load_post.append(decorator.toggle_decorations_on_load)
|
||||
if _autosave_link_transforms not in bpy.app.handlers.depsgraph_update_post:
|
||||
bpy.app.handlers.depsgraph_update_post.append(_autosave_link_transforms)
|
||||
bpy.types.TOPBAR_MT_file_import.append(ui.file_import_menu)
|
||||
bpy.types.TOPBAR_MT_file.prepend(ui.file_menu)
|
||||
bpy.types.TOPBAR_MT_file_context_menu.prepend(ui.file_menu)
|
||||
@@ -140,10 +170,11 @@ def register():
|
||||
def unregister():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.unregister_tool(workspace.ExploreTool)
|
||||
tool.Autosave.cancel_timer()
|
||||
del bpy.types.Scene.BIMProjectProperties
|
||||
del bpy.types.Scene.MeasureToolSettings
|
||||
bpy.app.handlers.load_post.remove(decorator.toggle_decorations_on_load)
|
||||
if _autosave_link_transforms in bpy.app.handlers.depsgraph_update_post:
|
||||
bpy.app.handlers.depsgraph_update_post.remove(_autosave_link_transforms)
|
||||
bpy.types.TOPBAR_MT_file.remove(ui.file_menu)
|
||||
bpy.types.TOPBAR_MT_file_context_menu.remove(ui.file_menu)
|
||||
|
||||
|
||||
@@ -99,6 +99,7 @@ class ProjectDecorator:
|
||||
|
||||
if geom.selected_edges:
|
||||
self.draw_batch("LINES", selected_vertices, selected_elements_color, geom.selected_edges)
|
||||
if geom.selected_tris:
|
||||
self.draw_batch(
|
||||
"TRIS", selected_vertices, tool.Blender.transparent_color(selected_elements_color), geom.selected_tris
|
||||
)
|
||||
|
||||
@@ -985,10 +985,8 @@ class LoadProject(bpy.types.Operator, IFCFileSelector, ImportHelper):
|
||||
),
|
||||
default=False,
|
||||
)
|
||||
skip_autosave_recovery: bpy.props.BoolProperty(default=False, options={"HIDDEN", "SKIP_SAVE"})
|
||||
use_detailed_tooltip: bpy.props.BoolProperty(default=False, options={"HIDDEN"})
|
||||
filename_ext = ".ifc"
|
||||
skip_recent: bpy.props.BoolProperty(default=False, options={"HIDDEN", "SKIP_SAVE"})
|
||||
|
||||
if TYPE_CHECKING:
|
||||
filepath: str
|
||||
@@ -997,7 +995,6 @@ class LoadProject(bpy.types.Operator, IFCFileSelector, ImportHelper):
|
||||
use_relative_path: bool
|
||||
should_start_fresh_session: bool
|
||||
import_without_ifc_data: bool
|
||||
skip_autosave_recovery: bool
|
||||
use_detailed_tooltip: bool
|
||||
|
||||
@classmethod
|
||||
@@ -1044,26 +1041,7 @@ class LoadProject(bpy.types.Operator, IFCFileSelector, ImportHelper):
|
||||
|
||||
return tooltip
|
||||
|
||||
def check_autosave_recovery(self, context: bpy.types.Context) -> set["rna_enums.OperatorReturnItems"] | None:
|
||||
if self.skip_autosave_recovery:
|
||||
return None
|
||||
autosaved_filepath = tool.Autosave.get_newer_autosaved_path(self.get_filepath_abs())
|
||||
if not autosaved_filepath:
|
||||
return None
|
||||
return bpy.ops.bim.load_autosaved_recovery_popup(
|
||||
"INVOKE_DEFAULT",
|
||||
original_filepath=str(self.get_filepath_abs()),
|
||||
autosaved_filepath=autosaved_filepath,
|
||||
is_advanced=self.is_advanced,
|
||||
use_relative_path=self.use_relative_path,
|
||||
should_start_fresh_session=self.should_start_fresh_session,
|
||||
import_without_ifc_data=self.import_without_ifc_data,
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
if recovery := self.check_autosave_recovery(context):
|
||||
return recovery
|
||||
|
||||
if (
|
||||
tool.Blender.get_addon_preferences().save_metadata_blend_file
|
||||
and self.should_start_fresh_session
|
||||
@@ -1158,8 +1136,7 @@ class LoadProject(bpy.types.Operator, IFCFileSelector, ImportHelper):
|
||||
props.should_save_metadata_for_this_file = metadata_doc is not None
|
||||
|
||||
tool.Blender.register_toolbar()
|
||||
if not self.skip_recent:
|
||||
tool.Project.add_recent_ifc_project(self.get_filepath_abs())
|
||||
tool.Project.add_recent_ifc_project(self.get_filepath_abs())
|
||||
|
||||
if self.is_advanced:
|
||||
pass
|
||||
@@ -1172,13 +1149,10 @@ class LoadProject(bpy.types.Operator, IFCFileSelector, ImportHelper):
|
||||
except:
|
||||
bonsai.last_error = traceback.format_exc()
|
||||
raise
|
||||
tool.Autosave.reset_timer()
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
if self.filepath:
|
||||
if recovery := self.check_autosave_recovery(context):
|
||||
return recovery
|
||||
return self.execute(context)
|
||||
return ImportHelper.invoke(self, context, event)
|
||||
|
||||
@@ -1386,10 +1360,18 @@ class LinkIfc(bpy.types.Operator, ImportHelper, tool.Ifc.Operator):
|
||||
)
|
||||
use_cache: bpy.props.BoolProperty(name="Use Cache", default=True)
|
||||
query: bpy.props.StringProperty(
|
||||
name="Query",
|
||||
name="Include",
|
||||
description=(
|
||||
"Custom selector query to use to load element from a linked model. E.g. 'IfcElement'.\n\n"
|
||||
"Default query - IfcElement, but excluding IfcProxy, IfcSpatialStructureElement, IfcSpatialElement, IfcFeatureElement."
|
||||
"Selector query for the elements to load from the linked model. E.g. 'IfcElement'.\n\n"
|
||||
"Default when empty - IfcElement, but excluding IfcProxy, IfcSpatialStructureElement, IfcSpatialElement, IfcFeatureElement."
|
||||
),
|
||||
)
|
||||
exclude: bpy.props.StringProperty(
|
||||
name="Exclude",
|
||||
description=(
|
||||
"Selector query whose matches are excluded from the loaded elements.\n\n"
|
||||
"Applied on top of the query (or the default set), providing the set "
|
||||
"difference a single query cannot express. E.g. 'IfcSlab, parent=\"X\"'."
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1403,6 +1385,7 @@ class LinkIfc(bpy.types.Operator, ImportHelper, tool.Ifc.Operator):
|
||||
use_relative_path: bool
|
||||
use_cache: bool
|
||||
query: str
|
||||
exclude: str
|
||||
|
||||
def draw(self, context):
|
||||
assert self.layout
|
||||
@@ -1421,6 +1404,7 @@ class LinkIfc(bpy.types.Operator, ImportHelper, tool.Ifc.Operator):
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "project_north")
|
||||
self.layout.prop(self, "query", placeholder="IfcElement")
|
||||
self.layout.prop(self, "exclude", placeholder='IfcSlab, parent="..."')
|
||||
|
||||
def _execute(self, context):
|
||||
start = time.time()
|
||||
@@ -1443,18 +1427,26 @@ class LinkIfc(bpy.types.Operator, ImportHelper, tool.Ifc.Operator):
|
||||
|
||||
new = props.links.add()
|
||||
if tool.Ifc.get():
|
||||
if not (document := existing_links.get(filepath)):
|
||||
# Look up by resolved absolute path so a file already linked
|
||||
# with a relative Location (or vice versa) reuses its document.
|
||||
resolved_filepath = Path(tool.Ifc.resolve_uri(filepath)).as_posix()
|
||||
if not (document := existing_links.get(resolved_filepath)):
|
||||
document = ifcopenshell.api.document.add_information(tool.Ifc.get())
|
||||
document.Name = Path(filepath).name
|
||||
document.Scope = "LINKED_MODEL"
|
||||
reference = ifcopenshell.api.document.add_reference(tool.Ifc.get(), information=document)
|
||||
reference[1] = ",".join([str(o) for o in np.eye(4).flatten().tolist()])
|
||||
reference.Location = filepath.replace("\\", "/")
|
||||
# Persist the filter per reference (Description is IFC4+ only).
|
||||
description = tool.Project.encode_link_filter(self.query, self.exclude, loaded=True)
|
||||
if description and hasattr(reference, "Description"):
|
||||
reference.Description = description
|
||||
new.ifc_definition_id = reference.id()
|
||||
new.name = filepath
|
||||
new.filepath = filepath
|
||||
new.query = self.query
|
||||
bpy.ops.bim.load_link(link_index=-1, use_cache=self.use_cache, query=self.query)
|
||||
new.exclude = self.exclude
|
||||
bpy.ops.bim.load_link(link_index=-1, use_cache=self.use_cache, query=self.query, exclude=self.exclude)
|
||||
|
||||
|
||||
class UnlinkIfc(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -1513,21 +1505,28 @@ class LoadLink(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Load the selected file"
|
||||
|
||||
# SKIP_SAVE: Blender reuses an operator's last-used property values on the
|
||||
# next interactive invocation, which would leak one link's query/cache
|
||||
# settings into another link's load.
|
||||
link_index: bpy.props.IntProperty(name="Link Index")
|
||||
use_cache: bpy.props.BoolProperty(name="Use Cache", default=True)
|
||||
query: bpy.props.StringProperty()
|
||||
use_cache: bpy.props.BoolProperty(name="Use Cache", default=True, options={"SKIP_SAVE"})
|
||||
query: bpy.props.StringProperty(options={"SKIP_SAVE"})
|
||||
exclude: bpy.props.StringProperty(options={"SKIP_SAVE"})
|
||||
|
||||
if TYPE_CHECKING:
|
||||
link_index: int
|
||||
use_cache: bool
|
||||
query: str
|
||||
exclude: str
|
||||
|
||||
def _execute(self, context):
|
||||
self.link = tool.Project.get_project_props().links[self.link_index]
|
||||
# Fall back to the Link's stored query so callers that omit it
|
||||
# Fall back to the Link's stored filter so callers that omit it
|
||||
# still replay the filter the link was created with.
|
||||
if not self.query and self.link.query:
|
||||
self.query = self.link.query
|
||||
if not self.exclude and self.link.exclude:
|
||||
self.exclude = self.link.exclude
|
||||
filepath = Path(tool.Ifc.resolve_uri(self.link.filepath))
|
||||
if not filepath.exists():
|
||||
self.report({"ERROR"}, f"File does not exist: '{filepath}'")
|
||||
@@ -1564,22 +1563,21 @@ class LoadLink(bpy.types.Operator, tool.Ifc.Operator):
|
||||
self.link.is_loaded = False
|
||||
|
||||
def link_ifc(self) -> Union[set[str], None]:
|
||||
blend_filepath = self.filepath_.with_suffix(".ifc.cache.blend")
|
||||
h5_filepath = self.filepath_.with_suffix(".ifc.cache.h5")
|
||||
json_filepath = self.filepath_.with_suffix(".ifc.cache.json")
|
||||
blend_filepath, json_filepath = tool.Project.get_link_cache_paths(self.filepath_, self.query, self.exclude)
|
||||
|
||||
def should_clear_cache() -> bool:
|
||||
if not self.use_cache:
|
||||
return True
|
||||
if not blend_filepath.exists():
|
||||
return False
|
||||
if not json_filepath.exists():
|
||||
return True
|
||||
data = json.loads(json_filepath.read_text())
|
||||
# Empty 'query' - model loaded without custom query.
|
||||
# Missing 'query' - model was loaded before custom queries were introduced in Bonsai.
|
||||
query = data.get("query", "")
|
||||
return query != self.query
|
||||
return data.get("query", "") != self.query or data.get("exclude", "") != self.exclude
|
||||
|
||||
if should_clear_cache():
|
||||
if should_clear_cache() and blend_filepath.exists():
|
||||
os.remove(blend_filepath)
|
||||
|
||||
if not blend_filepath.exists():
|
||||
@@ -1607,7 +1605,7 @@ def run():
|
||||
pprops.project_north = "{pprops.project_north}"
|
||||
# Use absolute path to be safe from cwd changes.
|
||||
try:
|
||||
bpy.ops.bim.load_linked_project(filepath=r"{str(self.filepath_)}", query={repr(self.query)})
|
||||
bpy.ops.bim.load_linked_project(filepath=r"{str(self.filepath_)}", query={repr(self.query)}, exclude={repr(self.exclude)})
|
||||
except RuntimeError as e:
|
||||
# Operator failed (returned CANCELLED with error report)
|
||||
print(f"Failed to load linked project: {{e}}")
|
||||
@@ -1656,7 +1654,7 @@ except Exception as e:
|
||||
if len(tool.Project.get_project_props().links) > 1:
|
||||
return # Only the first link sets the origin
|
||||
|
||||
json_filepath = self.filepath_.with_suffix(".ifc.cache.json")
|
||||
json_filepath = tool.Project.get_link_cache_paths(self.filepath_, self.query, self.exclude)[1]
|
||||
if not json_filepath.exists():
|
||||
return
|
||||
|
||||
@@ -1675,8 +1673,7 @@ except Exception as e:
|
||||
if not (crs_name := (ifcopenshell.util.geolocation.get_crs(tool.Ifc.get()) or {}).get("Name", "")):
|
||||
self.link.georeferenced = "NONE"
|
||||
return
|
||||
reference = tool.Ifc.get().by_id(self.link.ifc_definition_id)
|
||||
json_filepath = Path(reference.Location).with_suffix(".ifc.cache.json")
|
||||
json_filepath = tool.Project.get_link_cache_paths(self.filepath_, self.query, self.exclude)[1]
|
||||
if not json_filepath.exists():
|
||||
self.link.georeferenced = "NONE"
|
||||
return
|
||||
@@ -1688,43 +1685,211 @@ except Exception as e:
|
||||
self.link.georeferenced = "FULL_COMPATIBLE" if crs_name == data["model_crs"] else "NOT_COMPATIBLE"
|
||||
|
||||
|
||||
class ReloadLink(bpy.types.Operator):
|
||||
class ReloadLink(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.reload_link"
|
||||
bl_label = "Reload Link"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Reload the selected file"
|
||||
bl_description = "Reload the selected file, optionally changing its file path and load options"
|
||||
|
||||
# SKIP_SAVE: this operator distinguishes "provided" from "unset" properties
|
||||
# via is_property_set, so last-used property retention between interactive
|
||||
# invocations would leak one link's settings into another's reload.
|
||||
link_index: bpy.props.IntProperty(name="Link Index")
|
||||
filepath: bpy.props.StringProperty(
|
||||
name="File Path",
|
||||
description="Path to the linked IFC file",
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
use_relative_path: bpy.props.BoolProperty(
|
||||
name="Use Relative Path",
|
||||
description="Whether to store linked model path relative to the currently opened IFC file.",
|
||||
default=False,
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
use_cache: bpy.props.BoolProperty(
|
||||
name="Use Cache",
|
||||
description="Reuse the cached geometry if it's still valid instead of reprocessing the IFC",
|
||||
default=False,
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
query: bpy.props.StringProperty(
|
||||
name="Query",
|
||||
name="Include",
|
||||
description=(
|
||||
"Custom selector query to use to load element from a linked model. E.g. 'IfcElement'.\n\n"
|
||||
"Default query - IfcElement, but excluding IfcProxy, IfcSpatialStructureElement, IfcSpatialElement, IfcFeatureElement."
|
||||
"Selector query for the elements to load from the linked model. E.g. 'IfcElement'.\n\n"
|
||||
"Default when empty - IfcElement, but excluding IfcProxy, IfcSpatialStructureElement, IfcSpatialElement, IfcFeatureElement."
|
||||
),
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
exclude: bpy.props.StringProperty(
|
||||
name="Exclude",
|
||||
description=(
|
||||
"Selector query whose matches are excluded from the loaded elements.\n\n"
|
||||
"Applied on top of the query (or the default set), providing the set "
|
||||
"difference a single query cannot express. E.g. 'IfcSlab, parent=\"X\"'."
|
||||
),
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
link_index: int
|
||||
filepath: str
|
||||
use_relative_path: bool
|
||||
use_cache: bool
|
||||
query: str
|
||||
exclude: str
|
||||
|
||||
def invoke(self, context, event):
|
||||
link = tool.Project.get_project_props().links[self.link_index]
|
||||
self.query = link.query
|
||||
# Properties may arrive pre-set when the dialog is reopened
|
||||
# by bim.select_link_filepath - don't clobber them.
|
||||
if not self.properties.is_property_set("filepath"):
|
||||
self.filepath = link.filepath
|
||||
if not self.properties.is_property_set("use_relative_path"):
|
||||
self.use_relative_path = not Path(link.filepath).is_absolute()
|
||||
if not self.properties.is_property_set("query"):
|
||||
self.query = link.query
|
||||
if not self.properties.is_property_set("exclude"):
|
||||
self.exclude = link.exclude
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
def draw(self, context):
|
||||
assert self.layout
|
||||
pprops = tool.Project.get_project_props()
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self, "filepath")
|
||||
op = row.operator("bim.select_link_filepath", text="", icon="FILEBROWSER")
|
||||
op.link_index = self.link_index
|
||||
# Carry the current dialog state through the file browser round-trip.
|
||||
op.use_relative_path = self.use_relative_path
|
||||
op.use_cache = self.use_cache
|
||||
op.query = self.query
|
||||
op.exclude = self.exclude
|
||||
row = self.layout.row()
|
||||
row.prop(self, "use_relative_path")
|
||||
row = self.layout.row()
|
||||
row.prop(self, "use_cache")
|
||||
row = self.layout.row()
|
||||
row.label(text="False Origin Mode:")
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "false_origin_mode", text="")
|
||||
if pprops.false_origin_mode == "MANUAL":
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "false_origin")
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "project_north")
|
||||
self.layout.prop(self, "query", placeholder="IfcElement")
|
||||
self.layout.prop(self, "exclude", placeholder='IfcSlab, parent="..."')
|
||||
|
||||
def execute(self, context):
|
||||
def _execute(self, context):
|
||||
link = tool.Project.get_project_props().links[self.link_index]
|
||||
# An unset query means the operator was called without the dialog
|
||||
# (e.g. from a script) - preserve the link's stored query instead
|
||||
# of overwriting it with the empty default.
|
||||
# Unset properties mean the operator was called without the dialog
|
||||
# (e.g. from a script) - preserve the link's stored values instead
|
||||
# of overwriting them with the defaults.
|
||||
if self.properties.is_property_set("query"):
|
||||
link.query = self.query
|
||||
if self.properties.is_property_set("exclude"):
|
||||
link.exclude = self.exclude
|
||||
|
||||
filepath = self.filepath if self.properties.is_property_set("filepath") else link.filepath
|
||||
if self.properties.is_property_set("use_relative_path"):
|
||||
use_relative_path = self.use_relative_path
|
||||
else:
|
||||
use_relative_path = not Path(link.filepath).is_absolute()
|
||||
|
||||
abs_filepath = Path(tool.Ifc.resolve_uri(filepath))
|
||||
if not abs_filepath.exists():
|
||||
self.report({"ERROR"}, f"File does not exist: '{abs_filepath}'")
|
||||
return {"CANCELLED"}
|
||||
filepath = tool.Ifc.get_uri(abs_filepath, use_relative_path=use_relative_path)
|
||||
if filepath != link.filepath:
|
||||
link.name = filepath
|
||||
link.filepath = filepath
|
||||
if tool.Ifc.get() and link.ifc_definition_id:
|
||||
reference = tool.Ifc.get().by_id(link.ifc_definition_id)
|
||||
reference.Location = filepath.replace("\\", "/")
|
||||
if document := tool.Document.get_reference_document(reference):
|
||||
document.Name = Path(filepath).name
|
||||
if tool.Ifc.get() and link.ifc_definition_id:
|
||||
reference = tool.Ifc.get().by_id(link.ifc_definition_id)
|
||||
if hasattr(reference, "Description"):
|
||||
reference.Description = tool.Project.encode_link_filter(
|
||||
link.query, link.exclude, loaded=True, display_name=link.display_name
|
||||
)
|
||||
|
||||
bpy.ops.bim.unload_link(link_index=self.link_index)
|
||||
return bpy.ops.bim.load_link(link_index=self.link_index, use_cache=False, query=link.query) or {"FINISHED"}
|
||||
return bpy.ops.bim.load_link(
|
||||
link_index=self.link_index, use_cache=self.use_cache, query=link.query, exclude=link.exclude
|
||||
) or {"FINISHED"}
|
||||
|
||||
|
||||
class ReloadAllLinks(bpy.types.Operator):
|
||||
bl_idname = "bim.reload_all_links"
|
||||
bl_label = "Reload All Links"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Reload all loaded linked models from disk, rebuilding their caches"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not any(link.is_loaded for link in tool.Project.get_project_props().links):
|
||||
cls.poll_message_set("No loaded links to reload.")
|
||||
return False
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
props = tool.Project.get_project_props()
|
||||
reloaded = 0
|
||||
for i, link in enumerate(props.links):
|
||||
if not link.is_loaded:
|
||||
continue
|
||||
# Called without filter properties, reload_link preserves each
|
||||
# link's stored path, query and exclude.
|
||||
bpy.ops.bim.reload_link(link_index=i)
|
||||
reloaded += 1
|
||||
self.report({"INFO"}, f"Reloaded {reloaded} linked model(s).")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectLinkFilepath(bpy.types.Operator):
|
||||
bl_idname = "bim.select_link_filepath"
|
||||
bl_label = "Select Link File Path"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
bl_description = "Select a new file path for the linked model and return to the reload dialog"
|
||||
|
||||
link_index: bpy.props.IntProperty(name="Link Index")
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH", options={"SKIP_SAVE", "HIDDEN"})
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc", options={"HIDDEN"})
|
||||
# Reload dialog state carried through the file browser round-trip.
|
||||
use_relative_path: bpy.props.BoolProperty(options={"HIDDEN"})
|
||||
use_cache: bpy.props.BoolProperty(options={"HIDDEN"})
|
||||
query: bpy.props.StringProperty(options={"HIDDEN"})
|
||||
exclude: bpy.props.StringProperty(options={"HIDDEN"})
|
||||
|
||||
if TYPE_CHECKING:
|
||||
link_index: int
|
||||
filepath: str
|
||||
filter_glob: str
|
||||
use_relative_path: bool
|
||||
use_cache: bool
|
||||
query: str
|
||||
exclude: str
|
||||
|
||||
def invoke(self, context, event):
|
||||
link = tool.Project.get_project_props().links[self.link_index]
|
||||
self.filepath = tool.Ifc.resolve_uri(link.filepath)
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.bim.reload_link(
|
||||
"INVOKE_DEFAULT",
|
||||
link_index=self.link_index,
|
||||
filepath=self.filepath,
|
||||
use_relative_path=self.use_relative_path,
|
||||
use_cache=self.use_cache,
|
||||
query=self.query,
|
||||
exclude=self.exclude,
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ToggleLinkSelectability(bpy.types.Operator):
|
||||
@@ -1742,7 +1907,7 @@ class ToggleLinkSelectability(bpy.types.Operator):
|
||||
props = tool.Project.get_project_props()
|
||||
link = props.links[self.link_index]
|
||||
self.library_filepath = tool.Blender.ensure_blender_path_is_abs(
|
||||
Path(link.filepath).with_suffix(".ifc.cache.blend")
|
||||
tool.Project.get_link_cache_paths(link.filepath, link.query, link.exclude)[0]
|
||||
)
|
||||
link.is_selectable = (is_selectable := not link.is_selectable)
|
||||
for collection in self.get_linked_collections():
|
||||
@@ -1779,7 +1944,7 @@ class ToggleLinkVisibility(bpy.types.Operator):
|
||||
props = tool.Project.get_project_props()
|
||||
link = props.links[self.link_index]
|
||||
self.library_filepath = tool.Blender.ensure_blender_path_is_abs(
|
||||
Path(link.filepath).with_suffix(".ifc.cache.blend")
|
||||
tool.Project.get_link_cache_paths(link.filepath, link.query, link.exclude)[0]
|
||||
)
|
||||
if self.mode == "WIREFRAME":
|
||||
self.toggle_wireframe(link)
|
||||
@@ -1821,10 +1986,16 @@ class EnableEditingLink(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_link"
|
||||
bl_label = "Enable Editing Link"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Enable editing link location"
|
||||
bl_description = "Unlock the link's position for editing. Any movement is saved automatically"
|
||||
|
||||
link_index: bpy.props.IntProperty(name="Link Index", default=-1)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
link_index: int
|
||||
|
||||
def execute(self, context):
|
||||
link = tool.Project.get_project_props().active_link
|
||||
props = tool.Project.get_project_props()
|
||||
link = props.active_link if self.link_index == -1 else props.links[self.link_index]
|
||||
assert link
|
||||
link.is_editing = True
|
||||
obj = tool.Project.get_link_empty_handle(link)
|
||||
@@ -1833,70 +2004,25 @@ class EnableEditingLink(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableEditingLink(bpy.types.Operator):
|
||||
class DisableEditingLink(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.disable_editing_link"
|
||||
bl_label = "Disable Editing Link"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Disable editing link and restore to previously saved location"
|
||||
bl_description = "Lock the link at its current location"
|
||||
|
||||
def execute(self, context):
|
||||
link = tool.Project.get_project_props().active_link
|
||||
assert link
|
||||
link.is_editing = False
|
||||
obj = tool.Project.get_link_empty_handle(link)
|
||||
assert obj
|
||||
obj.matrix_world = tool.Project.calculate_link_matrix(link)
|
||||
tool.Geometry.lock_object(obj)
|
||||
return {"FINISHED"}
|
||||
link_index: bpy.props.IntProperty(name="Link Index", default=-1)
|
||||
|
||||
|
||||
class EditLink(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.edit_link"
|
||||
bl_label = "Edit Link"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Disable editing link and restore to previously saved location"
|
||||
if TYPE_CHECKING:
|
||||
link_index: int
|
||||
|
||||
def _execute(self, context):
|
||||
link = tool.Project.get_project_props().active_link
|
||||
props = tool.Project.get_project_props()
|
||||
link = props.active_link if self.link_index == -1 else props.links[self.link_index]
|
||||
assert link
|
||||
link.is_editing = False
|
||||
obj = tool.Project.get_link_empty_handle(link)
|
||||
assert obj
|
||||
new_obj_matrix = obj.matrix_world
|
||||
|
||||
filepath = Path(tool.Ifc.resolve_uri(link.filepath))
|
||||
with open(filepath.with_suffix(".ifc.cache.json"), "r") as f:
|
||||
metadata = json.load(f)
|
||||
|
||||
rot = ifcopenshell.util.shape_builder.np_rotation_matrix(
|
||||
radians(-float(metadata["model_project_north"])), 4, "Z"
|
||||
)
|
||||
global_matrix = rot @ np.eye(4)
|
||||
global_matrix[:, 3][:3] = [float(o) for o in metadata["model_origin_si"].split(",")]
|
||||
|
||||
gprops = tool.Georeference.get_georeference_props()
|
||||
rot = ifcopenshell.util.shape_builder.np_rotation_matrix(radians(-float(gprops.model_project_north)), 4, "Z")
|
||||
local_matrix = rot @ np.eye(4)
|
||||
local_matrix[:, 3][:3] = [float(o) for o in gprops.model_origin_si.split(",")]
|
||||
|
||||
# obj_matrix is typically calculated as:
|
||||
# obj_matrix = np.linalg.inv(local_matrix) @ transformation @ global_matrix
|
||||
identity_blender_matrix = np.linalg.inv(local_matrix) @ global_matrix
|
||||
if np.allclose(np.array(new_obj_matrix), identity_blender_matrix, atol=1e-5):
|
||||
link.has_transformation = False
|
||||
transformation = ",".join(map(str, np.eye(4).reshape(-1)))
|
||||
else:
|
||||
transformed_global_matrix = local_matrix @ np.array(new_obj_matrix)
|
||||
transformation = transformed_global_matrix @ np.linalg.inv(global_matrix)
|
||||
link.has_transformation = True
|
||||
transformation = ",".join(map(str, transformation.reshape(-1)))
|
||||
|
||||
if tool.Ifc.get():
|
||||
reference = tool.Ifc.get().by_id(link.ifc_definition_id)
|
||||
reference[1] = transformation
|
||||
else:
|
||||
link.transformation = transformation
|
||||
|
||||
tool.Project.save_link_transformation(link)
|
||||
obj.matrix_world = tool.Project.calculate_link_matrix(link)
|
||||
tool.Geometry.lock_object(obj)
|
||||
|
||||
@@ -1973,7 +2099,6 @@ class ExportIFC(bpy.types.Operator, ExportHelper):
|
||||
json_compact: bpy.props.BoolProperty(name="Export Compact IFCJSON", default=False)
|
||||
should_save_as: bpy.props.BoolProperty(name="Should Save As", default=False, options={"HIDDEN"})
|
||||
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
|
||||
skip_recent: bpy.props.BoolProperty(default=False, options={"HIDDEN", "SKIP_SAVE"})
|
||||
|
||||
if TYPE_CHECKING:
|
||||
filter_glob: str
|
||||
@@ -2034,23 +2159,13 @@ class ExportIFC(bpy.types.Operator, ExportHelper):
|
||||
return {"FINISHED"}
|
||||
|
||||
def _execute(self, context):
|
||||
project_props = tool.Project.get_project_props()
|
||||
project_props.use_relative_project_path = self.use_relative_path
|
||||
|
||||
# Fallback if filepath is not set
|
||||
if not getattr(self, "filepath", None) or self.filepath.strip() in ("", ".ifc"):
|
||||
props = tool.Blender.get_bim_props()
|
||||
if props.ifc_file:
|
||||
self.filepath = str(tool.Blender.ensure_blender_path_is_abs(Path(props.ifc_file)))
|
||||
else:
|
||||
self.report({"ERROR"}, "No filepath available for saving.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
committed, failed_commits = tool.Parametric.commit_pending_edits()
|
||||
# Previews are session-transient — discard rather than commit. Sibling
|
||||
# gizmo polls gate on each preview's is_active flag, and a stuck flag
|
||||
# persisted through the save would silently hide them on reload.
|
||||
preview_base.discard_pending_previews(context.scene)
|
||||
# Links loaded and visible right now auto-load on the next open.
|
||||
tool.Project.update_linked_models_state()
|
||||
# Suffix is appended to the IFC save-success report below so the auto-commit
|
||||
# info isn't immediately overwritten by the success message in Blender's
|
||||
# status bar (only the latest self.report({"INFO"}, ...) sticks).
|
||||
@@ -2108,8 +2223,7 @@ class ExportIFC(bpy.types.Operator, ExportHelper):
|
||||
settings.logger.info("Export finished in {:.2f} seconds".format(time.time() - start))
|
||||
print("Export finished in {:.2f} seconds".format(time.time() - start))
|
||||
# New project created in Bonsai should be in recent projects too.
|
||||
if not self.skip_recent:
|
||||
tool.Project.add_recent_ifc_project(Path(output_file))
|
||||
tool.Project.add_recent_ifc_project(Path(output_file))
|
||||
props = tool.Project.get_project_props()
|
||||
if props.use_relative_project_path and bpy.data.is_saved:
|
||||
output_file = os.path.relpath(output_file, bpy.path.abspath("//"))
|
||||
@@ -2143,7 +2257,6 @@ class ExportIFC(bpy.types.Operator, ExportHelper):
|
||||
)
|
||||
|
||||
bonsai.bim.handler.refresh_ui_data()
|
||||
tool.Autosave.reset_timer()
|
||||
|
||||
@classmethod
|
||||
def description(cls, context, properties):
|
||||
@@ -2152,97 +2265,6 @@ class ExportIFC(bpy.types.Operator, ExportHelper):
|
||||
return "Save the IFC file. Will save both .IFC/.BLEND files if synced together"
|
||||
|
||||
|
||||
class LoadAutosavedRecoveryPopup(bpy.types.Operator):
|
||||
bl_idname = "bim.load_autosaved_recovery_popup"
|
||||
bl_label = "Recover Autosaved File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
original_filepath: bpy.props.StringProperty(options={"SKIP_SAVE"})
|
||||
autosaved_filepath: bpy.props.StringProperty(options={"SKIP_SAVE"})
|
||||
is_advanced: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||
use_relative_path: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||
should_start_fresh_session: bpy.props.BoolProperty(default=True, options={"SKIP_SAVE"})
|
||||
import_without_ifc_data: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text="A newer autosaved copy was found:", icon="INFO")
|
||||
layout.label(text=os.path.basename(self.autosaved_filepath))
|
||||
layout.separator()
|
||||
layout.label(text="Do you want to load the autosaved version instead?")
|
||||
layout.label(text="(Cancel will load the original)")
|
||||
|
||||
def invoke(self, context, event):
|
||||
# invoke_props_dialog is modal - unlike invoke_popup/popup_menu, it
|
||||
# isn't dismissed by the mouse simply leaving its bounds. It always
|
||||
# renders both a fixed "Cancel" button and this confirm_text one, so
|
||||
# the question is framed as Yes/Cancel rather than adding separate
|
||||
# Load buttons on top.
|
||||
return context.window_manager.invoke_props_dialog(
|
||||
self, width=420, title="Recover Autosaved File", confirm_text="Yes"
|
||||
)
|
||||
|
||||
def _load(self, filepath: str, skip_recent: bool) -> set["rna_enums.OperatorReturnItems"]:
|
||||
return bpy.ops.bim.load_project(
|
||||
filepath=filepath,
|
||||
skip_autosave_recovery=True, # Prevent infinite loop
|
||||
is_advanced=self.is_advanced,
|
||||
use_relative_path=self.use_relative_path,
|
||||
should_start_fresh_session=self.should_start_fresh_session,
|
||||
import_without_ifc_data=self.import_without_ifc_data,
|
||||
skip_recent=skip_recent,
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
result = self._load(self.autosaved_filepath, skip_recent=True)
|
||||
# Re-point tracking at the original path so future saves write back
|
||||
# to it, not "_autosaved.ifc".
|
||||
tool.Ifc.set_path(self.original_filepath)
|
||||
return result
|
||||
|
||||
def cancel(self, context):
|
||||
# Also reached via Escape or a click outside the dialog, not just Cancel.
|
||||
self._load(self.original_filepath, skip_recent=False)
|
||||
|
||||
|
||||
class AutosavePrompt(bpy.types.Operator):
|
||||
bl_idname = "bim.autosave_prompt"
|
||||
bl_label = "Autosave Reminder"
|
||||
bl_options = set()
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(
|
||||
self, width=400, confirm_text="Save", title="Autosave Reminder"
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text="The autosave timer has expired.", icon="INFO")
|
||||
layout.label(text="Would you like to save your IFC project now?")
|
||||
|
||||
def execute(self, context):
|
||||
# Get current IFC path
|
||||
props = tool.Blender.get_bim_props()
|
||||
current_ifc_path = props.ifc_file
|
||||
|
||||
if not current_ifc_path:
|
||||
self.report({"WARNING"}, "No IFC file path set. Please save manually.")
|
||||
tool.Autosave.reset_timer()
|
||||
return {"CANCELLED"}
|
||||
|
||||
# Call save_project with explicit filepath using EXEC_DEFAULT
|
||||
result = bpy.ops.bim.save_project(
|
||||
"EXEC_DEFAULT", filepath=current_ifc_path, should_save_as=False, skip_recent=True
|
||||
)
|
||||
|
||||
tool.Autosave.reset_timer()
|
||||
return result
|
||||
|
||||
def cancel(self, context):
|
||||
tool.Autosave.reset_timer()
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
class LoadLinkedProject(bpy.types.Operator, ImportHelper):
|
||||
bl_idname = "bim.load_linked_project"
|
||||
bl_label = "Load Project For Viewing Only"
|
||||
@@ -2251,14 +2273,23 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper):
|
||||
|
||||
query: bpy.props.StringProperty()
|
||||
"""See ``bim.link_ifc``."""
|
||||
exclude: bpy.props.StringProperty()
|
||||
"""See ``bim.link_ifc``."""
|
||||
|
||||
if TYPE_CHECKING:
|
||||
query: str
|
||||
exclude: str
|
||||
|
||||
file: ifcopenshell.file
|
||||
meshes: dict[str, bpy.types.Mesh]
|
||||
# Material names is derived from diffuse as in 'r-g-b-a'.
|
||||
blender_mats: dict[str, bpy.types.Material]
|
||||
# Materials appended from external .blend styles, keyed by style id.
|
||||
# None means the style has no loadable external .blend style.
|
||||
external_style_mats: dict[int, Union[bpy.types.Material, None]]
|
||||
# Appended data-blocks keyed by (filepath, data_block_type, name)
|
||||
# so styles sharing the same external material don't append duplicates.
|
||||
appended_external_blocks: dict[tuple[str, str, str], Union[bpy.types.Material, None]]
|
||||
|
||||
def invoke(self, context, event):
|
||||
# Invoke is for debugging purposes, users are not intended to use this method really.
|
||||
@@ -2315,6 +2346,9 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper):
|
||||
else:
|
||||
self.elements |= set(self.file.by_type("IfcSpatialElement"))
|
||||
self.elements -= set(self.file.by_type("IfcFeatureElement"))
|
||||
if self.exclude:
|
||||
# The set difference a single selector query cannot express.
|
||||
self.elements -= ifcopenshell.util.selector.filter_elements(self.file, self.exclude)
|
||||
|
||||
if tool.Loader.settings.false_origin_mode == "MANUAL" and tool.Loader.settings.false_origin:
|
||||
tool.Loader.set_manual_blender_offset(self.file)
|
||||
@@ -2322,7 +2356,7 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper):
|
||||
tool.Loader.guess_false_origin(self.file)
|
||||
|
||||
tool.Georeference.set_model_origin()
|
||||
self.json_filepath = self.filepath + ".cache.json"
|
||||
self.json_filepath = str(tool.Project.get_link_cache_paths(self.filepath, self.query, self.exclude)[1])
|
||||
data = {
|
||||
"model_is_georeferenced": gprops.model_is_georeferenced,
|
||||
"model_crs": gprops.model_crs,
|
||||
@@ -2340,10 +2374,14 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper):
|
||||
"false_origin": pprops.false_origin,
|
||||
"project_north": pprops.project_north,
|
||||
"query": self.query,
|
||||
"exclude": self.exclude,
|
||||
}
|
||||
with open(self.json_filepath, "w") as f:
|
||||
json.dump(data, f)
|
||||
|
||||
self.external_style_mats = {}
|
||||
self.appended_external_blocks = {}
|
||||
|
||||
for settings in tool.Loader.settings.context_settings:
|
||||
if not self.elements:
|
||||
break
|
||||
@@ -2383,8 +2421,10 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper):
|
||||
mat = tuple(mat)
|
||||
blender_mat = blender_mats.get(mat, None)
|
||||
if not blender_mat:
|
||||
blender_mat = bpy.data.materials.new("Chunk")
|
||||
blender_mat.diffuse_color = mat
|
||||
blender_mat = self.get_external_material(int(mat[4]))
|
||||
if not blender_mat:
|
||||
blender_mat = bpy.data.materials.new("Chunk")
|
||||
blender_mat.diffuse_color = mat[:4]
|
||||
blender_mats[mat] = blender_mat
|
||||
mat_results.append(blender_mat)
|
||||
|
||||
@@ -2402,11 +2442,16 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper):
|
||||
while True: # Main loop.
|
||||
shape = iterator.get()
|
||||
assert isinstance(shape, W.TriangulationElement)
|
||||
results.add(self.file.by_id(shape.id))
|
||||
element = self.file.by_id(shape.id)
|
||||
results.add(element)
|
||||
geometry = shape.geometry
|
||||
|
||||
# Elements with a lot of geometry benefit from instancing to save memory
|
||||
if ifcopenshell.util.shape.get_faces(geometry).shape[0] > 333: # 333 tris
|
||||
# Elements with a lot of geometry benefit from instancing to save memory.
|
||||
# Multi-layer elements also take this path as they need their own
|
||||
# local-space mesh to be sliced into per-layer materials.
|
||||
if ifcopenshell.util.shape.get_faces(geometry).shape[0] > 333 or self.is_multilayer_element(
|
||||
element
|
||||
): # 333 tris
|
||||
self.process_occurrence(shape)
|
||||
if not iterator.next():
|
||||
if not chunked_verts:
|
||||
@@ -2423,9 +2468,15 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper):
|
||||
|
||||
ms = np.vstack([default_mat, ifcopenshell.util.shape.get_material_colors(shape.geometry)])
|
||||
mi = ifcopenshell.util.shape.get_faces_material_style_ids(shape.geometry)
|
||||
# Style ids ride along as a 5th column so styles with
|
||||
# external .blend materials survive the per-color dedup.
|
||||
style_ids = np.zeros((len(ms), 1))
|
||||
for geom_material_idx, geom_material in enumerate(shape.geometry.materials):
|
||||
if not geom_material.instance_id():
|
||||
ms[geom_material_idx + 1] = (0.8, 0.8, 0.8, 1)
|
||||
elif self.get_external_material(geom_material.instance_id()):
|
||||
style_ids[geom_material_idx + 1] = geom_material.instance_id()
|
||||
ms = np.hstack((ms, style_ids))
|
||||
chunked_materials.append(ms)
|
||||
chunked_material_ids.append(mi + material_offset + 1)
|
||||
material_offset += len(ms)
|
||||
@@ -2512,12 +2563,14 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper):
|
||||
diffuse = (material.diffuse.r(), material.diffuse.g(), material.diffuse.b(), alpha)
|
||||
else:
|
||||
diffuse = (0.8, 0.8, 0.8, 1) # Blender's default material
|
||||
material_name = f"{diffuse[0]}-{diffuse[1]}-{diffuse[2]}-{diffuse[3]}"
|
||||
blender_mat = self.blender_mats.get(material_name, None)
|
||||
blender_mat = self.get_external_material(material.instance_id())
|
||||
if not blender_mat:
|
||||
blender_mat = bpy.data.materials.new(material_name)
|
||||
blender_mat.diffuse_color = diffuse
|
||||
self.blender_mats[material_name] = blender_mat
|
||||
material_name = f"{diffuse[0]}-{diffuse[1]}-{diffuse[2]}-{diffuse[3]}"
|
||||
blender_mat = self.blender_mats.get(material_name, None)
|
||||
if not blender_mat:
|
||||
blender_mat = bpy.data.materials.new(material_name)
|
||||
blender_mat.diffuse_color = diffuse
|
||||
self.blender_mats[material_name] = blender_mat
|
||||
slot_index = mesh.materials.find(material.name)
|
||||
if slot_index == -1:
|
||||
mesh.materials.append(blender_mat)
|
||||
@@ -2530,6 +2583,8 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper):
|
||||
mesh.polygons.foreach_set("material_index", material_index)
|
||||
mesh.update()
|
||||
|
||||
mesh = tool.Loader.slice_layerset_mesh(element, mesh, style_to_material=self.get_style_material)
|
||||
|
||||
self.meshes[geometry.id] = mesh
|
||||
|
||||
obj = bpy.data.objects.new(tool.Loader.get_name(element), mesh)
|
||||
@@ -2542,6 +2597,88 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper):
|
||||
|
||||
self.collection.objects.link(obj)
|
||||
|
||||
def get_external_material(self, style_id: int) -> Union[bpy.types.Material, None]:
|
||||
"""Get the Blender material referenced by a style's external .blend style, if it has one.
|
||||
|
||||
The material is appended from the external .blend file on first use and
|
||||
cached, so it ends up saved inside the link's .cache.blend.
|
||||
"""
|
||||
if not style_id:
|
||||
return None
|
||||
if style_id in self.external_style_mats:
|
||||
return self.external_style_mats[style_id]
|
||||
|
||||
material = None
|
||||
# instance_id may also refer to an IfcMaterial when the item has
|
||||
# a material but no style, hence the class check.
|
||||
style = self.file.by_id(style_id)
|
||||
external = None
|
||||
if style.is_a("IfcSurfaceStyle"):
|
||||
external = next((s for s in style.Styles if s.is_a("IfcExternallyDefinedSurfaceStyle")), None)
|
||||
|
||||
if (
|
||||
external
|
||||
and external.Location
|
||||
and external.Location.endswith(".blend")
|
||||
and external.Identification
|
||||
and "/" in external.Identification
|
||||
):
|
||||
location = Path(external.Location)
|
||||
if not location.is_absolute():
|
||||
# Relative locations are relative to the linked IFC, not the host.
|
||||
location = Path(self.filepath).parent / location
|
||||
data_block_type, data_block = external.Identification.split("/", 1)
|
||||
key = (str(location), data_block_type, data_block)
|
||||
if key in self.appended_external_blocks:
|
||||
material = self.appended_external_blocks[key]
|
||||
elif not location.exists():
|
||||
print(f"WARNING. External style file not found for {style}: '{location}'")
|
||||
self.appended_external_blocks[key] = None
|
||||
else:
|
||||
db = tool.Blender.append_data_block(str(location), data_block_type, data_block)
|
||||
material = db["data_block"]
|
||||
if not isinstance(material, bpy.types.Material):
|
||||
print(f"WARNING. Failed to load external style for {style}: {db['msg'] or 'not a material'}")
|
||||
material = None
|
||||
else:
|
||||
# The source .blend may have been authored in a Bonsai session -
|
||||
# unlink any stale IFC id so it's not misinterpreted here or in the host.
|
||||
tool.Style.get_material_style_props(material).ifc_definition_id = 0
|
||||
self.appended_external_blocks[key] = material
|
||||
|
||||
self.external_style_mats[style_id] = material
|
||||
return material
|
||||
|
||||
def is_multilayer_element(self, element: ifcopenshell.entity_instance) -> bool:
|
||||
material = ifcopenshell.util.element.get_material(element)
|
||||
return bool(
|
||||
material and material.is_a("IfcMaterialLayerSetUsage") and len(material.ForLayerSet.MaterialLayers) > 1
|
||||
)
|
||||
|
||||
def get_style_material(self, style: ifcopenshell.entity_instance) -> Union[bpy.types.Material, None]:
|
||||
"""Resolve a style to a Blender material for slice_layerset_mesh.
|
||||
|
||||
Prefers the style's external .blend material, falling back to a flat
|
||||
diffuse material as used for the rest of the linked geometry.
|
||||
"""
|
||||
if material := self.get_external_material(style.id()):
|
||||
return material
|
||||
# IfcSurfaceStyleRendering is a subclass of IfcSurfaceStyleShading.
|
||||
shading = next((s for s in style.Styles if s.is_a("IfcSurfaceStyleShading")), None)
|
||||
if shading:
|
||||
colour = shading.SurfaceColour
|
||||
alpha = 1.0 - (getattr(shading, "Transparency", None) or 0.0)
|
||||
diffuse = (colour.Red, colour.Green, colour.Blue, alpha)
|
||||
else:
|
||||
diffuse = (0.8, 0.8, 0.8, 1.0)
|
||||
material_name = f"{diffuse[0]}-{diffuse[1]}-{diffuse[2]}-{diffuse[3]}"
|
||||
material = self.blender_mats.get(material_name, None)
|
||||
if not material:
|
||||
material = bpy.data.materials.new(material_name)
|
||||
material.diffuse_color = diffuse
|
||||
self.blender_mats[material_name] = material
|
||||
return material
|
||||
|
||||
def create_object(
|
||||
self,
|
||||
verts: np.ndarray,
|
||||
@@ -2615,7 +2752,7 @@ class QueryLinkedElement(bpy.types.Operator):
|
||||
|
||||
guid = tool.Project.Link.get_guid_by_face_index(obj, face_index)
|
||||
assert guid is not None
|
||||
tool.Project.Link.select_linked_element(context, obj, guid)
|
||||
tool.Project.Link.select_linked_element(context, obj, guid, instance_matrix)
|
||||
|
||||
self.report({"INFO"}, f"Loaded data for {guid}")
|
||||
ProjectDecorator.install(bpy.context)
|
||||
@@ -2740,6 +2877,27 @@ class AppendInspectedLinkedElement(AppendLibraryElement):
|
||||
if element_type and tool.Ifc.get_object(element_type) is None:
|
||||
self.import_type_from_ifc(element_type, context)
|
||||
|
||||
# If the link was moved, place the appended element where the link
|
||||
# is displayed rather than at its original coordinates.
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if isinstance(obj, bpy.types.Object):
|
||||
# Prefer matching the link by the queried instance's root empty -
|
||||
# the same file may be linked several times (different queries)
|
||||
# and moved to different locations.
|
||||
root = props.queried_obj_root
|
||||
linked_filepath = Path(queried_obj["ifc_filepath"])
|
||||
link_match = None
|
||||
for link in props.links:
|
||||
if root is not None and tool.Project.get_link_empty_handle(link) == root:
|
||||
link_match = link
|
||||
break
|
||||
if link_match is None and Path(tool.Ifc.resolve_uri(link.filepath)) == linked_filepath:
|
||||
link_match = link
|
||||
if link_match:
|
||||
delta = tool.Project.calculate_link_delta_matrix(link_match)
|
||||
if not delta.is_identity:
|
||||
obj.matrix_world = delta @ obj.matrix_world
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -261,8 +261,21 @@ class Link(PropertyGroup):
|
||||
default=0,
|
||||
)
|
||||
query: StringProperty(
|
||||
name="Query",
|
||||
description="Selector query used to filter elements when loading the linked model",
|
||||
name="Include",
|
||||
description="Selector query for the elements to load from the linked model",
|
||||
default="",
|
||||
)
|
||||
exclude: StringProperty(
|
||||
name="Exclude",
|
||||
description="Selector query whose matches are excluded when loading the linked model",
|
||||
default="",
|
||||
)
|
||||
display_name: StringProperty(
|
||||
name="Name",
|
||||
description=(
|
||||
"Optional display name to tell links apart (e.g. when the same file "
|
||||
"is linked several times). Shows the file path when empty"
|
||||
),
|
||||
default="",
|
||||
)
|
||||
|
||||
@@ -281,6 +294,8 @@ class Link(PropertyGroup):
|
||||
empty_handle: Union[bpy.types.Object, None]
|
||||
ifc_definition_id: int
|
||||
query: str
|
||||
exclude: str
|
||||
display_name: str
|
||||
|
||||
|
||||
class EditedObj(PropertyGroup):
|
||||
|
||||
@@ -492,17 +492,13 @@ class BIM_PT_links(Panel):
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.link_ifc")
|
||||
row.operator("bim.reload_all_links", text="", icon="FILE_REFRESH")
|
||||
if self.props.links:
|
||||
if self.props.active_link:
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
index = self.props.active_link_index
|
||||
if self.props.active_link.is_loaded:
|
||||
if self.props.active_link.is_editing:
|
||||
row.operator("bim.edit_link", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_link", text="", icon="CANCEL")
|
||||
else:
|
||||
row.operator("bim.enable_editing_link", text="", icon="GREASEPENCIL")
|
||||
row.operator("bim.select_linked_model_element", icon="VIEWZOOM", text="")
|
||||
row.operator("bim.select_link_handle", text="", icon="OBJECT_DATA").link_index = index
|
||||
row.operator("bim.unload_link", text="", icon="UNLINKED").link_index = index
|
||||
@@ -643,7 +639,12 @@ class BIM_UL_links(UIList):
|
||||
if item.has_transformation:
|
||||
row.label(text="", icon="OBJECT_ORIGIN")
|
||||
|
||||
row.label(text=item.filepath)
|
||||
# Double-click to rename; shows the file path while unset.
|
||||
row.prop(item, "display_name", text="", emboss=False, placeholder=item.filepath)
|
||||
if item.is_editing:
|
||||
row.operator("bim.disable_editing_link", text="", icon="UNLOCKED", emboss=False).link_index = index
|
||||
else:
|
||||
row.operator("bim.enable_editing_link", text="", icon="LOCKED", emboss=False).link_index = index
|
||||
icon = "RESTRICT_SELECT_OFF" if item.is_selectable else "RESTRICT_SELECT_ON"
|
||||
row.operator("bim.toggle_link_selectability", text="", icon=icon, emboss=False).link_index = index
|
||||
icon = "CUBE" if item.is_wireframe else "MESH_CUBE"
|
||||
@@ -655,7 +656,7 @@ class BIM_UL_links(UIList):
|
||||
op.link_index = index
|
||||
op.mode = "VISIBLE"
|
||||
else:
|
||||
row.label(text=item.filepath)
|
||||
row.prop(item, "display_name", text="", emboss=False, placeholder=item.filepath)
|
||||
|
||||
|
||||
class BIM_PT_purge(Panel):
|
||||
|
||||
@@ -577,43 +577,6 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
should_disable_undo_on_save: BoolProperty(
|
||||
name="Disable Undo When Saving (Faster saves, no undo for you!)", default=False
|
||||
)
|
||||
|
||||
def update_autosave_settings(self, context: bpy.types.Context) -> None:
|
||||
if self.autosave_enabled:
|
||||
tool.Autosave.reset_timer()
|
||||
else:
|
||||
tool.Autosave.cancel_timer()
|
||||
|
||||
autosave_enabled: BoolProperty(
|
||||
name="Enable IFC Autosave Timer",
|
||||
description="Periodically remind you to save or automatically create a backup copy of the IFC file",
|
||||
default=False,
|
||||
update=update_autosave_settings,
|
||||
)
|
||||
autosave_interval_minutes: bpy.props.IntProperty(
|
||||
name="Autosave Interval (Minutes)",
|
||||
description="Time between autosave reminders or backups. The timer resets whenever you open or save a project",
|
||||
default=10,
|
||||
min=1,
|
||||
max=1440,
|
||||
update=update_autosave_settings,
|
||||
)
|
||||
autosave_mode: bpy.props.EnumProperty(
|
||||
name="Autosave Mode",
|
||||
items=[
|
||||
(
|
||||
"PROMPT",
|
||||
"Prompt to Save",
|
||||
"Show a dialog offering to save the IFC project when the timer expires",
|
||||
),
|
||||
(
|
||||
"BACKUP",
|
||||
"Automatic Backup",
|
||||
"Save a backup copy as filename_autosaved.ifc when the timer expires",
|
||||
),
|
||||
],
|
||||
default="PROMPT",
|
||||
)
|
||||
should_stream: BoolProperty(name="Stream Data From IFC-SPF (Only for advanced users)", default=False)
|
||||
should_always_cache: BoolProperty(
|
||||
name="Always Cache Geometry",
|
||||
@@ -726,9 +689,6 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
bsdd_load_test_dictionaries: bool
|
||||
bsdd_baseurl: str
|
||||
should_disable_undo_on_save: bool
|
||||
autosave_enabled: bool
|
||||
autosave_interval_minutes: int
|
||||
autosave_mode: Literal["PROMPT", "BACKUP"]
|
||||
should_stream: bool
|
||||
should_always_cache: bool
|
||||
occurrence_name_style: Literal["CLASS", "TYPE", "CUSTOM"]
|
||||
@@ -877,12 +837,6 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
def draw_other_settings(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
|
||||
layout.prop(self, "opening_focus_opacity")
|
||||
layout.prop(self, "should_disable_undo_on_save")
|
||||
layout.separator()
|
||||
layout.label(text="Autosave:")
|
||||
layout.prop(self, "autosave_enabled")
|
||||
if self.autosave_enabled:
|
||||
layout.prop(self, "autosave_interval_minutes")
|
||||
layout.prop(self, "autosave_mode")
|
||||
layout.prop(self, "should_stream")
|
||||
layout.prop(self, "should_always_cache")
|
||||
layout.label(text="bSDD:")
|
||||
|
||||
@@ -302,25 +302,23 @@ def add_drawing(
|
||||
context=drawing.get_body_context(),
|
||||
ifc_representation_class=None,
|
||||
)
|
||||
|
||||
|
||||
drawings_parent_group = None
|
||||
for group in ifc.get().by_type("IfcGroup"):
|
||||
if group.Name == "DRAWINGS" and group.ObjectType == "DRAWINGS":
|
||||
drawings_parent_group = group
|
||||
break
|
||||
|
||||
|
||||
if not drawings_parent_group:
|
||||
drawings_parent_group = ifc.run("group.add_group")
|
||||
ifc.run(
|
||||
"group.edit_group", group=drawings_parent_group, attributes={"Name": "DRAWINGS", "ObjectType": "DRAWINGS"}
|
||||
)
|
||||
|
||||
ifc.run("group.edit_group", group=drawings_parent_group, attributes={"Name": "DRAWINGS", "ObjectType": "DRAWINGS"})
|
||||
|
||||
group = ifc.run("group.add_group")
|
||||
ifc.run("group.edit_group", group=group, attributes={"Name": drawing_name, "ObjectType": "DRAWING"})
|
||||
ifc.run("group.assign_group", group=group, products=[element])
|
||||
|
||||
|
||||
ifc.run("group.assign_group", group=drawings_parent_group, products=[group])
|
||||
|
||||
|
||||
collector.assign(camera)
|
||||
pset = ifc.run("pset.add_pset", product=element, name="EPset_Drawing")
|
||||
if drawing.get_unit_system() == "METRIC":
|
||||
@@ -357,7 +355,7 @@ def add_drawing(
|
||||
if document.Name == "DRAWINGS" and document.Scope == "DRAWINGS":
|
||||
drawings_parent_document = document
|
||||
break
|
||||
|
||||
|
||||
if not drawings_parent_document:
|
||||
drawings_parent_document = ifc.run("document.add_information")
|
||||
if ifc.get_schema() == "IFC2X3":
|
||||
@@ -365,7 +363,7 @@ def add_drawing(
|
||||
else:
|
||||
attributes = {"Identification": "DRAWINGS", "Name": "DRAWINGS", "Scope": "DRAWINGS"}
|
||||
ifc.run("document.edit_information", information=drawings_parent_document, attributes=attributes)
|
||||
|
||||
|
||||
information = ifc.run("document.add_information", parent=drawings_parent_document)
|
||||
uri = drawing.get_default_drawing_path(drawing_name)
|
||||
reference = ifc.run("document.add_reference", information=information)
|
||||
@@ -394,19 +392,17 @@ def duplicate_drawing(
|
||||
drawing_tool.set_name(new_drawing, drawing_name)
|
||||
group = drawing_tool.get_drawing_group(new_drawing)
|
||||
ifc.run("group.unassign_group", group=group, products=[new_drawing])
|
||||
|
||||
|
||||
drawings_parent_group = None
|
||||
for parent_group in ifc.get().by_type("IfcGroup"):
|
||||
if parent_group.Name == "DRAWINGS" and parent_group.ObjectType == "DRAWINGS":
|
||||
drawings_parent_group = parent_group
|
||||
break
|
||||
|
||||
|
||||
if not drawings_parent_group:
|
||||
drawings_parent_group = ifc.run("group.add_group")
|
||||
ifc.run(
|
||||
"group.edit_group", group=drawings_parent_group, attributes={"Name": "DRAWINGS", "ObjectType": "DRAWINGS"}
|
||||
)
|
||||
|
||||
ifc.run("group.edit_group", group=drawings_parent_group, attributes={"Name": "DRAWINGS", "ObjectType": "DRAWINGS"})
|
||||
|
||||
new_group = ifc.run("group.add_group")
|
||||
ifc.run("group.edit_group", group=new_group, attributes={"Name": drawing_name, "ObjectType": "DRAWING"})
|
||||
ifc.run("group.assign_group", group=new_group, products=[new_drawing])
|
||||
@@ -431,7 +427,7 @@ def duplicate_drawing(
|
||||
if document.Name == "DRAWINGS" and document.Scope == "DRAWINGS":
|
||||
drawings_parent_document = document
|
||||
break
|
||||
|
||||
|
||||
if not drawings_parent_document:
|
||||
drawings_parent_document = ifc.run("document.add_information")
|
||||
if ifc.get_schema() == "IFC2X3":
|
||||
|
||||
@@ -50,15 +50,14 @@ def copy_z_rotation_to_selected(
|
||||
flip: bool = False,
|
||||
) -> int:
|
||||
"""Apply ``active``'s Z-Euler rotation to each target."""
|
||||
source_z = surveyor.get_z_rotation(active) # ty: ignore[missing-argument]
|
||||
source_z = surveyor.get_z_rotation(active)
|
||||
if flip:
|
||||
source_z += math.pi
|
||||
rotated = 0
|
||||
for obj in targets:
|
||||
target_z = surveyor.get_z_rotation(obj) # ty: ignore[missing-argument]
|
||||
if abs(_z_rotation_diff(target_z, source_z)) < Z_ROTATION_ALIGNMENT_TOLERANCE:
|
||||
if abs(_z_rotation_diff(surveyor.get_z_rotation(obj), source_z)) < Z_ROTATION_ALIGNMENT_TOLERANCE:
|
||||
continue
|
||||
surveyor.set_z_rotation(obj, source_z) # ty: ignore[missing-argument]
|
||||
surveyor.set_z_rotation(obj, source_z)
|
||||
rotated += 1
|
||||
if ifc.get_entity(obj) is not None:
|
||||
bonsai.core.geometry.edit_object_placement(ifc, geometry, surveyor, obj=obj)
|
||||
|
||||
@@ -804,7 +804,7 @@ class Profile:
|
||||
|
||||
@interface
|
||||
class Parametric:
|
||||
def get_geom_generation(cls): pass
|
||||
def get_geom_generation(cls) -> int: pass
|
||||
def refresh_post_commit(cls, operator) -> None: pass
|
||||
|
||||
|
||||
|
||||
@@ -80,6 +80,3 @@ from bonsai.tool.type import Type
|
||||
from bonsai.tool.unit import Unit
|
||||
from bonsai.tool.wall import Wall
|
||||
from bonsai.tool.web import Web
|
||||
|
||||
# Have to move after import of tool.drawing
|
||||
from bonsai.tool.autosave import Autosave # isort: skip
|
||||
|
||||
@@ -1,188 +0,0 @@
|
||||
# Bonsai - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2026
|
||||
#
|
||||
# This file is part of Bonsai.
|
||||
#
|
||||
# Bonsai is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Bonsai is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# This file was generated with the assistance of an AI coding tool.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import atexit
|
||||
import logging
|
||||
import os
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Union
|
||||
|
||||
import bpy
|
||||
|
||||
import bonsai.tool as tool
|
||||
from bonsai.bim import export_ifc
|
||||
from bonsai.bim.module.model import preview_base
|
||||
|
||||
AUTOSAVING_SUFFIX = "_autosaving.ifc"
|
||||
AUTOSAVED_SUFFIX = "_autosaved.ifc"
|
||||
|
||||
_timer_callback: Union[Callable[[], None], None] = None
|
||||
# See cleanup_stale_autosave() for why this is a cached plain string rather
|
||||
# than looked up live.
|
||||
_active_ifc_path_cache: Union[str, None] = None
|
||||
|
||||
|
||||
class Autosave:
|
||||
@classmethod
|
||||
def get_paths(cls, ifc_path: Union[str, Path]) -> tuple[Path, Path, Path]:
|
||||
path = Path(ifc_path)
|
||||
stem = path.stem if path.suffix.lower() == ".ifc" else path.name
|
||||
parent = path.parent
|
||||
main_path = path if path.suffix.lower() == ".ifc" else parent / f"{stem}.ifc"
|
||||
autosaving_path = parent / f"{stem}{AUTOSAVING_SUFFIX}"
|
||||
autosaved_path = parent / f"{stem}{AUTOSAVED_SUFFIX}"
|
||||
return main_path, autosaving_path, autosaved_path
|
||||
|
||||
@classmethod
|
||||
def get_active_ifc_path(cls) -> Union[Path, None]:
|
||||
props = tool.Blender.get_bim_props()
|
||||
if not props.ifc_file:
|
||||
return None
|
||||
path = tool.Blender.ensure_blender_path_is_abs(Path(props.ifc_file))
|
||||
if path.suffix.lower() != ".ifc":
|
||||
return None
|
||||
return path
|
||||
|
||||
@classmethod
|
||||
def _update_active_ifc_path_cache(cls) -> None:
|
||||
global _active_ifc_path_cache
|
||||
ifc_path = cls.get_active_ifc_path()
|
||||
_active_ifc_path_cache = ifc_path.as_posix() if ifc_path is not None else None
|
||||
|
||||
@classmethod
|
||||
def is_enabled(cls) -> bool:
|
||||
return bool(tool.Blender.get_addon_preferences().autosave_enabled)
|
||||
|
||||
@classmethod
|
||||
def get_interval_seconds(cls) -> float:
|
||||
minutes = tool.Blender.get_addon_preferences().autosave_interval_minutes
|
||||
return max(1.0, float(minutes) * 60.0)
|
||||
|
||||
@classmethod
|
||||
def is_eligible(cls) -> bool:
|
||||
return cls.is_enabled() and tool.Ifc.get() is not None and cls.get_active_ifc_path() is not None
|
||||
|
||||
@classmethod
|
||||
def cancel_timer(cls) -> None:
|
||||
global _timer_callback
|
||||
if _timer_callback is not None and bpy.app.timers.is_registered(_timer_callback):
|
||||
bpy.app.timers.unregister(_timer_callback)
|
||||
_timer_callback = None
|
||||
|
||||
@classmethod
|
||||
def reset_timer(cls) -> None:
|
||||
cls.cancel_timer()
|
||||
cls._update_active_ifc_path_cache()
|
||||
if not cls.is_eligible():
|
||||
return
|
||||
|
||||
def on_timer() -> None:
|
||||
cls._on_timer_expired()
|
||||
return None
|
||||
|
||||
global _timer_callback
|
||||
_timer_callback = on_timer
|
||||
bpy.app.timers.register(on_timer, first_interval=cls.get_interval_seconds())
|
||||
|
||||
@classmethod
|
||||
def _on_timer_expired(cls) -> None:
|
||||
if not cls.is_eligible():
|
||||
return
|
||||
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
bim_props = tool.Blender.get_bim_props()
|
||||
|
||||
if bim_props.is_dirty:
|
||||
if prefs.autosave_mode == "PROMPT":
|
||||
bpy.ops.bim.autosave_prompt("INVOKE_DEFAULT")
|
||||
elif prefs.autosave_mode == "BACKUP":
|
||||
try:
|
||||
cls.perform_backup(bpy.context)
|
||||
except Exception as error:
|
||||
print(f"Bonsai: autosave backup failed: {error}")
|
||||
cls.reset_timer()
|
||||
|
||||
@classmethod
|
||||
def perform_backup(cls, context: bpy.types.Context) -> None:
|
||||
ifc_path = cls.get_active_ifc_path()
|
||||
if ifc_path is None:
|
||||
return
|
||||
|
||||
_, autosaving_path, autosaved_path = cls.get_paths(ifc_path)
|
||||
autosaving_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
tool.Parametric.commit_pending_edits()
|
||||
preview_base.discard_pending_previews(context.scene)
|
||||
|
||||
logger = logging.getLogger("ExportIFC")
|
||||
output_file = autosaving_path.as_posix().replace("\\", "/")
|
||||
settings = export_ifc.IfcExportSettings.factory(context, output_file, logger)
|
||||
export_ifc.IfcExporter(settings).export()
|
||||
|
||||
try:
|
||||
os.replace(autosaving_path, autosaved_path)
|
||||
except OSError:
|
||||
if autosaving_path.is_file():
|
||||
autosaving_path.unlink(missing_ok=True)
|
||||
raise
|
||||
|
||||
@classmethod
|
||||
def get_newer_autosaved_path(cls, ifc_path: Union[str, Path]) -> Union[str, None]:
|
||||
path = Path(ifc_path)
|
||||
if path.suffix.lower() != ".ifc" or not path.is_file():
|
||||
return None
|
||||
|
||||
_, _, autosaved_path = cls.get_paths(path)
|
||||
if not autosaved_path.is_file():
|
||||
return None
|
||||
if autosaved_path.stat().st_mtime > path.stat().st_mtime:
|
||||
return autosaved_path.as_posix().replace("\\", "/")
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def cleanup_stale_autosave(cls) -> None:
|
||||
"""Remove the active IFC's autosave file(s) on a graceful shutdown.
|
||||
|
||||
Registered via `atexit`, which only runs on a normal interpreter
|
||||
shutdown - never on an actual crash. So a deliberate quit (whether
|
||||
the user saved or chose "don't save") clears the recovery file and
|
||||
won't prompt on next startup, while a genuine crash leaves it in
|
||||
place for recovery, since no atexit callbacks fire then.
|
||||
|
||||
Deliberately reads only `_active_ifc_path_cache` - a plain string
|
||||
kept up to date by `reset_timer()` - rather than touching `bpy` here.
|
||||
By the time `atexit` fires, Blender's own C++ side is torn down far
|
||||
enough that even reading `bpy.context.scene` aborts the process
|
||||
(std::bad_optional_access) instead of raising a catchable exception.
|
||||
"""
|
||||
if _active_ifc_path_cache is None:
|
||||
return
|
||||
try:
|
||||
_, autosaving_path, autosaved_path = cls.get_paths(_active_ifc_path_cache)
|
||||
autosaving_path.unlink(missing_ok=True)
|
||||
autosaved_path.unlink(missing_ok=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
atexit.register(Autosave.cleanup_stale_autosave)
|
||||
@@ -23,7 +23,7 @@ import os
|
||||
import re
|
||||
from math import atan, radians
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional, Union, cast
|
||||
from typing import Any, Callable, Optional, Union, cast
|
||||
|
||||
import bmesh
|
||||
import bpy
|
||||
@@ -1073,7 +1073,19 @@ class Loader(bonsai.core.tool.Loader):
|
||||
return mesh
|
||||
|
||||
@classmethod
|
||||
def slice_layerset_mesh(cls, element: ifcopenshell.entity_instance, mesh: bpy.types.Mesh) -> bpy.types.Mesh:
|
||||
def slice_layerset_mesh(
|
||||
cls,
|
||||
element: ifcopenshell.entity_instance,
|
||||
mesh: bpy.types.Mesh,
|
||||
style_to_material: Optional[Callable[[ifcopenshell.entity_instance], Union[bpy.types.Material, None]]] = None,
|
||||
) -> bpy.types.Mesh:
|
||||
"""Bisect a layerset element's mesh at layer boundaries and assign each layer its material style.
|
||||
|
||||
:param style_to_material: Callback resolving an IfcSurfaceStyle to a Blender material.
|
||||
Defaults to the IFC-linked material, which only works for the actively edited project.
|
||||
"""
|
||||
if style_to_material is None:
|
||||
style_to_material = tool.Ifc.get_object
|
||||
if not (material := ifcopenshell.util.element.get_material(element)):
|
||||
return mesh
|
||||
elif material.is_a("IfcMaterialLayerSetUsage"):
|
||||
@@ -1121,7 +1133,8 @@ class Loader(bonsai.core.tool.Loader):
|
||||
continue
|
||||
if (material_index := styles.get(style, None)) is None:
|
||||
material_index = len(mesh.materials)
|
||||
mesh.materials.append(tool.Ifc.get_object(style))
|
||||
mesh.materials.append(style_to_material(style))
|
||||
styles[style] = material_index
|
||||
if i == last_i:
|
||||
for face in bisect_geom["geom"]:
|
||||
if isinstance(face, bmesh.types.BMFace):
|
||||
|
||||
@@ -59,7 +59,6 @@ from ifcopenshell.util.shape_builder import ShapeBuilder, np_to_3d
|
||||
from mathutils import Matrix, Vector
|
||||
|
||||
import bonsai.core.geometry
|
||||
import bonsai.core.model
|
||||
import bonsai.core.tool
|
||||
import bonsai.tool as tool
|
||||
from bonsai.bim import import_ifc
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
@@ -89,10 +90,90 @@ class Project(bonsai.core.tool.Project):
|
||||
else:
|
||||
link.empty_handle = empty
|
||||
|
||||
@classmethod
|
||||
def get_link_cache_paths(cls, filepath: Union[Path, str], query: str, exclude: str = "") -> tuple[Path, Path]:
|
||||
"""Get the (blend, json) cache paths for a linked model's filter.
|
||||
|
||||
Cache files are per-filter so the same IFC file can be linked several
|
||||
times with different include/exclude queries without the caches
|
||||
overwriting each other. An empty filter keeps the legacy un-suffixed
|
||||
names, and an include-only filter keeps the pre-exclude hash so
|
||||
existing caches stay valid.
|
||||
"""
|
||||
filepath = Path(filepath)
|
||||
if not query and not exclude:
|
||||
suffix = ""
|
||||
elif not exclude:
|
||||
suffix = "." + hashlib.md5(query.encode("utf-8")).hexdigest()[:8]
|
||||
else:
|
||||
suffix = "." + hashlib.md5(f"{query}\0{exclude}".encode("utf-8")).hexdigest()[:8]
|
||||
return (
|
||||
filepath.with_suffix(f".ifc.cache{suffix}.blend"),
|
||||
filepath.with_suffix(f".ifc.cache{suffix}.json"),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def encode_link_filter(
|
||||
cls, query: str, exclude: str, loaded: bool = False, display_name: str = ""
|
||||
) -> Union[str, None]:
|
||||
"""Serialize a link's filter and state for IfcDocumentReference.Description.
|
||||
|
||||
A plain include query is stored as-is (backwards compatible); an
|
||||
exclude, a loaded state or a display name promotes the value to a
|
||||
small JSON blob. The loaded flag makes the link auto-load on the
|
||||
next project open.
|
||||
"""
|
||||
if exclude or loaded or display_name:
|
||||
return json.dumps({"include": query, "exclude": exclude, "loaded": loaded, "name": display_name})
|
||||
return query or None
|
||||
|
||||
@classmethod
|
||||
def decode_link_filter(cls, description: Union[str, None]) -> tuple[str, str, bool, str]:
|
||||
"""Get (query, exclude, loaded, display_name) from a Description written by encode_link_filter."""
|
||||
if not description:
|
||||
return "", "", False, ""
|
||||
if description.startswith("{"):
|
||||
try:
|
||||
data = json.loads(description)
|
||||
if isinstance(data, dict):
|
||||
return (
|
||||
data.get("include", "") or "",
|
||||
data.get("exclude", "") or "",
|
||||
bool(data.get("loaded", False)),
|
||||
data.get("name", "") or "",
|
||||
)
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
return description, "", False, ""
|
||||
|
||||
@classmethod
|
||||
def update_linked_models_state(cls) -> None:
|
||||
"""Persist each link's loaded/visible state onto its document reference.
|
||||
|
||||
Called at IFC save time so links that were loaded and visible
|
||||
auto-load the next time the project is opened.
|
||||
"""
|
||||
if not tool.Ifc.get():
|
||||
return
|
||||
for link in cls.get_project_props().links:
|
||||
if not link.ifc_definition_id:
|
||||
continue
|
||||
try:
|
||||
reference = tool.Ifc.get().by_id(link.ifc_definition_id)
|
||||
except RuntimeError:
|
||||
continue
|
||||
if hasattr(reference, "Description"):
|
||||
reference.Description = cls.encode_link_filter(
|
||||
link.query,
|
||||
link.exclude,
|
||||
loaded=link.is_loaded and not link.is_hidden,
|
||||
display_name=link.display_name,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def calculate_link_matrix(cls, link: Link) -> Matrix:
|
||||
filepath = Path(tool.Ifc.resolve_uri(link.filepath))
|
||||
with open(filepath.with_suffix(".ifc.cache.json"), "r") as f:
|
||||
with open(cls.get_link_cache_paths(filepath, link.query, link.exclude)[1], "r") as f:
|
||||
metadata = json.load(f)
|
||||
|
||||
rot = ifcopenshell.util.shape_builder.np_rotation_matrix(
|
||||
@@ -117,6 +198,86 @@ class Project(bonsai.core.tool.Project):
|
||||
local_matrix[:, 3][:3] = [float(o) for o in gprops.model_origin_si.split(",")]
|
||||
return Matrix(np.linalg.inv(local_matrix) @ global_matrix)
|
||||
|
||||
@classmethod
|
||||
def get_link_transformation_matrix(cls, link: Link) -> Union[npt.NDArray[np.float64], None]:
|
||||
"""Get the link's saved 4x4 transformation in model coordinates, or None when identity."""
|
||||
if tool.Ifc.get():
|
||||
transformation = tool.Ifc.get().by_id(link.ifc_definition_id)[1] # Identification
|
||||
else:
|
||||
transformation = link.transformation
|
||||
if not transformation:
|
||||
return None
|
||||
matrix = np.fromstring(transformation, sep=",", dtype=np.float64).reshape(4, 4)
|
||||
if np.allclose(matrix, np.eye(4)):
|
||||
return None
|
||||
return matrix
|
||||
|
||||
@classmethod
|
||||
def calculate_link_delta_matrix(cls, link: Link) -> Matrix:
|
||||
"""Get the matrix mapping the link's unmoved world positions to its moved ones.
|
||||
|
||||
Returns identity when the link has no saved transformation.
|
||||
"""
|
||||
if tool.Ifc.get():
|
||||
transformation = tool.Ifc.get().by_id(link.ifc_definition_id)[1] # Identification
|
||||
else:
|
||||
transformation = link.transformation
|
||||
|
||||
if not transformation:
|
||||
return Matrix.Identity(4)
|
||||
transformation = np.fromstring(transformation, sep=",", dtype=np.float64).reshape(4, 4)
|
||||
if np.allclose(transformation, np.eye(4)):
|
||||
return Matrix.Identity(4)
|
||||
|
||||
gprops = tool.Georeference.get_georeference_props()
|
||||
rot = ifcopenshell.util.shape_builder.np_rotation_matrix(radians(-float(gprops.model_project_north)), 4, "Z")
|
||||
local_matrix = rot @ np.eye(4)
|
||||
local_matrix[:, 3][:3] = [float(o) for o in gprops.model_origin_si.split(",")]
|
||||
|
||||
# Link empty matrix is inv(local) @ transformation @ global (see
|
||||
# calculate_link_matrix), so moved = inv(local) @ T @ local @ unmoved.
|
||||
return Matrix(np.linalg.inv(local_matrix) @ transformation @ local_matrix)
|
||||
|
||||
@classmethod
|
||||
def save_link_transformation(cls, link: Link) -> None:
|
||||
"""Persist the link handle's current world matrix as the link's saved transformation."""
|
||||
obj = cls.get_link_empty_handle(link)
|
||||
assert obj
|
||||
new_obj_matrix = np.array(obj.matrix_world)
|
||||
|
||||
filepath = Path(tool.Ifc.resolve_uri(link.filepath))
|
||||
with open(cls.get_link_cache_paths(filepath, link.query, link.exclude)[1], "r") as f:
|
||||
metadata = json.load(f)
|
||||
|
||||
rot = ifcopenshell.util.shape_builder.np_rotation_matrix(
|
||||
radians(-float(metadata["model_project_north"])), 4, "Z"
|
||||
)
|
||||
global_matrix = rot @ np.eye(4)
|
||||
global_matrix[:, 3][:3] = [float(o) for o in metadata["model_origin_si"].split(",")]
|
||||
|
||||
gprops = tool.Georeference.get_georeference_props()
|
||||
rot = ifcopenshell.util.shape_builder.np_rotation_matrix(radians(-float(gprops.model_project_north)), 4, "Z")
|
||||
local_matrix = rot @ np.eye(4)
|
||||
local_matrix[:, 3][:3] = [float(o) for o in gprops.model_origin_si.split(",")]
|
||||
|
||||
# obj_matrix is typically calculated as:
|
||||
# obj_matrix = np.linalg.inv(local_matrix) @ transformation @ global_matrix
|
||||
identity_blender_matrix = np.linalg.inv(local_matrix) @ global_matrix
|
||||
if np.allclose(new_obj_matrix, identity_blender_matrix, atol=1e-5):
|
||||
link.has_transformation = False
|
||||
transformation = ",".join(map(str, np.eye(4).reshape(-1)))
|
||||
else:
|
||||
transformed_global_matrix = local_matrix @ new_obj_matrix
|
||||
transformation = transformed_global_matrix @ np.linalg.inv(global_matrix)
|
||||
link.has_transformation = True
|
||||
transformation = ",".join(map(str, transformation.reshape(-1)))
|
||||
|
||||
if tool.Ifc.get():
|
||||
reference = tool.Ifc.get().by_id(link.ifc_definition_id)
|
||||
reference[1] = transformation
|
||||
else:
|
||||
link.transformation = transformation
|
||||
|
||||
@classmethod
|
||||
def append_all_types_from_template(cls, template: str) -> None:
|
||||
# TODO refactor
|
||||
@@ -309,11 +470,17 @@ class Project(bonsai.core.tool.Project):
|
||||
|
||||
@classmethod
|
||||
def get_linked_models_documents(cls) -> dict[str, ifcopenshell.entity_instance]:
|
||||
"""Get linked model documents keyed by resolved absolute filepath (posix form).
|
||||
|
||||
Locations are stored either relative or absolute depending on how the
|
||||
link was created - resolving before keying ensures both forms of the
|
||||
same file match one document.
|
||||
"""
|
||||
linked_docs = {}
|
||||
for doc in tool.Ifc.get().by_type("IfcDocumentInformation"):
|
||||
if doc.Scope == "LINKED_MODEL":
|
||||
for reference in tool.Drawing.get_document_references(doc):
|
||||
linked_docs[Path(reference.Location).as_posix()] = doc
|
||||
linked_docs[Path(tool.Ifc.resolve_uri(reference.Location)).as_posix()] = doc
|
||||
break
|
||||
return linked_docs
|
||||
|
||||
@@ -321,27 +488,52 @@ class Project(bonsai.core.tool.Project):
|
||||
def load_linked_models_from_ifc(cls) -> None:
|
||||
links = tool.Project.get_project_props().links
|
||||
links.clear()
|
||||
references: list[ifcopenshell.entity_instance] = []
|
||||
for doc in tool.Ifc.get().by_type("IfcDocumentInformation"):
|
||||
if doc.Scope != "LINKED_MODEL":
|
||||
continue
|
||||
for reference in tool.Drawing.get_document_references(doc):
|
||||
filepath = reference.Location
|
||||
link = links.add()
|
||||
link.name = filepath
|
||||
link.filepath = filepath
|
||||
link.ifc_definition_id = reference.id()
|
||||
link.has_transformation = False
|
||||
if reference[1]:
|
||||
m = np.fromstring(reference[1], sep=",", dtype=np.float64).reshape(4, 4)
|
||||
link.has_transformation = not np.allclose(m, np.eye(4))
|
||||
# The selector query used at link time is persisted only in the
|
||||
# sidecar cache JSON; restore it so Reload/Load replay the filter.
|
||||
references.extend(tool.Drawing.get_document_references(doc))
|
||||
location_counts: defaultdict[str, int] = defaultdict(int)
|
||||
for reference in references:
|
||||
location_counts[reference.Location] += 1
|
||||
autoload_indices: list[int] = []
|
||||
for reference in references:
|
||||
filepath = reference.Location
|
||||
link = links.add()
|
||||
link.name = filepath
|
||||
link.filepath = filepath
|
||||
link.ifc_definition_id = reference.id()
|
||||
link.has_transformation = False
|
||||
if reference[1]:
|
||||
m = np.fromstring(reference[1], sep=",", dtype=np.float64).reshape(4, 4)
|
||||
link.has_transformation = not np.allclose(m, np.eye(4))
|
||||
# The selector filter used at link time is persisted per
|
||||
# reference in its Description (IFC4+); restore it so
|
||||
# Reload/Load replay the filter.
|
||||
query, exclude, loaded, display_name = cls.decode_link_filter(getattr(reference, "Description", None))
|
||||
if not query and not exclude and location_counts[filepath] == 1:
|
||||
# Fall back to the legacy sidecar cache JSON where older
|
||||
# versions persisted the query. Only unambiguous: with
|
||||
# several links to one file the shared JSON can't say
|
||||
# which link it belonged to.
|
||||
json_filepath = Path(tool.Ifc.resolve_uri(filepath)).with_suffix(".ifc.cache.json")
|
||||
if json_filepath.exists():
|
||||
try:
|
||||
link.query = json.loads(json_filepath.read_text()).get("query", "")
|
||||
query = json.loads(json_filepath.read_text()).get("query", "")
|
||||
except (OSError, json.JSONDecodeError):
|
||||
pass
|
||||
link.query = query
|
||||
link.exclude = exclude
|
||||
link.display_name = display_name
|
||||
if loaded:
|
||||
autoload_indices.append(len(links) - 1)
|
||||
|
||||
# Links that were loaded and visible at save time load automatically.
|
||||
for i in autoload_indices:
|
||||
if not Path(tool.Ifc.resolve_uri(links[i].filepath)).exists():
|
||||
print(f"WARNING: Not auto-loading missing linked model: {links[i].filepath}")
|
||||
continue
|
||||
bpy.ops.bim.load_link(link_index=i)
|
||||
|
||||
@classmethod
|
||||
def get_project_library_elements(
|
||||
@@ -858,9 +1050,16 @@ class Project(bonsai.core.tool.Project):
|
||||
|
||||
selected_vertices = [obj.matrix_world @ mesh.vertices[vi].co for vi in vert_map]
|
||||
for polygon in guid_polygons:
|
||||
selected_tris.append(tuple(vert_map[vi] for vi in polygon.vertices))
|
||||
selected_edges.extend(tuple([vert_map[vi] for vi in e]) for e in polygon.edge_keys)
|
||||
|
||||
# Polygons are not necessarily triangles (e.g. layerset-sliced
|
||||
# meshes contain ngons), so triangles come from the loop triangles.
|
||||
mesh.calc_loop_triangles()
|
||||
polygon_range = range(*slice_.indices(len(mesh.polygons)))
|
||||
for tri in mesh.loop_triangles:
|
||||
if tri.polygon_index in polygon_range:
|
||||
selected_tris.append(tuple(vert_map[vi] for vi in tri.vertices))
|
||||
|
||||
obj["selected_vertices"] = selected_vertices
|
||||
obj["selected_edges"] = selected_edges
|
||||
obj["selected_tris"] = selected_tris
|
||||
@@ -897,11 +1096,9 @@ class Project(bonsai.core.tool.Project):
|
||||
from bonsai.bim.module.project.data import LinksData
|
||||
from bonsai.bim.module.project.decorator import ProjectDecorator
|
||||
|
||||
# Not sure if there's a difference between `instance_matrix` coming from `ray_cast`
|
||||
# and usual `matrix_world`, maybe we can just get it from object always.
|
||||
if instance_matrix is None:
|
||||
instance_matrix = obj.matrix_world
|
||||
|
||||
# `instance_matrix` is the world matrix of the hit collection instance
|
||||
# from `ray_cast` (link empty matrix included). Without it, the root
|
||||
# empty is resolved as the collection's only instance.
|
||||
cls.deselect_queried_linked_element()
|
||||
cls.set_queried_linked_element(obj, guid, instance_matrix)
|
||||
cls.select_linked_element_geom(obj, guid)
|
||||
@@ -958,7 +1155,7 @@ class Project(bonsai.core.tool.Project):
|
||||
ProjectDecorator.install(context)
|
||||
|
||||
@classmethod
|
||||
def set_queried_linked_element(cls, obj: bpy.types.Object, guid: str, instance_matrix: Matrix) -> None:
|
||||
def set_queried_linked_element(cls, obj: bpy.types.Object, guid: str, instance_matrix: Matrix | None) -> None:
|
||||
props = tool.Project.get_project_props()
|
||||
props.queried_obj = obj
|
||||
props.queried_obj_root = cls.find_obj_root(obj, instance_matrix)
|
||||
@@ -977,17 +1174,22 @@ class Project(bonsai.core.tool.Project):
|
||||
del obj[field]
|
||||
|
||||
@classmethod
|
||||
def find_obj_root(cls, obj: bpy.types.Object, matrix: Matrix) -> bpy.types.Object | None:
|
||||
def find_obj_root(cls, obj: bpy.types.Object, matrix: Matrix | None) -> bpy.types.Object | None:
|
||||
collections = set(obj.users_collection)
|
||||
for o in bpy.data.objects:
|
||||
if (
|
||||
o.type != "EMPTY"
|
||||
or o.instance_type != "COLLECTION"
|
||||
or o.instance_collection not in collections
|
||||
or not np.allclose(matrix, o.matrix_world, atol=1e-4)
|
||||
):
|
||||
continue
|
||||
return o
|
||||
candidates = [
|
||||
o
|
||||
for o in bpy.data.objects
|
||||
if o.type == "EMPTY" and o.instance_type == "COLLECTION" and o.instance_collection in collections
|
||||
]
|
||||
if matrix is not None:
|
||||
# `matrix` is the instance's world matrix - the instancing
|
||||
# empty's matrix combined with the object's own local matrix
|
||||
# (non-identity for instanced occurrence objects).
|
||||
for o in candidates:
|
||||
if np.allclose(matrix, np.array(o.matrix_world) @ np.array(obj.matrix_world), atol=1e-4):
|
||||
return o
|
||||
if len(candidates) == 1:
|
||||
return candidates[0]
|
||||
|
||||
class SelectedGeometry(NamedTuple):
|
||||
selected_vertices: list[tuple[float, float, float]]
|
||||
@@ -996,8 +1198,11 @@ class Project(bonsai.core.tool.Project):
|
||||
|
||||
@classmethod
|
||||
def get_selected_geometry(cls, obj: bpy.types.Object) -> SelectedGeometry:
|
||||
# ID properties are returned as IDPropertyArrays (the whole
|
||||
# property when empty, the items otherwise), which the GPU module
|
||||
# rejects as batch indices - convert to plain tuples.
|
||||
return cls.SelectedGeometry(
|
||||
obj["selected_vertices"],
|
||||
obj["selected_edges"],
|
||||
obj["selected_tris"],
|
||||
[tuple(v) for v in obj["selected_vertices"]],
|
||||
[tuple(e) for e in obj["selected_edges"]],
|
||||
[tuple(t) for t in obj["selected_tris"]],
|
||||
)
|
||||
|
||||
@@ -35,7 +35,6 @@ from unittest.mock import Mock, patch
|
||||
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.pset
|
||||
import pytest
|
||||
|
||||
import bonsai.tool as tool
|
||||
|
||||
@@ -37,8 +37,6 @@ from unittest.mock import patch
|
||||
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.util.element
|
||||
import pytest
|
||||
|
||||
import bonsai.tool as tool
|
||||
|
||||
@@ -146,9 +146,7 @@ def test_fit_flow_segments_with_single_segment_dispatches_obstruction():
|
||||
mep.tool.Model, "get_flow_segment_profile", return_value=segment_profile
|
||||
), patch.object(mep.MEPAddObstruction, "_execute", return_value=None) as obstruction, patch.object(
|
||||
mep.MEPAddBend, "_execute", return_value=None
|
||||
) as bend, patch.object(
|
||||
mep.MEPAddTransition, "_execute", return_value=None
|
||||
) as transition:
|
||||
) as bend, patch.object(mep.MEPAddTransition, "_execute", return_value=None) as transition:
|
||||
mep.FitFlowSegments._execute(op, context=context)
|
||||
|
||||
assert obstruction.call_count == 1
|
||||
@@ -180,9 +178,7 @@ def test_fit_flow_segments_refuses_mixed_pipe_and_duct():
|
||||
mep.tool.Model, "get_flow_segment_profile", return_value=profile
|
||||
), patch.object(mep.MEPAddObstruction, "_execute", return_value=None) as obstruction, patch.object(
|
||||
mep.MEPAddBend, "_execute", return_value=None
|
||||
) as bend, patch.object(
|
||||
mep.MEPAddTransition, "_execute", return_value=None
|
||||
) as transition:
|
||||
) as bend, patch.object(mep.MEPAddTransition, "_execute", return_value=None) as transition:
|
||||
mep.FitFlowSegments._execute(op, context=context)
|
||||
|
||||
obstruction.assert_not_called()
|
||||
|
||||
@@ -173,9 +173,8 @@ def test_gizmo_group_class_wiring(gizmo_cls_name, bl_idname, is_element_predicat
|
||||
predicate = getattr(tool.Parametric, is_element_predicate)
|
||||
fake_element = Mock()
|
||||
fake_element.is_a.return_value = True
|
||||
with (
|
||||
patch.object(tool.Parametric, is_element_predicate, side_effect=predicate) as p,
|
||||
patch.object(tool.System, "has_parametric_body", return_value=True),
|
||||
with patch.object(tool.Parametric, is_element_predicate, side_effect=predicate) as p, patch.object(
|
||||
tool.System, "has_parametric_body", return_value=True
|
||||
):
|
||||
cls.is_element_type(fake_element)
|
||||
assert p.called, f"{gizmo_cls_name}.is_element_type did not delegate to Parametric.{is_element_predicate}"
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
# Bonsai - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2026
|
||||
#
|
||||
# This file is part of Bonsai.
|
||||
#
|
||||
# Bonsai is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Bonsai is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# This file was generated with the assistance of an AI coding tool.
|
||||
|
||||
import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from bonsai.tool.autosave import AUTOSAVED_SUFFIX, AUTOSAVING_SUFFIX, Autosave
|
||||
|
||||
pytestmark = pytest.mark.project
|
||||
|
||||
|
||||
class TestAutosavePaths:
|
||||
def test_get_paths_for_ifc_file(self):
|
||||
main_path, autosaving_path, autosaved_path = Autosave.get_paths("/tmp/myfile.ifc")
|
||||
assert main_path == Path("/tmp/myfile.ifc")
|
||||
assert autosaving_path == Path(f"/tmp/myfile{AUTOSAVING_SUFFIX}")
|
||||
assert autosaved_path == Path(f"/tmp/myfile{AUTOSAVED_SUFFIX}")
|
||||
|
||||
def test_get_newer_autosaved_path_when_missing(self, tmp_path):
|
||||
ifc_path = tmp_path / "myfile.ifc"
|
||||
ifc_path.write_text("ifc")
|
||||
assert Autosave.get_newer_autosaved_path(ifc_path) is None
|
||||
|
||||
def test_get_newer_autosaved_path_when_older(self, tmp_path):
|
||||
ifc_path = tmp_path / "myfile.ifc"
|
||||
autosaved_path = tmp_path / f"myfile{AUTOSAVED_SUFFIX}"
|
||||
ifc_path.write_text("ifc")
|
||||
autosaved_path.write_text("autosaved")
|
||||
past = time.time() - 10
|
||||
os.utime(ifc_path, (past, past))
|
||||
os.utime(autosaved_path, (time.time(), time.time()))
|
||||
assert Autosave.get_newer_autosaved_path(ifc_path) == autosaved_path.as_posix()
|
||||
|
||||
def test_get_newer_autosaved_path_when_not_newer(self, tmp_path):
|
||||
ifc_path = tmp_path / "myfile.ifc"
|
||||
autosaved_path = tmp_path / f"myfile{AUTOSAVED_SUFFIX}"
|
||||
ifc_path.write_text("ifc")
|
||||
autosaved_path.write_text("autosaved")
|
||||
now = time.time()
|
||||
os.utime(ifc_path, (now, now))
|
||||
past = now - 10
|
||||
os.utime(autosaved_path, (past, past))
|
||||
assert Autosave.get_newer_autosaved_path(ifc_path) is None
|
||||
|
||||
def test_get_newer_autosaved_path_ignores_non_ifc(self, tmp_path):
|
||||
path = tmp_path / "myfile.ifczip"
|
||||
path.write_text("zip")
|
||||
assert Autosave.get_newer_autosaved_path(path) is None
|
||||
@@ -139,5 +139,6 @@ def test_every_cancel_ops_entry_has_a_real_preview_propertygroup() -> None:
|
||||
orphaned = [attr for attr, _op in preview_base.PREVIEW_CANCEL_OPS if attr not in declared_attrs]
|
||||
assert not orphaned, (
|
||||
"PREVIEW_CANCEL_OPS contains entries whose PointerProperty child no longer "
|
||||
f"exists on {UMBRELLA_CLASS}. Drop the stale tuple(s):\n " + "\n ".join(orphaned)
|
||||
f"exists on {UMBRELLA_CLASS}. Drop the stale tuple(s):\n "
|
||||
+ "\n ".join(orphaned)
|
||||
)
|
||||
|
||||
@@ -24,7 +24,6 @@ import time
|
||||
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import pytest
|
||||
|
||||
from bonsai import tool as tool
|
||||
|
||||
@@ -23,7 +23,6 @@ import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.geometry
|
||||
import ifcopenshell.api.material
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.api.root
|
||||
import ifcopenshell.api.style
|
||||
import ifcopenshell.api.type
|
||||
|
||||
@@ -501,3 +501,72 @@ class TestGettingLinkedElementGeomSlice:
|
||||
obj = cast(bpy.types.Object, obj)
|
||||
slice_ = subject.Link.get_linked_element_geom_slice(obj, "aaa")
|
||||
assert range(15)[slice_] == range(5)
|
||||
|
||||
|
||||
class TestEncodeDecodeLinkFilter:
|
||||
def test_plain_include_round_trip(self):
|
||||
assert subject.encode_link_filter("IfcWall", "") == "IfcWall"
|
||||
assert subject.decode_link_filter("IfcWall") == ("IfcWall", "", False, "")
|
||||
|
||||
def test_empty_filter_encodes_to_none(self):
|
||||
assert subject.encode_link_filter("", "") is None
|
||||
assert subject.decode_link_filter(None) == ("", "", False, "")
|
||||
assert subject.decode_link_filter("") == ("", "", False, "")
|
||||
|
||||
def test_exclude_promotes_to_json(self):
|
||||
encoded = subject.encode_link_filter('IfcElement, group="X"', 'IfcSlab, parent="Y"')
|
||||
assert encoded.startswith("{")
|
||||
assert subject.decode_link_filter(encoded) == ('IfcElement, group="X"', 'IfcSlab, parent="Y"', False, "")
|
||||
|
||||
def test_loaded_promotes_to_json(self):
|
||||
encoded = subject.encode_link_filter("IfcWall", "", loaded=True)
|
||||
assert encoded.startswith("{")
|
||||
assert subject.decode_link_filter(encoded) == ("IfcWall", "", True, "")
|
||||
|
||||
def test_loaded_without_filter(self):
|
||||
encoded = subject.encode_link_filter("", "", loaded=True)
|
||||
assert subject.decode_link_filter(encoded) == ("", "", True, "")
|
||||
|
||||
def test_legacy_non_json_decodes_as_include(self):
|
||||
legacy = 'IfcElement, location="House - Type B"'
|
||||
assert subject.decode_link_filter(legacy) == (legacy, "", False, "")
|
||||
|
||||
def test_malformed_json_decodes_as_include(self):
|
||||
assert subject.decode_link_filter("{not json") == ("{not json", "", False, "")
|
||||
|
||||
def test_display_name_promotes_to_json(self):
|
||||
encoded = subject.encode_link_filter("IfcWall", "", display_name="North Wing")
|
||||
assert encoded.startswith("{")
|
||||
assert subject.decode_link_filter(encoded) == ("IfcWall", "", False, "North Wing")
|
||||
|
||||
|
||||
class TestGetLinkCachePaths:
|
||||
def test_empty_filter_keeps_legacy_names(self):
|
||||
blend, json_ = subject.get_link_cache_paths("/x/File A.ifc", "")
|
||||
assert blend.name == "File A.ifc.cache.blend"
|
||||
assert json_.name == "File A.ifc.cache.json"
|
||||
|
||||
def test_include_only_hash_matches_pre_exclude_formula(self):
|
||||
# Existing caches were keyed by md5(query)[:8]; they must stay valid.
|
||||
import hashlib
|
||||
|
||||
blend, _ = subject.get_link_cache_paths("/x/File A.ifc", "IfcWall")
|
||||
expected = hashlib.md5(b"IfcWall").hexdigest()[:8]
|
||||
assert blend.name == f"File A.ifc.cache.{expected}.blend"
|
||||
|
||||
def test_blend_and_json_share_a_suffix(self):
|
||||
blend, json_ = subject.get_link_cache_paths("/x/File A.ifc", "IfcWall", "IfcDoor")
|
||||
assert blend.name.removesuffix("blend") == json_.name.removesuffix("json")
|
||||
|
||||
def test_same_include_different_exclude_do_not_collide(self):
|
||||
# The reason the cache key hashes both strings: same-include links
|
||||
# with different excludes must not serve each other's geometry.
|
||||
a, _ = subject.get_link_cache_paths("/x/f.ifc", "IfcElement", "IfcSlab")
|
||||
b, _ = subject.get_link_cache_paths("/x/f.ifc", "IfcElement", "IfcDoor")
|
||||
c, _ = subject.get_link_cache_paths("/x/f.ifc", "IfcElement", "")
|
||||
assert len({a.name, b.name, c.name}) == 3
|
||||
|
||||
def test_exclude_only_distinct_from_empty_filter(self):
|
||||
a, _ = subject.get_link_cache_paths("/x/f.ifc", "", "IfcDoor")
|
||||
b, _ = subject.get_link_cache_paths("/x/f.ifc", "", "")
|
||||
assert a.name != b.name
|
||||
|
||||
@@ -57,8 +57,7 @@ class CsvHeader(TypedDict):
|
||||
|
||||
# Formula
|
||||
Formula: NotRequired[str]
|
||||
# QuantityClass: NotRequired[str]
|
||||
|
||||
#QuantityClass: NotRequired[str]
|
||||
|
||||
# Currently we assume that if column is not part of the main header,
|
||||
# then it is a cost value category. So here we list any additional column
|
||||
@@ -98,8 +97,7 @@ class CostItem(TypedDict):
|
||||
Query: Union[str, None]
|
||||
|
||||
Formula: Union[str, None]
|
||||
# QuantityClass: Union[str, None]
|
||||
|
||||
#QuantityClass: Union[str, None]
|
||||
|
||||
class Csv2Ifc:
|
||||
# Inputs.
|
||||
@@ -422,7 +420,7 @@ class Csv2Ifc:
|
||||
products=results,
|
||||
formula=cost_item["Formula"],
|
||||
ifc_class=ifc_quantity_class,
|
||||
)
|
||||
)
|
||||
|
||||
self.create_cost_items(cost_item["children"], cost_item["ifc"])
|
||||
|
||||
|
||||
@@ -252,10 +252,6 @@ int main(int argc, char** argv) {
|
||||
("stderr-progress", "output progress to stderr stream")
|
||||
("yes,y", "answer 'yes' automatically to possible confirmation queries (e.g. overwriting an existing output file)")
|
||||
("no-progress", "suppress possible progress bar type of prints that use carriage return")
|
||||
("fail-on-error", "return a non-zero exit code when one or more errors were logged during "
|
||||
"geometry conversion (e.g. an element failed to convert). By default IfcConvert exits "
|
||||
"successfully as long as an output file could be written, even if some elements were "
|
||||
"silently dropped. Enable this flag so scripts and CI can detect partial conversions.")
|
||||
("log-format", po::value<std::string>(&log_format), "log format: plain or json")
|
||||
("log-file", new po::typed_value<path_t, char_t>(&log_file), "redirect log output to file");
|
||||
|
||||
@@ -453,7 +449,6 @@ int main(int argc, char** argv) {
|
||||
|
||||
const bool mmap = vmap.count("mmap") != 0;
|
||||
const bool no_progress = vmap.count("no-progress") != 0;
|
||||
const bool fail_on_error = vmap.count("fail-on-error") != 0;
|
||||
const bool quiet = vmap.count("quiet") != 0;
|
||||
const bool stderr_progress = vmap.count("stderr-progress") != 0;
|
||||
|
||||
@@ -890,7 +885,6 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
|
||||
if (!serializer->ready()) {
|
||||
logger.Error("SYS", 25, "Unable to open output file '" + IfcUtil::path::to_utf8(output_filename) + "' for writing; check that the directory exists and is writable");
|
||||
IfcUtil::path::delete_file(IfcUtil::path::to_utf8(output_temp_filename));
|
||||
write_log(!quiet);
|
||||
return EXIT_FAILURE;
|
||||
@@ -1226,11 +1220,6 @@ int main(int argc, char** argv) {
|
||||
successful = false;
|
||||
}
|
||||
|
||||
if (fail_on_error && logger.MaxSeverity() >= Logger::LOG_ERROR) {
|
||||
logger.Error("SYS", 26, "Errors encountered during processing, failing due to --fail-on-error.");
|
||||
successful = false;
|
||||
}
|
||||
|
||||
if (logger.Verbosity() == Logger::LOG_PERF) {
|
||||
logger.PrintPerformanceStats();
|
||||
}
|
||||
|
||||
@@ -361,8 +361,8 @@ namespace ifcopenshell {
|
||||
|
||||
struct CircleSegments : public SettingBase<CircleSegments, int> {
|
||||
static constexpr const char* const name = "circle-segments";
|
||||
static constexpr const char* const description = "Number of segments to approximate full circles in the CGAL kernel. When 0 (the default) the segment count is derived from mesher-linear-deflection instead, so curves stay within the deflection tolerance regardless of radius.";
|
||||
static constexpr int defaultvalue = 0;
|
||||
static constexpr const char* const description = "Number of segments to approximate full circles in CGAL kernel.";
|
||||
static constexpr int defaultvalue = 16;
|
||||
};
|
||||
|
||||
struct CgalSmoothAngleDegrees : public SettingBase<CgalSmoothAngleDegrees, double> {
|
||||
|
||||
@@ -391,11 +391,6 @@ namespace {
|
||||
}
|
||||
};
|
||||
|
||||
// Representative radius used to size the polygonal approximation of a conic.
|
||||
// For an ellipse the larger semi-axis is the conservative choice.
|
||||
inline double conic_radius(const taxonomy::circle::ptr& c) { return c->radius; }
|
||||
inline double conic_radius(const taxonomy::ellipse::ptr& e) { return e->radius > e->radius2 ? e->radius : e->radius2; }
|
||||
|
||||
struct cgal_curve_creation_visitor {
|
||||
Settings& settings_;
|
||||
parameter_range param;
|
||||
@@ -430,36 +425,7 @@ namespace {
|
||||
if (b <= a) {
|
||||
b += 2 * M_PI;
|
||||
}
|
||||
const double span = std::fabs(a - b);
|
||||
// CircleSegments controls how conics (circles, ellipses, arcs) are approximated
|
||||
// in the CGAL kernel. Two modes, one or the other:
|
||||
// - CircleSegments == 0 (the default): the segment count is derived from
|
||||
// MesherLinearDeflection, so the chord deviation stays within the mesher's
|
||||
// linear deflection regardless of radius. This matches the deflection based
|
||||
// meshing the OpenCascade kernel already does and fixes issue #8051, where
|
||||
// large radius arcs (curved curtain wall mullions) collapsed to straight chords
|
||||
// because a fixed segment count is radius agnostic.
|
||||
// - CircleSegments > 0: it is used directly as the number of segments for a full
|
||||
// circle, giving deterministic, radius independent output.
|
||||
int num_segments;
|
||||
const int circle_segments = settings_.get<settings::CircleSegments>().get();
|
||||
if (circle_segments > 0) {
|
||||
num_segments = (int)std::ceil(span / (2 * M_PI) * circle_segments);
|
||||
} else {
|
||||
const double radius = conic_radius(t);
|
||||
const double deflection = settings_.get<settings::MesherLinearDeflection>().get();
|
||||
if (deflection > 0. && radius > deflection) {
|
||||
const double max_segment_angle = 2.0 * std::acos(1.0 - deflection / radius);
|
||||
num_segments = (int)std::ceil(span / max_segment_angle);
|
||||
} else {
|
||||
// Radius within the deflection tolerance (or no deflection set): a chord per
|
||||
// quarter turn already keeps the deviation within tolerance.
|
||||
num_segments = (int)std::ceil(span / (M_PI / 2.));
|
||||
}
|
||||
}
|
||||
if (num_segments < 1) {
|
||||
num_segments = 1;
|
||||
}
|
||||
int num_segments = (int)std::ceil(std::fabs(a - b) / (2 * M_PI) * settings_.get<settings::CircleSegments>().get());
|
||||
double du = (b - a) / num_segments;
|
||||
taxonomy::point3 P;
|
||||
// @nb for loop is not inclusive of the both end points
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
#include <ShapeFix_Shape.hxx>
|
||||
#include <ShapeFix_ShapeTolerance.hxx>
|
||||
#include <BRep_Tool.hxx>
|
||||
#include <BRepExtrema_DistShapeShape.hxx>
|
||||
|
||||
#include <Standard_Macro.hxx>
|
||||
#include <TopoDS_Shape.hxx>
|
||||
@@ -357,27 +356,6 @@ bool OpenCascadeKernel::convert(const taxonomy::face::ptr face, TopoDS_Shape& re
|
||||
return false;
|
||||
}
|
||||
|
||||
// #527: A face whose inner boundary intersects the outer boundary (or
|
||||
// another inner boundary) is invalid per the schema. Open Cascade heals or
|
||||
// drops such a face silently, so the intended hole is lost with no
|
||||
// diagnostic. The distance between two non-intersecting loops is strictly
|
||||
// positive; a distance at (or below) the modelling precision means the
|
||||
// boundaries touch or cross. Emit a clear warning so the invalid input is
|
||||
// not silently lost. wires() is ordered outer-first, inner-bounds after.
|
||||
if (fd.wires().size() > 1) {
|
||||
const auto& fwires = fd.wires();
|
||||
bool reported = false;
|
||||
for (size_t i = 1; i < fwires.size() && !reported; ++i) {
|
||||
for (size_t j = 0; j < i && !reported; ++j) {
|
||||
BRepExtrema_DistShapeShape dss(fwires[i], fwires[j]);
|
||||
if (dss.IsDone() && dss.Value() < precision_) {
|
||||
logger().Warning("GEO", 402, "Face inner boundary intersects another face boundary", face->instance);
|
||||
reported = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fd.surface().IsNull()) {
|
||||
// Use the first wire to find a plane manually for polygonal wires
|
||||
const TopoDS_Wire& wire = fd.wires().front();
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* *
|
||||
* IfcOpenShell is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the Lesser GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3.0 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* IfcOpenShell is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* Lesser GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
#include "mapping.h"
|
||||
#define mapping POSTFIX_SCHEMA(mapping)
|
||||
using namespace ifcopenshell::geometry;
|
||||
|
||||
#include "../profile_helper.h"
|
||||
|
||||
// In IFC2X3 IfcAsymmetricIShapeProfileDef is a subtype of IfcIShapeProfileDef and is
|
||||
// therefore dispatched (and handled) by the IfcIShapeProfileDef mapping. From IFC4
|
||||
// onwards it is a standalone subtype of IfcParameterizedProfileDef with its own
|
||||
// Bottom*/Top* attributes, so nothing mapped it and the extrusion came out empty.
|
||||
// The presence of the standalone BottomFlangeWidth attribute is the discriminator:
|
||||
// it is only defined in the schemas where the type is standalone (IFC4 / IFC4X3).
|
||||
#ifdef SCHEMA_IfcAsymmetricIShapeProfileDef_HAS_BottomFlangeWidth
|
||||
|
||||
taxonomy::ptr mapping::map_impl(const IfcSchema::IfcAsymmetricIShapeProfileDef* inst) {
|
||||
// Bottom flange (half width), overall depth (half), web (half thickness).
|
||||
const double xb = inst->BottomFlangeWidth() / 2.0 * length_unit_;
|
||||
const double xt = inst->TopFlangeWidth() / 2.0 * length_unit_;
|
||||
const double y = inst->OverallDepth() / 2.0 * length_unit_;
|
||||
const double d1 = inst->WebThickness() / 2.0 * length_unit_;
|
||||
|
||||
// Bottom flange thickness; top flange thickness defaults to the bottom one.
|
||||
const double ftb = inst->BottomFlangeThickness() * length_unit_;
|
||||
const double ftt = inst->TopFlangeThickness().get_value_or(inst->BottomFlangeThickness()) * length_unit_;
|
||||
|
||||
// Optional fillet radii (web/flange transition) and flange edge radii.
|
||||
const double fb = inst->BottomFlangeFilletRadius().get_value_or(0.) * length_unit_;
|
||||
const double ft_top = inst->TopFlangeFilletRadius().get_value_or(0.) * length_unit_;
|
||||
const double feb = inst->BottomFlangeEdgeRadius().get_value_or(0.) * length_unit_;
|
||||
const double fet = inst->TopFlangeEdgeRadius().get_value_or(0.) * length_unit_;
|
||||
|
||||
// Optional flange slopes: the inner edge of the flange rises towards the web.
|
||||
const double bottomSlope = inst->BottomFlangeSlope().get_value_or(0.) * angle_unit_;
|
||||
const double topSlope = inst->TopFlangeSlope().get_value_or(0.) * angle_unit_;
|
||||
const double dyb = (xb - d1) * tan(bottomSlope);
|
||||
const double dyt = (xt - d1) * tan(topSlope);
|
||||
|
||||
const double tol = settings_.get<settings::Precision>().get();
|
||||
|
||||
if (xb < tol || xt < tol || y < tol || d1 < tol || ftb < tol || ftt < tol) {
|
||||
logger_.Message(Logger::LOG_NOTICE, "GEO", 264, "Skipping zero sized profile:", inst);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
taxonomy::matrix4::ptr m4;
|
||||
bool has_position = true;
|
||||
#ifdef SCHEMA_IfcParameterizedProfileDef_Position_IS_OPTIONAL
|
||||
has_position = !!inst->Position();
|
||||
#endif
|
||||
if (has_position) {
|
||||
m4 = taxonomy::cast<taxonomy::matrix4>(map(inst->Position()));
|
||||
}
|
||||
|
||||
// Twelve corner points, running counter-clockwise from the bottom-left, with the
|
||||
// bottom flange (xb) possibly wider than the top flange (xt). Fillet/edge radii are
|
||||
// attached to the corner they round, matching the symmetric IfcIShapeProfileDef.
|
||||
return profile_helper(m4, {
|
||||
{{-xb,-y}},
|
||||
{{xb,-y}},
|
||||
{{xb,-y + ftb}, {feb}},
|
||||
{{d1,-y + ftb + dyb},{fb} },
|
||||
{{d1,y - ftt - dyt},{ft_top} },
|
||||
{{xt,y - ftt}, {fet}},
|
||||
{{xt,y}},
|
||||
{{-xt,y}},
|
||||
{{-xt,y - ftt}, {fet}},
|
||||
{{-d1,y - ftt - dyt},{ft_top} },
|
||||
{{-d1,-y + ftb + dyb},{fb} },
|
||||
{{-xb,-y + ftb}, {feb}}
|
||||
});
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -39,25 +39,8 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcPolygonalFaceSet* inst) {
|
||||
|
||||
int max_index = (int)points.size();
|
||||
|
||||
// When the optional PnIndex is present, CoordIndex values do not index into
|
||||
// CoordList directly but into PnIndex, which in turn remaps to CoordList.
|
||||
// Both index levels are 1-based per the IFC specification.
|
||||
auto pn_index = inst->PnIndex();
|
||||
auto resolve = [&](int idx) -> const taxonomy::point3::ptr& {
|
||||
if (pn_index) {
|
||||
if (idx < 1 || idx > (int)pn_index->size()) {
|
||||
throw IfcParse::IfcException("IfcPolygonalFaceSet PnIndex out of bounds for index " + boost::lexical_cast<std::string>(idx));
|
||||
}
|
||||
idx = (*pn_index)[idx - 1];
|
||||
}
|
||||
if (idx < 1 || idx > max_index) {
|
||||
throw IfcParse::IfcException("IfcPolygonalFaceSet index out of bounds for index " + boost::lexical_cast<std::string>(idx));
|
||||
}
|
||||
return points[idx - 1];
|
||||
};
|
||||
|
||||
auto shell = taxonomy::make<taxonomy::shell>();
|
||||
|
||||
|
||||
for (auto& f : *polygonal_faces) {
|
||||
auto fa = taxonomy::make<taxonomy::face>();
|
||||
shell->children.push_back(fa);
|
||||
@@ -69,14 +52,17 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcPolygonalFaceSet* inst) {
|
||||
auto indices = f->CoordIndex();
|
||||
taxonomy::point3::ptr previous;
|
||||
for (std::vector<int>::const_iterator jt = indices.begin(); jt != indices.end(); ++jt) {
|
||||
auto current = resolve(*jt);
|
||||
if (*jt < 1 || *jt > max_index) {
|
||||
throw IfcParse::IfcException("IfcPolygonalFaceSet index out of bounds for index " + boost::lexical_cast<std::string>(*jt));
|
||||
}
|
||||
auto current = points[(*jt) - 1];
|
||||
if (jt != indices.begin()) {
|
||||
loop->children.push_back(taxonomy::make<taxonomy::edge>(previous, current));
|
||||
}
|
||||
previous = current;
|
||||
}
|
||||
if (!indices.empty()) {
|
||||
auto current = resolve(indices.front());
|
||||
auto current = points[indices.front() - 1];
|
||||
loop->children.push_back(taxonomy::make<taxonomy::edge>(previous, current));
|
||||
}
|
||||
}
|
||||
@@ -91,14 +77,17 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcPolygonalFaceSet* inst) {
|
||||
loop->external = false;
|
||||
|
||||
for (std::vector<int>::const_iterator jt = li.begin(); jt != li.end(); ++jt) {
|
||||
auto current = resolve(*jt);
|
||||
if (*jt < 1 || *jt > max_index) {
|
||||
throw IfcParse::IfcException("IfcPolygonalFaceSet index out of bounds for index " + boost::lexical_cast<std::string>(*jt));
|
||||
}
|
||||
auto current = points[(*jt) - 1];
|
||||
if (jt != li.begin()) {
|
||||
loop->children.push_back(taxonomy::make<taxonomy::edge>(previous, current));
|
||||
}
|
||||
previous = current;
|
||||
}
|
||||
if (!li.empty()) {
|
||||
auto current = resolve(li.front());
|
||||
auto current = points[li.front() - 1];
|
||||
loop->children.push_back(taxonomy::make<taxonomy::edge>(previous, current));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,23 +39,6 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcTriangulatedFaceSet* inst) {
|
||||
|
||||
int max_index = (int)points.size();
|
||||
|
||||
// When the optional PnIndex is present, CoordIndex values do not index into
|
||||
// CoordList directly but into PnIndex, which in turn remaps to CoordList.
|
||||
// Both index levels are 1-based per the IFC specification.
|
||||
auto pn_index = inst->PnIndex();
|
||||
auto resolve = [&](int idx) -> const taxonomy::point3::ptr& {
|
||||
if (pn_index) {
|
||||
if (idx < 1 || idx > (int)pn_index->size()) {
|
||||
throw IfcParse::IfcException("IfcTriangulatedFaceSet PnIndex out of bounds for index " + boost::lexical_cast<std::string>(idx));
|
||||
}
|
||||
idx = (*pn_index)[idx - 1];
|
||||
}
|
||||
if (idx < 1 || idx > max_index) {
|
||||
throw IfcParse::IfcException("IfcTriangulatedFaceSet index out of bounds for index " + boost::lexical_cast<std::string>(idx));
|
||||
}
|
||||
return points[idx - 1];
|
||||
};
|
||||
|
||||
auto shell = taxonomy::make<taxonomy::shell>();
|
||||
|
||||
for (auto& indices : indices_list) {
|
||||
@@ -68,7 +51,10 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcTriangulatedFaceSet* inst) {
|
||||
loop->external = true;
|
||||
taxonomy::point3::ptr first, previous;
|
||||
for (std::vector<int>::const_iterator jt = indices.begin(); jt != indices.end(); ++jt) {
|
||||
const taxonomy::point3::ptr& current = resolve(*jt);
|
||||
if (*jt < 1 || *jt > max_index) {
|
||||
throw IfcParse::IfcException("IfcTriangulatedFaceSet index out of bounds for index " + boost::lexical_cast<std::string>(*jt));
|
||||
}
|
||||
const taxonomy::point3::ptr& current = points[(*jt) - 1];
|
||||
if (jt == indices.begin()) {
|
||||
first = current;
|
||||
} else {
|
||||
|
||||
@@ -89,11 +89,7 @@ BIND(IfcRectangleHollowProfileDef);
|
||||
BIND(IfcRectangleProfileDef);
|
||||
BIND(IfcTrapeziumProfileDef);
|
||||
BIND(IfcCShapeProfileDef);
|
||||
// In IFC2X3 IfcAsymmetricIShapeProfileDef is a subtype of IfcIShapeProfileDef and is
|
||||
// mapped by it; from IFC4 onwards it is a standalone type and needs its own binding.
|
||||
#ifdef SCHEMA_IfcAsymmetricIShapeProfileDef_HAS_BottomFlangeWidth
|
||||
BIND(IfcAsymmetricIShapeProfileDef);
|
||||
#endif
|
||||
// IfcAsymmetricIShapeProfileDef included
|
||||
BIND(IfcIShapeProfileDef);
|
||||
BIND(IfcLShapeProfileDef);
|
||||
BIND(IfcTShapeProfileDef);
|
||||
|
||||
@@ -311,12 +311,8 @@ CLI Manual
|
||||
output.
|
||||
--force-space-transparency arg Overrides transparency of spaces in
|
||||
geometry output.
|
||||
--circle-segments arg (= 0) Number of segments to approximate full
|
||||
circles in the CGAL kernel. When 0 (the
|
||||
default) the segment count is derived from
|
||||
mesher-linear-deflection instead, so curves
|
||||
stay within the deflection tolerance
|
||||
regardless of radius.
|
||||
--circle-segments arg (= 16) Number of segments to approximate full
|
||||
circles in CGAL kernel.
|
||||
--cgal-smooth-angle-degrees arg (= -1)
|
||||
Angle in degrees under which adjacent
|
||||
facets will have averaged vertex
|
||||
|
||||
@@ -72,8 +72,6 @@ Filtering is typically used to select any IFC element or type.
|
||||
|
||||
"``IfcPump, location=""Level 3""``", "Locations bubble up the hierarchy. So if a pump is in a space and that space is on Level 3, then you can say ""all pumps on level 3"" which will include that pump in the space."
|
||||
|
||||
"``IfcElement, query:""parent.Name""=""My Site""``", "Only elements *immediately* under ""My Site"" in the spatial hierarchy. Unlike the ``location`` and ``parent`` filters, which both match at any depth, the ``parent`` query key resolves the direct parent only, so nested storeys (and their contents) are excluded."
|
||||
|
||||
The filter elements syntax works by specifying one or more groups of filters
|
||||
separated by a ``+`` character. Each filter group will return a set of filtered
|
||||
elements, and these are unioned together.
|
||||
@@ -113,15 +111,6 @@ will search through all IfcTypeProducts and IfcProducts in the IFC project.
|
||||
"Parent", "Filter", "``parent{{=}}{{value}}``", "``parent=Foo`` specifies the criteria that elements must be a direct or indirect child in the spatial hierarchy to an element with a ``Name`` attribute with a value of ``Foo``."
|
||||
"Query", "Filter", "``query:{{keys}}{{=}}{{value}}``", "``query:types.count=0`` specifies the criteria that elements must have zero type occurrences. The query keys corresponds to the syntax used in the `Getting element values`_ section"
|
||||
|
||||
.. note::
|
||||
|
||||
The ``location`` and ``parent`` filters both match at **any depth** in the
|
||||
spatial hierarchy. To match only elements *immediately* contained in (or
|
||||
aggregated under) a spatial element, use the ``parent`` query key, which
|
||||
resolves the direct parent only. For example,
|
||||
``query:"parent.Name"="My Site"`` selects elements directly under ``My
|
||||
Site`` but excludes anything nested inside its sub-storeys or spaces.
|
||||
|
||||
When you specify a filter with a ``{{=}}`` check, you can choose from one of
|
||||
the following comparison checks:
|
||||
|
||||
@@ -202,7 +191,7 @@ Valid keys are:
|
||||
"``storey``", "Gets the first IfcBuildingStorey spatial element that an element is contained in."
|
||||
"``building``", "Gets the first IfcBuilding spatial element that an element is contained in."
|
||||
"``site``", "Gets the first IfcSite spatial element that an element is contained in."
|
||||
"``parent``", "Gets the **immediate** parent element in the spatial hierarchy (the direct spatial container, or the direct aggregate/nest/fill/void parent). Combine with ``.Name`` in a query filter to match only immediate children, e.g. ``query:""parent.Name""=""My Site""``."
|
||||
"``parent``", "Gets the parent element in the spatial hierarchy."
|
||||
"``classification``", "Gets the element's classification reference(s)"
|
||||
"``group``", "Gets the element's group(s)"
|
||||
"``system``", "Gets the element's system(s). This is a subset of group(s)."
|
||||
|
||||
@@ -228,10 +228,10 @@ circle-segments
|
||||
+------+-----------------------+---------+
|
||||
| Type | IfcConvert Option | Default |
|
||||
+======+=======================+=========+
|
||||
| INT | ``--circle-segments`` | 0 |
|
||||
| INT | ``--circle-segments`` | 16 |
|
||||
+------+-----------------------+---------+
|
||||
|
||||
Number of segments to approximate full circles in the CGAL kernel. When 0 (the default) the segment count is derived from mesher-linear-deflection instead, so curves stay within the deflection tolerance regardless of radius.
|
||||
Number of segments to approximate full circles in CGAL kernel.
|
||||
|
||||
context-identifiers
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -231,7 +231,7 @@ def open(
|
||||
kwargs = {"mmap": mmap}
|
||||
if logger is not None:
|
||||
kwargs["logger"] = logger
|
||||
f = ifcopenshell_wrapper.open(str(path.absolute()), **kwargs)
|
||||
f = ifcopenshell_wrapper.open(str(path.absolute()), **kwargs) # ty: ignore[unknown-argument]
|
||||
else:
|
||||
f = ifcopenshell_wrapper.open(str(path.absolute()), False, *((logger,) if logger is not None else ()))
|
||||
return file(f)
|
||||
|
||||
@@ -49,7 +49,6 @@ Future versions of this API may support:
|
||||
|
||||
from ._get_segment_start_point_label import register_referent_name_callback
|
||||
from .add_stationing_referent import add_stationing_referent
|
||||
from .add_positioning_referent import add_positioning_referent
|
||||
from .add_vertical_layout import add_vertical_layout
|
||||
from .add_zero_length_segment import add_zero_length_segment
|
||||
from .create import create
|
||||
@@ -95,7 +94,6 @@ from .util import *
|
||||
|
||||
__all__ = [
|
||||
"add_stationing_referent",
|
||||
"add_positioning_referent",
|
||||
"add_vertical_layout",
|
||||
"add_zero_length_segment",
|
||||
"create",
|
||||
|
||||
@@ -22,6 +22,8 @@ import numpy as np
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
|
||||
import ifcopenshell.util.unit
|
||||
from ifcopenshell import entity_instance
|
||||
from ifcopenshell.api.alignment._get_segment_endpoint import _get_segment_endpoint
|
||||
from ifcopenshell.api.alignment._update_zero_length_segment_placement import _update_zero_length_segment_placement
|
||||
|
||||
@@ -22,11 +22,28 @@ import numpy as np
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.alignment
|
||||
from ifcopenshell.api.alignment import _map_alignment_cant_segment
|
||||
from ifcopenshell.api.alignment._update_zero_length_segment_placement import _update_zero_length_segment_placement
|
||||
import ifcopenshell.api.nest
|
||||
from ifcopenshell import entity_instance
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.util.alignment
|
||||
import ifcopenshell.util.unit
|
||||
from ifcopenshell import entity_instance, ifcopenshell_wrapper
|
||||
from ifcopenshell.api.alignment._add_segment_to_curve import _add_segment_to_curve
|
||||
from ifcopenshell.api.alignment._get_segment_endpoint import _get_segment_endpoint
|
||||
from ifcopenshell.api.alignment._get_segment_start_point_label import (
|
||||
_get_segment_start_point_label,
|
||||
)
|
||||
from ifcopenshell.api.alignment._map_alignment_cant_segment import (
|
||||
_map_alignment_cant_segment,
|
||||
)
|
||||
from ifcopenshell.api.alignment._map_alignment_horizontal_segment import (
|
||||
_map_alignment_horizontal_segment,
|
||||
)
|
||||
from ifcopenshell.api.alignment._map_alignment_vertical_segment import (
|
||||
_map_alignment_vertical_segment,
|
||||
)
|
||||
|
||||
|
||||
def _add_segment_to_layout(
|
||||
|
||||
@@ -18,7 +18,11 @@
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.util.alignment
|
||||
from ifcopenshell import entity_instance
|
||||
from ifcopenshell.api.alignment._get_segment_start_point_label import (
|
||||
_get_segment_start_point_label,
|
||||
)
|
||||
|
||||
|
||||
def _add_zero_length_segment(file: ifcopenshell.file, layout: entity_instance) -> None:
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.geom
|
||||
from ifcopenshell import entity_instance, ifcopenshell_wrapper
|
||||
from ifcopenshell.api.alignment._map_alignment_segment import _map_alignment_segment
|
||||
from typing import Union
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.alignment
|
||||
from ifcopenshell.api.alignment.update_fallback_position import update_fallback_position
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.guid
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
|
||||
def add_positioning_referent(
|
||||
file: ifcopenshell.file,
|
||||
name: str,
|
||||
alignment: entity_instance,
|
||||
distance_along: float,
|
||||
station: float,
|
||||
positioned_product: entity_instance,
|
||||
) -> entity_instance:
|
||||
"""
|
||||
Semantically defines the position of a product along an alignment by adding an IfcReferent to the alignment that defines the stationing system.
|
||||
|
||||
:param alignment: the alignment to receive the referent
|
||||
:param distance_along: distance along the alignment basis curve
|
||||
:param station: station value
|
||||
:param name: name to assign to IfcReferent.Name, typically a stringized version of the station value
|
||||
:param positioned_product: the product whose position is informed by the referent
|
||||
:return: referent
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
alignment = model.by_type("IfcAlignment")[0]
|
||||
pier = model.by_type("IfcBridgePart")[0]
|
||||
ifcopenshell.api.alignment.add_positioning_referent(model,name="Pier 1 Sta 1+00",alignment=alignment,distance_along=0.0,station=100.0,positioned_product=pier)
|
||||
"""
|
||||
|
||||
curve = ifcopenshell.api.alignment.get_curve(alignment)
|
||||
|
||||
object_placement = None
|
||||
representation = None
|
||||
if curve and curve.is_a("IfcCompositeCurve") and 0 < len(curve.Segments):
|
||||
object_placement = file.createIfcLinearPlacement(
|
||||
RelativePlacement=file.createIfcAxis2PlacementLinear(
|
||||
Location=file.createIfcPointByDistanceExpression(
|
||||
DistanceAlong=file.createIfcLengthMeasure(distance_along),
|
||||
OffsetLateral=None,
|
||||
OffsetVertical=None,
|
||||
OffsetLongitudinal=None,
|
||||
BasisCurve=curve,
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
update_fallback_position(file, object_placement)
|
||||
else:
|
||||
object_placement = file.createIfcLocalPlacement(
|
||||
PlacementRelTo=None,
|
||||
RelativePlacement=file.createIfcAxis2Placement2D(
|
||||
Location=file.createIfcCartesianPoint(alignment.ObjectPlacement.RelativePlacement.Location.Coordinates)
|
||||
),
|
||||
)
|
||||
|
||||
# this commented out code is what you would do to add a geometric representation of the referent
|
||||
# the example is a circle. a better way would be to pass a representation into the function
|
||||
# representation = file.create_entity(
|
||||
# name="IfcCircle",
|
||||
# position=file.createIfcAxis2Placement2D(Location=file.createIfcCartesianPoint(Coordinates=(0.0, 0.0)),
|
||||
# radius=1.0)
|
||||
# )
|
||||
|
||||
# create referent for the station
|
||||
referent = file.createIfcReferent(
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=name,
|
||||
Description=None,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=object_placement,
|
||||
Representation=representation,
|
||||
PredefinedType="POSITION",
|
||||
)
|
||||
pset_stationing = ifcopenshell.api.pset.add_pset(file, product=referent, name="Pset_Stationing")
|
||||
ifcopenshell.api.pset.edit_pset(file, pset=pset_stationing, properties={"Station": station})
|
||||
|
||||
if len(referent.Positions) == 0:
|
||||
rel_positions = file.createIfcRelPositions(
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
RelatingPositioningElement=referent,
|
||||
RelatedProducts=[
|
||||
positioned_product,
|
||||
],
|
||||
)
|
||||
else:
|
||||
referent.Positions[0].RelatedProducts += (positioned_product,)
|
||||
|
||||
return referent
|
||||
@@ -16,35 +16,35 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from typing import Optional
|
||||
import numpy as np
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.alignment
|
||||
from ifcopenshell.api.alignment.update_fallback_position import update_fallback_position
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.element
|
||||
from ifcopenshell import entity_instance
|
||||
import ifcopenshell.util.unit
|
||||
from ifcopenshell import entity_instance, ifcopenshell_wrapper
|
||||
|
||||
|
||||
def add_stationing_referent(
|
||||
file: ifcopenshell.file,
|
||||
name: str,
|
||||
alignment: entity_instance,
|
||||
distance_along: float,
|
||||
station: float,
|
||||
incoming_station: Optional[float] = None,
|
||||
on_basis_curve: Optional[bool] = None,
|
||||
name: str,
|
||||
positioned_product: entity_instance,
|
||||
) -> entity_instance:
|
||||
"""
|
||||
Adds an IfcReferent to the alignment that defines the stationing system.
|
||||
Adds an IfcReferent to the alignment with the Pset_Stationing property set.
|
||||
|
||||
:param name: name to assign to IfcReferent.Name, typically a stringized version of the station value
|
||||
:param alignment: the alignment to receive the referent
|
||||
:param distance_along: distance along the alignment basis curve
|
||||
:param station: station value
|
||||
:param incoming_station: station value of the incoming segment, only set to specify a station equation
|
||||
:param on_basis_curve: whether the referent is positioned on the basis curve or the alignment curve, if None the function will default to the basis curve
|
||||
:param name: name to assign to IfcReferent.Name, typically a stringized version of the station value
|
||||
:param positioned_product: the product whose position is informed by the referent
|
||||
:return: referent
|
||||
|
||||
Example:
|
||||
@@ -52,21 +52,14 @@ def add_stationing_referent(
|
||||
.. code:: python
|
||||
|
||||
alignment = model.by_type("IfcAlignment")[0]
|
||||
ifcopenshell.api.alignment.add_stationing_referent(model,name="1+00.0",alignment=alignment,distance_along=0.0,station=100.0)
|
||||
ifcopenshell.api.alignment.add_stationing_referent(model,alignment=alignment,distance_along=0.0,station=100.0)
|
||||
"""
|
||||
|
||||
if on_basis_curve is None:
|
||||
on_basis_curve = True
|
||||
|
||||
curve = (
|
||||
ifcopenshell.api.alignment.get_basis_curve(alignment)
|
||||
if on_basis_curve
|
||||
else ifcopenshell.api.alignment.get_curve(alignment)
|
||||
)
|
||||
basis_curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
|
||||
|
||||
object_placement = None
|
||||
representation = None
|
||||
if curve and curve.is_a("IfcCompositeCurve") and 0 < len(curve.Segments):
|
||||
if basis_curve and basis_curve.is_a("IfcCompositeCurve") and 0 < len(basis_curve.Segments):
|
||||
object_placement = file.createIfcLinearPlacement(
|
||||
RelativePlacement=file.createIfcAxis2PlacementLinear(
|
||||
Location=file.createIfcPointByDistanceExpression(
|
||||
@@ -74,7 +67,7 @@ def add_stationing_referent(
|
||||
OffsetLateral=None,
|
||||
OffsetVertical=None,
|
||||
OffsetLongitudinal=None,
|
||||
BasisCurve=curve,
|
||||
BasisCurve=basis_curve,
|
||||
)
|
||||
),
|
||||
)
|
||||
@@ -107,12 +100,8 @@ def add_stationing_referent(
|
||||
Representation=representation,
|
||||
PredefinedType="STATION",
|
||||
)
|
||||
properties = {"Station": station}
|
||||
if incoming_station is not None:
|
||||
properties["IncomingStation"] = incoming_station
|
||||
|
||||
pset_stationing = ifcopenshell.api.pset.add_pset(file, product=referent, name="Pset_Stationing")
|
||||
ifcopenshell.api.pset.edit_pset(file, pset=pset_stationing, properties=properties)
|
||||
ifcopenshell.api.pset.edit_pset(file, pset=pset_stationing, properties={"Station": station})
|
||||
|
||||
nest = ifcopenshell.api.alignment.get_referent_nest(file, alignment)
|
||||
if nest is None:
|
||||
@@ -126,4 +115,15 @@ def add_stationing_referent(
|
||||
nest.RelatedObjects, key=lambda x: ifcopenshell.util.element.get_pset(x, name="Pset_Stationing", prop="Station")
|
||||
)
|
||||
|
||||
if len(referent.Positions) == 0:
|
||||
rel_positions = file.createIfcRelPositions(
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
RelatingPositioningElement=referent,
|
||||
RelatedProducts=[
|
||||
positioned_product,
|
||||
],
|
||||
)
|
||||
else:
|
||||
referent.Positions[0].RelatedProducts += (positioned_product,)
|
||||
|
||||
return referent
|
||||
|
||||
@@ -51,6 +51,18 @@ def _move_vertical_layout_to_child_alignment(
|
||||
# aggregate the child alignment to the parent alignment
|
||||
ifcopenshell.api.aggregate.assign_object(file, products=[child_alignment], relating_object=parent_alignment)
|
||||
|
||||
# move all referents positioning segments of the vertical layout to the referent nest of the child alignment
|
||||
child_referent_nest = ifcopenshell.api.alignment.get_referent_nest(file, child_alignment)
|
||||
parent_referent_nest = ifcopenshell.api.alignment.get_referent_nest(file, parent_alignment)
|
||||
for referent in parent_referent_nest.RelatedObjects:
|
||||
for product in referent.Positions[0].RelatedProducts:
|
||||
if product.is_a("IfcAlignmentSegment") and product.Nests[0].RelatingObject == vertical_layout:
|
||||
# ifcopenshell.api.nest.change_nest(file,referent,child_alignment) - this doesn't work because referent is assigned to child_alignment.IsNestedBy[0].RelatedObjects
|
||||
# and it needs to be assigned to child_alignment.IsNestedBy[1].RelatedObjects
|
||||
# move the referent manually - unassign it and add it to the child alignment's referent nest
|
||||
ifcopenshell.api.nest.unassign_object(file, [referent])
|
||||
child_referent_nest.RelatedObjects += (referent,)
|
||||
|
||||
# if the parent alignment has a representation, move the Axis/Curve3D represention to the child alignment
|
||||
base_curve = ifcopenshell.api.alignment.get_basis_curve(parent_alignment)
|
||||
if base_curve:
|
||||
|
||||
@@ -23,8 +23,18 @@ import ifcopenshell.api.alignment
|
||||
from ifcopenshell.api.alignment._get_segment_endpoint import _get_segment_endpoint
|
||||
from ifcopenshell.api.alignment._update_zero_length_segment_placement import _update_zero_length_segment_placement
|
||||
import ifcopenshell.api.nest
|
||||
import ifcopenshell.ifcopenshell_wrapper as wrapper
|
||||
import ifcopenshell.util.unit
|
||||
from ifcopenshell import entity_instance
|
||||
from ifcopenshell.api.alignment._get_segment_start_point_label import (
|
||||
_get_segment_start_point_label,
|
||||
)
|
||||
from ifcopenshell.api.alignment._map_alignment_horizontal_segment import (
|
||||
_map_alignment_horizontal_segment,
|
||||
)
|
||||
from ifcopenshell.api.alignment._map_alignment_vertical_segment import (
|
||||
_map_alignment_vertical_segment,
|
||||
)
|
||||
from ifcopenshell.api.alignment._update_curve_segment_transition_code import (
|
||||
_update_curve_segment_transition_code,
|
||||
)
|
||||
|
||||
@@ -87,7 +87,9 @@ def create(
|
||||
_create_geometric_representation(file, alignment)
|
||||
|
||||
referent_name = ifcopenshell.util.alignment.station_as_string(file, start_station)
|
||||
referent = ifcopenshell.api.alignment.add_stationing_referent(file, referent_name, alignment, 0.0, start_station)
|
||||
referent = ifcopenshell.api.alignment.add_stationing_referent(
|
||||
file, alignment, 0.0, start_station, referent_name, alignment
|
||||
)
|
||||
|
||||
for layout in alignment_layouts:
|
||||
_add_zero_length_segment(file, layout)
|
||||
|
||||
@@ -141,7 +141,7 @@ def create_as_polyline(
|
||||
|
||||
# define stationing
|
||||
name = ifcopenshell.util.alignment.station_as_string(file, start_station)
|
||||
referent = ifcopenshell.api.alignment.add_stationing_referent(file, name, alignment, 0.0, start_station)
|
||||
referent = ifcopenshell.api.alignment.add_stationing_referent(file, alignment, 0.0, start_station, name, alignment)
|
||||
|
||||
# IFC 4.1.4.1.1 Alignment Aggregation To Project
|
||||
project = file.by_type("IfcProject")[0]
|
||||
|
||||
@@ -21,7 +21,9 @@ from typing import Union
|
||||
import numpy as np
|
||||
|
||||
import ifcopenshell
|
||||
from ifcopenshell import entity_instance
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.geom
|
||||
from ifcopenshell import entity_instance, ifcopenshell_wrapper
|
||||
from ifcopenshell.api.alignment._add_segment_to_layout import _add_segment_to_layout
|
||||
|
||||
|
||||
|
||||
@@ -16,47 +16,23 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.util.element
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
|
||||
def _distance_along_of_referent(referent: entity_instance) -> float:
|
||||
placement = referent.ObjectPlacement
|
||||
if placement.is_a("IfcLinearPlacement"):
|
||||
return placement.RelativePlacement.Location.DistanceAlong.wrappedValue
|
||||
# IfcLocalPlacement fallback (e.g. semantic-only alignment, or the placement could not yet
|
||||
# be expressed relative to a basis curve) carries no DistanceAlong; it is only ever used for
|
||||
# the starting referent, at distance 0.0.
|
||||
return 0.0
|
||||
|
||||
|
||||
def distance_along_from_station(file: ifcopenshell.file, alignment: entity_instance, station: float) -> Optional[float]:
|
||||
def distance_along_from_station(file: ifcopenshell.file, alignment: entity_instance, station: float) -> float:
|
||||
"""
|
||||
Given a station, returns the distance along the horizontal alignment.
|
||||
|
||||
If the alignment does not have stationing defined with an IfcReferent, the start of the alignment is assumed
|
||||
to be at station 0.0. That is, the station is the distance along.
|
||||
|
||||
Station equations (where Pset_Stationing.IncomingStation is set on a referent) are taken into account.
|
||||
For each STATION referent nested to the alignment, DistanceAlong (D) and the outgoing station (S, i.e.
|
||||
Pset_Stationing.Station) are read off, sorted by DistanceAlong. The requested station is located within
|
||||
the segment defined by the last referent whose outgoing station is less than or equal to it, and the
|
||||
distance along is computed as D + (station - S) for that referent.
|
||||
|
||||
If the station falls within a gap introduced by a forward (gap) station equation - that is, it was skipped
|
||||
over by the equation - there is no distance along that corresponds to it, and None is returned.
|
||||
|
||||
Note that an overlap (backward) station equation causes a range of stations to correspond to two distinct
|
||||
distances along the alignment, one on either side of the equation. This implementation returns the distance
|
||||
along in the segment following the equation (i.e. the outgoing side).
|
||||
.. note:: The current implementation does not account for station equations and assumes stationing is increasing along the alignment.
|
||||
|
||||
:param alignment: the alignment
|
||||
:param station: station value
|
||||
:return: distance along the horizontal alignment, or None if the station falls inside a station equation gap
|
||||
:return: distance along the horizontal alignment
|
||||
|
||||
Example:
|
||||
|
||||
@@ -67,36 +43,6 @@ def distance_along_from_station(file: ifcopenshell.file, alignment: entity_insta
|
||||
print(dist_along) # 100.00
|
||||
"""
|
||||
|
||||
referent_nest = ifcopenshell.api.alignment.get_referent_nest(file, alignment)
|
||||
if referent_nest is None:
|
||||
start_station = ifcopenshell.api.alignment.get_alignment_start_station(file, alignment)
|
||||
return station - start_station
|
||||
|
||||
stations = [
|
||||
(
|
||||
_distance_along_of_referent(referent),
|
||||
ifcopenshell.util.element.get_pset(referent, name="Pset_Stationing", prop="Station"),
|
||||
)
|
||||
for referent in referent_nest.RelatedObjects
|
||||
]
|
||||
stations.sort(key=lambda entry: entry[0])
|
||||
|
||||
index = None
|
||||
for i, (distance_along, outgoing_station) in enumerate(stations):
|
||||
if outgoing_station <= station:
|
||||
index = i
|
||||
|
||||
if index is None:
|
||||
# station precedes the alignment's starting station; extrapolate from the first referent
|
||||
distance_along, outgoing_station = stations[0]
|
||||
return distance_along + (station - outgoing_station)
|
||||
|
||||
distance_along, outgoing_station = stations[index]
|
||||
|
||||
if index + 1 < len(stations):
|
||||
next_distance_along, _ = stations[index + 1]
|
||||
if station - outgoing_station > next_distance_along - distance_along:
|
||||
# the station was skipped over by a forward (gap) station equation
|
||||
return None
|
||||
|
||||
return distance_along + (station - outgoing_station)
|
||||
start_station = ifcopenshell.api.alignment.get_alignment_start_station(file, alignment)
|
||||
dist_along = station - start_station
|
||||
return dist_along
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
import numpy as np
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.util.placement
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ def assign_cost_item_quantity(
|
||||
"products": products or [],
|
||||
"prop_name": prop_name,
|
||||
"formula": formula,
|
||||
"ifc_class": ifc_class,
|
||||
"ifc_class" : ifc_class
|
||||
}
|
||||
return usecase.execute()
|
||||
|
||||
@@ -134,7 +134,7 @@ class Usecase:
|
||||
continue
|
||||
self.assign_cost_control(related_object=product, cost_item=self.settings["cost_item"])
|
||||
if self.settings["formula"]:
|
||||
tree = ast.parse(self.settings["formula"], mode="eval")
|
||||
tree = ast.parse(self.settings["formula"], mode = "eval")
|
||||
collector = VariableExtractor()
|
||||
collector.visit(tree)
|
||||
variables = collector.variables
|
||||
@@ -144,10 +144,10 @@ class Usecase:
|
||||
value = getter(product, variable)
|
||||
|
||||
if value is None:
|
||||
print(
|
||||
f"WARNING: Variable '{variable}' in product '{product.Name}' "
|
||||
f"is missing (None). Check Pset/Qset or property name."
|
||||
)
|
||||
print(
|
||||
f"WARNING: Variable '{variable}' in product '{product.Name}' "
|
||||
f"is missing (None). Check Pset/Qset or property name."
|
||||
)
|
||||
elif value == 0:
|
||||
print(
|
||||
f"WARNING: Variable '{variable}' in product '{product.Name}' "
|
||||
@@ -159,9 +159,7 @@ class Usecase:
|
||||
|
||||
new_quantity = None
|
||||
for quantity in self.quantities:
|
||||
if (
|
||||
quantity.Formula == self.settings["formula"] and len(self.settings["products"]) == 1
|
||||
): # Todo improve it
|
||||
if quantity.Formula == self.settings["formula"] and len(self.settings["products"]) == 1: #Todo improve it
|
||||
new_quantity = quantity
|
||||
self.settings["ifc_class"] = quantity.is_a()
|
||||
continue
|
||||
@@ -186,23 +184,23 @@ class Usecase:
|
||||
self.update_cost_item_count()
|
||||
|
||||
def get_value_from_pset(
|
||||
self,
|
||||
product: ifcopenshell.entity_instance,
|
||||
v: str,
|
||||
self,
|
||||
product:ifcopenshell.entity_instance,
|
||||
v: str,
|
||||
) -> float:
|
||||
pset_name = v.split(".")[0]
|
||||
pset = ifcopenshell.util.element.get_pset(product, pset_name)
|
||||
pset_property_name = v.split(".")[1]
|
||||
return (pset or {}).get(pset_property_name, None)
|
||||
return (pset or {}).get(pset_property_name,None)
|
||||
|
||||
def get_value_from_qset(
|
||||
self,
|
||||
product: ifcopenshell.entity_instance,
|
||||
v: str,
|
||||
self,
|
||||
product:ifcopenshell.entity_instance,
|
||||
v: str,
|
||||
) -> float:
|
||||
qtos = ifcopenshell.util.element.get_psets(product, qtos_only=True)
|
||||
qtos = ifcopenshell.util.element.get_psets(product, qtos_only = True)
|
||||
quantities = next(iter(qtos.values()), {})
|
||||
return (quantities or {}).get(v, None)
|
||||
return (quantities or {}).get(v,None)
|
||||
|
||||
def assign_cost_control(
|
||||
self, related_object: ifcopenshell.entity_instance, cost_item: ifcopenshell.entity_instance
|
||||
@@ -245,7 +243,6 @@ class Usecase:
|
||||
count += 1
|
||||
quantity[3] = count
|
||||
|
||||
|
||||
OPERATORS = {
|
||||
ast.Add: operator.add,
|
||||
ast.Sub: operator.sub,
|
||||
@@ -255,20 +252,18 @@ OPERATORS = {
|
||||
ast.USub: operator.neg,
|
||||
}
|
||||
|
||||
|
||||
def build_full_name(node):
|
||||
# used for variables with dots
|
||||
#used for variables with dots
|
||||
parts = []
|
||||
while isinstance(node, ast.Attribute):
|
||||
parts.append(node.attr)
|
||||
node = node.value
|
||||
parts.append(node.attr)
|
||||
node = node.value
|
||||
|
||||
if isinstance(node, ast.Name):
|
||||
parts.append(node.id)
|
||||
|
||||
return ".".join(reversed(parts))
|
||||
|
||||
|
||||
class VariableExtractor(ast.NodeVisitor):
|
||||
def __init__(self):
|
||||
self.variables = set()
|
||||
@@ -279,7 +274,6 @@ class VariableExtractor(ast.NodeVisitor):
|
||||
def visit_Attribute(self, node):
|
||||
self.variables.add(build_full_name(node))
|
||||
|
||||
|
||||
class FormulaEvaluator(ast.NodeVisitor):
|
||||
def __init__(self, values):
|
||||
self.values = values
|
||||
@@ -287,7 +281,7 @@ class FormulaEvaluator(ast.NodeVisitor):
|
||||
def visit_BinOp(self, node):
|
||||
left = self.visit(node.left)
|
||||
right = self.visit(node.right)
|
||||
return OPERATORS[type(node.op)](left, right) # ty: ignore[too-many-positional-arguments]
|
||||
return OPERATORS[type(node.op)](left, right)
|
||||
|
||||
def visit_Name(self, node):
|
||||
return self.values[node.id]
|
||||
|
||||
@@ -221,7 +221,8 @@ for id in to_emit:
|
||||
statements.append("%s << %s" % (id, stmt))
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(r"""
|
||||
print(
|
||||
r"""
|
||||
# This file is generated by IfcOpenShell ifcexpressparser bootstrap.py
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -260,4 +261,6 @@ if __name__ == "__main__":
|
||||
mdl = importlib.import_module(output)
|
||||
mdl.Generator(m).emit()
|
||||
sys.stdout.write(m.schema.name)
|
||||
""" % ("\n ".join(statements)))
|
||||
"""
|
||||
% ("\n ".join(statements))
|
||||
)
|
||||
|
||||
@@ -695,7 +695,6 @@ codegen_rule("MOD", lambda context: "%")
|
||||
codegen_rule("TRUE", lambda context: "True")
|
||||
codegen_rule("FALSE", lambda context: "False")
|
||||
|
||||
|
||||
def _dotted_name(node: ast.AST):
|
||||
"""Return dotted name for Name/Attribute chains, else None."""
|
||||
if isinstance(node, ast.Name):
|
||||
@@ -705,7 +704,6 @@ def _dotted_name(node: ast.AST):
|
||||
return f"{base}.{node.attr}" if base else node.attr
|
||||
return None
|
||||
|
||||
|
||||
class AttributeGetattrTransformer(ast.NodeTransformer):
|
||||
def visit_Attribute(self, node):
|
||||
parents = []
|
||||
@@ -722,7 +720,7 @@ class AttributeGetattrTransformer(ast.NodeTransformer):
|
||||
if isinstance(node.ctx, ast.Store):
|
||||
return node
|
||||
|
||||
if _dotted_name(node) in ("ifcopenshell.create_entity", "str.lower"):
|
||||
if _dotted_name(node) in ('ifcopenshell.create_entity', 'str.lower'):
|
||||
return node
|
||||
|
||||
if node.attr.startswith("__"):
|
||||
|
||||
@@ -363,18 +363,24 @@ class EarlyBoundCodeWriter:
|
||||
)
|
||||
)
|
||||
|
||||
self.statements[self.statements.index("{factory_placeholder}")] = """
|
||||
self.statements[self.statements.index("{factory_placeholder}")] = (
|
||||
"""
|
||||
class %(schema_name)s_instance_factory : public IfcParse::instance_factory {
|
||||
virtual IfcUtil::IfcBaseClass* operator()(const IfcParse::declaration* decl, IfcEntityInstanceData&& data) const {
|
||||
%(instance_mapping)s
|
||||
}
|
||||
};
|
||||
""" % locals()
|
||||
"""
|
||||
% locals()
|
||||
)
|
||||
|
||||
""
|
||||
self.statements[self.statements.index("{string_pool_placeholder}")] = """
|
||||
self.statements[self.statements.index("{string_pool_placeholder}")] = (
|
||||
"""
|
||||
const std::string strings[] = {%s};
|
||||
""" % ",".join(map(lambda s: '"%s"s' % s, self.strings))
|
||||
"""
|
||||
% ",".join(map(lambda s: '"%s"s' % s, self.strings))
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return "\n".join(self.statements)
|
||||
|
||||
@@ -145,7 +145,8 @@ class configuration:
|
||||
config.set(
|
||||
"snippets",
|
||||
"print all wall ids",
|
||||
self.config_encode("""
|
||||
self.config_encode(
|
||||
"""
|
||||
###########################################################################
|
||||
# A simple script that iterates over all walls in the current model #
|
||||
# and prints their Globally unique IDs (GUIDS) to the console window #
|
||||
@@ -153,13 +154,15 @@ class configuration:
|
||||
|
||||
for wall in model.by_type("IfcWall"):
|
||||
print ("wall with global id: "+str(wall.GlobalId))
|
||||
""".lstrip()),
|
||||
""".lstrip()
|
||||
),
|
||||
)
|
||||
|
||||
config.set(
|
||||
"snippets",
|
||||
"print properties of current selection",
|
||||
self.config_encode("""
|
||||
self.config_encode(
|
||||
"""
|
||||
###########################################################################
|
||||
# A simple script that iterates over all IfcPropertySets of the currently #
|
||||
# selected object and prints them to the console #
|
||||
@@ -177,7 +180,8 @@ if selection:
|
||||
for prop in relDefinesByProperties.RelatingPropertyDefinition.HasProperties:
|
||||
print ("{:<20} :{}".format(prop.Name,prop.NominalValue.wrappedValue))
|
||||
print ("\\n")
|
||||
""".lstrip()),
|
||||
""".lstrip()
|
||||
),
|
||||
)
|
||||
with open(conf_file, "w") as configfile:
|
||||
config.write(configfile)
|
||||
|
||||
@@ -1697,16 +1697,10 @@ class uninitialized_tag: ...
|
||||
|
||||
def arrange_polygons(settings, polygons): ...
|
||||
def clear_schemas(): ...
|
||||
def construct_iterator(geometry_library, settings, file, num_threads, logger=...): ...
|
||||
def construct_iterator_with_include_exclude(
|
||||
geometry_library, settings, file, elems, include, num_threads, logger=...
|
||||
): ...
|
||||
def construct_iterator_with_include_exclude_globalid(
|
||||
geometry_library, settings, file, elems, include, num_threads, logger=...
|
||||
): ...
|
||||
def construct_iterator_with_include_exclude_id(
|
||||
geometry_library, settings, file, elems, include, num_threads, logger=...
|
||||
): ...
|
||||
def construct_iterator(geometry_library, settings, file, num_threads): ...
|
||||
def construct_iterator_with_include_exclude(geometry_library, settings, file, elems, include, num_threads): ...
|
||||
def construct_iterator_with_include_exclude_globalid(geometry_library, settings, file, elems, include, num_threads): ...
|
||||
def construct_iterator_with_include_exclude_id(geometry_library, settings, file, elems, include, num_threads): ...
|
||||
def convert_loop_to_function_item(loop): ...
|
||||
def create_box(*args): ...
|
||||
def create_epeck(*args): ...
|
||||
@@ -1723,8 +1717,8 @@ def line_segments_to_polygons(s, eps, segments): ...
|
||||
def map_shape(settings, instance): ...
|
||||
def nary_union(sequence): ...
|
||||
def new_IfcBaseClass(schema_identifier: str, name: str) -> entity_instance: ...
|
||||
def open(fn: str, readonly: bool = False, logger=...) -> file: ...
|
||||
def parse_ifcxml(filename, logger=...): ...
|
||||
def open(fn: str, readonly: bool = False) -> file: ...
|
||||
def parse_ifcxml(filename): ...
|
||||
def polygons_to_svg(*args): ...
|
||||
def read(data): ...
|
||||
def register_schema(arg1): ...
|
||||
|
||||
@@ -56,7 +56,7 @@ def append_zero_length_segments(file: ifcopenshell.file) -> ifcopenshell.file:
|
||||
for alignment in alignments:
|
||||
layouts = ifcopenshell.api.alignment.get_alignment_layouts(alignment)
|
||||
for layout in layouts:
|
||||
ifcopenshell.api.alignment.add_zero_length_segment(patched_file, layout)
|
||||
ifcopenshell.api.alignment.add_zero_length_segment(patched_file, layout, include_referent=False)
|
||||
curve = ifcopenshell.api.alignment.get_layout_curve(layout)
|
||||
if curve:
|
||||
ifcopenshell.api.alignment.add_zero_length_segment(patched_file, curve)
|
||||
|
||||
@@ -355,7 +355,8 @@ def get_cost_rate(
|
||||
|
||||
class CostValueUnserialiser:
|
||||
def parse(self, formula: str):
|
||||
l = lark.Lark("""start: formula
|
||||
l = lark.Lark(
|
||||
"""start: formula
|
||||
formula: operand (operator operand)*
|
||||
operand: value | category "(" formula ")"
|
||||
value: NUMBER?
|
||||
@@ -392,7 +393,8 @@ class CostValueUnserialiser:
|
||||
NEWLINE: (CR? LF)+
|
||||
|
||||
%ignore WS // Disregard spaces in text
|
||||
""")
|
||||
"""
|
||||
)
|
||||
start = l.parse(formula)
|
||||
return self.get_formula(start.children[0])
|
||||
|
||||
|
||||
@@ -265,44 +265,6 @@ class DocExtractor:
|
||||
description = description.strip()
|
||||
return description
|
||||
|
||||
def extract_full_description(self, html: str) -> str:
|
||||
"""Extract the full definition text from markdown-derived HTML.
|
||||
|
||||
Entity/type documentation often introduces a bulleted list mid-definition
|
||||
(e.g. "... may include:" followed by a `<ul>`), or continues with another
|
||||
paragraph after it. Naively taking only the first `<p>` silently drops
|
||||
that content (see #4624). Instead, walk all top-level paragraph/list
|
||||
elements in document order, stopping before any `<blockquote>` (which in
|
||||
these docs holds the HISTORY/NOTE remarks).
|
||||
"""
|
||||
soup = BeautifulSoup(html, features="lxml")
|
||||
body = soup.body or soup
|
||||
parts = []
|
||||
for child in body.find_all(["p", "ul", "ol"], recursive=False):
|
||||
if child.name in ("ul", "ol"):
|
||||
items = [li.get_text() for li in child.find_all("li", recursive=False)]
|
||||
parts.append(" ".join(f"- {item}" for item in items))
|
||||
else:
|
||||
part = child.get_text()
|
||||
# Some IFC2X3 docs put the HISTORY remark in a plain leading
|
||||
# paragraph rather than a blockquote; it is metadata, not part
|
||||
# of the definition.
|
||||
if re.match(r"\s*(HISTORY|IFC2x Edition)\b", part):
|
||||
continue
|
||||
parts.append(part)
|
||||
text = " ".join(parts)
|
||||
# A remark can also be a "lazy" blockquote continuation inside a
|
||||
# paragraph (a literal "> HISTORY ..." tail that markdown does not
|
||||
# turn into a <blockquote>) or an inline "HISTORY: ..." sentence; cut
|
||||
# the definition there.
|
||||
text = re.split(r"\s*(?:>\s*)?(?:HISTORY\s*:|>\s*HISTORY\b|(?:>\s*)?IFC2x Edition\b)", text)[0]
|
||||
# strip inline kramdown/pandoc attribute-list markers that survive as literal
|
||||
# text once we're no longer limited to the first paragraph, e.g.
|
||||
# "{ .change-ifc2x4}", "{ .note}", "{: .extDef}".
|
||||
text = re.sub(r"\{[^{}]*\}", "", text)
|
||||
text = re.sub(r"\s+", " ", text).strip()
|
||||
return text
|
||||
|
||||
def extract_ifc2x3(self):
|
||||
print("Parsing data for Ifc2.3.0.1")
|
||||
if not IFC2x3_DOCS_LOCATION.is_dir():
|
||||
@@ -375,7 +337,7 @@ class DocExtractor:
|
||||
with open(md_path, "r", encoding="utf-8-sig") as fi:
|
||||
# convert markdown to html for easier parsing
|
||||
html = markdown(fi.read())
|
||||
entity_description = self.extract_full_description(html)
|
||||
entity_description = BeautifulSoup(html, features="lxml").find("p").text
|
||||
entity_description = entity_description.replace("\n", " ")
|
||||
entity_description = entity_description.replace("\u00a0", " ")
|
||||
|
||||
@@ -480,14 +442,9 @@ class DocExtractor:
|
||||
with open(md_path, "r", encoding="utf-8-sig") as fi:
|
||||
# convert markdown to html for easier parsing
|
||||
html = markdown(fi.read())
|
||||
property_set_description = self.extract_full_description(html)
|
||||
property_set_description = BeautifulSoup(html, features="lxml").find("p").text
|
||||
property_set_description = property_set_description.replace("\n", " ")
|
||||
# case-insensitive: some pset docs use "History:" instead of "HISTORY:",
|
||||
# which only becomes reachable now that extract_full_description() walks
|
||||
# past the first paragraph.
|
||||
property_set_description = re.split(
|
||||
r"HISTORY:", property_set_description, maxsplit=1, flags=re.IGNORECASE
|
||||
)[0]
|
||||
property_set_description = property_set_description.split("HISTORY:", 1)[0]
|
||||
property_set_description = property_set_description.strip()
|
||||
property_set_dict["description"] = property_set_description
|
||||
else:
|
||||
@@ -559,7 +516,7 @@ class DocExtractor:
|
||||
with open(md_path, "r", encoding="utf-8-sig") as fi:
|
||||
# convert markdown to html for easier parsing
|
||||
html = markdown(fi.read())
|
||||
description = self.extract_full_description(html)
|
||||
description = BeautifulSoup(html, features="lxml").find("p").text
|
||||
description = description.replace("\n", " ")
|
||||
description = description.replace("\u00a0", " ")
|
||||
property_dict["description"] = description
|
||||
@@ -593,7 +550,7 @@ class DocExtractor:
|
||||
with open(md_path, "r", encoding="utf-8-sig") as fi:
|
||||
# convert markdown to html for easier parsing
|
||||
html = markdown(fi.read())
|
||||
type_description = self.extract_full_description(html)
|
||||
type_description = BeautifulSoup(html, features="lxml").find("p").text
|
||||
type_description = type_description.replace("\n", " ")
|
||||
type_description = type_description.replace("\u00a0", " ")
|
||||
type_description = type_description.replace("Definition from ISO/CD 10303-46:1992: ", "")
|
||||
@@ -705,7 +662,7 @@ class DocExtractor:
|
||||
with open(md_path, "r", encoding="utf-8-sig") as fi:
|
||||
# convert markdown to html for easier parsing
|
||||
html = markdown(fi.read())
|
||||
entity_description = self.extract_full_description(html)
|
||||
entity_description = BeautifulSoup(html, features="lxml").find("p").text
|
||||
entity_description = entity_description.replace("\n", " ")
|
||||
entity_description = entity_description.replace("\u00a0", " ")
|
||||
entity_description = entity_description.replace("{ .extDef}", "")
|
||||
@@ -812,14 +769,9 @@ class DocExtractor:
|
||||
with open(md_path, "r", encoding="utf-8-sig") as fi:
|
||||
# convert markdown to html for easier parsing
|
||||
html = markdown(fi.read())
|
||||
property_set_description = self.extract_full_description(html)
|
||||
property_set_description = BeautifulSoup(html, features="lxml").find("p").text
|
||||
property_set_description = property_set_description.replace("\n", " ")
|
||||
# case-insensitive: some pset docs use "History:" instead of "HISTORY:",
|
||||
# which only becomes reachable now that extract_full_description() walks
|
||||
# past the first paragraph.
|
||||
property_set_description = re.split(
|
||||
r"HISTORY:", property_set_description, maxsplit=1, flags=re.IGNORECASE
|
||||
)[0]
|
||||
property_set_description = property_set_description.split("HISTORY:", 1)[0]
|
||||
property_set_description = property_set_description.strip()
|
||||
property_set_dict["description"] = property_set_description
|
||||
else:
|
||||
@@ -902,7 +854,7 @@ class DocExtractor:
|
||||
with open(md_path, "r", encoding="utf-8-sig") as fi:
|
||||
# convert markdown to html for easier parsing
|
||||
html = markdown(fi.read())
|
||||
description = self.extract_full_description(html)
|
||||
description = BeautifulSoup(html, features="lxml").find("p").text
|
||||
description = description.replace("\n", " ")
|
||||
description = description.replace("\u00a0", " ")
|
||||
property_dict["description"] = description
|
||||
@@ -936,7 +888,7 @@ class DocExtractor:
|
||||
with open(md_path, "r", encoding="utf-8-sig") as fi:
|
||||
# convert markdown to html for easier parsing
|
||||
html = markdown(fi.read().replace("{ .extDef}", ""))
|
||||
type_description = self.extract_full_description(html)
|
||||
type_description = BeautifulSoup(html, features="lxml").find("p").text
|
||||
type_description = type_description.replace("\n", " ")
|
||||
type_description = type_description.replace("\u00a0", " ")
|
||||
type_description = type_description.replace("{ .extDef}", "")
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"IfcAbsorbedDoseMeasure": {
|
||||
"description": "IfcAbsorbedDoseMeasure is a measure of the absorbed radioactivity dose. Usually measured in Gray (Gy, J/kg). Type: REAL",
|
||||
"description": "IfcAbsorbedDoseMeasure is a measure of the absorbed radioactivity dose.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcabsorbeddosemeasure.htm"
|
||||
},
|
||||
"IfcAccelerationMeasure": {
|
||||
"description": "IfcAccelerationMeasure is a measure of acceleration. Usually measured in m/s2. Type: REAL",
|
||||
"description": "IfcAccelerationMeasure is a measure of acceleration.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcaccelerationmeasure.htm"
|
||||
},
|
||||
"IfcActionRequestTypeEnum": {
|
||||
@@ -48,7 +48,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcbuildingcontrolsdomain/lexical/ifcalarmtypeenum.htm"
|
||||
},
|
||||
"IfcAmountOfSubstanceMeasure": {
|
||||
"description": "An amount of substance measure is the value for the quantity of a substance when compared with the number of atoms in 0.012 kg of carbon 12. Usually measure in mole (mol). Type: REAL",
|
||||
"description": "An amount of substance measure is the value for the quantity of a substance when compared with the number of atoms in 0.012 kg of carbon 12.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcamountofsubstancemeasure.htm"
|
||||
},
|
||||
"IfcAnalysisModelTypeEnum": {
|
||||
@@ -60,23 +60,23 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcstructuralanalysisdomain/lexical/ifcanalysistheorytypeenum.htm"
|
||||
},
|
||||
"IfcAngularVelocityMeasure": {
|
||||
"description": "IfcAngularVelocityMeasure is a measure of the velocity of a body measured in terms of angle subtended per unit time. Usually measured in radians/s. Type: REAL",
|
||||
"description": "IfcAngularVelocityMeasure is a measure of the velocity of a body measured in terms of angle subtended per unit time.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcangularvelocitymeasure.htm"
|
||||
},
|
||||
"IfcAppliedValueSelect": {
|
||||
"description": "IfcAppliedValueSelect defines a value to be calculated within a formula. Types are used as follows: - IfcValue: A constant value using project default units. - IfcMeasureWithUnit: A constant value using specified units. - IfcReference: A value referenced on an object attribute. For cost values, the following guidance applies: - IfcMeasureWithUnit allows the specification of both the actual figure for the value together with the currency in which the value is represented. - Selecting IfcMonetaryMeasure allows the specification only of the value, the currency being as set by the global context. - Selecting IfcRatioMeasure assumes that the amount is a percentage or other REAL number. Note that if the amount is normally specified as -20%, then this figure will need to be converted to a multiplier of 0.8",
|
||||
"description": "IfcAppliedValueSelect defines a value to be calculated within a formula.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifccostresource/lexical/ifcappliedvalueselect.htm"
|
||||
},
|
||||
"IfcArcIndex": {
|
||||
"description": "The IfcArcIndex describes a single circular arc segment within a poly curve by providing a list on indices. The first index is the start point of the circular arc, the second index is a point on arc, the third index is the end point of the circular arc. The three points shall not be co-linear. Informal Propositions: - The second index, resolving to a point on arc, shall resolve into a Cartesian point that has approximately the same distance to the start point and the end point of the circular arc. This is due to avoid numeric instability, if the point on arc is too close to either the start or the end point.",
|
||||
"description": "The IfcArcIndex describes a single circular arc segment within a poly curve by providing a list on indices. The first index is the start point of the circular arc, the second index is a point on arc, the third index is the end point of the circular arc. The three points shall not be co-linear.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcgeometryresource/lexical/ifcarcindex.htm"
|
||||
},
|
||||
"IfcAreaDensityMeasure": {
|
||||
"description": "IfcAreaDensityMeasure is a measure of the density of a two-dimensional object and is calculated as the mass per unit area. Usually measured in kg/m2. Type: REAL",
|
||||
"description": "IfcAreaDensityMeasure is a measure of the density of a two-dimensional object and is calculated as the mass per unit area.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcareadensitymeasure.htm"
|
||||
},
|
||||
"IfcAreaMeasure": {
|
||||
"description": "An area measure is the value of the extent of a surface. Usually measured in square metre (m2). Type: REAL",
|
||||
"description": "An area measure is the value of the extent of a surface.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcareameasure.htm"
|
||||
},
|
||||
"IfcArithmeticOperatorEnum": {
|
||||
@@ -124,7 +124,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifchvacdomain/lexical/ifcboilertypeenum.htm"
|
||||
},
|
||||
"IfcBoolean": {
|
||||
"description": "IfcBoolean is a defined data type of simple data type Boolean. It is required since a select type (IfcSimpleValue) cannot directly include simple types in its select list. A Boolean type can have value TRUE or FALSE. Type: BOOLEAN",
|
||||
"description": "IfcBoolean is a defined data type of simple data type Boolean. It is required since a select type (IfcSimpleValue) cannot directly include simple types in its select list. A Boolean type can have value TRUE or FALSE.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcboolean.htm"
|
||||
},
|
||||
"IfcBooleanOperand": {
|
||||
@@ -136,7 +136,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcgeometricmodelresource/lexical/ifcbooleanoperator.htm"
|
||||
},
|
||||
"IfcBoxAlignment": {
|
||||
"description": "The box alignment specifies the alignment of the text box relative to its position. The following string values shall be used: - top-left - top-middle - top-right - middle-left - center - middle-right - bottom-left - bottom-middle - bottom-right Figure 1 illustrates alignment values. Figure 2 illustrates use of alignment values together with the placement and planar extent.",
|
||||
"description": "The box alignment specifies the alignment of the text box relative to its position. The following string values shall be used:",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcpresentationdefinitionresource/lexical/ifcboxalignment.htm"
|
||||
},
|
||||
"IfcBuildingElementPartTypeEnum": {
|
||||
@@ -172,11 +172,11 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcelectricaldomain/lexical/ifccablesegmenttypeenum.htm"
|
||||
},
|
||||
"IfcCardinalPointReference": {
|
||||
"description": "An IfcCardinalPointReference is an index reference to significant points of a section profile. This index is used to describe the spatial relationship between the section of a member and a reference axis of the same member. Indexes 1...9 refer to points at the bounding box of a profile. Indexes 10...19 refer to points defined by geometric centroid (usually centre of gravity) and shear centre, and their combinations with bounding box coordinates. In particular, the following index values are specified in this specification: - bottom left - bottom centre - bottom right - mid-depth left - mid-depth centre - mid-depth right - top left - top centre - top right - geometric centroid - bottom in line with the geometric centroid - left in line with the geometric centroid - right in line with the geometric centroid - top in line with the geometric centroid - shear centre - bottom in line with the shear centre - left in line with the shear centre - right in line with the shear centre - top in line with the shear centre Other index values are possible but outside the scope of this specification. Figure 1 illustrates cardinal point values. Figure 2 illustrates an example extrusion shape with arbitrary profile (IfcArbitraryClosedProfileDef), aligned \"mid-depth right\" on the member axis. The line of sight follows the extrusion direction Z which points into the drawing plane of above illustration. Hence, \"left\" is in the positive X direction of the IfcProfileDef. \"Top\" is in the positive Y direction of the IfcProfileDef.",
|
||||
"description": "An IfcCardinalPointReference is an index reference to significant points of a section profile. This index is used to describe the spatial relationship between the section of a member and a reference axis of the same member.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmaterialresource/lexical/ifccardinalpointreference.htm"
|
||||
},
|
||||
"IfcChangeActionEnum": {
|
||||
"description": "IfcChangeActionEnum identifies the type of change that might have occurred to the object during the last session (for example, added, modified, deleted). This information is required in a partial model exchange scenario so that an application or model server will know how an object might have been affected by the previous application. Valid enumerations are: Consider Application A will create an IFC dataset that it wants to publish to others for modification and have the ability to subsequently merge these changes back into the original model. Before publication, it may want to set the IfcChangeActionEnum to NOCHANGE to establish a baseline so that other application changes can be easily identified. Application B then receives this IFC dataset and adds a new object and sets IfcChangeActionEnum to ADDED with Application B defined as the OwningApplication. Application B then modifies an existing object and (re)defines the LastModifiedDate to the time of the modification, LastModifyingUser to the IfcPersonAndOrganization making the change, and sets the LastModifyingApplication to Application B. When Application A receives this modified dataset, it can determine which objects have been added and modified by Application B and either merge or reject these changes as necessary. Consequently, the intent is that an application only modifies the value of IfcChangeActionEnum when it does something to the object, with the further intent that a model server is responsible for clearing the IfcChangeActionEnum back to NOCHANGE when it is ready to be republished.",
|
||||
"description": "IfcChangeActionEnum identifies the type of change that might have occurred to the object during the last session (for example, added, modified, deleted). This information is required in a partial model exchange scenario so that an application or model server will know how an object might have been affected by the previous application. Valid enumerations are:",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcutilityresource/lexical/ifcchangeactionenum.htm"
|
||||
},
|
||||
"IfcChillerTypeEnum": {
|
||||
@@ -216,7 +216,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcelectricaldomain/lexical/ifccommunicationsappliancetypeenum.htm"
|
||||
},
|
||||
"IfcComplexNumber": {
|
||||
"description": "IfcComplexNumber is a representation of a complex number expressed as an array with two elements. The first element (index 1) denotes the real component which is the numerical component of a complex number whose square roots can be calculated explicitly. The second element (index 2) denotes the imaginary component which is the numerical component of a complex number whose square roots cannot be determined other than through the provision of the square of the imaginary number j where j\\^2 = -1. Note that the imaginary component may be referred to as i in certain references. Type: ARRAY [1:2] OF REAL",
|
||||
"description": "IfcComplexNumber is a representation of a complex number expressed as an array with two elements. The first element (index 1) denotes the real component which is the numerical component of a complex number whose square roots can be calculated explicitly. The second element (index 2) denotes the imaginary component which is the numerical component of a complex number whose square roots cannot be determined other than through the provision of the square of the imaginary number j where j\\^2 = -1. Note that the imaginary component may be referred to as i in certain references.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifccomplexnumber.htm"
|
||||
},
|
||||
"IfcComplexPropertyTemplateTypeEnum": {
|
||||
@@ -224,7 +224,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifckernel/lexical/ifccomplexpropertytemplatetypeenum.htm"
|
||||
},
|
||||
"IfcCompoundPlaneAngleMeasure": {
|
||||
"description": "IfcCompoundPlaneAngleMeasure is a compound measure of plane angle in degrees, minutes, seconds, and optionally millionth-seconds of arc. Type: LIST [3:4] OF INTEGER Value restrictions - The first integer measure is the number of degrees and is generally not range-restricted. However, when IfcCompoundPlaneAngleMeasure is used to express geographic coordinates, only latitudes of [-90, 90] and longitudes of [-180, 180] are used in practice. - The second integer measure is the number of minutes and shall be in the range (-60, 60). - The third integer measure is the number of seconds and shall be in the range (-60, 60). - The optional fourth integer measure is the number of millionth-seconds and shall be in the range (-1 000 000, 1 000 000). Signedness All measure components have the same sign (positive or negative). It is therefore trivial to convert between floating point representation (decimal degrees) and compound representation regardless whether the angle is greater or smaller than zero. Example: Use in string representations When a compound plane angle measure is formatted for display or printout, the signs of the fractional components will usually be discarded because, to a human reader, the sign of the first component alone already indicates the sense of the angle: Another often encountered display format of latitudes and longitudes is to omit the signs and print N, S, E, W indicators instead, for example, 50°58'33\"S . When stored as IfcCompoundPlaneAngleMeasure however, a compound plane angle measure is always signed, with same sign of all components.",
|
||||
"description": "IfcCompoundPlaneAngleMeasure is a compound measure of plane angle in degrees, minutes, seconds, and optionally millionth-seconds of arc.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifccompoundplaneanglemeasure.htm"
|
||||
},
|
||||
"IfcCompressorTypeEnum": {
|
||||
@@ -236,7 +236,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifchvacdomain/lexical/ifccondensertypeenum.htm"
|
||||
},
|
||||
"IfcConnectionTypeEnum": {
|
||||
"description": "This enumeration defines the different ways how path based elements (such as IfcWallStandardCase) can connect, as shown in Figure 1. The enumerated items shall be used in the following combinations:",
|
||||
"description": "This enumeration defines the different ways how path based elements (such as IfcWallStandardCase) can connect, as shown in Figure 1.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcsharedbldgelements/lexical/ifcconnectiontypeenum.htm"
|
||||
},
|
||||
"IfcConstraintEnum": {
|
||||
@@ -256,7 +256,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcconstructionmgmtdomain/lexical/ifcconstructionproductresourcetypeenum.htm"
|
||||
},
|
||||
"IfcContextDependentMeasure": {
|
||||
"description": "The value of a physical quantity as defined within the exchange context. Type: REAL",
|
||||
"description": "The value of a physical quantity as defined within the exchange context.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifccontextdependentmeasure.htm"
|
||||
},
|
||||
"IfcControllerTypeEnum": {
|
||||
@@ -284,7 +284,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcsharedmgmtelements/lexical/ifccostscheduletypeenum.htm"
|
||||
},
|
||||
"IfcCountMeasure": {
|
||||
"description": "A count measure is the value of a count of items. Type: NUMBER",
|
||||
"description": "A count measure is the value of a count of items.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifccountmeasure.htm"
|
||||
},
|
||||
"IfcCoveringTypeEnum": {
|
||||
@@ -304,7 +304,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcsharedbldgelements/lexical/ifccurtainwalltypeenum.htm"
|
||||
},
|
||||
"IfcCurvatureMeasure": {
|
||||
"description": "IfcCurvatureMeasure is a measure for curvature, which is defined as the change of slope per length. This is typically a computed value in structural analysis. It is usually measured in rad/m. Type: REAL",
|
||||
"description": "IfcCurvatureMeasure is a measure for curvature, which is defined as the change of slope per length. This is typically a computed value in structural analysis. It is usually measured in rad/m.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifccurvaturemeasure.htm"
|
||||
},
|
||||
"IfcCurveFontOrScaledCurveFontSelect": {
|
||||
@@ -336,19 +336,19 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcdatetimeresource/lexical/ifcdataoriginenum.htm"
|
||||
},
|
||||
"IfcDate": {
|
||||
"description": "The IfcData identifies a particular calender day, expressed by year, calender month and day in month. It is expressed by a string value following a particular lexical representation. The lexical representation for IfcDate is the YYYY-MM-DD, where YYYY represents the calendar year, MM the ordinal number of the calendar month, and DD the ordinal number of the day within the calendar month. No left truncation is allowed. An optional following time zone qualifier is allowed. To accommodate year values outside the range from 0001 to 9999, additional digits can be added to the left of this representation and a preceding \"-\" sign is allowed.",
|
||||
"description": "The IfcData identifies a particular calender day, expressed by year, calender month and day in month. It is expressed by a string value following a particular lexical representation.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcdatetimeresource/lexical/ifcdate.htm"
|
||||
},
|
||||
"IfcDateTime": {
|
||||
"description": "The IfcDataTime identifies a particular point in time, expressed by hours, minutes and optional seconds elapsed within a calender day, expressed by year, calender month and day in month. It is expressed by a string value following a particular lexical representation. This lexical representation for IfcDataTime is YYYY-MM-DDThh:mm:ss where \"YYYY\" represent the year, \"MM\" the month and \"DD\" the day, preceded by an optional leading \"-\" sign to indicate a negative year number. If the sign is omitted, \"+\" is assumed. The letter \"T\" is the date/time separator and \"hh\", \"mm\", \"ss\" represent hour, minute and second respectively. Additional digits can be used to increase the precision of fractional seconds if desired i.e the format ss.ss... with any number of digits after the decimal point is supported. The fractional seconds part is optional; other parts of the lexical form are not optional. To accommodate year values greater than 9999 additional digits can be added to the left of this representation. Leading zeros are required if the year value would otherwise have fewer than four digits; otherwise they are forbidden. The year 0000 is prohibited.",
|
||||
"description": "The IfcDataTime identifies a particular point in time, expressed by hours, minutes and optional seconds elapsed within a calender day, expressed by year, calender month and day in month. It is expressed by a string value following a particular lexical representation.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcdatetimeresource/lexical/ifcdatetime.htm"
|
||||
},
|
||||
"IfcDayInMonthNumber": {
|
||||
"description": "IfcDayInMonthNumber is an integer that defines the position of the specified day in a month. Type: INTEGER",
|
||||
"description": "IfcDayInMonthNumber is an integer that defines the position of the specified day in a month.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcdatetimeresource/lexical/ifcdayinmonthnumber.htm"
|
||||
},
|
||||
"IfcDayInWeekNumber": {
|
||||
"description": "The IfcDayInWeekNumber is an integer that defines the position of the specified day in a week. The positions have the following meaning that assigns the ordinal day number in the week to the Calendar day name. Ordinal day numbers map to calendar days as follows: - 1: Monday - 2: Tuesday - 3: Wednesday - 4: Thursday - 5: Friday - 6: Saturday - 7: Sunday",
|
||||
"description": "The IfcDayInWeekNumber is an integer that defines the position of the specified day in a week. The positions have the following meaning that assigns the ordinal day number in the week to the Calendar day name.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcdatetimeresource/lexical/ifcdayinweeknumber.htm"
|
||||
},
|
||||
"IfcDefinitionSelect": {
|
||||
@@ -356,7 +356,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifckernel/lexical/ifcdefinitionselect.htm"
|
||||
},
|
||||
"IfcDerivedMeasureValue": {
|
||||
"description": "IfcDerivedMeasureValue is a select type for selecting between derived measure types. SELECT - IfcAbsorbedDoseMeasure - IfcAccelerationMeasure - IfcAngularVelocityMeasure - IfcAreaDensityMeasure - IfcCompoundPlaneAngleMeasure - IfcCurvatureMeasure - IfcDoseEquivalentMeasure - IfcDynamicViscosityMeasure - IfcElectricCapacitanceMeasure - IfcElectricChargeMeasure - IfcElectricConductanceMeasure - IfcElectricResistanceMeasure - IfcElectricVoltageMeasure - IfcEnergyMeasure - IfcForceMeasure - IfcFrequencyMeasure - IfcHeatFluxDensityMeasure - IfcHeatingValueMeasure - IfcIlluminanceMeasure - IfcInductanceMeasure - IfcIntegerCountRateMeasure - IfcIonConcentrationMeasure - IfcIsothermalMoistureCapacityMeasure - IfcKinematicViscosityMeasure - IfcLinearForceMeasure - IfcLinearMomentMeasure - IfcLinearStiffnessMeasure - IfcLinearVelocityMeasure - IfcLuminousFluxMeasure - IfcLuminousIntensityDistributionMeasure - IfcMagneticFluxDensityMeasure - IfcMagneticFluxMeasure - IfcMassDensityMeasure - IfcMassFlowRateMeasure - IfcMassPerLengthMeasure - IfcModulusOfElasticityMeasure - IfcModulusOfLinearSubgradeReactionMeasure - IfcModulusOfRotationalSubgradeReactionMeasure - IfcModulusOfSubgradeReactionMeasure - IfcMoistureDiffusivityMeasure - IfcMolecularWeightMeasure - IfcMomentOfInertiaMeasure - IfcMonetaryMeasure - IfcPHMeasure - IfcPlanarForceMeasure - IfcPowerMeasure - IfcPressureMeasure - IfcRadioActivityMeasure - IfcRotationalFrequencyMeasure - IfcRotationalMassMeasure - IfcRotationalStiffnessMeasure - IfcSectionModulusMeasure - IfcSectionalAreaIntegralMeasure - IfcShearModulusMeasure - IfcSoundPowerLevelMeasure - IfcSoundPowerMeasure - IfcSoundPressureLevelMeasure - IfcSoundPressureMeasure - IfcSpecificHeatCapacityMeasure - IfcTemperatureGradientMeasure - IfcTemperatureRateOfChangeMeasure - IfcThermalAdmittanceMeasure - IfcThermalConductivityMeasure - IfcThermalExpansionCoefficientMeasure - IfcThermalResistanceMeasure - IfcThermalTransmittanceMeasure - IfcTorqueMeasure - IfcVaporPermeabilityMeasure - IfcVolumetricFlowRateMeasure - IfcWarpingConstantMeasure - IfcWarpingMomentMeasure",
|
||||
"description": "IfcDerivedMeasureValue is a select type for selecting between derived measure types.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcderivedmeasurevalue.htm"
|
||||
},
|
||||
"IfcDerivedUnitEnum": {
|
||||
@@ -364,7 +364,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcderivedunitenum.htm"
|
||||
},
|
||||
"IfcDescriptiveMeasure": {
|
||||
"description": "A descriptive measure is a human interpretable definition of a quantifiable value. The mode of interpretation has to be established for the exchange context. Type: STRING",
|
||||
"description": "A descriptive measure is a human interpretable definition of a quantifiable value. The mode of interpretation has to be established for the exchange context.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcdescriptivemeasure.htm"
|
||||
},
|
||||
"IfcDimensionCount": {
|
||||
@@ -388,7 +388,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcsharedbldgserviceelements/lexical/ifcdistributionporttypeenum.htm"
|
||||
},
|
||||
"IfcDistributionSystemEnum": {
|
||||
"description": "This enumeration identifies different types of distribution systems. It is used to designate systems by their function as well as ports of devices within such systems to restrict connectivity to compatible connections. Ports for cable carriers may be connected using IfcCableCarrierSegment and IfcCableCarrierFitting. Type objects for cable carrier segments and fittings (IfcCableCarrierSegmentType and IfcCableCarrierFittingType that are not specific to a particular system type may have ports with PredefinedType of NOTDEFINED which indicates that occurrences of such objects may connect to ports of any other cable-carrier based port. Valid enumerations for cable carriers are the same as that for cables, and may be asserted if ports of the contained cables are all of the same type.",
|
||||
"description": "This enumeration identifies different types of distribution systems. It is used to designate systems by their function as well as ports of devices within such systems to restrict connectivity to compatible connections.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcsharedbldgserviceelements/lexical/ifcdistributionsystemenum.htm"
|
||||
},
|
||||
"IfcDocumentConfidentialityEnum": {
|
||||
@@ -404,11 +404,11 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcexternalreferenceresource/lexical/ifcdocumentstatusenum.htm"
|
||||
},
|
||||
"IfcDoorPanelOperationEnum": {
|
||||
"description": "This enumeration defines the basic ways how individual door panels operate as shown in Figure 1. The opening direction of the door panels is given by the local placement of the IfcDoor. The positive y-axis determines the direction as shown in Figure 2.",
|
||||
"description": "This enumeration defines the basic ways how individual door panels operate as shown in Figure 1.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcarchitecturedomain/lexical/ifcdoorpaneloperationenum.htm"
|
||||
},
|
||||
"IfcDoorPanelPositionEnum": {
|
||||
"description": "This enumeration defines the basic ways to describe the location of a door panel within a door lining. Figure 1 shows the designation of a door panel with PanelPosition = LEFT and a door panel with PanelPosition = RIGHT within a door style with OperationType = DOUBLE_DOOR_SINGLE_SWING. The position is given as shown in the XZ plane of the local placement, looking into the direction of the positive Y axis. !(../../../../../../figures/ifcdoorpanelpositionenum-fig01.gif \"Figure 1 \u2014 Door panel positions\")",
|
||||
"description": "This enumeration defines the basic ways to describe the location of a door panel within a door lining.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcarchitecturedomain/lexical/ifcdoorpanelpositionenum.htm"
|
||||
},
|
||||
"IfcDoorStyleConstructionEnum": {
|
||||
@@ -416,7 +416,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcarchitecturedomain/lexical/ifcdoorstyleconstructionenum.htm"
|
||||
},
|
||||
"IfcDoorStyleOperationEnum": {
|
||||
"description": "This enumeration defines the basic ways to describe how doors operate as shown in Figure 1. NOTE - Figures are shown in the ground view. - Figures (symbolic representation) depend on the national building code. - These figures are only shown as illustrations, the actual representation in the ground view might differ. - Open to the outside is declared as open into the direction of the positive y-axis, determined by the ObjectPlacement at IfcDoor - The location of the panel relative to the wall thickness is defined by the ObjectPlacement at IfcDoor, and the IfcDoorLiningProperties.LiningOffset parameter.",
|
||||
"description": "This enumeration defines the basic ways to describe how doors operate as shown in Figure 1.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcarchitecturedomain/lexical/ifcdoorstyleoperationenum.htm"
|
||||
},
|
||||
"IfcDoorTypeEnum": {
|
||||
@@ -424,15 +424,15 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcsharedbldgelements/lexical/ifcdoortypeenum.htm"
|
||||
},
|
||||
"IfcDoorTypeOperationEnum": {
|
||||
"description": "This enumeration defines the basic ways to describe how doors operate, as shown in Figure 1. It combines the partitioning of the door into a single or multiple door panels and the operation types of that panels. In the most common case of swinging doors the IfcDoorTypeOperationEnum defined the hinge side (left hing or right hung) and the opening direction (opening to the left, opening to the right). Whether the door opens inwards or outwards is determined by the local coordinate system of the IfcDoor, or IfcDoorStandardCase. NOTE - Figures are shown in the ground view. - Figures (symbolic representation) depend on the national building code. - These figures are only shown as illustrations, the actual representation in the ground view might differ. - Open to the outside is declared as open into the direction of the positive y-axis, determined by the ObjectPlacement at IfcDoor - The location of the panel relative to the wall thickness is defined by the ObjectPlacement at IfcDoor, and the IfcDoorLiningProperties.LiningOffset parameter.",
|
||||
"description": "This enumeration defines the basic ways to describe how doors operate, as shown in Figure 1. It combines the partitioning of the door into a single or multiple door panels and the operation types of that panels.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcsharedbldgelements/lexical/ifcdoortypeoperationenum.htm"
|
||||
},
|
||||
"IfcDoseEquivalentMeasure": {
|
||||
"description": "IfcDoseEquivalentMeasure is a measure of the radioactive dose equivalent. Usually measured in Sievert (Sv, J/kg). Type: REAL",
|
||||
"description": "IfcDoseEquivalentMeasure is a measure of the radioactive dose equivalent.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcdoseequivalentmeasure.htm"
|
||||
},
|
||||
"IfcDuctFittingTypeEnum": {
|
||||
"description": "This enumeration is used to identify the primary purpose of a duct fitting. This is a very basic categorization mechanism to generically identify the duct fitting type. Subcategories of duct fittings are not enumerated. Enumerated Item Definitions: - BEND: A fitting with typically two ports used to change the direction of flow between connected elements. - CONNECTOR: Connector fitting, typically used to join two ports together within a flow distribution system (e.g., a coupling used to join two duct segments). - ENTRY: Entry fitting, typically unconnected at one port and connected to a flow distribution system at the other (e.g., an outside air duct system intake opening). - EXIT: Exit fitting, typically unconnected at one port and connected to a flow distribution system at the other (e.g., an exhaust air discharge opening). - JUNCTION: A fitting with typically more than two ports used to redistribute flow among the ports and/or to change the direction of flow between connected elements (e.g, tee, cross, wye, etc.). - OBSTRUCTION: A fitting with typically two ports used to obstruct or restrict flow between the connected elements (e.g., screen, perforated plate, etc.). - TRANSITION: A fitting with typically two ports having different shapes or sizes. Can also be used to change the direction of flow between connected elements. - USERDEFINED: User-defined fitting. - NOTDEFINED: Undefined fitting.",
|
||||
"description": "This enumeration is used to identify the primary purpose of a duct fitting. This is a very basic categorization mechanism to generically identify the duct fitting type. Subcategories of duct fittings are not enumerated.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifchvacdomain/lexical/ifcductfittingtypeenum.htm"
|
||||
},
|
||||
"IfcDuctSegmentTypeEnum": {
|
||||
@@ -444,11 +444,11 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifchvacdomain/lexical/ifcductsilencertypeenum.htm"
|
||||
},
|
||||
"IfcDuration": {
|
||||
"description": "The IfcDuration identifies a quantity of time (or a \"lenght\" of an event occurring in time). This lexical representation for IfcDataTime is PnYnMnDTnHnMnS, where nY represents the number of years, nM the number of months, nD the number of days, 'T' is the date/time separator, nH the number of hours, nM the number of minutes and nS the number of seconds. The number of seconds can include decimal digits to arbitrary precision.",
|
||||
"description": "The IfcDuration identifies a quantity of time (or a \"lenght\" of an event occurring in time).",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcdatetimeresource/lexical/ifcduration.htm"
|
||||
},
|
||||
"IfcDynamicViscosityMeasure": {
|
||||
"description": "IfcDynamicViscosityMeasure is a measure of the viscous resistance of a medium. Usually measured in Pascal second (Pa s). Type: REAL",
|
||||
"description": "IfcDynamicViscosityMeasure is a measure of the viscous resistance of a medium.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcdynamicviscositymeasure.htm"
|
||||
},
|
||||
"IfcElectricApplianceTypeEnum": {
|
||||
@@ -456,19 +456,19 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcelectricaldomain/lexical/ifcelectricappliancetypeenum.htm"
|
||||
},
|
||||
"IfcElectricCapacitanceMeasure": {
|
||||
"description": "IfcElectricCapacitanceMeasure is a measure of the electric capacitance. Usually measured in Farad (F, C/V = A s/V). Type: REAL",
|
||||
"description": "IfcElectricCapacitanceMeasure is a measure of the electric capacitance.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcelectriccapacitancemeasure.htm"
|
||||
},
|
||||
"IfcElectricChargeMeasure": {
|
||||
"description": "IfcElectricChargeMeasure is a measure of the electric charge. Usually measured in Coulomb (C, A s). Type: REAL",
|
||||
"description": "IfcElectricChargeMeasure is a measure of the electric charge.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcelectricchargemeasure.htm"
|
||||
},
|
||||
"IfcElectricConductanceMeasure": {
|
||||
"description": "IfcElectricConductanceMeasure is a measure of the electric conductance. Usually measured in Siemens (S, 1/Ohm = A/V). Type: REAL",
|
||||
"description": "IfcElectricConductanceMeasure is a measure of the electric conductance.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcelectricconductancemeasure.htm"
|
||||
},
|
||||
"IfcElectricCurrentMeasure": {
|
||||
"description": "The value for the movement of electrically charged particles. Usually measured in Ampere (A). Type: REAL",
|
||||
"description": "The value for the movement of electrically charged particles.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcelectriccurrentmeasure.htm"
|
||||
},
|
||||
"IfcElectricDistributionBoardTypeEnum": {
|
||||
@@ -488,7 +488,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcelectricaldomain/lexical/ifcelectricmotortypeenum.htm"
|
||||
},
|
||||
"IfcElectricResistanceMeasure": {
|
||||
"description": "IfcElectricResistanceMeasure is a measure of the electric resistance. Usually measured in Ohm (V/A). Type: REAL",
|
||||
"description": "IfcElectricResistanceMeasure is a measure of the electric resistance.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcelectricresistancemeasure.htm"
|
||||
},
|
||||
"IfcElectricTimeControlTypeEnum": {
|
||||
@@ -496,7 +496,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcelectricaldomain/lexical/ifcelectrictimecontroltypeenum.htm"
|
||||
},
|
||||
"IfcElectricVoltageMeasure": {
|
||||
"description": "IfcElectricVoltageMeasure is a measure of electromotive force. Usually measured in Volts (V, W/A). Type: REAL",
|
||||
"description": "IfcElectricVoltageMeasure is a measure of electromotive force.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcelectricvoltagemeasure.htm"
|
||||
},
|
||||
"IfcElementAssemblyTypeEnum": {
|
||||
@@ -508,7 +508,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcproductextension/lexical/ifcelementcompositionenum.htm"
|
||||
},
|
||||
"IfcEnergyMeasure": {
|
||||
"description": "IfcEnergyMeasure is a measure of energy required or used. Usually measured in Joules, (J, Nm). Type: REAL",
|
||||
"description": "IfcEnergyMeasure is a measure of energy required or used.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcenergymeasure.htm"
|
||||
},
|
||||
"IfcEngineTypeEnum": {
|
||||
@@ -556,7 +556,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcplumbingfireprotectiondomain/lexical/ifcfiresuppressionterminaltypeenum.htm"
|
||||
},
|
||||
"IfcFlowDirectionEnum": {
|
||||
"description": "This enumeration defines the flow direction at a distribution port. - For pipe-based ports, the direction is the physical flow direction. - For duct-based ports, the direction is the physical flow direction.",
|
||||
"description": "This enumeration defines the flow direction at a distribution port.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcsharedbldgserviceelements/lexical/ifcflowdirectionenum.htm"
|
||||
},
|
||||
"IfcFlowInstrumentTypeEnum": {
|
||||
@@ -568,15 +568,15 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifchvacdomain/lexical/ifcflowmetertypeenum.htm"
|
||||
},
|
||||
"IfcFontStyle": {
|
||||
"description": "The IfcFontStyle type defines whether the normal, the italic or the oblique faces within a font family shall be used. Values are: - normal - italic - oblique",
|
||||
"description": "The IfcFontStyle type defines whether the normal, the italic or the oblique faces within a font family shall be used. Values are:",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcpresentationappearanceresource/lexical/ifcfontstyle.htm"
|
||||
},
|
||||
"IfcFontVariant": {
|
||||
"description": "The IfcFontVariant type defines whether the normal or the small-caps faces within a font family shall be used. Values are: - normal - small-caps",
|
||||
"description": "The IfcFontVariant type defines whether the normal or the small-caps faces within a font family shall be used. Values are:",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcpresentationappearanceresource/lexical/ifcfontvariant.htm"
|
||||
},
|
||||
"IfcFontWeight": {
|
||||
"description": "The IfcFontWeight type defines the weight of the font. Values are: - normal - bold - 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900",
|
||||
"description": "The IfcFontWeight type defines the weight of the font. Values are:",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcpresentationappearanceresource/lexical/ifcfontweight.htm"
|
||||
},
|
||||
"IfcFootingTypeEnum": {
|
||||
@@ -584,11 +584,11 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcstructuralelementsdomain/lexical/ifcfootingtypeenum.htm"
|
||||
},
|
||||
"IfcForceMeasure": {
|
||||
"description": "IfcForceMeasure is a measure of the force. Usually measured in Newton (N, kg m/s2). Type: REAL",
|
||||
"description": "IfcForceMeasure is a measure of the force.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcforcemeasure.htm"
|
||||
},
|
||||
"IfcFrequencyMeasure": {
|
||||
"description": "IfcFrequencyMeasure is a measure of the number of times that an item vibrates in unit time. Usually measured in cycles/s or Hertz (Hz). Type: REAL",
|
||||
"description": "IfcFrequencyMeasure is a measure of the number of times that an item vibrates in unit time.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcfrequencymeasure.htm"
|
||||
},
|
||||
"IfcFurnitureTypeEnum": {
|
||||
@@ -612,7 +612,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcrepresentationresource/lexical/ifcglobalorlocalenum.htm"
|
||||
},
|
||||
"IfcGloballyUniqueId": {
|
||||
"description": "An IfcGloballyUniqueId holds an encoded string identifier that is used to uniquely identify an IFC object. An IfcGloballyUniqueId is a Globally Unique Identifier (GUID) which is an auto-generated 128-bit number. Since this identifier is required for all IFC object instances, it is desirable to compress it to reduce overhead. The encoding of the base 64 character set is shown below: The resulting string is a fixed 22 character length string to be exchanged within the IFC exchange file structure.",
|
||||
"description": "An IfcGloballyUniqueId holds an encoded string identifier that is used to uniquely identify an IFC object. An IfcGloballyUniqueId is a Globally Unique Identifier (GUID) which is an auto-generated 128-bit number. Since this identifier is required for all IFC object instances, it is desirable to compress it to reduce overhead. The encoding of the base 64 character set is shown below:",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcutilityresource/lexical/ifcgloballyuniqueid.htm"
|
||||
},
|
||||
"IfcGridPlacementDirectionSelect": {
|
||||
@@ -624,7 +624,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcproductextension/lexical/ifcgridtypeenum.htm"
|
||||
},
|
||||
"IfcHatchLineDistanceSelect": {
|
||||
"description": "The IfcHatchLineDistanceSelect is a selection between different ways to determine the distance and optionally the start point of hatch lines, either by an offset distance measure or by a vector. The vector, if selected, acts as a one time repeat factor in the fill area style hatching for determining the origin of the repeated hatch line relative to the origin of the previous hatch line, Given the initial position of any hatch line, the one direction repeat factor determines two new positions according to the equation:",
|
||||
"description": "The IfcHatchLineDistanceSelect is a selection between different ways to determine the distance and optionally the start point of hatch lines, either by an offset distance measure or by a vector.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcpresentationappearanceresource/lexical/ifchatchlinedistanceselect.htm"
|
||||
},
|
||||
"IfcHeatExchangerTypeEnum": {
|
||||
@@ -632,7 +632,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifchvacdomain/lexical/ifcheatexchangertypeenum.htm"
|
||||
},
|
||||
"IfcHeatFluxDensityMeasure": {
|
||||
"description": "IfcHeatFluxDensityMeasure is a measure of the density of heat flux within a body. Usually measured in W/m2 (J/s m2). Type: REAL",
|
||||
"description": "IfcHeatFluxDensityMeasure is a measure of the density of heat flux within a body.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcheatfluxdensitymeasure.htm"
|
||||
},
|
||||
"IfcHeatingValueMeasure": {
|
||||
@@ -644,23 +644,23 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifchvacdomain/lexical/ifchumidifiertypeenum.htm"
|
||||
},
|
||||
"IfcIdentifier": {
|
||||
"description": "An identifier is an alphanumeric string which allows an individual thing to be identified. It may not provide natural-language meaning. Type: STRING of up to 255 characters Value restrictions As a merely machine-readable string for identification purposes, an identifier is usually machine-generated and locale-independent (in contrast to human-readable labels, IfcLabel).",
|
||||
"description": "An identifier is an alphanumeric string which allows an individual thing to be identified. It may not provide natural-language meaning.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcidentifier.htm"
|
||||
},
|
||||
"IfcIlluminanceMeasure": {
|
||||
"description": "IfcIlluminanceMeasure is a measure of the illuminance. Usually measured in Lux (lx, Lumen/m2 = Candela Steradian/m2). Type: REAL",
|
||||
"description": "IfcIlluminanceMeasure is a measure of the illuminance.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcilluminancemeasure.htm"
|
||||
},
|
||||
"IfcInductanceMeasure": {
|
||||
"description": "IfcInductanceMeasure is a measure of the inductance. Usually measure in Henry (H, Weber/A = V s/A). Type: REAL",
|
||||
"description": "IfcInductanceMeasure is a measure of the inductance.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcinductancemeasure.htm"
|
||||
},
|
||||
"IfcInteger": {
|
||||
"description": "IfcInteger is a defined type of simple data type Integer. It is required since a select type (IfcSimpleValue) cannot include directly simple types in its select list. In principle, the domain of IfcInteger (being an Integer) is all integer numbers. Here the number of bits used for the IfcInteger representation is unconstrained, but in practice it is implementation specific. Type: INTEGER",
|
||||
"description": "IfcInteger is a defined type of simple data type Integer. It is required since a select type (IfcSimpleValue) cannot include directly simple types in its select list.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcinteger.htm"
|
||||
},
|
||||
"IfcIntegerCountRateMeasure": {
|
||||
"description": "IfcIntegerCountRateMeasure is a measure of the integer number of units flowing per unit time. Type: INTEGER",
|
||||
"description": "IfcIntegerCountRateMeasure is a measure of the integer number of units flowing per unit time.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcintegercountratemeasure.htm"
|
||||
},
|
||||
"IfcInterceptorTypeEnum": {
|
||||
@@ -680,7 +680,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcionconcentrationmeasure.htm"
|
||||
},
|
||||
"IfcIsothermalMoistureCapacityMeasure": {
|
||||
"description": "IfcIsothermalMoistureCapacityMeasure is a measure of isothermal moisture capacity. Usually measured in m3/kg. Type: REAL",
|
||||
"description": "IfcIsothermalMoistureCapacityMeasure is a measure of isothermal moisture capacity.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcisothermalmoisturecapacitymeasure.htm"
|
||||
},
|
||||
"IfcJunctionBoxTypeEnum": {
|
||||
@@ -688,7 +688,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcelectricaldomain/lexical/ifcjunctionboxtypeenum.htm"
|
||||
},
|
||||
"IfcKinematicViscosityMeasure": {
|
||||
"description": "IfcKinematicViscosityMeasure is a measure of the viscous resistance of a medium to a moving body. Usually measured in m2/s. Type: REAL",
|
||||
"description": "IfcKinematicViscosityMeasure is a measure of the viscous resistance of a medium to a moving body.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifckinematicviscositymeasure.htm"
|
||||
},
|
||||
"IfcKnotType": {
|
||||
@@ -696,7 +696,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcgeometryresource/lexical/ifcknottype.htm"
|
||||
},
|
||||
"IfcLabel": {
|
||||
"description": "A label is the term by which something may be referred to. It is a string which represents the human-interpretable name of something and shall have a natural-language meaning. Type: STRING of up to 255 characters Value restrictions As a human-readable string for naming purposes, a label is usually human-specified and locale-dependent (in contrast to purely machine-readable identifiers, IfcIdentifier).",
|
||||
"description": "A label is the term by which something may be referred to. It is a string which represents the human-interpretable name of something and shall have a natural-language meaning.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifclabel.htm"
|
||||
},
|
||||
"IfcLaborResourceTypeEnum": {
|
||||
@@ -708,7 +708,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcelectricaldomain/lexical/ifclamptypeenum.htm"
|
||||
},
|
||||
"IfcLanguageId": {
|
||||
"description": "The IfcLanguageId identifies the language in which a natural language text is expressed. It uses a language tag to identify the language. Type: IfcIdentifier",
|
||||
"description": "The IfcLanguageId identifies the language in which a natural language text is expressed. It uses a language tag to identify the language.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcexternalreferenceresource/lexical/ifclanguageid.htm"
|
||||
},
|
||||
"IfcLayerSetDirectionEnum": {
|
||||
@@ -720,7 +720,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcpresentationorganizationresource/lexical/ifclayereditem.htm"
|
||||
},
|
||||
"IfcLengthMeasure": {
|
||||
"description": "An IfcLengthMeasure is the value of a distance. Usually measured in millimeters (mm). Type: REAL",
|
||||
"description": "An IfcLengthMeasure is the value of a distance.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifclengthmeasure.htm"
|
||||
},
|
||||
"IfcLibrarySelect": {
|
||||
@@ -732,7 +732,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcpresentationorganizationresource/lexical/ifclightdistributioncurveenum.htm"
|
||||
},
|
||||
"IfcLightDistributionDataSourceSelect": {
|
||||
"description": "A goniometric light gets its intensity distribution function (how much light goes in any one direction) from one of two sources: (i) an industry-standard file, (ii) from distribution data passed directly via the IfcLightIntensityDistribution. The light distribution provides the luminous intensity distribution according to some standardized light distribution curves. SELECT",
|
||||
"description": "A goniometric light gets its intensity distribution function (how much light goes in any one direction) from one of two sources: (i) an industry-standard file, (ii) from distribution data passed directly via the IfcLightIntensityDistribution.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcpresentationorganizationresource/lexical/ifclightdistributiondatasourceselect.htm"
|
||||
},
|
||||
"IfcLightEmissionSourceEnum": {
|
||||
@@ -748,19 +748,19 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcgeometryresource/lexical/ifclineindex.htm"
|
||||
},
|
||||
"IfcLinearForceMeasure": {
|
||||
"description": "IfcLinearForceMeasure is a measure of linear force. Usually measured in N/m. Type: REAL",
|
||||
"description": "IfcLinearForceMeasure is a measure of linear force.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifclinearforcemeasure.htm"
|
||||
},
|
||||
"IfcLinearMomentMeasure": {
|
||||
"description": "IfcLinearMomentMeasure is a measure of linear moment. Usually measured in Nm/m. Type: REAL",
|
||||
"description": "IfcLinearMomentMeasure is a measure of linear moment.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifclinearmomentmeasure.htm"
|
||||
},
|
||||
"IfcLinearStiffnessMeasure": {
|
||||
"description": "IfcLinearStiffnessMeasure is a measure of linear stiffness. Usually measured in N/m. Type: REAL",
|
||||
"description": "IfcLinearStiffnessMeasure is a measure of linear stiffness.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifclinearstiffnessmeasure.htm"
|
||||
},
|
||||
"IfcLinearVelocityMeasure": {
|
||||
"description": "IfcLinearVelocityMeasure is a measure of the velocity of a body measured in terms of distance moved per unit time. Usually measured in m/s. Type: REAL",
|
||||
"description": "IfcLinearVelocityMeasure is a measure of the velocity of a body measured in terms of distance moved per unit time.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifclinearvelocitymeasure.htm"
|
||||
},
|
||||
"IfcLoadGroupTypeEnum": {
|
||||
@@ -768,51 +768,51 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcstructuralanalysisdomain/lexical/ifcloadgrouptypeenum.htm"
|
||||
},
|
||||
"IfcLogical": {
|
||||
"description": "IfcLogical_IfcSimpleValue) cannot directly include simple types in its select list). Logical datatype can have values TRUE, FALSE or UNKNOWN._ Type: LOGICAL",
|
||||
"description": "IfcLogical_IfcSimpleValue) cannot directly include simple types in its select list). Logical datatype can have values TRUE, FALSE or UNKNOWN._",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifclogical.htm"
|
||||
},
|
||||
"IfcLogicalOperatorEnum": {
|
||||
"description": "IfcLogicalOperatorEnum is an enumeration that defines the logical operators that may be applied for the satisfaction of one or more operands (IfcConstraint) at a time. Table 1 illustrates application of IfcLogicalOperatorEnum in a case of three operands, A, B and C, for each operator.",
|
||||
"description": "IfcLogicalOperatorEnum is an enumeration that defines the logical operators that may be applied for the satisfaction of one or more operands (IfcConstraint) at a time.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcconstraintresource/lexical/ifclogicaloperatorenum.htm"
|
||||
},
|
||||
"IfcLuminousFluxMeasure": {
|
||||
"description": "IfcLuminousFluxMeasure is a measure of the luminous flux. Usually measured in Lumen (lm, Candela Steradian). Type: REAL",
|
||||
"description": "IfcLuminousFluxMeasure is a measure of the luminous flux.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcluminousfluxmeasure.htm"
|
||||
},
|
||||
"IfcLuminousIntensityDistributionMeasure": {
|
||||
"description": "IfcLuminousIntensityDistributionMeasure is a measure of the luminous intensity of a light source that changes according to the direction of the ray. It is normally based on some standardized distribution light distribution curves. Usually measured in Candela/Lumen (cd/lm) or (cd/klm). Type: REAL",
|
||||
"description": "IfcLuminousIntensityDistributionMeasure is a measure of the luminous intensity of a light source that changes according to the direction of the ray. It is normally based on some standardized distribution light distribution curves.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcluminousintensitydistributionmeasure.htm"
|
||||
},
|
||||
"IfcLuminousIntensityMeasure": {
|
||||
"description": "An IfcLuminousIntensityMeasure is the value for the brightness of a body. Usually measured in candela (cd). Type: REAL",
|
||||
"description": "An IfcLuminousIntensityMeasure is the value for the brightness of a body.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcluminousintensitymeasure.htm"
|
||||
},
|
||||
"IfcMagneticFluxDensityMeasure": {
|
||||
"description": "IfcMagneticFluxDensityMeasure is a measure of the magnetic flux density. Usually measured in Tesla (T, Weber/m2 = V s/m2). Type: REAL",
|
||||
"description": "IfcMagneticFluxDensityMeasure is a measure of the magnetic flux density.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcmagneticfluxdensitymeasure.htm"
|
||||
},
|
||||
"IfcMagneticFluxMeasure": {
|
||||
"description": "IfcMagneticFluxMeasure is a measure of the magnetic flux. Usually measured in Weber (Wb, V s). Type: REAL",
|
||||
"description": "IfcMagneticFluxMeasure is a measure of the magnetic flux.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcmagneticfluxmeasure.htm"
|
||||
},
|
||||
"IfcMassDensityMeasure": {
|
||||
"description": "IfcMassDensityMeasure is a measure of the density of a medium. Usually measured in kg/m3. Type: REAL",
|
||||
"description": "IfcMassDensityMeasure is a measure of the density of a medium.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcmassdensitymeasure.htm"
|
||||
},
|
||||
"IfcMassFlowRateMeasure": {
|
||||
"description": "IfcMassFlowRateMeasure is a measure of the mass of a medium flowing per unit time. Usually measured in kg/s. Type: REAL",
|
||||
"description": "IfcMassFlowRateMeasure is a measure of the mass of a medium flowing per unit time.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcmassflowratemeasure.htm"
|
||||
},
|
||||
"IfcMassMeasure": {
|
||||
"description": "An IfcMassMeasure is the value of the amount of matter that a body contains. Usually measured in kilograms (kg) or grams (g). Type: REAL",
|
||||
"description": "An IfcMassMeasure is the value of the amount of matter that a body contains.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcmassmeasure.htm"
|
||||
},
|
||||
"IfcMassPerLengthMeasure": {
|
||||
"description": "IfcMassPerLengthMeasure is a measure for mass per length. For example for rolled steel profiles the weight of an imaginary beam is usually expressed by kg/m length for cost calculation and structural analysis purposes. Type: REAL",
|
||||
"description": "IfcMassPerLengthMeasure is a measure for mass per length. For example for rolled steel profiles the weight of an imaginary beam is usually expressed by kg/m length for cost calculation and structural analysis purposes.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcmassperlengthmeasure.htm"
|
||||
},
|
||||
"IfcMaterialSelect": {
|
||||
"description": "IfcMaterialSelect provides selection of either a material definition or a material usage definition that can be assigned to an element, a resource or another entity within this specification. - IfcMaterialDefinition IfcMaterial IfcMaterialLayer IfcMaterialLayerSet IfcMaterialProfile IfcMaterialProfileSet IfcMaterialConstituent IfcMaterialConstituentSet - IfcMaterialUsageDefinition IfcMaterialLayerSetUsage IfcMaterialProfileSetUsage - IfcMaterialList",
|
||||
"description": "IfcMaterialSelect provides selection of either a material definition or a material usage definition that can be assigned to an element, a resource or another entity within this specification.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmaterialresource/lexical/ifcmaterialselect.htm"
|
||||
},
|
||||
"IfcMeasureValue": {
|
||||
@@ -832,19 +832,19 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcsharedbldgelements/lexical/ifcmembertypeenum.htm"
|
||||
},
|
||||
"IfcMetricValueSelect": {
|
||||
"description": "IfcMetricValueSelect is a select type that enables selection of the data type for the value component of an IfcMetric. Types are used as follows: - IfcValue: A constant value using project default units. - IfcMeasureWithUnit: A constant value using specified units. - IfcAppliedValue: A value calculated from a formula. - IfcTable: A value retrieved from a table. - IfcTimeSeries: A value that varies over time. - IfcReference: A value referenced on an object attribute.",
|
||||
"description": "IfcMetricValueSelect is a select type that enables selection of the data type for the value component of an IfcMetric.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcconstraintresource/lexical/ifcmetricvalueselect.htm"
|
||||
},
|
||||
"IfcModulusOfElasticityMeasure": {
|
||||
"description": "IfcModulusOfElasticityMeasure is a measure of modulus of elasticity. Usually measured in N/m2. Type: REAL",
|
||||
"description": "IfcModulusOfElasticityMeasure is a measure of modulus of elasticity.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcmodulusofelasticitymeasure.htm"
|
||||
},
|
||||
"IfcModulusOfLinearSubgradeReactionMeasure": {
|
||||
"description": "IfcModulusOfLinearSubgradeReactionMeasure is a measure for modulus of linear subgrade reaction, which expresses the elastic bedding of a linear structural element per length, such as for a beam. It is typically measured in N/m\\^2. Type: REAL",
|
||||
"description": "IfcModulusOfLinearSubgradeReactionMeasure is a measure for modulus of linear subgrade reaction, which expresses the elastic bedding of a linear structural element per length, such as for a beam. It is typically measured in N/m\\^2.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcmodulusoflinearsubgradereactionmeasure.htm"
|
||||
},
|
||||
"IfcModulusOfRotationalSubgradeReactionMeasure": {
|
||||
"description": "IfcModulusOfRotationalSubgradeReactionMeasure is a measure for modulus of rotational subgrade reaction, which expresses the rotational elastic bedding of a linear structural element per length, such as for a beam. It is typically measured in Nm/(m*rad). Type: REAL",
|
||||
"description": "IfcModulusOfRotationalSubgradeReactionMeasure is a measure for modulus of rotational subgrade reaction, which expresses the rotational elastic bedding of a linear structural element per length, such as for a beam. It is typically measured in Nm/(m*rad).",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcmodulusofrotationalsubgradereactionmeasure.htm"
|
||||
},
|
||||
"IfcModulusOfRotationalSubgradeReactionSelect": {
|
||||
@@ -852,7 +852,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcstructuralloadresource/lexical/ifcmodulusofrotationalsubgradereactionselect.htm"
|
||||
},
|
||||
"IfcModulusOfSubgradeReactionMeasure": {
|
||||
"description": "IfcModulusOfSubgradeReactionMeasure is a geotechnical measure describing interaction between foundation structures and the soil. May also be known as bedding measure. Usually measured in N/m3. Type: REAL Figure 1 illustrates elastic support of a planar member. !(../../../../../../figures/ifcmodulusofsubgradereactionmeasure.gif \"Figure 1 \u2014 Modulus of subgrade reaction measure\")",
|
||||
"description": "IfcModulusOfSubgradeReactionMeasure is a geotechnical measure describing interaction between foundation structures and the soil. May also be known as bedding measure.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcmodulusofsubgradereactionmeasure.htm"
|
||||
},
|
||||
"IfcModulusOfSubgradeReactionSelect": {
|
||||
@@ -864,23 +864,23 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcstructuralloadresource/lexical/ifcmodulusoftranslationalsubgradereactionselect.htm"
|
||||
},
|
||||
"IfcMoistureDiffusivityMeasure": {
|
||||
"description": "IfcMoistureDiffusivityMeasure is a measure of moisture diffusivity. Usually measured in m3/s. Type: REAL",
|
||||
"description": "IfcMoistureDiffusivityMeasure is a measure of moisture diffusivity.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcmoisturediffusivitymeasure.htm"
|
||||
},
|
||||
"IfcMolecularWeightMeasure": {
|
||||
"description": "IfcMolecularWeightMeasure is a measure of molecular weight of material (typically gas). Usually measured in g/mole. Type: REAL",
|
||||
"description": "IfcMolecularWeightMeasure is a measure of molecular weight of material (typically gas).",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcmolecularweightmeasure.htm"
|
||||
},
|
||||
"IfcMomentOfInertiaMeasure": {
|
||||
"description": "IfcMomentOfInertiaMeasure is a measure of moment of inertia. Usually measured in m4. Type: REAL",
|
||||
"description": "IfcMomentOfInertiaMeasure is a measure of moment of inertia.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcmomentofinertiameasure.htm"
|
||||
},
|
||||
"IfcMonetaryMeasure": {
|
||||
"description": "A monetary measure is the value of an amount of money without regard to its currency. Type: REAL",
|
||||
"description": "A monetary measure is the value of an amount of money without regard to its currency.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcmonetarymeasure.htm"
|
||||
},
|
||||
"IfcMonthInYearNumber": {
|
||||
"description": "IfcMonthInYearNumber is an integer that defines the position of the specified month in a year. Calendar month numbers map to calendar month names as follows: - 1: January - 2: February - 3: March - 4: April - 5: May - 6: June - 7: July - 8: August - 9: September - 10: October - 11: November - 12: December Type: INTEGER",
|
||||
"description": "IfcMonthInYearNumber is an integer that defines the position of the specified month in a year.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcdatetimeresource/lexical/ifcmonthinyearnumber.htm"
|
||||
},
|
||||
"IfcMotorConnectionTypeEnum": {
|
||||
@@ -888,11 +888,11 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcelectricaldomain/lexical/ifcmotorconnectiontypeenum.htm"
|
||||
},
|
||||
"IfcNonNegativeLengthMeasure": {
|
||||
"description": "A non-negative length measure is a length measure that is greater than or equal to zero. Type: IfcLengthMeasure",
|
||||
"description": "A non-negative length measure is a length measure that is greater than or equal to zero.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcnonnegativelengthmeasure.htm"
|
||||
},
|
||||
"IfcNormalisedRatioMeasure": {
|
||||
"description": "IfcNormalisedRatioMeasure is a dimensionless measure to express ratio values ranging from 0.0 to 1.0. Type: REAL",
|
||||
"description": "IfcNormalisedRatioMeasure is a dimensionless measure to express ratio values ranging from 0.0 to 1.0.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcnormalisedratiomeasure.htm"
|
||||
},
|
||||
"IfcNullStyle": {
|
||||
@@ -900,7 +900,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcpresentationappearanceresource/lexical/ifcnullstyle.htm"
|
||||
},
|
||||
"IfcNumericMeasure": {
|
||||
"description": "An IfcNumericMeasure is the numeric value of a physical quantity. Type: NUMBER",
|
||||
"description": "An IfcNumericMeasure is the numeric value of a physical quantity.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcnumericmeasure.htm"
|
||||
},
|
||||
"IfcObjectReferenceSelect": {
|
||||
@@ -932,11 +932,11 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcphmeasure.htm"
|
||||
},
|
||||
"IfcParameterValue": {
|
||||
"description": "An IfcParameterValue is the value which specifies the amount of a parameter in some parameter space. Type: REAL",
|
||||
"description": "An IfcParameterValue is the value which specifies the amount of a parameter in some parameter space.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcparametervalue.htm"
|
||||
},
|
||||
"IfcPerformanceHistoryTypeEnum": {
|
||||
"description": "This enumeration is used to identify the primary purpose of performance history. The IfcPerformanceHistoryTypeEnum contains the following: - USERDEFINED: User-defined. - NOTDEFINED: Undefined.",
|
||||
"description": "This enumeration is used to identify the primary purpose of performance history. The IfcPerformanceHistoryTypeEnum contains the following:",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifccontrolextension/lexical/ifcperformancehistorytypeenum.htm"
|
||||
},
|
||||
"IfcPermeableCoveringOperationEnum": {
|
||||
@@ -968,11 +968,11 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifchvacdomain/lexical/ifcpipesegmenttypeenum.htm"
|
||||
},
|
||||
"IfcPlanarForceMeasure": {
|
||||
"description": "IfcPlanarForceMeasure is a measure of force on an area. Usually measured in N/m2. Type: REAL",
|
||||
"description": "IfcPlanarForceMeasure is a measure of force on an area.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcplanarforcemeasure.htm"
|
||||
},
|
||||
"IfcPlaneAngleMeasure": {
|
||||
"description": "An IfcPlaneAngleMeasure is the value of an angle in a plane. Usually measured in radian (rad, m/m = 1), but also grads may be used. The grad unit has to be declared as a conversion based unit based on radian unit. Type: REAL",
|
||||
"description": "An IfcPlaneAngleMeasure is the value of an angle in a plane.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcplaneanglemeasure.htm"
|
||||
},
|
||||
"IfcPlateTypeEnum": {
|
||||
@@ -984,23 +984,23 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcgeometricconstraintresource/lexical/ifcpointorvertexpoint.htm"
|
||||
},
|
||||
"IfcPositiveInteger": {
|
||||
"description": "IfcPositiveInteger is a defined type based on simple data type Integer with the additional restriction to positive integers (excluding zero). In principle, the domain of IfcInteger is all integer numbers larger than zero. Here the number of bits used for the IfcInteger representation is unconstrained, but in practice it is implementation specific. Type: INTEGER",
|
||||
"description": "IfcPositiveInteger is a defined type based on simple data type Integer with the additional restriction to positive integers (excluding zero).",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcpositiveinteger.htm"
|
||||
},
|
||||
"IfcPositiveLengthMeasure": {
|
||||
"description": "An IfcPositiveLengthMeasure is a length measure that is greater than zero. Type: IfcLengthMeasure",
|
||||
"description": "An IfcPositiveLengthMeasure is a length measure that is greater than zero.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcpositivelengthmeasure.htm"
|
||||
},
|
||||
"IfcPositivePlaneAngleMeasure": {
|
||||
"description": "An IfcPositivePlaneAngleMeasure is a plane angle measure that is greater than zero. Type: IfcPlaneAngleMeasure",
|
||||
"description": "An IfcPositivePlaneAngleMeasure is a plane angle measure that is greater than zero.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcpositiveplaneanglemeasure.htm"
|
||||
},
|
||||
"IfcPositiveRatioMeasure": {
|
||||
"description": "An IfcPositiveRatioMeasure is a ratio measure that is greater than zero. Type: IfcRatioMeasure",
|
||||
"description": "An IfcPositiveRatioMeasure is a ratio measure that is greater than zero.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcpositiveratiomeasure.htm"
|
||||
},
|
||||
"IfcPowerMeasure": {
|
||||
"description": "IfcPowerMeasure is a measure of power required or used. Usually measured in Watts (W, J/s). Type: REAL",
|
||||
"description": "IfcPowerMeasure is a measure of power required or used.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcpowermeasure.htm"
|
||||
},
|
||||
"IfcPreferredSurfaceCurveRepresentation": {
|
||||
@@ -1016,7 +1016,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcpresentationappearanceresource/lexical/ifcpresentationstyleselect.htm"
|
||||
},
|
||||
"IfcPressureMeasure": {
|
||||
"description": "IfcPressureMeasure is a measure of the quantity of a medium acting on a unit area. Usually measured in Pascals (Pa, N/m2). Type: REAL",
|
||||
"description": "IfcPressureMeasure is a measure of the quantity of a medium acting on a unit area.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcpressuremeasure.htm"
|
||||
},
|
||||
"IfcProcedureTypeEnum": {
|
||||
@@ -1044,7 +1044,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcsharedmgmtelements/lexical/ifcprojectordertypeenum.htm"
|
||||
},
|
||||
"IfcProjectedOrTrueLengthEnum": {
|
||||
"description": "This enumeration type is needed for load definition and is only considered if the load values are given as global actions and if they define linear or planar loads (that is, one- or two-dimensionally distributed loads). Figure 1 illustrates the interpretation of a load definition depending on the enumeration types IfcGlobalOrLocalEnum and IfcProjectedOrTrueLengthEnum.",
|
||||
"description": "This enumeration type is needed for load definition and is only considered if the load values are given as global actions and if they define linear or planar loads (that is, one- or two-dimensionally distributed loads).",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcstructuralanalysisdomain/lexical/ifcprojectedortruelengthenum.htm"
|
||||
},
|
||||
"IfcProjectionElementTypeEnum": {
|
||||
@@ -1076,7 +1076,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifchvacdomain/lexical/ifcpumptypeenum.htm"
|
||||
},
|
||||
"IfcRadioActivityMeasure": {
|
||||
"description": "IfcRadioActivityMeasure is a measure of activity of radionuclide. Usually measured in Becquerel (Bq, 1/s). Type: REAL",
|
||||
"description": "IfcRadioActivityMeasure is a measure of activity of radionuclide.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcradioactivitymeasure.htm"
|
||||
},
|
||||
"IfcRailingTypeEnum": {
|
||||
@@ -1088,15 +1088,15 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcsharedbldgelements/lexical/ifcrampflighttypeenum.htm"
|
||||
},
|
||||
"IfcRampTypeEnum": {
|
||||
"description": "This enumeration defines the basic configuration of the ramp type in terms of the number and shape of ramp flights, as shown in Figure 1. The type also distinguished turns by landings. In addition the subdivision of the straight and changing direction ramps is included. The ramp configurations are given for ramps without and with one and two landings. Ramps which are subdivided into more than two landings, or ramps with non-regular shapes are to be defined with type being USERDEFINED or NOTDEFINED.",
|
||||
"description": "This enumeration defines the basic configuration of the ramp type in terms of the number and shape of ramp flights, as shown in Figure 1. The type also distinguished turns by landings. In addition the subdivision of the straight and changing direction ramps is included. The ramp configurations are given for ramps without and with one and two landings.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcsharedbldgelements/lexical/ifcramptypeenum.htm"
|
||||
},
|
||||
"IfcRatioMeasure": {
|
||||
"description": "An IfcRatioMeasure is the value of the relation between two physical quantities that are of the same kind. Type: REAL",
|
||||
"description": "An IfcRatioMeasure is the value of the relation between two physical quantities that are of the same kind.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcratiomeasure.htm"
|
||||
},
|
||||
"IfcReal": {
|
||||
"description": "IfcReal is a defined type of simple data type REAL. It is required since a select type (IfcSimpleValue), cannot directly include simple types in its select list. In principle, the domain of IfcReal (being a Real) is all rational, irrational and scientific real numbers. Here the precision is unconstrained, but in practice it is implementation specific. Type: REAL",
|
||||
"description": "IfcReal is a defined type of simple data type REAL. It is required since a select type (IfcSimpleValue), cannot directly include simple types in its select list.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcreal.htm"
|
||||
},
|
||||
"IfcRecurrenceTypeEnum": {
|
||||
@@ -1136,19 +1136,19 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcactorresource/lexical/ifcroleenum.htm"
|
||||
},
|
||||
"IfcRoofTypeEnum": {
|
||||
"description": "This enumeration defines the basic configuration of the roof in terms of the different roof shapes, as illustrated in Figure 1. Roofs which are subdivided into more than these basic shapes or roofs with non-regular shapes (free form roofs) have the type FREEFORM.",
|
||||
"description": "This enumeration defines the basic configuration of the roof in terms of the different roof shapes, as illustrated in Figure 1.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcsharedbldgelements/lexical/ifcrooftypeenum.htm"
|
||||
},
|
||||
"IfcRotationalFrequencyMeasure": {
|
||||
"description": "IfcRotationalFrequencyMeasure is a measure of the number of cycles that an item revolves in unit time. Usually measured in cycles/s. Type: REAL",
|
||||
"description": "IfcRotationalFrequencyMeasure is a measure of the number of cycles that an item revolves in unit time.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcrotationalfrequencymeasure.htm"
|
||||
},
|
||||
"IfcRotationalMassMeasure": {
|
||||
"description": "The rotational mass measure denotes the inertia of a body with respect to angular acceleration. It is usually measured in kg*m\\^2. Type: REAL",
|
||||
"description": "The rotational mass measure denotes the inertia of a body with respect to angular acceleration.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcrotationalmassmeasure.htm"
|
||||
},
|
||||
"IfcRotationalStiffnessMeasure": {
|
||||
"description": "IfcRotationalStiffnessMeasure is a measure of rotational stiffness. Usually measured in Nm/rad. Type: REAL",
|
||||
"description": "IfcRotationalStiffnessMeasure is a measure of rotational stiffness.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcrotationalstiffnessmeasure.htm"
|
||||
},
|
||||
"IfcRotationalStiffnessSelect": {
|
||||
@@ -1168,7 +1168,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcplumbingfireprotectiondomain/lexical/ifcsanitaryterminaltypeenum.htm"
|
||||
},
|
||||
"IfcSectionModulusMeasure": {
|
||||
"description": "IfcSectionModulusMeasure is a measure for the resistance of a cross section against bending or torsional moment. It is usually measured in m\\^3. Type: REAL",
|
||||
"description": "IfcSectionModulusMeasure is a measure for the resistance of a cross section against bending or torsional moment. It is usually measured in m\\^3.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcsectionmodulusmeasure.htm"
|
||||
},
|
||||
"IfcSectionTypeEnum": {
|
||||
@@ -1176,7 +1176,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcprofileresource/lexical/ifcsectiontypeenum.htm"
|
||||
},
|
||||
"IfcSectionalAreaIntegralMeasure": {
|
||||
"description": "The sectional area integral measure is typically used in torsional analysis. It is usually measured in m\\^5. Type: REAL",
|
||||
"description": "The sectional area integral measure is typically used in torsional analysis. It is usually measured in m\\^5.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcsectionalareaintegralmeasure.htm"
|
||||
},
|
||||
"IfcSegmentIndexSelect": {
|
||||
@@ -1196,7 +1196,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcsharedbldgelements/lexical/ifcshadingdevicetypeenum.htm"
|
||||
},
|
||||
"IfcShearModulusMeasure": {
|
||||
"description": "IfcShearModulusMeasure is a measure of shear modulus. Usually measured in N/m2. Type: REAL",
|
||||
"description": "IfcShearModulusMeasure is a measure of shear modulus.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcshearmodulusmeasure.htm"
|
||||
},
|
||||
"IfcShell": {
|
||||
@@ -1208,7 +1208,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifckernel/lexical/ifcsimplepropertytemplatetypeenum.htm"
|
||||
},
|
||||
"IfcSimpleValue": {
|
||||
"description": "IfcSimpleValue is a select type for selecting between simple value types. SELECT - IfcInteger: Defined type of simple type INTEGER. - IfcReal: Defined type of simple type REAL. - IfcBoolean: Defined type of simple type BOOLEAN. - IfcLogical: Defined type of simple type LOGICAL. - IfcIdentifier: Defined type of simple type STRING for identification purposes. - IfcLabel: Defined type of simple type STRING for naming purposes. - IfcText: Defined type of simple type STRING for descriptive purposes. - IfcDateTime: Defined type of simple type STRING to represent a date and time. - IfcDate: Defined type of simple type STRING to represent a date. - IfcTime: Defined type of simple type STRING to represent a time. - IfcDuration: Defined type of simple type STRING to represent a duration. - IfcTimeStamp: Defined type of simple type INTEGER to represent a point in time by seconds elapsed since 1970.",
|
||||
"description": "IfcSimpleValue is a select type for selecting between simple value types.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcsimplevalue.htm"
|
||||
},
|
||||
"IfcSizeSelect": {
|
||||
@@ -1224,7 +1224,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcelectricaldomain/lexical/ifcsolardevicetypeenum.htm"
|
||||
},
|
||||
"IfcSolidAngleMeasure": {
|
||||
"description": "An IfcSolidAngleMeasure is the value of an angle in a solid. Usually measured in Steradians, (sr, m2/m2). Type: REAL",
|
||||
"description": "An IfcSolidAngleMeasure is the value of an angle in a solid.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcsolidanglemeasure.htm"
|
||||
},
|
||||
"IfcSolidOrShell": {
|
||||
@@ -1232,23 +1232,23 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcgeometricconstraintresource/lexical/ifcsolidorshell.htm"
|
||||
},
|
||||
"IfcSoundPowerLevelMeasure": {
|
||||
"description": "A sound power level measure is a measure of total radiated noise with units of decibels with a reference value of picowatts. Type: REAL",
|
||||
"description": "A sound power level measure is a measure of total radiated noise with units of decibels with a reference value of picowatts.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcsoundpowerlevelmeasure.htm"
|
||||
},
|
||||
"IfcSoundPowerMeasure": {
|
||||
"description": "A sound power measure is a measure of total radiated noise with units of watts (sonic energy per time unit). Type: REAL",
|
||||
"description": "A sound power measure is a measure of total radiated noise with units of watts (sonic energy per time unit).",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcsoundpowermeasure.htm"
|
||||
},
|
||||
"IfcSoundPressureLevelMeasure": {
|
||||
"description": "A sound pressure level measure is a measure of the pressure fluctuations superimposed over the ambient pressure level with units of decibels with a reference value of micropascals. Type: REAL",
|
||||
"description": "A sound pressure level measure is a measure of the pressure fluctuations superimposed over the ambient pressure level with units of decibels with a reference value of micropascals.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcsoundpressurelevelmeasure.htm"
|
||||
},
|
||||
"IfcSoundPressureMeasure": {
|
||||
"description": "A sound pressure measure is a measure of the pressure fluctuations superimposed over the ambient pressure level with units of pascals. Type: REAL",
|
||||
"description": "A sound pressure measure is a measure of the pressure fluctuations superimposed over the ambient pressure level with units of pascals.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcsoundpressuremeasure.htm"
|
||||
},
|
||||
"IfcSpaceBoundarySelect": {
|
||||
"description": "The IfcSpaceBoundarySelect selects either an internal space for internal or external space boundaries, or an external spatial element for external space boundaries at the outer envelop of the building. SELECT - IfcSpace, - IfcExternalSpatialElement",
|
||||
"description": "The IfcSpaceBoundarySelect selects either an internal space for internal or external space boundaries, or an external spatial element for external space boundaries at the outer envelop of the building.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcproductextension/lexical/ifcspaceboundaryselect.htm"
|
||||
},
|
||||
"IfcSpaceHeaterTypeEnum": {
|
||||
@@ -1264,11 +1264,11 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcproductextension/lexical/ifcspatialzonetypeenum.htm"
|
||||
},
|
||||
"IfcSpecificHeatCapacityMeasure": {
|
||||
"description": "IfcSpecificHeatCapacityMeasure defines the specific heat of material: The heat energy absorbed per temperature unit. Usually measured in J / kg Kelvin. Type: REAL",
|
||||
"description": "IfcSpecificHeatCapacityMeasure defines the specific heat of material: The heat energy absorbed per temperature unit.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcspecificheatcapacitymeasure.htm"
|
||||
},
|
||||
"IfcSpecularExponent": {
|
||||
"description": "The IfcSpecularExponent defines the datatype for exponent determining the sharpness of the 'reflection'. The reflection is made sharper with large values of the exponent, such as 10.0. Small values, such as 1.0, decrease the specular fall-off. IfcSpecularExponent is of type REAL.",
|
||||
"description": "The IfcSpecularExponent defines the datatype for exponent determining the sharpness of the 'reflection'. The reflection is made sharper with large values of the exponent, such as 10.0. Small values, such as 1.0, decrease the specular fall-off.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcpresentationappearanceresource/lexical/ifcspecularexponent.htm"
|
||||
},
|
||||
"IfcSpecularHighlightSelect": {
|
||||
@@ -1276,7 +1276,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcpresentationappearanceresource/lexical/ifcspecularhighlightselect.htm"
|
||||
},
|
||||
"IfcSpecularRoughness": {
|
||||
"description": "The IfcSpecularRoughness defines the datatype for the reflection resulting from the roughness of a surface through the height of surface impurities where the specular highlight is made sharper with small values for the roughness, such as 0.1. Applies to \"glass\", \"metal\", \"mirror\" and \"plastic\" reflection models. Larger values, close to 1.0 decrease the specular fall-off. IfcSpecularRoughness is of type REAL. It is constraint to values between (and including) 0 and 1.",
|
||||
"description": "The IfcSpecularRoughness defines the datatype for the reflection resulting from the roughness of a surface through the height of surface impurities where the specular highlight is made sharper with small values for the roughness, such as 0.1. Applies to \"glass\", \"metal\", \"mirror\" and \"plastic\" reflection models. Larger values, close to 1.0 decrease the specular fall-off.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcpresentationappearanceresource/lexical/ifcspecularroughness.htm"
|
||||
},
|
||||
"IfcStackTerminalTypeEnum": {
|
||||
@@ -1288,11 +1288,11 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcsharedbldgelements/lexical/ifcstairflighttypeenum.htm"
|
||||
},
|
||||
"IfcStairTypeEnum": {
|
||||
"description": "This enumeration defines the basic configuration of the stair type in terms of the number of stair flights and the number of landings, as illustrated in Figure 1. The type also distinguished turns by windings or by landings. In addition the subdivision of the straight and changing direction stairs is included. The stair configurations are given for stairs without and with one, two or three landings. Stairs which are subdivided into more than three landings, or stairs with non-regular shapes are to be defined with type being USERDEFINED or NOTDEFINED.",
|
||||
"description": "This enumeration defines the basic configuration of the stair type in terms of the number of stair flights and the number of landings, as illustrated in Figure 1. The type also distinguished turns by windings or by landings. In addition the subdivision of the straight and changing direction stairs is included. The stair configurations are given for stairs without and with one, two or three landings.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcsharedbldgelements/lexical/ifcstairtypeenum.htm"
|
||||
},
|
||||
"IfcStateEnum": {
|
||||
"description": "The IfcStateEnum enumeration identifies the state or accessibility of the object (for example, read/write, locked). Valid enumerations are:",
|
||||
"description": "The IfcStateEnum enumeration identifies the state or accessibility of the object (for example, read/write, locked).",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcutilityresource/lexical/ifcstateenum.htm"
|
||||
},
|
||||
"IfcStructuralActivityAssignmentSelect": {
|
||||
@@ -1316,7 +1316,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcstructuralanalysisdomain/lexical/ifcstructuralsurfacemembertypeenum.htm"
|
||||
},
|
||||
"IfcStyleAssignmentSelect": {
|
||||
"description": "The style assignment select is a selection of two wasy of assigning presentation styles to an IfcStyledItem. - by directly assigning presentation styles as subtypes of IfcPresentationStyle - by assigning presentation stypes via an intermediate collection entity IfcPresentationStyleAssignment",
|
||||
"description": "The style assignment select is a selection of two wasy of assigning presentation styles to an IfcStyledItem.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcpresentationappearanceresource/lexical/ifcstyleassignmentselect.htm"
|
||||
},
|
||||
"IfcSubContractResourceTypeEnum": {
|
||||
@@ -1360,11 +1360,11 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcprocessextension/lexical/ifctasktypeenum.htm"
|
||||
},
|
||||
"IfcTemperatureGradientMeasure": {
|
||||
"description": "The temperature gradient measures the difference of a temperature per length, as for instance used in an external wall or its layers. It is usually measured in K/m. Type: REAL",
|
||||
"description": "The temperature gradient measures the difference of a temperature per length, as for instance used in an external wall or its layers. It is usually measured in K/m.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifctemperaturegradientmeasure.htm"
|
||||
},
|
||||
"IfcTemperatureRateOfChangeMeasure": {
|
||||
"description": "The temperature rate of change measures the difference of a temperature per time (positive: rise, negative: fall), as for instance used with heat sensors. It is for example measured in K/s (Kelvin per second). Type: REAL",
|
||||
"description": "The temperature rate of change measures the difference of a temperature per time (positive: rise, negative: fall), as for instance used with heat sensors. It is for example measured in K/s (Kelvin per second).",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifctemperaturerateofchangemeasure.htm"
|
||||
},
|
||||
"IfcTendonAnchorTypeEnum": {
|
||||
@@ -1376,15 +1376,15 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcstructuralelementsdomain/lexical/ifctendontypeenum.htm"
|
||||
},
|
||||
"IfcText": {
|
||||
"description": "An IfcText is an alphanumeric string of characters which is intended to be read and understood by a human being. It is for information purposes only. Type: STRING",
|
||||
"description": "An IfcText is an alphanumeric string of characters which is intended to be read and understood by a human being. It is for information purposes only.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifctext.htm"
|
||||
},
|
||||
"IfcTextAlignment": {
|
||||
"description": "The IfcTextAlignment describes how text is aligned within the element. Values are: - left - right - center - justify",
|
||||
"description": "The IfcTextAlignment describes how text is aligned within the element. Values are:",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcpresentationappearanceresource/lexical/ifctextalignment.htm"
|
||||
},
|
||||
"IfcTextDecoration": {
|
||||
"description": "The IfcTextDecoration describes decorations that are added to the text of an element. Values are: - none - underline - overline - line-through",
|
||||
"description": "The IfcTextDecoration describes decorations that are added to the text of an element. Values are:",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcpresentationappearanceresource/lexical/ifctextdecoration.htm"
|
||||
},
|
||||
"IfcTextFontName": {
|
||||
@@ -1400,39 +1400,39 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcpresentationdefinitionresource/lexical/ifctextpath.htm"
|
||||
},
|
||||
"IfcTextTransformation": {
|
||||
"description": "The IfcTextTransformation describes how the cases of characters are handled. Values are: - capitalize: uppercases the first character of each word - uppercase: uppercases all letters of the element - lowercase: lowercases all letters of the element - none",
|
||||
"description": "The IfcTextTransformation describes how the cases of characters are handled. Values are:",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcpresentationappearanceresource/lexical/ifctexttransformation.htm"
|
||||
},
|
||||
"IfcThermalAdmittanceMeasure": {
|
||||
"description": "IfcThermalAdmittanceMeasure is the measure of the ability of a surface to smooth out temperature variations. Usually measured in Watt / m2 Kelvin. Type: REAL",
|
||||
"description": "IfcThermalAdmittanceMeasure is the measure of the ability of a surface to smooth out temperature variations.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcthermaladmittancemeasure.htm"
|
||||
},
|
||||
"IfcThermalConductivityMeasure": {
|
||||
"description": "IfcThermalConductivityMeasure is a measure of thermal conductivity. Usually measured in Watt / m Kelvin. Type: REAL",
|
||||
"description": "IfcThermalConductivityMeasure is a measure of thermal conductivity.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcthermalconductivitymeasure.htm"
|
||||
},
|
||||
"IfcThermalExpansionCoefficientMeasure": {
|
||||
"description": "IfcThermalExpansionCoeffientMeasure is a measure of the thermal expansion coefficient of a material, which expresses its elongation (as a ratio) per temperature difference. It is usually measured in 1/K. A positive elongation per (positive) rise of temperature is expressed by a positive value. Type: REAL",
|
||||
"description": "IfcThermalExpansionCoeffientMeasure is a measure of the thermal expansion coefficient of a material, which expresses its elongation (as a ratio) per temperature difference. It is usually measured in 1/K. A positive elongation per (positive) rise of temperature is expressed by a positive value.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcthermalexpansioncoefficientmeasure.htm"
|
||||
},
|
||||
"IfcThermalResistanceMeasure": {
|
||||
"description": "IfcThermalResistanceMeasure is a measure of the resistance offered by a body to the flow of energy. Usually measured in m2 Kelvin/Watt.",
|
||||
"description": "IfcThermalResistanceMeasure is a measure of the resistance offered by a body to the flow of energy.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcthermalresistancemeasure.htm"
|
||||
},
|
||||
"IfcThermalTransmittanceMeasure": {
|
||||
"description": "IfcThermalTransmittanceMeasure is a measure of the rate at which energy is transmitted through a body. Usually measured in Watts/m2 Kelvin. Type: REAL",
|
||||
"description": "IfcThermalTransmittanceMeasure is a measure of the rate at which energy is transmitted through a body.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcthermaltransmittancemeasure.htm"
|
||||
},
|
||||
"IfcThermodynamicTemperatureMeasure": {
|
||||
"description": "A thermodynamic temperature measure is the value for the degree of heat of a body. Usually measured in degrees Kelvin (K). Type: REAL",
|
||||
"description": "A thermodynamic temperature measure is the value for the degree of heat of a body.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcthermodynamictemperaturemeasure.htm"
|
||||
},
|
||||
"IfcTime": {
|
||||
"description": "The IfcTime identifies a time within a day, expressed by hours, minutes and second. It is expressed by a string value following a particular lexical representation. The lexical representation for IfcTime is: hh:mm:ss where where hh represents hours, mm minutes and ss seconds. Additional digits can be used to increase the precision of fractional seconds if desired i.e the format ss.ss... A time zone indicator may be provided by a representation of the different to the Coordinated Universal Time. It is appended with a sign [+/-] followed by hh and optionally :mm.",
|
||||
"description": "The IfcTime identifies a time within a day, expressed by hours, minutes and second. It is expressed by a string value following a particular lexical representation.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcdatetimeresource/lexical/ifctime.htm"
|
||||
},
|
||||
"IfcTimeMeasure": {
|
||||
"description": "An IfcTimeMeasure is the value of the duration of periods. Measured in seconds (s) or days (d) or other units of time. Type: REAL",
|
||||
"description": "An IfcTimeMeasure is the value of the duration of periods.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifctimemeasure.htm"
|
||||
},
|
||||
"IfcTimeOrRatioSelect": {
|
||||
@@ -1444,11 +1444,11 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcdatetimeresource/lexical/ifctimeseriesdatatypeenum.htm"
|
||||
},
|
||||
"IfcTimeStamp": {
|
||||
"description": "IfcTimeStamp is an indication of date and time by measuring the number of seconds which have elapsed since 1 January 1970, 00:00:00 UTC. Type: INTEGER",
|
||||
"description": "IfcTimeStamp is an indication of date and time by measuring the number of seconds which have elapsed since 1 January 1970, 00:00:00 UTC.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcdatetimeresource/lexical/ifctimestamp.htm"
|
||||
},
|
||||
"IfcTorqueMeasure": {
|
||||
"description": "IfcTorqueMeasure is a measure of the torque or moment of a couple. Usually measured in N m. Type: REAL",
|
||||
"description": "IfcTorqueMeasure is a measure of the torque or moment of a couple.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifctorquemeasure.htm"
|
||||
},
|
||||
"IfcTransformerTypeEnum": {
|
||||
@@ -1456,7 +1456,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcelectricaldomain/lexical/ifctransformertypeenum.htm"
|
||||
},
|
||||
"IfcTransitionCode": {
|
||||
"description": "The IfcTransitionCode indicated the continuity between consecutive segments of a curve or surface. Figure 1 illustrates transition types",
|
||||
"description": "The IfcTransitionCode indicated the continuity between consecutive segments of a curve or surface.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcgeometryresource/lexical/ifctransitioncode.htm"
|
||||
},
|
||||
"IfcTranslationalStiffnessSelect": {
|
||||
@@ -1480,11 +1480,11 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifchvacdomain/lexical/ifctubebundletypeenum.htm"
|
||||
},
|
||||
"IfcURIReference": {
|
||||
"description": "The IfcURIReference provides for identifying a Uniform Resource Identifier (URI). A URI can be classified as a locator or a name or both, that is it may comprise a Uniform Resource Locator (URL) and/or a Uniform Resource Name (URN). - A Uniform Resource Locator, URL, is a string conforming to a standardized format, which refers to a resource on the internet (such as a document or an image) by its location. - A Uniform Resource Name, URN, is intended to serve as persistent, location-independent resource identifier and is designed to make it easy to map other namespaces (that share the properties of URNs) into URN-space.",
|
||||
"description": "The IfcURIReference provides for identifying a Uniform Resource Identifier (URI). A URI can be classified as a locator or a name or both, that is it may comprise a Uniform Resource Locator (URL) and/or a Uniform Resource Name (URN).",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcexternalreferenceresource/lexical/ifcurireference.htm"
|
||||
},
|
||||
"IfcUnit": {
|
||||
"description": "SELECT - IfcNamedUnit: A unit which is identified by a name. - IfcDerivedUnit: A unit which is derived from an expression of units. - IfcMonetaryUnit: A unit for defining currencies.",
|
||||
"description": "A unit is a physical quantity, with a value of one, which is used as a standard in terms of which other quantities are expressed.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcunit.htm"
|
||||
},
|
||||
"IfcUnitEnum": {
|
||||
@@ -1500,7 +1500,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifchvacdomain/lexical/ifcunitaryequipmenttypeenum.htm"
|
||||
},
|
||||
"IfcValue": {
|
||||
"description": "IfcValue is a select type for selecting between more specialised select types IfcSimpleValue, IfcMeasureValue and IfcDerivedMeasureValue. SELECT - IfcSimpleValue A select type for basic defined types of simple data type. - IfcMeasureValue A select type for basic measure types of ISO 10303-41. - IfcDerivedMeasureValue A select type for derived measure types.",
|
||||
"description": "IfcValue is a select type for selecting between more specialised select types IfcSimpleValue, IfcMeasureValue and IfcDerivedMeasureValue.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcvalue.htm"
|
||||
},
|
||||
"IfcValveTypeEnum": {
|
||||
@@ -1508,7 +1508,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifchvacdomain/lexical/ifcvalvetypeenum.htm"
|
||||
},
|
||||
"IfcVaporPermeabilityMeasure": {
|
||||
"description": "IfcVaporPermeabilityMeasure is a measure of vapor permeability. Usually measured in kg / s m Pascal. Type: REAL",
|
||||
"description": "IfcVaporPermeabilityMeasure is a measure of vapor permeability.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcvaporpermeabilitymeasure.htm"
|
||||
},
|
||||
"IfcVectorOrDirection": {
|
||||
@@ -1524,11 +1524,11 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcstructuralelementsdomain/lexical/ifcvoidingfeaturetypeenum.htm"
|
||||
},
|
||||
"IfcVolumeMeasure": {
|
||||
"description": "An IfcVolumeMeasure is the value of the solid content of a body. Usually measured in cubic metre (m3). Type: REAL",
|
||||
"description": "An IfcVolumeMeasure is the value of the solid content of a body.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcvolumemeasure.htm"
|
||||
},
|
||||
"IfcVolumetricFlowRateMeasure": {
|
||||
"description": "IfcVolumetricFlowRateMeasure is a measure of the volume of a medium flowing per unit time. Usually measured in m3/s. Type: REAL",
|
||||
"description": "IfcVolumetricFlowRateMeasure is a measure of the volume of a medium flowing per unit time.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcvolumetricflowratemeasure.htm"
|
||||
},
|
||||
"IfcWallTypeEnum": {
|
||||
@@ -1536,11 +1536,11 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcsharedbldgelements/lexical/ifcwalltypeenum.htm"
|
||||
},
|
||||
"IfcWarpingConstantMeasure": {
|
||||
"description": "IfcWarpingConstantMeasure is a measure for the warping constant or warping resistance of a cross section under torsional loading. It is usually measured in m\\^6. Type: REAL",
|
||||
"description": "IfcWarpingConstantMeasure is a measure for the warping constant or warping resistance of a cross section under torsional loading. It is usually measured in m\\^6.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcwarpingconstantmeasure.htm"
|
||||
},
|
||||
"IfcWarpingMomentMeasure": {
|
||||
"description": "The warping moment measure is a measure for the warping moment, which occurs in warping torsional analysis. It is usually measured in kN*m\\^2. Type: REAL",
|
||||
"description": "The warping moment measure is a measure for the warping moment, which occurs in warping torsional analysis. It is usually measured in kN*m\\^2.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcmeasureresource/lexical/ifcwarpingmomentmeasure.htm"
|
||||
},
|
||||
"IfcWarpingStiffnessSelect": {
|
||||
@@ -1552,11 +1552,11 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcplumbingfireprotectiondomain/lexical/ifcwasteterminaltypeenum.htm"
|
||||
},
|
||||
"IfcWindowPanelOperationEnum": {
|
||||
"description": "This enumeration defines the basic ways to describe how window panels operate, as shown in Figure 2. The opening direction of the window panels is given by the local placement of the IfcWindow. The positive y-axis determines the direction as shown in Figure 2. NOTE - Figures are shown as viewed from the outside (in direction of the positive y-axis). - Figures (symbolic representation) depend on the national building code - These figures are only shown as illustrations",
|
||||
"description": "This enumeration defines the basic ways to describe how window panels operate, as shown in Figure 2.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcarchitecturedomain/lexical/ifcwindowpaneloperationenum.htm"
|
||||
},
|
||||
"IfcWindowPanelPositionEnum": {
|
||||
"description": "This enumeration defines the basic configuration of the window type in terms of the location of window panels. The window configurations are given for windows with one, two or three panels (including fixed panels) as shown in Figure 1. It corresponds to the OperationType of the IfcWindowStyle definition, which references the IfcWindowPanelProperties. Windows which are subdivided into more than three panels have to be defined by the geometry only. The type of such windows is given by an IfcWindowType.OperationType = USERDEFINED or NOTDEFINED (see IfcWindowStyleOperationEnum for details). NOTE - The figures are shown as elevations in the XZ plane of the local placement of the window, looking into the direction of the positive Y axis. - These figures are only shown as illustrations.",
|
||||
"description": "This enumeration defines the basic configuration of the window type in terms of the location of window panels. The window configurations are given for windows with one, two or three panels (including fixed panels) as shown in Figure 1. It corresponds to the OperationType of the IfcWindowStyle definition, which references the IfcWindowPanelProperties.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcarchitecturedomain/lexical/ifcwindowpanelpositionenum.htm"
|
||||
},
|
||||
"IfcWindowStyleConstructionEnum": {
|
||||
@@ -1564,7 +1564,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcarchitecturedomain/lexical/ifcwindowstyleconstructionenum.htm"
|
||||
},
|
||||
"IfcWindowStyleOperationEnum": {
|
||||
"description": "This enumeration defines the basic configuration of the window type in terms of the number of window panels and the subdivision of the total window. The window configurations are given for windows with one, two or three panels (including fixed panels) as shown in Figure 1. Windows which are subdivided into more than three panels have to be defined by the geometry only. The type of such windows is USERDEFINED. NOTE - The way how each panel operates is defined at the IfcWindowPanelProperties.OperationType. - The reference from the window panel to the location of that panel in the window style configuration is handled by the IfcWindowPanelProperties.PanelPosition. - The figures are shown as elevations in the XZ plane of the local placement of the window, looking into the direction of the positive Y axis. - These figures are only shown as illustrations",
|
||||
"description": "This enumeration defines the basic configuration of the window type in terms of the number of window panels and the subdivision of the total window. The window configurations are given for windows with one, two or three panels (including fixed panels) as shown in Figure 1.",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcarchitecturedomain/lexical/ifcwindowstyleoperationenum.htm"
|
||||
},
|
||||
"IfcWindowTypeEnum": {
|
||||
@@ -1572,7 +1572,7 @@
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcsharedbldgelements/lexical/ifcwindowtypeenum.htm"
|
||||
},
|
||||
"IfcWindowTypePartitioningEnum": {
|
||||
"description": "This enumeration defines the basic configuration of the window type in terms of the number of window panels and the subdivision of the total window as shown in Figure 1. The window configurations are given for windows with one, two or three panels (including fixed panels). Windows which are subdivided into more than three panels have to be defined by the geometry only. The type of such windows is USERDEFINED. NOTE - The way how each panel operates is defined at the IfcWindowPanelProperties.OperationType. - The reference from the window panel to the location of that panel in the window style configuration is handled by the IfcWindowPanelProperties.PanelPosition. - The figures are shown as elevations in the XZ plane of the local placement of the window, looking into the direction of the positive Y axis. - These figures are only shown as illustrations",
|
||||
"description": "This enumeration defines the basic configuration of the window type in terms of the number of window panels and the subdivision of the total window as shown in Figure 1. The window configurations are given for windows with one, two or three panels (including fixed panels).",
|
||||
"spec_url": "https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcsharedbldgelements/lexical/ifcwindowtypepartitioningenum.htm"
|
||||
},
|
||||
"IfcWorkCalendarTypeEnum": {
|
||||
|
||||
@@ -39,7 +39,8 @@ import ifcopenshell.util.shape
|
||||
import ifcopenshell.util.system
|
||||
import ifcopenshell.util.unit
|
||||
|
||||
filter_elements_grammar = lark.Lark("""start: filter_group
|
||||
filter_elements_grammar = lark.Lark(
|
||||
"""start: filter_group
|
||||
filter_group: facet_list ("+" facet_list)*
|
||||
facet_list: facet ("," facet)*
|
||||
|
||||
@@ -110,9 +111,11 @@ filter_elements_grammar = lark.Lark("""start: filter_group
|
||||
NEWLINE: (CR? LF)+
|
||||
|
||||
%ignore WS // Disregard spaces in text
|
||||
""")
|
||||
"""
|
||||
)
|
||||
|
||||
get_element_grammar = lark.Lark("""start: keys
|
||||
get_element_grammar = lark.Lark(
|
||||
"""start: keys
|
||||
|
||||
keys: key ("." key)*
|
||||
key: quoted_string | regex_string | unquoted_string
|
||||
@@ -127,9 +130,11 @@ get_element_grammar = lark.Lark("""start: keys
|
||||
WS: /[ \\t\\f\\r\\n]/+
|
||||
|
||||
%ignore WS // Disregard spaces in text
|
||||
""")
|
||||
"""
|
||||
)
|
||||
|
||||
format_grammar = lark.Lark("""start: expression
|
||||
format_grammar = lark.Lark(
|
||||
"""start: expression
|
||||
|
||||
?expression: add_sub
|
||||
?add_sub: mul_div
|
||||
@@ -188,7 +193,8 @@ format_grammar = lark.Lark("""start: expression
|
||||
NEWLINE: (CR? LF)+
|
||||
|
||||
%ignore WS // Disregard spaces in text
|
||||
""")
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class FormatTransformer(lark.Transformer):
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.context
|
||||
import ifcopenshell.api.unit
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def test_add_positioning_referent():
|
||||
file = ifcopenshell.file(schema="IFC4X3")
|
||||
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
||||
length = ifcopenshell.api.unit.add_si_unit(file, unit_type="LENGTHUNIT")
|
||||
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
||||
geometric_representation_context = ifcopenshell.api.context.add_context(file, context_type="Model")
|
||||
axis_model_representation_subcontext = ifcopenshell.api.context.add_context(
|
||||
file,
|
||||
context_type="Model",
|
||||
context_identifier="Axis",
|
||||
target_view="MODEL_VIEW",
|
||||
parent=geometric_representation_context,
|
||||
)
|
||||
|
||||
alignment = ifcopenshell.api.alignment.create(file, "TestAlignment", start_station=2000.0)
|
||||
|
||||
horizontal_layout = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
|
||||
segment = ifcopenshell.api.alignment.get_layout_segments(horizontal_layout)[0]
|
||||
|
||||
referent = ifcopenshell.api.alignment.add_positioning_referent(
|
||||
file, "P.C.", alignment, distance_along=0.0, station=2000.0, positioned_product=segment
|
||||
)
|
||||
|
||||
assert referent.is_a("IfcReferent")
|
||||
assert referent.PredefinedType == "POSITION"
|
||||
assert referent.Name == "P.C."
|
||||
assert ifcopenshell.util.element.get_pset(element=referent, name="Pset_Stationing")
|
||||
assert ifcopenshell.util.element.get_pset(element=referent, name="Pset_Stationing", prop="Station") == 2000.0
|
||||
assert referent.ObjectPlacement != None
|
||||
|
||||
assert len(referent.Positions) == 1
|
||||
rel_positions = referent.Positions[0]
|
||||
assert rel_positions.is_a("IfcRelPositions")
|
||||
assert rel_positions.RelatingPositioningElement == referent
|
||||
assert rel_positions.RelatedProducts == (segment,)
|
||||
|
||||
|
||||
def test_add_positioning_referent_creates_separate_referent_per_call():
|
||||
file = ifcopenshell.file(schema="IFC4X3")
|
||||
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
||||
length = ifcopenshell.api.unit.add_si_unit(file, unit_type="LENGTHUNIT")
|
||||
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
||||
geometric_representation_context = ifcopenshell.api.context.add_context(file, context_type="Model")
|
||||
axis_model_representation_subcontext = ifcopenshell.api.context.add_context(
|
||||
file,
|
||||
context_type="Model",
|
||||
context_identifier="Axis",
|
||||
target_view="MODEL_VIEW",
|
||||
parent=geometric_representation_context,
|
||||
)
|
||||
|
||||
alignment = ifcopenshell.api.alignment.create(file, "TestAlignment", start_station=2000.0)
|
||||
|
||||
horizontal_layout = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
|
||||
segment = ifcopenshell.api.alignment.get_layout_segments(horizontal_layout)[0]
|
||||
|
||||
first_referent = ifcopenshell.api.alignment.add_positioning_referent(
|
||||
file, "P.C.", alignment, distance_along=0.0, station=2000.0, positioned_product=segment
|
||||
)
|
||||
|
||||
other_product = file.createIfcBuildingElementProxy(GlobalId=ifcopenshell.guid.new(), Name="Sign")
|
||||
second_referent = ifcopenshell.api.alignment.add_positioning_referent(
|
||||
file, "P.C.", alignment, distance_along=0.0, station=2000.0, positioned_product=other_product
|
||||
)
|
||||
|
||||
# each call creates its own IfcReferent, each with its own IfcRelPositions to the product passed in
|
||||
assert first_referent != second_referent
|
||||
assert len(first_referent.Positions) == 1
|
||||
assert first_referent.Positions[0].RelatedProducts == (segment,)
|
||||
assert len(second_referent.Positions) == 1
|
||||
assert second_referent.Positions[0].RelatedProducts == (other_product,)
|
||||
|
||||
|
||||
test_add_positioning_referent()
|
||||
test_add_positioning_referent_creates_separate_referent_per_call()
|
||||
@@ -1,115 +0,0 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.context
|
||||
import ifcopenshell.api.unit
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def _create_test_file():
|
||||
file = ifcopenshell.file(schema="IFC4X3")
|
||||
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
||||
length = ifcopenshell.api.unit.add_si_unit(file, unit_type="LENGTHUNIT")
|
||||
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
||||
geometric_representation_context = ifcopenshell.api.context.add_context(file, context_type="Model")
|
||||
axis_model_representation_subcontext = ifcopenshell.api.context.add_context(
|
||||
file,
|
||||
context_type="Model",
|
||||
context_identifier="Axis",
|
||||
target_view="MODEL_VIEW",
|
||||
parent=geometric_representation_context,
|
||||
)
|
||||
return file
|
||||
|
||||
|
||||
def _create_test_alignment_with_vertical(file):
|
||||
# include_vertical=True so that get_curve() (IfcGradientCurve, on the "Axis" representation)
|
||||
# and get_basis_curve() (IfcCompositeCurve, on the "FootPrint" representation) are different
|
||||
# entities, letting the on_basis_curve option be observed.
|
||||
alignment = ifcopenshell.api.alignment.create(file, "TestAlignment", include_vertical=True, start_station=0.0)
|
||||
assert ifcopenshell.api.alignment.get_basis_curve(alignment).is_a("IfcCompositeCurve")
|
||||
assert ifcopenshell.api.alignment.get_curve(alignment).is_a("IfcGradientCurve")
|
||||
assert ifcopenshell.api.alignment.get_basis_curve(alignment) != ifcopenshell.api.alignment.get_curve(alignment)
|
||||
return alignment
|
||||
|
||||
|
||||
def _assert_common_referent_asserts(referent, name, station):
|
||||
assert referent.is_a("IfcReferent")
|
||||
assert referent.PredefinedType == "STATION"
|
||||
assert referent.Name == name
|
||||
assert ifcopenshell.util.element.get_pset(element=referent, name="Pset_Stationing")
|
||||
assert ifcopenshell.util.element.get_pset(element=referent, name="Pset_Stationing", prop="Station") == station
|
||||
assert referent.ObjectPlacement != None
|
||||
|
||||
|
||||
def test_add_stationing_referent_on_basis_curve_none_defaults_to_basis_curve():
|
||||
# on_basis_curve=None should behave the same as on_basis_curve=True
|
||||
file = _create_test_file()
|
||||
alignment = _create_test_alignment_with_vertical(file)
|
||||
|
||||
referent = ifcopenshell.api.alignment.add_stationing_referent(
|
||||
file, "1+00.000", alignment, distance_along=100.0, station=100.0, on_basis_curve=None
|
||||
)
|
||||
|
||||
_assert_common_referent_asserts(referent, "1+00.000", 100.0)
|
||||
|
||||
assert referent.ObjectPlacement.is_a("IfcLinearPlacement")
|
||||
assert referent.ObjectPlacement.RelativePlacement.Location.BasisCurve == ifcopenshell.api.alignment.get_basis_curve(
|
||||
alignment
|
||||
)
|
||||
|
||||
|
||||
def test_add_stationing_referent_on_basis_curve_true():
|
||||
file = _create_test_file()
|
||||
alignment = _create_test_alignment_with_vertical(file)
|
||||
|
||||
referent = ifcopenshell.api.alignment.add_stationing_referent(
|
||||
file, "1+00.000", alignment, distance_along=100.0, station=100.0, on_basis_curve=True
|
||||
)
|
||||
|
||||
_assert_common_referent_asserts(referent, "1+00.000", 100.0)
|
||||
|
||||
assert referent.ObjectPlacement.is_a("IfcLinearPlacement")
|
||||
assert referent.ObjectPlacement.RelativePlacement.Location.BasisCurve == ifcopenshell.api.alignment.get_basis_curve(
|
||||
alignment
|
||||
)
|
||||
|
||||
|
||||
def test_add_stationing_referent_on_basis_curve_false():
|
||||
# with a vertical layout present, on_basis_curve=False positions the referent on the
|
||||
# alignment curve (IfcGradientCurve) rather than on the basis curve (IfcCompositeCurve).
|
||||
file = _create_test_file()
|
||||
alignment = _create_test_alignment_with_vertical(file)
|
||||
|
||||
referent = ifcopenshell.api.alignment.add_stationing_referent(
|
||||
file, "1+00.000", alignment, distance_along=100.0, station=100.0, on_basis_curve=False
|
||||
)
|
||||
|
||||
_assert_common_referent_asserts(referent, "1+00.000", 100.0)
|
||||
|
||||
assert referent.ObjectPlacement.is_a("IfcLinearPlacement")
|
||||
basis_curve = referent.ObjectPlacement.RelativePlacement.Location.BasisCurve
|
||||
assert basis_curve == ifcopenshell.api.alignment.get_curve(alignment)
|
||||
assert basis_curve != ifcopenshell.api.alignment.get_basis_curve(alignment)
|
||||
|
||||
|
||||
test_add_stationing_referent_on_basis_curve_none_defaults_to_basis_curve()
|
||||
test_add_stationing_referent_on_basis_curve_true()
|
||||
test_add_stationing_referent_on_basis_curve_false()
|
||||
@@ -48,26 +48,5 @@ def test_add_stationing_to_alignment():
|
||||
assert ifcopenshell.util.element.get_pset(element=referent, name="Pset_Stationing", prop="Station") == 2000.0
|
||||
assert referent.ObjectPlacement != None
|
||||
|
||||
# add a station equation at 1000 distance along. this is station 3+000 in coming and 4+000 outgoing.
|
||||
# this is a gap equation.
|
||||
second_referent = ifcopenshell.api.alignment.add_stationing_referent(
|
||||
file, "4+000.000", alignment, distance_along=1000.0, station=4000.0, incoming_station=3000.0
|
||||
)
|
||||
|
||||
referent_nest = ifcopenshell.api.alignment.get_referent_nest(file, alignment)
|
||||
assert len(referent_nest.RelatedObjects) == 2
|
||||
|
||||
assert second_referent == referent_nest.RelatedObjects[1]
|
||||
|
||||
assert second_referent.PredefinedType == "STATION"
|
||||
assert second_referent.Name == "4+000.000"
|
||||
assert ifcopenshell.util.element.get_pset(element=second_referent, name="Pset_Stationing")
|
||||
assert ifcopenshell.util.element.get_pset(element=second_referent, name="Pset_Stationing", prop="Station") == 4000.0
|
||||
assert (
|
||||
ifcopenshell.util.element.get_pset(element=second_referent, name="Pset_Stationing", prop="IncomingStation")
|
||||
== 3000.0
|
||||
)
|
||||
assert second_referent.ObjectPlacement != None
|
||||
|
||||
|
||||
test_add_stationing_to_alignment()
|
||||
|
||||
@@ -21,12 +21,9 @@ import math
|
||||
|
||||
import pytest
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.aggregate
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.context
|
||||
import ifcopenshell.api.spatial
|
||||
import ifcopenshell.api.unit
|
||||
import ifcopenshell.util.unit
|
||||
import numpy as np
|
||||
|
||||
|
||||
def test_create_representation():
|
||||
|
||||
@@ -53,56 +53,4 @@ def test_distance_along_from_station():
|
||||
assert ifcopenshell.api.alignment.distance_along_from_station(file, alignment, 17525.36) == pytest.approx(7525.36)
|
||||
|
||||
|
||||
def test_distance_along_from_station_with_station_equations():
|
||||
# Reproduces the worked example from the IFC Alignment Geometry Implementation Guide, chapter 9.2.6:
|
||||
# a gap equation (P3: incoming 14+00.00, outgoing 17+00.00) and an overlap equation
|
||||
# (P4: incoming 19+00.00, outgoing 18+50.00).
|
||||
file = ifcopenshell.file(schema="IFC4X3")
|
||||
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
||||
length = ifcopenshell.api.unit.add_conversion_based_unit(file, name="foot")
|
||||
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
||||
geometric_representation_context = ifcopenshell.api.context.add_context(file, context_type="Model")
|
||||
axis_model_representation_subcontext = ifcopenshell.api.context.add_context(
|
||||
file,
|
||||
context_type="Model",
|
||||
context_identifier="Axis",
|
||||
target_view="MODEL_VIEW",
|
||||
parent=geometric_representation_context,
|
||||
)
|
||||
|
||||
coordinates = [(500.0, 2500.0), (3340.0, 660.0), (4340.0, 5000.0), (7600.0, 4560.0), (8480.0, 2010.0)]
|
||||
radii = [(1000.0), (1250.0), (950.0)]
|
||||
vpoints = [(0.0, 100.0), (2000.0, 135.0), (5000.0, 105.0), (7400.0, 153.0), (9800.0, 105.0), (12800.0, 90.0)]
|
||||
lengths = [(1600.0), (1200.0), (2000.0), (800.0)]
|
||||
|
||||
alignment = ifcopenshell.api.alignment.create_by_pi_method(
|
||||
file, "TestAlignment", coordinates, radii, vpoints, lengths, start_station=1000.0
|
||||
)
|
||||
|
||||
ifcopenshell.api.alignment.add_stationing_referent(
|
||||
file, "P3", alignment, distance_along=400.0, station=1700.0, incoming_station=1400.0
|
||||
)
|
||||
ifcopenshell.api.alignment.add_stationing_referent(
|
||||
file, "P4", alignment, distance_along=600.0, station=1850.0, incoming_station=1900.0
|
||||
)
|
||||
|
||||
distance_along_from_station = ifcopenshell.api.alignment.distance_along_from_station
|
||||
|
||||
# between P2 and P3: Sta. 13+00.00
|
||||
assert distance_along_from_station(file, alignment, 1300.0) == pytest.approx(300.0)
|
||||
|
||||
# between P3 and P4: Sta. 18+00.00
|
||||
assert distance_along_from_station(file, alignment, 1800.0) == pytest.approx(500.0)
|
||||
|
||||
# between P4 and P5: Sta. 19+25.00
|
||||
assert distance_along_from_station(file, alignment, 1925.0) == pytest.approx(675.0)
|
||||
|
||||
# Sta. 15+00.00 falls inside the gap opened by the equation at P3 and has no corresponding distance along
|
||||
assert distance_along_from_station(file, alignment, 1500.0) is None
|
||||
|
||||
# Sta. 18+75.00 falls inside the overlap zone at P4; the post-equation (outgoing) match is returned
|
||||
assert distance_along_from_station(file, alignment, 1875.0) == pytest.approx(625.0)
|
||||
|
||||
|
||||
test_distance_along_from_station()
|
||||
test_distance_along_from_station_with_station_equations()
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
def test_skip_over_non_entity_instance():
|
||||
data = """
|
||||
ISO-10303-21;
|
||||
|
||||
@@ -46,4 +46,4 @@ def test_file(filename):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main(["-sx", __file__, "--import-mode=importlib"])
|
||||
pytest.main(["-sx", __file__, '--import-mode=importlib'])
|
||||
|
||||
@@ -187,15 +187,6 @@ void IfcUtil::sanitate_material_name(std::string& str) {
|
||||
}
|
||||
|
||||
void IfcUtil::escape_xml(std::string& str) {
|
||||
// Strip characters that are illegal in XML 1.0. Control characters other
|
||||
// than tab (0x09), newline (0x0A) and carriage return (0x0D) are not valid
|
||||
// XML 1.0 characters and cannot even be represented as numeric character
|
||||
// references, so they would otherwise make the serialized XML/SVG output
|
||||
// non-well-formed. Bytes belonging to a valid UTF-8 multibyte sequence are
|
||||
// always >= 0x80, so filtering on the low control range leaves them intact.
|
||||
str.erase(std::remove_if(str.begin(), str.end(), [](unsigned char c) {
|
||||
return c < 0x20 && c != '\t' && c != '\n' && c != '\r';
|
||||
}), str.end());
|
||||
boost::replace_all(str, "&", "&");
|
||||
boost::replace_all(str, "\"", """);
|
||||
boost::replace_all(str, "'", "'");
|
||||
|
||||
@@ -111,7 +111,7 @@ class Patcher(ifcpatch.BasePatcher):
|
||||
if element.is_a("IfcProject"):
|
||||
proj = self.new.add(element)
|
||||
for ctx in element.RepresentationContexts or ():
|
||||
for coop in getattr(ctx, "HasCoordinateOperation", ()):
|
||||
for coop in getattr(ctx, 'HasCoordinateOperation', ()):
|
||||
self.new.add(coop)
|
||||
return proj
|
||||
return ifcopenshell.api.project.append_asset(
|
||||
|
||||
@@ -33,7 +33,9 @@ class TestDowngradeIndexedPolyCurve(test.bootstrap.IFC4):
|
||||
Points=point_list,
|
||||
Segments=segments,
|
||||
)
|
||||
self.file.create_entity("IfcArbitraryClosedProfileDef", ProfileType="AREA", OuterCurve=curve)
|
||||
self.file.create_entity(
|
||||
"IfcArbitraryClosedProfileDef", ProfileType="AREA", OuterCurve=curve
|
||||
)
|
||||
return curve
|
||||
|
||||
def test_run_without_segments(self):
|
||||
@@ -78,7 +80,9 @@ class TestDowngradeIndexedPolyCurve(test.bootstrap.IFC4):
|
||||
Points=point_list,
|
||||
Segments=[self.file.createIfcLineIndex((1, 2, 3, 4, 1))],
|
||||
)
|
||||
self.file.create_entity("IfcArbitraryClosedProfileDef", ProfileType="AREA", OuterCurve=curve)
|
||||
self.file.create_entity(
|
||||
"IfcArbitraryClosedProfileDef", ProfileType="AREA", OuterCurve=curve
|
||||
)
|
||||
ifcpatch.execute(
|
||||
{"input": "input.ifc", "file": self.file, "recipe": "DowngradeIndexedPolyCurve", "arguments": []}
|
||||
)
|
||||
@@ -106,7 +110,9 @@ class TestDowngradeIndexedPolyCurve(test.bootstrap.IFC4):
|
||||
self.file.createIfcLineIndex((3, 4)),
|
||||
],
|
||||
)
|
||||
self.file.create_entity("IfcArbitraryClosedProfileDef", ProfileType="AREA", OuterCurve=curve)
|
||||
self.file.create_entity(
|
||||
"IfcArbitraryClosedProfileDef", ProfileType="AREA", OuterCurve=curve
|
||||
)
|
||||
ifcpatch.execute(
|
||||
{"input": "input.ifc", "file": self.file, "recipe": "DowngradeIndexedPolyCurve", "arguments": []}
|
||||
)
|
||||
|
||||
@@ -977,14 +977,12 @@ struct ShapeRTTI : public boost::static_visitor<PyObject*>
|
||||
if (item == nullptr) {
|
||||
throw IfcParse::IfcException("Failed to convert placement");
|
||||
}
|
||||
/*
|
||||
if (st.get<ifcopenshell::geometry::settings::ConvertBackUnits>().get()) {
|
||||
// we pass the settings to the Transformation object, but access the data just offloads to the
|
||||
// generic cartesian_base<Matrix4> so there's no time to apply the settings to the translation part.
|
||||
item = ifcopenshell::geometry::taxonomy::matrix4::ptr(item->clone_());
|
||||
item->components().col(3).head<3>() /= kernel.settings().get<ifcopenshell::geometry::settings::LengthUnit>().get();
|
||||
}
|
||||
*/
|
||||
return new IfcGeom::Transformation(kernel.settings(), item);
|
||||
} else {
|
||||
if (!representation) {
|
||||
|
||||
@@ -305,7 +305,7 @@ ptree* descend(Logger& logger, ifcopenshell::geometry::abstract_mapping* mapping
|
||||
<IfcSchema::IfcObject, IfcSchema::IfcRelDefinesByProperties, IfcSchema::IfcPropertySetDefinition>
|
||||
(logger, object, &IfcSchema::IfcObject::IsDefinedBy, &IfcSchema::IfcRelDefinesByProperties::RelatingPropertyDefinition);
|
||||
|
||||
#ifdef SCHEMA_HAS_IfcPropertySetDefinitionSet
|
||||
#ifdef SCHEMAS_HAS_IfcPropertySetDefinitionSet
|
||||
aggregate_of<IfcSchema::IfcPropertySetDefinitionSet>::ptr property_set_sets = get_related
|
||||
<IfcSchema::IfcObject, IfcSchema::IfcRelDefinesByProperties, IfcSchema::IfcPropertySetDefinitionSet>
|
||||
(logger, object, &IfcSchema::IfcObject::IsDefinedBy, &IfcSchema::IfcRelDefinesByProperties::RelatingPropertyDefinition);
|
||||
|
||||
Reference in New Issue
Block a user