@bitbybit-dev/ifc 0.0.0-stage → 1.5.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE +42 -0
- package/README.md +92 -2
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/lib/api/dto-registry.d.ts +8 -0
- package/lib/api/dto-registry.js +109 -0
- package/lib/api/ifc-service.d.ts +109 -0
- package/lib/api/ifc-service.js +55 -0
- package/lib/api/inputs/base-inputs.d.ts +5 -0
- package/lib/api/inputs/base-inputs.js +5 -0
- package/lib/api/inputs/ifc-inputs.d.ts +2727 -0
- package/lib/api/inputs/ifc-inputs.js +2498 -0
- package/lib/api/inputs/index.d.ts +3 -0
- package/lib/api/inputs/index.js +3 -0
- package/lib/api/resolved-inputs/index.d.ts +70 -0
- package/lib/api/resolved-inputs/index.js +1 -0
- package/lib/api/services/assemblies.d.ts +28 -0
- package/lib/api/services/assemblies.js +49 -0
- package/lib/api/services/beams.d.ts +33 -0
- package/lib/api/services/beams.js +59 -0
- package/lib/api/services/columns.d.ts +26 -0
- package/lib/api/services/columns.js +56 -0
- package/lib/api/services/defaults.constants.d.ts +44 -0
- package/lib/api/services/defaults.constants.js +44 -0
- package/lib/api/services/doors.d.ts +45 -0
- package/lib/api/services/doors.js +94 -0
- package/lib/api/services/geometry.d.ts +51 -0
- package/lib/api/services/geometry.js +87 -0
- package/lib/api/services/index.d.ts +17 -0
- package/lib/api/services/index.js +17 -0
- package/lib/api/services/materials.d.ts +46 -0
- package/lib/api/services/materials.js +86 -0
- package/lib/api/services/member-support.d.ts +7 -0
- package/lib/api/services/member-support.js +29 -0
- package/lib/api/services/members.d.ts +24 -0
- package/lib/api/services/members.js +51 -0
- package/lib/api/services/model.d.ts +244 -0
- package/lib/api/services/model.js +409 -0
- package/lib/api/services/openings.d.ts +57 -0
- package/lib/api/services/openings.js +102 -0
- package/lib/api/services/properties.d.ts +89 -0
- package/lib/api/services/properties.js +142 -0
- package/lib/api/services/quantities.d.ts +38 -0
- package/lib/api/services/quantities.js +51 -0
- package/lib/api/services/roofs.d.ts +24 -0
- package/lib/api/services/roofs.js +52 -0
- package/lib/api/services/service-support.d.ts +8 -0
- package/lib/api/services/service-support.js +37 -0
- package/lib/api/services/service-types.d.ts +3 -0
- package/lib/api/services/service-types.js +1 -0
- package/lib/api/services/site.d.ts +37 -0
- package/lib/api/services/site.js +80 -0
- package/lib/api/services/slabs.d.ts +31 -0
- package/lib/api/services/slabs.js +59 -0
- package/lib/api/services/spaces.d.ts +36 -0
- package/lib/api/services/spaces.js +64 -0
- package/lib/api/services/spatial.d.ts +54 -0
- package/lib/api/services/spatial.js +75 -0
- package/lib/api/services/walls.d.ts +140 -0
- package/lib/api/services/walls.js +268 -0
- package/lib/api/services/windows.d.ts +46 -0
- package/lib/api/services/windows.js +76 -0
- package/lib/build/assemblies.d.ts +4 -0
- package/lib/build/assemblies.js +45 -0
- package/lib/build/build-types.d.ts +345 -0
- package/lib/build/build-types.js +1 -0
- package/lib/build/checks.d.ts +6 -0
- package/lib/build/checks.js +32 -0
- package/lib/build/constants.d.ts +27 -0
- package/lib/build/constants.js +43 -0
- package/lib/build/contexts.d.ts +9 -0
- package/lib/build/contexts.js +52 -0
- package/lib/build/curves.d.ts +3 -0
- package/lib/build/curves.js +23 -0
- package/lib/build/entity-writer.d.ts +23 -0
- package/lib/build/entity-writer.js +97 -0
- package/lib/build/fillings.d.ts +7 -0
- package/lib/build/fillings.js +182 -0
- package/lib/build/georeference.d.ts +5 -0
- package/lib/build/georeference.js +44 -0
- package/lib/build/materials.d.ts +18 -0
- package/lib/build/materials.js +166 -0
- package/lib/build/math.d.ts +10 -0
- package/lib/build/math.js +47 -0
- package/lib/build/measure.d.ts +9 -0
- package/lib/build/measure.js +195 -0
- package/lib/build/moving.d.ts +4 -0
- package/lib/build/moving.js +61 -0
- package/lib/build/openings.d.ts +12 -0
- package/lib/build/openings.js +156 -0
- package/lib/build/outlines.d.ts +4 -0
- package/lib/build/outlines.js +40 -0
- package/lib/build/placement.d.ts +16 -0
- package/lib/build/placement.js +106 -0
- package/lib/build/profiles.d.ts +4 -0
- package/lib/build/profiles.js +35 -0
- package/lib/build/project.d.ts +11 -0
- package/lib/build/project.js +134 -0
- package/lib/build/properties.d.ts +10 -0
- package/lib/build/properties.js +182 -0
- package/lib/build/quantities.d.ts +7 -0
- package/lib/build/quantities.js +262 -0
- package/lib/build/relationships.d.ts +9 -0
- package/lib/build/relationships.js +67 -0
- package/lib/build/removal.d.ts +3 -0
- package/lib/build/removal.js +106 -0
- package/lib/build/representations.d.ts +6 -0
- package/lib/build/representations.js +46 -0
- package/lib/build/roof-clipping.d.ts +5 -0
- package/lib/build/roof-clipping.js +79 -0
- package/lib/build/roofs.d.ts +4 -0
- package/lib/build/roofs.js +131 -0
- package/lib/build/site.d.ts +7 -0
- package/lib/build/site.js +112 -0
- package/lib/build/slab-openings.d.ts +4 -0
- package/lib/build/slab-openings.js +71 -0
- package/lib/build/slabs-columns-beams.d.ts +8 -0
- package/lib/build/slabs-columns-beams.js +73 -0
- package/lib/build/solids.d.ts +5 -0
- package/lib/build/solids.js +33 -0
- package/lib/build/spaces.d.ts +6 -0
- package/lib/build/spaces.js +55 -0
- package/lib/build/spatial.d.ts +17 -0
- package/lib/build/spatial.js +111 -0
- package/lib/build/type-objects.d.ts +4 -0
- package/lib/build/type-objects.js +9 -0
- package/lib/build/wall-edits.d.ts +5 -0
- package/lib/build/wall-edits.js +138 -0
- package/lib/build/wall-geometry.d.ts +15 -0
- package/lib/build/wall-geometry.js +159 -0
- package/lib/build/wall-parameters.d.ts +3 -0
- package/lib/build/wall-parameters.js +47 -0
- package/lib/build/walls.d.ts +12 -0
- package/lib/build/walls.js +238 -0
- package/lib/geometry/advanced.d.ts +27 -0
- package/lib/geometry/advanced.js +292 -0
- package/lib/geometry/colours.d.ts +3 -0
- package/lib/geometry/colours.js +109 -0
- package/lib/geometry/curves.d.ts +32 -0
- package/lib/geometry/curves.js +370 -0
- package/lib/geometry/edges.constants.d.ts +5 -0
- package/lib/geometry/edges.constants.js +5 -0
- package/lib/geometry/edges.d.ts +21 -0
- package/lib/geometry/edges.js +164 -0
- package/lib/geometry/elements.d.ts +3 -0
- package/lib/geometry/elements.js +152 -0
- package/lib/geometry/errors.d.ts +3 -0
- package/lib/geometry/errors.js +6 -0
- package/lib/geometry/faceted.d.ts +31 -0
- package/lib/geometry/faceted.js +214 -0
- package/lib/geometry/geometry-types.d.ts +103 -0
- package/lib/geometry/geometry-types.js +1 -0
- package/lib/geometry/groups.d.ts +21 -0
- package/lib/geometry/groups.js +106 -0
- package/lib/geometry/items.d.ts +34 -0
- package/lib/geometry/items.js +313 -0
- package/lib/geometry/operators.d.ts +3 -0
- package/lib/geometry/operators.js +65 -0
- package/lib/geometry/profiles.d.ts +4 -0
- package/lib/geometry/profiles.js +277 -0
- package/lib/geometry/recipe-builder.d.ts +14 -0
- package/lib/geometry/recipe-builder.js +48 -0
- package/lib/geometry/references.d.ts +4 -0
- package/lib/geometry/references.js +17 -0
- package/lib/geometry/regions.d.ts +4 -0
- package/lib/geometry/regions.js +26 -0
- package/lib/geometry/surfaces.constants.d.ts +17 -0
- package/lib/geometry/surfaces.constants.js +17 -0
- package/lib/geometry/surfaces.d.ts +6 -0
- package/lib/geometry/surfaces.js +306 -0
- package/lib/geometry/swept-solids.constants.d.ts +7 -0
- package/lib/geometry/swept-solids.constants.js +6 -0
- package/lib/geometry/swept-solids.d.ts +25 -0
- package/lib/geometry/swept-solids.js +243 -0
- package/lib/index.d.ts +6 -0
- package/lib/index.js +6 -0
- package/lib/model/constants.d.ts +30 -0
- package/lib/model/constants.js +30 -0
- package/lib/model/entity-lookup.d.ts +9 -0
- package/lib/model/entity-lookup.js +20 -0
- package/lib/model/guid.d.ts +7 -0
- package/lib/model/guid.js +60 -0
- package/lib/model/id-map.d.ts +10 -0
- package/lib/model/id-map.js +80 -0
- package/lib/model/index.d.ts +4 -0
- package/lib/model/index.js +2 -0
- package/lib/model/io.d.ts +6 -0
- package/lib/model/io.js +136 -0
- package/lib/model/is-model.d.ts +9 -0
- package/lib/model/is-model.js +11 -0
- package/lib/model/model-types.d.ts +111 -0
- package/lib/model/model-types.js +1 -0
- package/lib/model/overlay-indexes.d.ts +17 -0
- package/lib/model/overlay-indexes.js +69 -0
- package/lib/model/references.d.ts +2 -0
- package/lib/model/references.js +51 -0
- package/lib/model/rows-by-id.d.ts +8 -0
- package/lib/model/rows-by-id.js +39 -0
- package/lib/model/snapshot.d.ts +36 -0
- package/lib/model/snapshot.js +144 -0
- package/lib/model/source.d.ts +29 -0
- package/lib/model/source.js +172 -0
- package/lib/model/transaction.d.ts +39 -0
- package/lib/model/transaction.js +250 -0
- package/lib/model/upgrade.d.ts +2 -0
- package/lib/model/upgrade.js +78 -0
- package/lib/schema/constants.d.ts +3 -0
- package/lib/schema/constants.js +3 -0
- package/lib/schema/generated/ifc4.d.ts +2 -0
- package/lib/schema/generated/ifc4.js +1204 -0
- package/lib/schema/generated/ifc4x3.d.ts +2 -0
- package/lib/schema/generated/ifc4x3.js +1325 -0
- package/lib/schema/registry.d.ts +7 -0
- package/lib/schema/registry.js +37 -0
- package/lib/schema/schema-types.d.ts +45 -0
- package/lib/schema/schema-types.js +1 -0
- package/lib/schema/schema.d.ts +23 -0
- package/lib/schema/schema.js +136 -0
- package/lib/step/constants.d.ts +48 -0
- package/lib/step/constants.js +48 -0
- package/lib/step/encoder.d.ts +21 -0
- package/lib/step/encoder.js +321 -0
- package/lib/step/errors.d.ts +39 -0
- package/lib/step/errors.js +45 -0
- package/lib/step/file-index.d.ts +2 -0
- package/lib/step/file-index.js +221 -0
- package/lib/step/header.d.ts +4 -0
- package/lib/step/header.js +52 -0
- package/lib/step/numbers.d.ts +3 -0
- package/lib/step/numbers.js +73 -0
- package/lib/step/reader.d.ts +2 -0
- package/lib/step/reader.js +160 -0
- package/lib/step/scanner.d.ts +13 -0
- package/lib/step/scanner.js +203 -0
- package/lib/step/step-types.d.ts +170 -0
- package/lib/step/step-types.js +1 -0
- package/lib/step/strings.d.ts +2 -0
- package/lib/step/strings.js +150 -0
- package/lib/step/values.d.ts +13 -0
- package/lib/step/values.js +51 -0
- package/package.json +51 -5
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { isList, isReference, ref } from "../step/values";
|
|
2
|
+
const SCANNED_LIST_LENGTH = 32;
|
|
3
|
+
const listMembers = new WeakMap();
|
|
4
|
+
function listHolds(list, id) {
|
|
5
|
+
if (list.length < SCANNED_LIST_LENGTH) {
|
|
6
|
+
return list.some((item) => isReference(item) && item.ref === id);
|
|
7
|
+
}
|
|
8
|
+
let members = listMembers.get(list);
|
|
9
|
+
if (!members) {
|
|
10
|
+
members = new Set(list.filter(isReference).map((item) => item.ref));
|
|
11
|
+
listMembers.set(list, members);
|
|
12
|
+
}
|
|
13
|
+
return members.has(id);
|
|
14
|
+
}
|
|
15
|
+
function refersTo(reader, relationship, attribute, id) {
|
|
16
|
+
const value = reader.attribute(relationship, attribute);
|
|
17
|
+
return isReference(value) ? value.ref === id : isList(value) && listHolds(value, id);
|
|
18
|
+
}
|
|
19
|
+
function relationshipsWhere(reader, roles, attribute, id) {
|
|
20
|
+
return reader.referencesTo(id).filter((user) => reader.typeOf(user) === roles.relationship && refersTo(reader, user, attribute, id));
|
|
21
|
+
}
|
|
22
|
+
export function relatingOf(reader, roles, part) {
|
|
23
|
+
const relationship = relationshipsWhere(reader, roles, roles.related, part)[0];
|
|
24
|
+
if (relationship === undefined) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
const whole = reader.attribute(relationship, roles.relating);
|
|
28
|
+
return isReference(whole) ? whole.ref : undefined;
|
|
29
|
+
}
|
|
30
|
+
export function relationshipsOf(reader, roles, whole) {
|
|
31
|
+
return relationshipsWhere(reader, roles, roles.relating, whole);
|
|
32
|
+
}
|
|
33
|
+
export function relate(tx, writer, roles, whole, parts) {
|
|
34
|
+
return writer.create(roles.relationship, { GlobalId: tx.globalId(undefined), [roles.relating]: ref(whole), [roles.related]: parts.map(ref) });
|
|
35
|
+
}
|
|
36
|
+
export function appendToRelationship(tx, writer, roles, whole, parts) {
|
|
37
|
+
const existing = relationshipsOf(tx, roles, whole)[0];
|
|
38
|
+
if (existing === undefined) {
|
|
39
|
+
return relate(tx, writer, roles, whole, parts);
|
|
40
|
+
}
|
|
41
|
+
const current = tx.attribute(existing, roles.related);
|
|
42
|
+
tx.update(existing, { [roles.related]: [...(isList(current) ? current : []), ...parts.map(ref)] });
|
|
43
|
+
return existing;
|
|
44
|
+
}
|
|
45
|
+
export function detach(tx, roles, part) {
|
|
46
|
+
const emptied = [];
|
|
47
|
+
for (const relationship of relationshipsWhere(tx, roles, roles.related, part)) {
|
|
48
|
+
const current = tx.attribute(relationship, roles.related);
|
|
49
|
+
const remaining = isList(current) ? current.filter((item) => !(isReference(item) && item.ref === part)) : [];
|
|
50
|
+
if (remaining.length) {
|
|
51
|
+
tx.update(relationship, { [roles.related]: remaining });
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const whole = tx.attribute(relationship, roles.relating);
|
|
55
|
+
tx.delete(relationship);
|
|
56
|
+
if (isReference(whole)) {
|
|
57
|
+
emptied.push(whole.ref);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return emptied;
|
|
61
|
+
}
|
|
62
|
+
export function relatedOf(reader, roles, whole) {
|
|
63
|
+
return relationshipsOf(reader, roles, whole).flatMap((relationship) => {
|
|
64
|
+
const value = reader.attribute(relationship, roles.related);
|
|
65
|
+
return (isList(value) ? value : [value]).filter(isReference).map((item) => item.ref);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { isList, isReference, referencesIn } from "../step/values";
|
|
2
|
+
import { AGGREGATION, CONTAINMENT, TYPE_DEFINITION } from "./constants";
|
|
3
|
+
import { fillingsOf, openingsOf } from "./openings";
|
|
4
|
+
import { relatedOf } from "./relationships";
|
|
5
|
+
import { describeObject } from "./spatial";
|
|
6
|
+
import { readableWall } from "./wall-geometry";
|
|
7
|
+
import { joinsOf, regenerateWall } from "./walls";
|
|
8
|
+
const RELATIONSHIP = "IfcRelationship";
|
|
9
|
+
const KEPT_STRUCTURE = ["IfcProject", "IfcSite", "IfcBuilding"];
|
|
10
|
+
const SPATIAL_ELEMENT = "IfcSpatialElement";
|
|
11
|
+
const TYPE_OBJECT = "IfcTypeObject";
|
|
12
|
+
const WALL = "IfcWall";
|
|
13
|
+
function partsOf(reader, whole) {
|
|
14
|
+
return relatedOf(reader, AGGREGATION, whole);
|
|
15
|
+
}
|
|
16
|
+
function counted(count, what) {
|
|
17
|
+
return `${count} ${what}${count === 1 ? "" : "s"}`;
|
|
18
|
+
}
|
|
19
|
+
function refuseKept(reader, object) {
|
|
20
|
+
const type = reader.entity(object).type;
|
|
21
|
+
if (KEPT_STRUCTURE.some((kept) => reader.schema.isSubtypeOf(type, kept))) {
|
|
22
|
+
throw new Error(`${describeObject(reader, object)} is part of the model's structure and is not removed`);
|
|
23
|
+
}
|
|
24
|
+
if (reader.schema.isSubtypeOf(type, SPATIAL_ELEMENT)) {
|
|
25
|
+
const held = relatedOf(reader, CONTAINMENT, object).length + partsOf(reader, object).length;
|
|
26
|
+
if (held) {
|
|
27
|
+
throw new Error(`${describeObject(reader, object)} still holds ${counted(held, "object")}; remove them first`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (reader.schema.isSubtypeOf(type, TYPE_OBJECT)) {
|
|
31
|
+
const occurrences = relatedOf(reader, TYPE_DEFINITION, object).length;
|
|
32
|
+
if (occurrences) {
|
|
33
|
+
throw new Error(`${describeObject(reader, object)} is the type of ${counted(occurrences, "element")}; remove them first`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function removalOf(reader, object) {
|
|
38
|
+
refuseKept(reader, object);
|
|
39
|
+
const removing = new Set();
|
|
40
|
+
const work = [object];
|
|
41
|
+
while (work.length) {
|
|
42
|
+
const id = work.pop();
|
|
43
|
+
if (removing.has(id)) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
removing.add(id);
|
|
47
|
+
work.push(...openingsOf(reader, id), ...fillingsOf(reader, id));
|
|
48
|
+
if (!reader.schema.isSubtypeOf(reader.entity(id).type, SPATIAL_ELEMENT)) {
|
|
49
|
+
work.push(...partsOf(reader, id));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return removing;
|
|
53
|
+
}
|
|
54
|
+
function unlink(tx, id, removing) {
|
|
55
|
+
const freed = [];
|
|
56
|
+
for (const user of [...tx.referencesTo(id)]) {
|
|
57
|
+
const entity = tx.get(user);
|
|
58
|
+
if (!entity || removing.has(user) || !tx.schema.isSubtypeOf(entity.type, RELATIONSHIP)) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const changes = [];
|
|
62
|
+
let emptied = false;
|
|
63
|
+
tx.schema.entity(entity.type).attributes.forEach((attribute, index) => {
|
|
64
|
+
var _a;
|
|
65
|
+
const value = (_a = entity.args[index]) !== null && _a !== void 0 ? _a : null;
|
|
66
|
+
const goes = (item) => isReference(item) && removing.has(item.ref);
|
|
67
|
+
if (goes(value)) {
|
|
68
|
+
emptied = true;
|
|
69
|
+
}
|
|
70
|
+
else if (isList(value) && value.some(goes)) {
|
|
71
|
+
const remaining = value.filter((item) => !goes(item));
|
|
72
|
+
emptied || (emptied = remaining.length === 0);
|
|
73
|
+
changes.push([attribute.name, remaining]);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
if (emptied) {
|
|
77
|
+
freed.push(...entity.args.flatMap((arg) => referencesIn(arg)));
|
|
78
|
+
tx.delete(user);
|
|
79
|
+
}
|
|
80
|
+
else if (changes.length) {
|
|
81
|
+
tx.update(user, Object.fromEntries(changes));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return freed;
|
|
85
|
+
}
|
|
86
|
+
export function removeObject(tx, writer, object, tolerance, parallelTolerance) {
|
|
87
|
+
var _a;
|
|
88
|
+
const removing = removalOf(tx, object);
|
|
89
|
+
const trimmed = new Set();
|
|
90
|
+
for (const id of removing) {
|
|
91
|
+
if (tx.schema.isSubtypeOf(tx.entity(id).type, WALL)) {
|
|
92
|
+
joinsOf(tx, id).filter((join) => join.theirs !== "ATPATH" && !removing.has(join.other)).forEach((join) => trimmed.add(join.other));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const freed = [];
|
|
96
|
+
for (const id of removing) {
|
|
97
|
+
freed.push(...unlink(tx, id, removing), ...tx.entity(id).args.flatMap((arg) => referencesIn(arg)));
|
|
98
|
+
}
|
|
99
|
+
removing.forEach((id) => tx.delete(id));
|
|
100
|
+
tx.dropIfUnused(freed.filter((id) => !removing.has(id)));
|
|
101
|
+
for (const wall of trimmed) {
|
|
102
|
+
if (((_a = readableWall(tx, wall)) === null || _a === void 0 ? void 0 : _a.body) !== undefined) {
|
|
103
|
+
regenerateWall(tx, writer, wall, tolerance, parallelTolerance);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { IfcTransaction } from "../model/transaction";
|
|
2
|
+
import type { ModelReader } from "./build-types";
|
|
3
|
+
export declare function representationOf(reader: ModelReader, product: number, identifier: string): number | undefined;
|
|
4
|
+
export declare function firstItem(reader: ModelReader, representation: number): number;
|
|
5
|
+
export declare function unwrapClippings(reader: ModelReader, item: number): [number, number[]];
|
|
6
|
+
export declare function replaceRepresentation(tx: IfcTransaction, product: number, oldRepresentation: number | undefined, newRepresentation: number): void;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { isList, isReference, ref } from "../step/values";
|
|
2
|
+
import { requiredReference } from "./checks";
|
|
3
|
+
export function representationOf(reader, product, identifier) {
|
|
4
|
+
const shape = reader.attribute(product, "Representation");
|
|
5
|
+
if (!isReference(shape)) {
|
|
6
|
+
return undefined;
|
|
7
|
+
}
|
|
8
|
+
const representations = reader.attribute(shape.ref, "Representations");
|
|
9
|
+
for (const representation of isList(representations) ? representations : []) {
|
|
10
|
+
if (isReference(representation) && reader.attribute(representation.ref, "RepresentationIdentifier") === identifier) {
|
|
11
|
+
return representation.ref;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
export function firstItem(reader, representation) {
|
|
17
|
+
const items = reader.attribute(representation, "Items");
|
|
18
|
+
const first = isList(items) ? items[0] : undefined;
|
|
19
|
+
if (!isReference(first)) {
|
|
20
|
+
throw new Error(`#${representation} holds no items`);
|
|
21
|
+
}
|
|
22
|
+
return first.ref;
|
|
23
|
+
}
|
|
24
|
+
export function unwrapClippings(reader, item) {
|
|
25
|
+
const halfSpaces = [];
|
|
26
|
+
let current = item;
|
|
27
|
+
while (reader.entity(current).type === "IfcBooleanClippingResult") {
|
|
28
|
+
const second = reader.attribute(current, "SecondOperand");
|
|
29
|
+
const first = reader.attribute(current, "FirstOperand");
|
|
30
|
+
if (!isReference(second) || !isReference(first)) {
|
|
31
|
+
throw new Error(`#${current} is a clipping without operands`);
|
|
32
|
+
}
|
|
33
|
+
halfSpaces.unshift(second.ref);
|
|
34
|
+
current = first.ref;
|
|
35
|
+
}
|
|
36
|
+
return [current, halfSpaces];
|
|
37
|
+
}
|
|
38
|
+
export function replaceRepresentation(tx, product, oldRepresentation, newRepresentation) {
|
|
39
|
+
const shape = requiredReference(tx, product, "Representation");
|
|
40
|
+
const representations = tx.attribute(shape, "Representations");
|
|
41
|
+
const kept = (isList(representations) ? representations : []).filter((item) => !(isReference(item) && item.ref === oldRepresentation));
|
|
42
|
+
tx.update(shape, { Representations: [...kept, ref(newRepresentation)] });
|
|
43
|
+
if (oldRepresentation !== undefined) {
|
|
44
|
+
tx.dropIfUnused([oldRepresentation]);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { IfcTransaction } from "../model/transaction";
|
|
2
|
+
import type { ModelReader, Plane3, WallGeometry } from "./build-types";
|
|
3
|
+
import type { EntityWriter } from "./entity-writer";
|
|
4
|
+
export declare function clippingPlanes(reader: ModelReader, wall: WallGeometry, body: number): Plane3[];
|
|
5
|
+
export declare function clipWallByRoof(tx: IfcTransaction, writer: EntityWriter, wall: number, roof: number, tolerance: number): void;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { composeAxes, vectorToWorld } from "@bitbybit-dev/base/lib/api/services/helpers/frame-axes";
|
|
2
|
+
import { add2, dot3, scale2, scale3 } from "@bitbybit-dev/base/lib/api/services/helpers/vectors";
|
|
3
|
+
import { isReference } from "../step/values";
|
|
4
|
+
import { AGGREGATION, VERTICAL_COSINE } from "./constants";
|
|
5
|
+
import { normalize3 } from "./math";
|
|
6
|
+
import { absoluteFrame, axisPlacementFrame, directionOf, objectPlacementOf } from "./placement";
|
|
7
|
+
import { relatedOf } from "./relationships";
|
|
8
|
+
import { firstItem, representationOf, unwrapClippings } from "./representations";
|
|
9
|
+
import { frameOf } from "./solids";
|
|
10
|
+
import { describeObject } from "./spatial";
|
|
11
|
+
import { readWall, sideLine } from "./wall-geometry";
|
|
12
|
+
import { clipWall } from "./walls";
|
|
13
|
+
function undersideOf(reader, part) {
|
|
14
|
+
const body = representationOf(reader, part, "Body");
|
|
15
|
+
const [solid] = body === undefined ? [undefined] : unwrapClippings(reader, firstItem(reader, body));
|
|
16
|
+
if (solid === undefined || reader.entity(solid).type !== "IfcExtrudedAreaSolid") {
|
|
17
|
+
throw new Error(`${describeObject(reader, part)} has no body this library clips with: an outline extruded through the roof is expected`);
|
|
18
|
+
}
|
|
19
|
+
const frame = composeAxes(absoluteFrame(reader, objectPlacementOf(reader, part)), frameOf(reader, reader.attribute(solid, "Position")));
|
|
20
|
+
if (Math.abs(frame.z[2]) < 1 - VERTICAL_COSINE) {
|
|
21
|
+
throw new Error(`${describeObject(reader, part)} stands upright, so it is no roof plane to clip with`);
|
|
22
|
+
}
|
|
23
|
+
const up = frame.z[2] > 0 ? frame.z : scale3(frame.z, -1);
|
|
24
|
+
const depth = reader.attribute(solid, "Depth");
|
|
25
|
+
const through = vectorToWorld(frame, normalize3(directionOf(reader, reader.attribute(solid, "ExtrudedDirection"), [0, 0, 1])));
|
|
26
|
+
const far = scale3(through, typeof depth === "number" ? depth : 0);
|
|
27
|
+
const farPoint = [frame.origin[0] + far[0], frame.origin[1] + far[1], frame.origin[2] + far[2]];
|
|
28
|
+
return { origin: dot3(farPoint, up) < dot3(frame.origin, up) ? farPoint : frame.origin, normal: up };
|
|
29
|
+
}
|
|
30
|
+
function heightAt(plane, point) {
|
|
31
|
+
const [x, y, z] = plane.normal;
|
|
32
|
+
return plane.origin[2] - ((point[0] - plane.origin[0]) * x + (point[1] - plane.origin[1]) * y) / z;
|
|
33
|
+
}
|
|
34
|
+
function footprintCorners(wall) {
|
|
35
|
+
return [wall.low, wall.high].flatMap((across) => {
|
|
36
|
+
const side = sideLine(wall, across);
|
|
37
|
+
return [side.point, add2(side.point, scale2(side.direction, wall.length))];
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
export function clippingPlanes(reader, wall, body) {
|
|
41
|
+
return unwrapClippings(reader, firstItem(reader, body))[1].flatMap((halfSpace) => {
|
|
42
|
+
const surface = reader.attribute(halfSpace, "BaseSurface");
|
|
43
|
+
const position = isReference(surface) && reader.entity(surface.ref).type === "IfcPlane" ? reader.attribute(surface.ref, "Position") : null;
|
|
44
|
+
if (!isReference(position)) {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
const frame = composeAxes(wall.frame, axisPlacementFrame(reader, position.ref));
|
|
48
|
+
return [{ origin: frame.origin, normal: reader.attribute(halfSpace, "AgreementFlag") === true ? scale3(frame.z, -1) : frame.z }];
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function samePlane(a, b, tolerance) {
|
|
52
|
+
const offset = (plane) => dot3(plane.origin, plane.normal);
|
|
53
|
+
return dot3(a.normal, b.normal) >= VERTICAL_COSINE && Math.abs(offset(a) - offset(b)) <= tolerance;
|
|
54
|
+
}
|
|
55
|
+
export function clipWallByRoof(tx, writer, wall, roof, tolerance) {
|
|
56
|
+
const parts = relatedOf(tx, AGGREGATION, roof);
|
|
57
|
+
if (!parts.length) {
|
|
58
|
+
throw new Error(`${describeObject(tx, roof)} has no parts to clip a wall with`);
|
|
59
|
+
}
|
|
60
|
+
const geometry = readWall(tx, wall);
|
|
61
|
+
if (geometry.body === undefined) {
|
|
62
|
+
throw new Error(`${describeObject(tx, wall)} has no body to clip`);
|
|
63
|
+
}
|
|
64
|
+
const bottom = geometry.frame.origin[2] + geometry.base;
|
|
65
|
+
const top = bottom + geometry.height;
|
|
66
|
+
const corners = footprintCorners(geometry);
|
|
67
|
+
const existing = clippingPlanes(tx, geometry, geometry.body);
|
|
68
|
+
for (const part of parts) {
|
|
69
|
+
const plane = undersideOf(tx, part);
|
|
70
|
+
const heights = corners.map((corner) => heightAt(plane, corner));
|
|
71
|
+
if (heights.every((height) => height <= bottom + tolerance)) {
|
|
72
|
+
throw new Error(`The underside of ${describeObject(tx, part)} is below the bottom of ${describeObject(tx, wall)}, so clipping by it would leave nothing of the wall`);
|
|
73
|
+
}
|
|
74
|
+
if (heights.some((height) => height < top - tolerance) && !existing.some((known) => samePlane(known, plane, tolerance))) {
|
|
75
|
+
clipWall(tx, writer, wall, plane.origin, plane.normal);
|
|
76
|
+
existing.push(plane);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { IfcTransaction } from "../model/transaction";
|
|
2
|
+
import type { RoofSpec } from "./build-types";
|
|
3
|
+
import type { EntityWriter } from "./entity-writer";
|
|
4
|
+
export declare function createRoof(tx: IfcTransaction, writer: EntityWriter, spec: RoofSpec, tolerance: number): number;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { WORLD_AXES } from "@bitbybit-dev/base/lib/api/services/helpers/frame-axes";
|
|
2
|
+
import { add2, cross2, length2, scale2, subtract2 } from "@bitbybit-dev/base/lib/api/services/helpers/vectors";
|
|
3
|
+
import { wound2 } from "@bitbybit-dev/base/lib/api/services/helpers/polygons";
|
|
4
|
+
import * as Inputs from "../api/inputs";
|
|
5
|
+
import { enumValue, ref } from "../step/values";
|
|
6
|
+
import { AGGREGATION, DEGREES_TO_RADIANS } from "./constants";
|
|
7
|
+
import { bodyContext } from "./contexts";
|
|
8
|
+
import { associateNewMaterial, layerSetThickness, layerSetUsage } from "./materials";
|
|
9
|
+
import { normalize2 } from "./math";
|
|
10
|
+
import { cleanOutline } from "./outlines";
|
|
11
|
+
import { objectPlacementOf } from "./placement";
|
|
12
|
+
import { relate } from "./relationships";
|
|
13
|
+
import { containIn } from "./spatial";
|
|
14
|
+
const RECTANGLE_CORNERS = 4;
|
|
15
|
+
const RIGHT_ANGLE_COSINE = 1e-6;
|
|
16
|
+
const MAX_PITCH_DEGREES = 89;
|
|
17
|
+
const ROOF_TYPES = {
|
|
18
|
+
[Inputs.IFC.roofKindEnum.flat]: "FLAT_ROOF",
|
|
19
|
+
[Inputs.IFC.roofKindEnum.monoPitch]: "SHED_ROOF",
|
|
20
|
+
[Inputs.IFC.roofKindEnum.gable]: "GABLE_ROOF",
|
|
21
|
+
[Inputs.IFC.roofKindEnum.hip]: "HIP_ROOF",
|
|
22
|
+
};
|
|
23
|
+
function rectangle(points, tolerance) {
|
|
24
|
+
const corners = cleanOutline(points, tolerance, "roof's outline");
|
|
25
|
+
if (corners.length !== RECTANGLE_CORNERS) {
|
|
26
|
+
throw new Error(`A roof's outline is a rectangle of ${RECTANGLE_CORNERS} corners, got ${corners.length}`);
|
|
27
|
+
}
|
|
28
|
+
corners.forEach((corner, index) => {
|
|
29
|
+
const before = normalize2(subtract2(corner, corners[(index + RECTANGLE_CORNERS - 1) % RECTANGLE_CORNERS]));
|
|
30
|
+
const after = normalize2(subtract2(corners[(index + 1) % RECTANGLE_CORNERS], corner));
|
|
31
|
+
if (Math.abs(before[0] * after[0] + before[1] * after[1]) > RIGHT_ANGLE_COSINE) {
|
|
32
|
+
throw new Error(`A roof's outline is a rectangle, and its corner ${index} is not a right angle`);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
return corners;
|
|
36
|
+
}
|
|
37
|
+
function expanded(corners, overhang) {
|
|
38
|
+
const along = normalize2(subtract2(corners[1], corners[0]));
|
|
39
|
+
const across = normalize2(subtract2(corners[3], corners[0]));
|
|
40
|
+
const out = (point, alongSign, acrossSign) => add2(point, add2(scale2(along, alongSign * overhang), scale2(across, acrossSign * overhang)));
|
|
41
|
+
return [out(corners[0], -1, -1), out(corners[1], 1, -1), out(corners[2], 1, 1), out(corners[3], -1, 1)];
|
|
42
|
+
}
|
|
43
|
+
function eavesPlane(corners, from, outline) {
|
|
44
|
+
const start = corners[from];
|
|
45
|
+
const along = normalize2(subtract2(corners[(from + 1) % RECTANGLE_CORNERS], start));
|
|
46
|
+
const across = normalize2(subtract2(corners[(from + RECTANGLE_CORNERS - 1) % RECTANGLE_CORNERS], start));
|
|
47
|
+
return { origin: start, along, inward: across, outline };
|
|
48
|
+
}
|
|
49
|
+
function gablePlanes(q) {
|
|
50
|
+
const middle = (a, b) => scale2(add2(a, b), 1 / 2);
|
|
51
|
+
const ridgeStart = middle(q[0], q[3]);
|
|
52
|
+
const ridgeEnd = middle(q[1], q[2]);
|
|
53
|
+
return [eavesPlane(q, 0, [q[0], q[1], ridgeEnd, ridgeStart]), eavesPlane(q, 2, [q[2], q[3], ridgeStart, ridgeEnd])];
|
|
54
|
+
}
|
|
55
|
+
function hipPlanes(q, tolerance) {
|
|
56
|
+
const corners = length2(subtract2(q[1], q[0])) >= length2(subtract2(q[2], q[1])) ? q : [q[1], q[2], q[3], q[0]];
|
|
57
|
+
const along = normalize2(subtract2(corners[1], corners[0]));
|
|
58
|
+
const across = normalize2(subtract2(corners[3], corners[0]));
|
|
59
|
+
const half = length2(subtract2(corners[3], corners[0])) / 2;
|
|
60
|
+
const ridgeStart = add2(corners[0], add2(scale2(along, half), scale2(across, half)));
|
|
61
|
+
const ridgeEnd = add2(corners[1], add2(scale2(along, -half), scale2(across, half)));
|
|
62
|
+
const pointed = !(length2(subtract2(ridgeEnd, ridgeStart)) > tolerance);
|
|
63
|
+
const tops = [pointed ? [ridgeStart] : [ridgeEnd, ridgeStart], [ridgeEnd], pointed ? [ridgeStart] : [ridgeStart, ridgeEnd], [ridgeStart]];
|
|
64
|
+
return corners.map((corner, index) => eavesPlane(corners, index, [corner, corners[(index + 1) % RECTANGLE_CORNERS], ...tops[index]]));
|
|
65
|
+
}
|
|
66
|
+
function planesOf(spec, q, tolerance) {
|
|
67
|
+
switch (spec.kind) {
|
|
68
|
+
case Inputs.IFC.roofKindEnum.monoPitch:
|
|
69
|
+
return [eavesPlane(q, 0, q)];
|
|
70
|
+
case Inputs.IFC.roofKindEnum.gable:
|
|
71
|
+
return gablePlanes(q);
|
|
72
|
+
case Inputs.IFC.roofKindEnum.hip:
|
|
73
|
+
return hipPlanes(q, tolerance);
|
|
74
|
+
default:
|
|
75
|
+
return [{ origin: q[0], along: [1, 0], inward: [0, 1], outline: q }];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function planeFrame(plane, height, slope) {
|
|
79
|
+
const along = cross2(plane.along, plane.inward) > 0 ? plane.along : scale2(plane.along, -1);
|
|
80
|
+
const cos = Math.cos(slope);
|
|
81
|
+
const sin = Math.sin(slope);
|
|
82
|
+
const up = [plane.inward[0] * cos, plane.inward[1] * cos, sin];
|
|
83
|
+
const x = [along[0], along[1], 0];
|
|
84
|
+
return { origin: [plane.origin[0], plane.origin[1], height], x, y: up, z: [x[1] * up[2], -x[0] * up[2], x[0] * up[1] - x[1] * up[0]] };
|
|
85
|
+
}
|
|
86
|
+
function inPlane(plane, frame, slope, point) {
|
|
87
|
+
const offset = subtract2(point, plane.origin);
|
|
88
|
+
return [offset[0] * frame.x[0] + offset[1] * frame.x[1], (offset[0] * plane.inward[0] + offset[1] * plane.inward[1]) / Math.cos(slope)];
|
|
89
|
+
}
|
|
90
|
+
export function createRoof(tx, writer, spec, tolerance) {
|
|
91
|
+
var _a;
|
|
92
|
+
const flat = spec.kind === Inputs.IFC.roofKindEnum.flat;
|
|
93
|
+
if (!flat && !(spec.pitch > 0 && spec.pitch <= MAX_PITCH_DEGREES)) {
|
|
94
|
+
throw new Error(`A sloped roof's pitch is more than 0 and at most ${MAX_PITCH_DEGREES} degrees, got ${spec.pitch}`);
|
|
95
|
+
}
|
|
96
|
+
const thickness = layerSetThickness(tx, spec.layerSet);
|
|
97
|
+
if (!(thickness > 0)) {
|
|
98
|
+
throw new Error("A roof's layers must add up to more than zero thickness");
|
|
99
|
+
}
|
|
100
|
+
if (!(spec.overhang >= 0)) {
|
|
101
|
+
throw new Error(`A roof's overhang is zero or more, got ${spec.overhang}`);
|
|
102
|
+
}
|
|
103
|
+
const slope = flat ? 0 : spec.pitch * DEGREES_TO_RADIANS;
|
|
104
|
+
const outer = expanded(rectangle(spec.outline, tolerance), spec.overhang);
|
|
105
|
+
const eaves = spec.baseOffset - spec.overhang * Math.tan(slope);
|
|
106
|
+
const placement = writer.localPlacement(objectPlacementOf(tx, spec.storey), WORLD_AXES);
|
|
107
|
+
const roof = writer.create("IfcRoof", {
|
|
108
|
+
GlobalId: tx.globalId(spec.id),
|
|
109
|
+
Name: (_a = spec.name) !== null && _a !== void 0 ? _a : null,
|
|
110
|
+
ObjectPlacement: ref(placement),
|
|
111
|
+
PredefinedType: enumValue(ROOF_TYPES[spec.kind]),
|
|
112
|
+
});
|
|
113
|
+
containIn(tx, writer, spec.storey, roof);
|
|
114
|
+
const parts = planesOf(spec, outer, tolerance).map((plane) => {
|
|
115
|
+
var _a;
|
|
116
|
+
const frame = planeFrame(plane, flat ? spec.baseOffset : eaves, slope);
|
|
117
|
+
const outline = wound2(plane.outline.map((point) => inPlane(plane, frame, slope, point)), true);
|
|
118
|
+
const solid = writer.extrusion(writer.profile({ outer: outline, holes: [] }), WORLD_AXES, thickness);
|
|
119
|
+
const body = writer.shapeRepresentation(bodyContext(tx, writer), "Body", "SweptSolid", [solid]);
|
|
120
|
+
return writer.create("IfcSlab", {
|
|
121
|
+
GlobalId: tx.globalId(undefined),
|
|
122
|
+
Name: (_a = spec.name) !== null && _a !== void 0 ? _a : null,
|
|
123
|
+
ObjectPlacement: ref(writer.localPlacement(placement, frame)),
|
|
124
|
+
Representation: ref(writer.productShape([body])),
|
|
125
|
+
PredefinedType: enumValue("ROOF"),
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
relate(tx, writer, AGGREGATION, roof, parts);
|
|
129
|
+
associateNewMaterial(tx, writer, parts, layerSetUsage(writer, spec.layerSet, "AXIS3", "NEGATIVE", thickness));
|
|
130
|
+
return roof;
|
|
131
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ModelSnapshot } from "../model/snapshot";
|
|
2
|
+
import type { IfcTransaction } from "../model/transaction";
|
|
3
|
+
import type { TerrainSpec, TreeSpec } from "./build-types";
|
|
4
|
+
import type { EntityWriter } from "./entity-writer";
|
|
5
|
+
export declare function onlySite(model: ModelSnapshot | IfcTransaction): number;
|
|
6
|
+
export declare function createTerrain(tx: IfcTransaction, writer: EntityWriter, spec: TerrainSpec, tolerance: number): number;
|
|
7
|
+
export declare function createTree(tx: IfcTransaction, writer: EntityWriter, spec: TreeSpec): number;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { WORLD_AXES } from "@bitbybit-dev/base/lib/api/services/helpers/frame-axes";
|
|
2
|
+
import { frustum, icosphere } from "@bitbybit-dev/base/lib/api/services/helpers/mesh-primitives";
|
|
3
|
+
import { isInsidePolygon2, wound2 } from "@bitbybit-dev/base/lib/api/services/helpers/polygons";
|
|
4
|
+
import { enumValue, ref } from "../step/values";
|
|
5
|
+
import { POINT3_SIZE, TRIANGLE_CORNERS } from "./constants";
|
|
6
|
+
import { bodyContext } from "./contexts";
|
|
7
|
+
import { associateNewMaterial, dressParts, materialsOfParts, requireMaterial } from "./materials";
|
|
8
|
+
import { cleanOutline } from "./outlines";
|
|
9
|
+
import { objectPlacementOf } from "./placement";
|
|
10
|
+
import { containIn } from "./spatial";
|
|
11
|
+
const GEOGRAPHIC_ELEMENT = "IfcGeographicElement";
|
|
12
|
+
const CROWN_DETAIL = 1;
|
|
13
|
+
const CROWN_STRETCH = 1.25;
|
|
14
|
+
const CROWN_SHARE_OF_HEIGHT = 0.45;
|
|
15
|
+
const CROWN_RIPPLE = 0.07;
|
|
16
|
+
const CROWN_LOBES = 3;
|
|
17
|
+
const TRUNK_SIDES = 8;
|
|
18
|
+
const TRUNK_TOP_SHARE = 0.6;
|
|
19
|
+
const TRUNK_INTO_CROWN = 0.35;
|
|
20
|
+
export function onlySite(model) {
|
|
21
|
+
const sites = model.byType("IfcSite");
|
|
22
|
+
if (sites.length !== 1) {
|
|
23
|
+
throw new Error(`Site elements are added to the model's one site, and it has ${sites.length}`);
|
|
24
|
+
}
|
|
25
|
+
return sites[0].id;
|
|
26
|
+
}
|
|
27
|
+
function triangulatedFaceSet(writer, mesh, origin) {
|
|
28
|
+
const points = [];
|
|
29
|
+
for (let at = 0; at < mesh.positions.length; at += POINT3_SIZE) {
|
|
30
|
+
points.push([mesh.positions[at] + origin[0], mesh.positions[at + 1] + origin[1], mesh.positions[at + 2] + origin[2]]);
|
|
31
|
+
}
|
|
32
|
+
const triangles = [];
|
|
33
|
+
for (let at = 0; at < mesh.indices.length; at += TRIANGLE_CORNERS) {
|
|
34
|
+
triangles.push([mesh.indices[at] + 1, mesh.indices[at + 1] + 1, mesh.indices[at + 2] + 1]);
|
|
35
|
+
}
|
|
36
|
+
const coordinates = writer.create("IfcCartesianPointList3D", { CoordList: points });
|
|
37
|
+
return writer.create("IfcTriangulatedFaceSet", { Coordinates: ref(coordinates), Closed: true, CoordIndex: triangles });
|
|
38
|
+
}
|
|
39
|
+
function crownMesh(spec) {
|
|
40
|
+
const upward = Math.min(spec.crownRadius * CROWN_STRETCH, spec.height * CROWN_SHARE_OF_HEIGHT);
|
|
41
|
+
const sphere = icosphere(CROWN_DETAIL);
|
|
42
|
+
const positions = sphere.positions.slice();
|
|
43
|
+
for (let at = 0; at < positions.length; at += POINT3_SIZE) {
|
|
44
|
+
const [x, y, z] = [positions[at], positions[at + 1], positions[at + 2]];
|
|
45
|
+
const ripple = 1 + CROWN_RIPPLE * Math.sin(CROWN_LOBES * Math.atan2(y, x) + 2 * z);
|
|
46
|
+
positions[at] = x * spec.crownRadius * ripple;
|
|
47
|
+
positions[at + 1] = y * spec.crownRadius * ripple;
|
|
48
|
+
positions[at + 2] = z * upward * ripple;
|
|
49
|
+
}
|
|
50
|
+
return { mesh: { positions, indices: sphere.indices }, centre: spec.height - upward * (1 + CROWN_RIPPLE) };
|
|
51
|
+
}
|
|
52
|
+
export function createTerrain(tx, writer, spec, tolerance) {
|
|
53
|
+
if (!(spec.depth > 0)) {
|
|
54
|
+
throw new Error(`The terrain's depth must be more than zero, got ${spec.depth}`);
|
|
55
|
+
}
|
|
56
|
+
const site = onlySite(tx);
|
|
57
|
+
const material = spec.material === undefined ? undefined : requireMaterial(tx, spec.material);
|
|
58
|
+
const outer = wound2(cleanOutline(spec.outline, tolerance, "outline"), true);
|
|
59
|
+
const holes = spec.holes.map((hole, index) => {
|
|
60
|
+
const cleaned = cleanOutline(hole, tolerance, `hole ${index}`);
|
|
61
|
+
if (!cleaned.every((point) => isInsidePolygon2(point, outer))) {
|
|
62
|
+
throw new Error(`Hole ${index} reaches outside the terrain's outline`);
|
|
63
|
+
}
|
|
64
|
+
return wound2(cleaned, false);
|
|
65
|
+
});
|
|
66
|
+
const placement = writer.localPlacement(objectPlacementOf(tx, site), Object.assign(Object.assign({}, WORLD_AXES), { origin: [0, 0, spec.elevation] }));
|
|
67
|
+
const solid = writer.extrusion(writer.profile({ outer, holes }), WORLD_AXES, spec.depth, [0, 0, -1]);
|
|
68
|
+
const body = writer.shapeRepresentation(bodyContext(tx, writer), "Body", "SweptSolid", [solid]);
|
|
69
|
+
const terrain = writer.create(GEOGRAPHIC_ELEMENT, {
|
|
70
|
+
GlobalId: tx.globalId(spec.id),
|
|
71
|
+
Name: spec.name,
|
|
72
|
+
ObjectPlacement: ref(placement),
|
|
73
|
+
Representation: ref(writer.productShape([body])),
|
|
74
|
+
PredefinedType: enumValue("TERRAIN"),
|
|
75
|
+
});
|
|
76
|
+
containIn(tx, writer, site, terrain);
|
|
77
|
+
if (material !== undefined) {
|
|
78
|
+
associateNewMaterial(tx, writer, [terrain], material);
|
|
79
|
+
}
|
|
80
|
+
return terrain;
|
|
81
|
+
}
|
|
82
|
+
export function createTree(tx, writer, spec) {
|
|
83
|
+
for (const [what, value] of [["height", spec.height], ["crown radius", spec.crownRadius], ["trunk radius", spec.trunkRadius]]) {
|
|
84
|
+
if (!(value > 0) || !Number.isFinite(value)) {
|
|
85
|
+
throw new Error(`The tree's ${what} must be more than zero, got ${value}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (spec.trunkRadius >= spec.crownRadius) {
|
|
89
|
+
throw new Error("A tree's trunk must be narrower than its crown");
|
|
90
|
+
}
|
|
91
|
+
const site = onlySite(tx);
|
|
92
|
+
const { mesh, centre } = crownMesh(spec);
|
|
93
|
+
const trunkTop = centre + (spec.height - centre) * TRUNK_INTO_CROWN;
|
|
94
|
+
const parts = [
|
|
95
|
+
{ name: "Foliage", items: [triangulatedFaceSet(writer, mesh, [0, 0, centre])], material: spec.crownMaterial },
|
|
96
|
+
{ name: "Trunk", items: [triangulatedFaceSet(writer, frustum(TRUNK_SIDES, spec.trunkRadius, spec.trunkRadius * TRUNK_TOP_SHARE, trunkTop), [0, 0, 0])], material: spec.trunkMaterial },
|
|
97
|
+
];
|
|
98
|
+
const materials = materialsOfParts(tx, parts);
|
|
99
|
+
const placement = writer.localPlacement(objectPlacementOf(tx, site), Object.assign(Object.assign({}, WORLD_AXES), { origin: [spec.position[0], spec.position[1], spec.elevation] }));
|
|
100
|
+
const body = writer.shapeRepresentation(bodyContext(tx, writer), "Body", "Tessellation", parts.flatMap((part) => part.items));
|
|
101
|
+
const tree = writer.create(GEOGRAPHIC_ELEMENT, {
|
|
102
|
+
GlobalId: tx.globalId(spec.id),
|
|
103
|
+
Name: spec.name,
|
|
104
|
+
ObjectPlacement: ref(placement),
|
|
105
|
+
Representation: ref(writer.productShape([body])),
|
|
106
|
+
ObjectType: spec.species,
|
|
107
|
+
PredefinedType: enumValue("USERDEFINED"),
|
|
108
|
+
});
|
|
109
|
+
containIn(tx, writer, site, tree);
|
|
110
|
+
dressParts(tx, writer, tree, parts, materials);
|
|
111
|
+
return tree;
|
|
112
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { IfcTransaction } from "../model/transaction";
|
|
2
|
+
import type { SlabOpeningSpec } from "./build-types";
|
|
3
|
+
import type { EntityWriter } from "./entity-writer";
|
|
4
|
+
export declare function createSlabOpening(tx: IfcTransaction, writer: EntityWriter, spec: SlabOpeningSpec, tolerance: number): number;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { WORLD_AXES, vectorToWorld } from "@bitbybit-dev/base/lib/api/services/helpers/frame-axes";
|
|
2
|
+
import { wound2 } from "@bitbybit-dev/base/lib/api/services/helpers/polygons";
|
|
3
|
+
import { enumValue, isReference, ref } from "../step/values";
|
|
4
|
+
import { VERTICAL_COSINE, VOIDING } from "./constants";
|
|
5
|
+
import { bodyContext } from "./contexts";
|
|
6
|
+
import { polylinePoints } from "./curves";
|
|
7
|
+
import { normalize3, placedOnPlan } from "./math";
|
|
8
|
+
import { openingMargin } from "./openings";
|
|
9
|
+
import { cleanOutline, edgesCross, isInsideOrOn } from "./outlines";
|
|
10
|
+
import { absoluteFrame, directionOf, objectPlacementOf } from "./placement";
|
|
11
|
+
import { firstItem, representationOf, unwrapClippings } from "./representations";
|
|
12
|
+
import { frameOf } from "./solids";
|
|
13
|
+
import { describeObject } from "./spatial";
|
|
14
|
+
const ARBITRARY_PROFILES = ["IfcArbitraryClosedProfileDef", "IfcArbitraryProfileDefWithVoids"];
|
|
15
|
+
function extrudedSolidOf(reader, slab) {
|
|
16
|
+
const body = representationOf(reader, slab, "Body");
|
|
17
|
+
if (body === undefined) {
|
|
18
|
+
throw new Error(`${describeObject(reader, slab)} has no Body representation to cut`);
|
|
19
|
+
}
|
|
20
|
+
const [solid] = unwrapClippings(reader, firstItem(reader, body));
|
|
21
|
+
if (reader.entity(solid).type !== "IfcExtrudedAreaSolid") {
|
|
22
|
+
throw new Error(`${describeObject(reader, slab)} has a body this library does not cut: an ${reader.entity(solid).type}, where an outline extruded up or down is expected`);
|
|
23
|
+
}
|
|
24
|
+
return solid;
|
|
25
|
+
}
|
|
26
|
+
function verticalSpan(reader, slab, solid, frame) {
|
|
27
|
+
const direction = vectorToWorld(frame, normalize3(directionOf(reader, reader.attribute(solid, "ExtrudedDirection"), [0, 0, 1])));
|
|
28
|
+
const depth = reader.attribute(solid, "Depth");
|
|
29
|
+
if (typeof depth !== "number" || Math.abs(frame.z[2]) < VERTICAL_COSINE || Math.abs(direction[2]) < VERTICAL_COSINE) {
|
|
30
|
+
throw new Error(`${describeObject(reader, slab)} is not extruded straight up or down, so this library does not cut it`);
|
|
31
|
+
}
|
|
32
|
+
const far = frame.origin[2] + direction[2] * depth;
|
|
33
|
+
return [Math.min(frame.origin[2], far), Math.max(frame.origin[2], far)];
|
|
34
|
+
}
|
|
35
|
+
function outlineOf(reader, solid, frame) {
|
|
36
|
+
const profile = reader.attribute(solid, "SweptArea");
|
|
37
|
+
if (!isReference(profile) || !ARBITRARY_PROFILES.includes(reader.entity(profile.ref).type)) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
const outer = reader.attribute(profile.ref, "OuterCurve");
|
|
41
|
+
const points = isReference(outer) ? polylinePoints(reader, outer.ref) : undefined;
|
|
42
|
+
return points === null || points === void 0 ? void 0 : points.map((point) => placedOnPlan(frame, point));
|
|
43
|
+
}
|
|
44
|
+
export function createSlabOpening(tx, writer, spec, tolerance) {
|
|
45
|
+
var _a;
|
|
46
|
+
const placement = objectPlacementOf(tx, spec.slab);
|
|
47
|
+
if (absoluteFrame(tx, placement).z[2] < VERTICAL_COSINE) {
|
|
48
|
+
throw new Error(`${describeObject(tx, spec.slab)} is not level, so this library does not cut it`);
|
|
49
|
+
}
|
|
50
|
+
const solid = extrudedSolidOf(tx, spec.slab);
|
|
51
|
+
const frame = frameOf(tx, tx.attribute(solid, "Position"));
|
|
52
|
+
const [bottom, top] = verticalSpan(tx, spec.slab, solid, frame);
|
|
53
|
+
const outline = wound2(cleanOutline(spec.outline, tolerance, "opening's outline"), true);
|
|
54
|
+
const slabOutline = outlineOf(tx, solid, frame);
|
|
55
|
+
if (slabOutline && (!outline.every((point) => isInsideOrOn(point, slabOutline, tolerance)) || edgesCross(outline, slabOutline, tolerance))) {
|
|
56
|
+
throw new Error(`The opening's outline reaches outside ${describeObject(tx, spec.slab)}`);
|
|
57
|
+
}
|
|
58
|
+
const margin = openingMargin(top - bottom);
|
|
59
|
+
const openingPlacement = writer.localPlacement(placement, Object.assign(Object.assign({}, WORLD_AXES), { origin: [0, 0, top + margin] }));
|
|
60
|
+
const cut = writer.extrusion(writer.profile({ outer: outline, holes: [] }), WORLD_AXES, top - bottom + 2 * margin, [0, 0, -1]);
|
|
61
|
+
const body = writer.shapeRepresentation(bodyContext(tx, writer), "Body", "SweptSolid", [cut]);
|
|
62
|
+
const opening = writer.create("IfcOpeningElement", {
|
|
63
|
+
GlobalId: tx.globalId(spec.id),
|
|
64
|
+
Name: (_a = spec.name) !== null && _a !== void 0 ? _a : null,
|
|
65
|
+
ObjectPlacement: ref(openingPlacement),
|
|
66
|
+
Representation: ref(writer.productShape([body])),
|
|
67
|
+
PredefinedType: enumValue("OPENING"),
|
|
68
|
+
});
|
|
69
|
+
writer.create(VOIDING.relationship, { GlobalId: tx.globalId(undefined), [VOIDING.relating]: ref(spec.slab), [VOIDING.related]: ref(opening) });
|
|
70
|
+
return opening;
|
|
71
|
+
}
|