ifcfactory 0.1.0__py3-none-any.whl
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.
- ifcfactory/README.md +177 -0
- ifcfactory/__init__.py +114 -0
- ifcfactory/_internal/__init__.py +6 -0
- ifcfactory/_internal/material_base.py +299 -0
- ifcfactory/_internal/primitives_base.py +237 -0
- ifcfactory/_internal/pset_base.py +110 -0
- ifcfactory/_internal/unit_base.py +66 -0
- ifcfactory/element.py +246 -0
- ifcfactory/examples/example_bimfactory.py +588 -0
- ifcfactory/operations.py +353 -0
- ifcfactory/primitives.py +395 -0
- ifcfactory-0.1.0.dist-info/METADATA +226 -0
- ifcfactory-0.1.0.dist-info/RECORD +15 -0
- ifcfactory-0.1.0.dist-info/WHEEL +4 -0
- ifcfactory-0.1.0.dist-info/licenses/LICENSE +458 -0
ifcfactory/README.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# ifcfactory
|
|
2
|
+
|
|
3
|
+
A modular geometry system for creating BIM elements with composable geometry creation.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
ifcfactory offers a declarative, composable framework for creating complex BIM geometry.
|
|
8
|
+
It leverages Pydantic models for strong type safety and validation,
|
|
9
|
+
while providing seamless integration with IfcOpenShell for reliable IFC file generation.
|
|
10
|
+
|
|
11
|
+
## Structure
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
ifcfactory/
|
|
15
|
+
├── __init__.py # Main public API
|
|
16
|
+
├── element.py # High-level BIM elements
|
|
17
|
+
├── primitives.py # Geometric shapes (Box, Cylinder, Sphere, etc.)
|
|
18
|
+
├── operations.py # Transformations and Boolean operations
|
|
19
|
+
├── _internal/ # Internal implementation (not for direct user access)
|
|
20
|
+
│ ├── __init__.py # Internal package marker
|
|
21
|
+
│ ├── primitives_base.py # Base classes and helper functions
|
|
22
|
+
│ ├── material_base.py # Materials and styling
|
|
23
|
+
│ ├── unit_base.py # Unit conversion utilities (pint ↔ IFC)
|
|
24
|
+
│ └── pset_base.py # Property set templates
|
|
25
|
+
├── examples/
|
|
26
|
+
│ └── example_bimfactory.py
|
|
27
|
+
└── README.md
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Public API
|
|
31
|
+
|
|
32
|
+
### High-Level Elements
|
|
33
|
+
|
|
34
|
+
- **`BIMFactoryElement`**: Container for complete BIM elements with IFC integration
|
|
35
|
+
|
|
36
|
+
### 2D Profiles
|
|
37
|
+
|
|
38
|
+
- **`Rect(width, height)`**: Rectangular profile
|
|
39
|
+
- **`Circle(radius)`**: Circular profile
|
|
40
|
+
- **`Ellipse(semi_axis1, semi_axis2)`**: Elliptical profile
|
|
41
|
+
- **`Polygon(points)`**: Arbitrary polygon from list of 2D points
|
|
42
|
+
|
|
43
|
+
### 3D Representations
|
|
44
|
+
|
|
45
|
+
- **`Box(width, depth, height)`**: Rectangular box
|
|
46
|
+
- **`Cube(size)`**: Cube with equal dimensions
|
|
47
|
+
- **`Cylinder(radius, height)`**: Circular cylinder
|
|
48
|
+
- **`EllipticalCylinder(semi_axis1, semi_axis2, height)`**: Elliptical cylinder
|
|
49
|
+
- **`NgonCylinder(radius, height, sides)`**: N-sided cylinder
|
|
50
|
+
- **`Sphere(radius, detail=1)`**: Icosphere with configurable detail
|
|
51
|
+
- **`Extrusion(basis, depth)`**: Extrude any 2D profile
|
|
52
|
+
- **`ExtrudedNgonAsMesh(basis, height)`**: N-sided extrusion as mesh
|
|
53
|
+
- **`MeshRepresentation(vertices, faces)`**: Custom mesh geometry
|
|
54
|
+
|
|
55
|
+
### Operations
|
|
56
|
+
|
|
57
|
+
- **`Translate(vec, item)`**: Move geometry in 3D space
|
|
58
|
+
- **`RotateZ(degrees, item)`**: Rotate around Z-axis
|
|
59
|
+
- **`Transform(matrix, item)`**: Apply 4x4 transformation matrix
|
|
60
|
+
- **`Boolean(operation, children)`**: Boolean operations (union, difference, intersection)
|
|
61
|
+
- **`BooleanOperationTypes`**: Enum for boolean operation types
|
|
62
|
+
|
|
63
|
+
### Materials & Styling
|
|
64
|
+
|
|
65
|
+
- **`Style`**: Visual styling with colors and transparency
|
|
66
|
+
- **`Material`**: Physical material properties
|
|
67
|
+
|
|
68
|
+
### Property Sets
|
|
69
|
+
|
|
70
|
+
- **`PropertySetTemplate`**: Template for creating property sets
|
|
71
|
+
|
|
72
|
+
### Units
|
|
73
|
+
|
|
74
|
+
- Dimension variables: `L` (length), `A` (area), `V` (volume), `M` (mass), `T` (time), `E` (current), `K` (temperature),
|
|
75
|
+
`N` (force), `J` (energy), `P` (power)
|
|
76
|
+
- **`pint_to_ifc`**: Mapping dictionary for unit conversion
|
|
77
|
+
- **`ureg`**: Pint unit registry
|
|
78
|
+
- **`Dim`**: Dimension class
|
|
79
|
+
|
|
80
|
+
## Key Features
|
|
81
|
+
|
|
82
|
+
- **Type Safety**: Built on Pydantic for runtime type checking and validation
|
|
83
|
+
- **Composable**: Mix and match primitives to create complex geometry
|
|
84
|
+
- **Cacheable**: Built elements are cached to prevent duplication
|
|
85
|
+
- **Unit Aware**: Automatic unit conversion between pint and IFC types
|
|
86
|
+
- **Material Support**: Visual styling and material assignment
|
|
87
|
+
- **Property Sets**: Support for IFC property sets and quantities
|
|
88
|
+
- **API**: Internal implementation hidden in `_internal` module
|
|
89
|
+
|
|
90
|
+
## Complete Working Example
|
|
91
|
+
|
|
92
|
+
Here's a complete example that creates a simple building with multiple wall types:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
import ifcopenshell
|
|
96
|
+
import ifcopenshell.api
|
|
97
|
+
import ifcopenshell.api.aggregate
|
|
98
|
+
import ifcopenshell.api.context
|
|
99
|
+
import ifcopenshell.api.root
|
|
100
|
+
import ifcopenshell.api.unit
|
|
101
|
+
|
|
102
|
+
from ifcfactory import (
|
|
103
|
+
BIMFactoryElement, Box, Cube, Cylinder, Extrusion, Rect, Sphere
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def create_basic_ifc_setup(project_name: str):
|
|
108
|
+
"""Create basic IFC model setup using IfcOpenShell API"""
|
|
109
|
+
# Create IFC model
|
|
110
|
+
ifc_model = ifcopenshell.file(schema="IFC4")
|
|
111
|
+
|
|
112
|
+
# Create project
|
|
113
|
+
project_entity = ifcopenshell.api.root.create_entity(ifc_model, ifc_class="IfcProject", name=project_name)
|
|
114
|
+
|
|
115
|
+
# Create units (meters)
|
|
116
|
+
ifcopenshell.api.unit.assign_unit(ifc_model, length={"is_metric": True, "raw": "METERS"})
|
|
117
|
+
|
|
118
|
+
# Create contexts
|
|
119
|
+
model_context = ifcopenshell.api.context.add_context(ifc_model, context_type="Model")
|
|
120
|
+
ifcopenshell.api.context.add_context(
|
|
121
|
+
ifc_model, context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model_context
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# Create site
|
|
125
|
+
site_entity = ifcopenshell.api.root.create_entity(ifc_model, ifc_class="IfcSite", name="Default Site")
|
|
126
|
+
ifcopenshell.api.aggregate.assign_object(ifc_model, relating_object=project_entity, products=[site_entity])
|
|
127
|
+
|
|
128
|
+
# Create building
|
|
129
|
+
building_entity = ifcopenshell.api.root.create_entity(ifc_model, ifc_class="IfcBuilding", name="Default Building")
|
|
130
|
+
ifcopenshell.api.aggregate.assign_object(ifc_model, relating_object=site_entity, products=[building_entity])
|
|
131
|
+
|
|
132
|
+
return ifc_model, project_entity, site_entity, building_entity
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# Create IFC model and setup
|
|
136
|
+
model, proj, site, building = create_basic_ifc_setup("Example Building")
|
|
137
|
+
|
|
138
|
+
# Create complete project structure using BIMFactoryElement
|
|
139
|
+
BIMFactoryElement(
|
|
140
|
+
inst=building,
|
|
141
|
+
children=[
|
|
142
|
+
# Box wall positioned at origin
|
|
143
|
+
BIMFactoryElement(type="IfcWall", name="Box Wall", children=[Box(width=5.0, depth=0.3, height=3.0)]),
|
|
144
|
+
# Cube wall
|
|
145
|
+
BIMFactoryElement(type="IfcWall", name="Cube Wall", children=[Cube(size=4.0)]),
|
|
146
|
+
# Cylinder wall
|
|
147
|
+
BIMFactoryElement(type="IfcWall", name="Cylinder Wall", children=[Cylinder(radius=1.5, height=4.0)]),
|
|
148
|
+
# Extruded slab
|
|
149
|
+
BIMFactoryElement(
|
|
150
|
+
type="IfcSlab",
|
|
151
|
+
name="Extruded Slab",
|
|
152
|
+
children=[Extrusion(basis=Rect(width=5.0, height=2.5), depth=0.3)],
|
|
153
|
+
),
|
|
154
|
+
# Sphere element
|
|
155
|
+
BIMFactoryElement(
|
|
156
|
+
type="IfcBuildingElementProxy",
|
|
157
|
+
name="Sphere Element",
|
|
158
|
+
children=[Sphere(radius=1.5, detail=2)]
|
|
159
|
+
),
|
|
160
|
+
],
|
|
161
|
+
).build(model)
|
|
162
|
+
|
|
163
|
+
# Save the model
|
|
164
|
+
model.write("example_building.ifc")
|
|
165
|
+
print("Saved: example_building.ifc")
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
This example demonstrates:
|
|
169
|
+
|
|
170
|
+
- Setting up a proper IFC model with project hierarchy
|
|
171
|
+
- Creating various geometric primitives (Box, Cube, Cylinder, Sphere)
|
|
172
|
+
- Using profile-based extrusions (Rect → Extrusion)
|
|
173
|
+
- Organizing elements in a hierarchical BIM structure
|
|
174
|
+
- Saving the result as an IFC file
|
|
175
|
+
|
|
176
|
+
For more comprehensive examples, see `examples/example_bimfactory.py` which demonstrates all features including
|
|
177
|
+
transformations, boolean operations, materials, and property sets.
|
ifcfactory/__init__.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BIM Factory Module
|
|
3
|
+
==================
|
|
4
|
+
|
|
5
|
+
Copyright (C) 2025 Freie und Hansestadt Hamburg, Landesbetrieb Geoinformation und Vermessung
|
|
6
|
+
BIM-Leitstelle, Ahmed Salem <ahmed.salem@gv.hamburg.de>
|
|
7
|
+
|
|
8
|
+
Developed in collaboration with Thomas Krijnen <mail@thomaskrijnen.com>
|
|
9
|
+
|
|
10
|
+
This library is free software; you can redistribute it and/or
|
|
11
|
+
modify it under the terms of the GNU Lesser General Public
|
|
12
|
+
License as published by the Free Software Foundation; either
|
|
13
|
+
version 2.1 of the License, or (at your option) any later version.
|
|
14
|
+
|
|
15
|
+
This library is distributed in the hope that it will be useful,
|
|
16
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
17
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
18
|
+
Lesser General Public License for more details.
|
|
19
|
+
|
|
20
|
+
You should have received a copy of the GNU Lesser General Public
|
|
21
|
+
License along with this library; if not, write to the Free Software
|
|
22
|
+
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
23
|
+
|
|
24
|
+
Main Components:
|
|
25
|
+
- Base classes and helper functions (_internal/primitives_base.py)
|
|
26
|
+
- Primitive geometry objects (primitives.py)
|
|
27
|
+
- Geometric operations (operations.py)
|
|
28
|
+
- Material and styling definitions (_internal/material_base.py)
|
|
29
|
+
- Unit conversion utilities (_internal/unit_base.py)
|
|
30
|
+
- Property set base classes (_internal/pset_base.py)
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
# Import high-level BIM elements
|
|
34
|
+
from .element import BIMFactoryElement
|
|
35
|
+
|
|
36
|
+
# Import styles and materials
|
|
37
|
+
from ._internal.material_base import Material, Style
|
|
38
|
+
|
|
39
|
+
# Import operations
|
|
40
|
+
from .operations import Boolean, BooleanOperationTypes, RotateZ, Transform, Translate
|
|
41
|
+
from .primitives import (
|
|
42
|
+
Box, # Profile classes; Representation item classes
|
|
43
|
+
Circle,
|
|
44
|
+
Cube,
|
|
45
|
+
Cylinder,
|
|
46
|
+
Ellipse,
|
|
47
|
+
EllipticalCylinder,
|
|
48
|
+
ExtrudedNgonAsMesh,
|
|
49
|
+
Extrusion,
|
|
50
|
+
MeshRepresentation,
|
|
51
|
+
NgonCylinder,
|
|
52
|
+
Polygon,
|
|
53
|
+
Rect,
|
|
54
|
+
Sphere,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Import property set base
|
|
58
|
+
from ._internal.pset_base import PropertySetTemplate
|
|
59
|
+
|
|
60
|
+
# Import units
|
|
61
|
+
from ._internal.unit_base import A, Dim, E, J, L, M, N, P, T, V, K, pint_to_ifc, ureg
|
|
62
|
+
|
|
63
|
+
# Import base classes for advanced usage
|
|
64
|
+
from ._internal.primitives_base import ElementInterface, Primitive, Profile, RepresentationItem
|
|
65
|
+
|
|
66
|
+
__all__ = [
|
|
67
|
+
# Element classes
|
|
68
|
+
"BIMFactoryElement",
|
|
69
|
+
# Profile classes
|
|
70
|
+
"Rect",
|
|
71
|
+
"Circle",
|
|
72
|
+
"Ellipse",
|
|
73
|
+
"Polygon",
|
|
74
|
+
# Representation item classes
|
|
75
|
+
"Extrusion",
|
|
76
|
+
"Box",
|
|
77
|
+
"Cube",
|
|
78
|
+
"ExtrudedNgonAsMesh",
|
|
79
|
+
"NgonCylinder",
|
|
80
|
+
"Cylinder",
|
|
81
|
+
"EllipticalCylinder",
|
|
82
|
+
"Sphere",
|
|
83
|
+
"MeshRepresentation",
|
|
84
|
+
# Operations
|
|
85
|
+
"BooleanOperationTypes",
|
|
86
|
+
"Boolean",
|
|
87
|
+
"Translate",
|
|
88
|
+
"RotateZ",
|
|
89
|
+
"Transform",
|
|
90
|
+
# Styles and materials
|
|
91
|
+
"Style",
|
|
92
|
+
"Material",
|
|
93
|
+
# Property sets
|
|
94
|
+
"PropertySetTemplate",
|
|
95
|
+
# Units
|
|
96
|
+
"ureg",
|
|
97
|
+
"Dim",
|
|
98
|
+
"L",
|
|
99
|
+
"A",
|
|
100
|
+
"V",
|
|
101
|
+
"M",
|
|
102
|
+
"T",
|
|
103
|
+
"E",
|
|
104
|
+
"K",
|
|
105
|
+
"N",
|
|
106
|
+
"J",
|
|
107
|
+
"P",
|
|
108
|
+
"pint_to_ifc",
|
|
109
|
+
# Base classes (for advanced usage)
|
|
110
|
+
"ElementInterface",
|
|
111
|
+
"Primitive",
|
|
112
|
+
"Profile",
|
|
113
|
+
"RepresentationItem",
|
|
114
|
+
]
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Style and Material Classes
|
|
3
|
+
==========================
|
|
4
|
+
|
|
5
|
+
Copyright (C) 2025 Freie und Hansestadt Hamburg, Landesbetrieb Geoinformation und Vermessung
|
|
6
|
+
BIM-Leitstelle, Ahmed Salem <ahmed.salem@gv.hamburg.de>
|
|
7
|
+
|
|
8
|
+
Developed in collaboration with Thomas Krijnen <mail@thomaskrijnen.com>
|
|
9
|
+
|
|
10
|
+
This library is free software; you can redistribute it and/or
|
|
11
|
+
modify it under the terms of the GNU Lesser General Public
|
|
12
|
+
License as published by the Free Software Foundation; either
|
|
13
|
+
version 2.1 of the License, or (at your option) any later version.
|
|
14
|
+
|
|
15
|
+
This library is distributed in the hope that it will be useful,
|
|
16
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
17
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
18
|
+
Lesser General Public License for more details.
|
|
19
|
+
|
|
20
|
+
You should have received a copy of the GNU Lesser General Public
|
|
21
|
+
License along with this library; if not, write to the Free Software
|
|
22
|
+
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
23
|
+
|
|
24
|
+
This module contains classes for styling and material definitions
|
|
25
|
+
for IFC elements.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from typing import Optional, Tuple
|
|
29
|
+
|
|
30
|
+
import ifcopenshell
|
|
31
|
+
import ifcopenshell.api.material
|
|
32
|
+
import ifcopenshell.api.style
|
|
33
|
+
|
|
34
|
+
from .primitives_base import Primitive, RepresentationItem
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Style(Primitive, RepresentationItem):
|
|
38
|
+
"""Styling wrapper that applies color, transparency, and optionally a CAD layer to representation items."""
|
|
39
|
+
|
|
40
|
+
item: RepresentationItem
|
|
41
|
+
rgb: str | Tuple[float, float, float]
|
|
42
|
+
transparency: Optional[float] = None
|
|
43
|
+
cad_layer: Optional[str] = None
|
|
44
|
+
|
|
45
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
46
|
+
|
|
47
|
+
def build(self, model: ifcopenshell.file) -> ifcopenshell.entity_instance:
|
|
48
|
+
"""
|
|
49
|
+
Build a styled representation with color, transparency, and optional CAD layer assignment.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
model: The IFC model instance.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
ifcopenshell.entity_instance: The styled representation.
|
|
56
|
+
"""
|
|
57
|
+
inst = self.item.build(model)
|
|
58
|
+
# assign_color_to_representation(model, inst, self.rgb, self.transparency)
|
|
59
|
+
|
|
60
|
+
if self.cad_layer:
|
|
61
|
+
# Use rgb (converted to tuple if needed) for the layer color
|
|
62
|
+
if isinstance(self.rgb, tuple):
|
|
63
|
+
color = self.rgb
|
|
64
|
+
elif isinstance(self.rgb, str):
|
|
65
|
+
color = tuple(float(x.strip()) for x in self.rgb.split(","))
|
|
66
|
+
else:
|
|
67
|
+
color = (1.0, 1.0, 1.0)
|
|
68
|
+
assign_layer_to_representation(model, inst, self.cad_layer, color)
|
|
69
|
+
|
|
70
|
+
assign_color_to_element(model, inst, self.rgb, self.transparency)
|
|
71
|
+
|
|
72
|
+
return inst
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class Material(Primitive):
|
|
76
|
+
"""
|
|
77
|
+
Material definition for IFC elements.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
name (str): Name of the material.
|
|
81
|
+
category (Optional[str]): Category of the material.
|
|
82
|
+
rgb (Tuple[float, float, float]): Normalized RGB color as floats in [0, 1].
|
|
83
|
+
transparency (Optional[float]): Transparency value (0.0 = opaque, 1.0 = fully transparent).
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
name: str
|
|
87
|
+
category: Optional[str] = None
|
|
88
|
+
rgb: Tuple[float, float, float] # Normalized RGB in [0, 1]
|
|
89
|
+
transparency: Optional[float] = None
|
|
90
|
+
_build_result: Optional[ifcopenshell.entity_instance] = None
|
|
91
|
+
|
|
92
|
+
def build(self, model):
|
|
93
|
+
if res := getattr(self, "_build_result", None):
|
|
94
|
+
# build it only once
|
|
95
|
+
return res
|
|
96
|
+
inst = ifcopenshell.api.material.add_material(
|
|
97
|
+
model,
|
|
98
|
+
name=self.name,
|
|
99
|
+
**({"category": self.category} if model.schema != "IFC2X3" else {}),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
style = ifcopenshell.api.style.add_style(model)
|
|
103
|
+
ifcopenshell.api.style.add_surface_style(
|
|
104
|
+
model,
|
|
105
|
+
style=style,
|
|
106
|
+
ifc_class=("IfcSurfaceStyleShading" if model.schema != "IFC2X3" else "IfcSurfaceStyleRendering"),
|
|
107
|
+
attributes={
|
|
108
|
+
"SurfaceColour": {
|
|
109
|
+
"Name": None,
|
|
110
|
+
"Red": self.rgb[0],
|
|
111
|
+
"Green": self.rgb[1],
|
|
112
|
+
"Blue": self.rgb[1],
|
|
113
|
+
},
|
|
114
|
+
"Transparency": self.transparency,
|
|
115
|
+
},
|
|
116
|
+
)
|
|
117
|
+
context = [x for x in model.by_type("IfcGeometricRepresentationContext") if x.ContextIdentifier == "Body"][0]
|
|
118
|
+
ifcopenshell.api.style.assign_material_style(model, material=inst, style=style, context=context)
|
|
119
|
+
self._build_result = inst
|
|
120
|
+
return inst
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def ifc_normalise_color(rgb_color_str: str) -> list[float]:
|
|
124
|
+
"""
|
|
125
|
+
Normalize RGB color string to IFC-compatible values in range [0.1, 1].
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
rgb_color_str (str): RGB color string in format "r,g,b" (e.g., "255,0,0").
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
list[float]: Normalized RGB values as a list of three floats.
|
|
132
|
+
|
|
133
|
+
Raises:
|
|
134
|
+
ValueError: If rgb_color_str is not in the expected format.
|
|
135
|
+
"""
|
|
136
|
+
rgb_color = rgb_color_str.split(",")
|
|
137
|
+
r, g, b = (float(rgb_color[0]), float(rgb_color[1]), float(rgb_color[2]))
|
|
138
|
+
|
|
139
|
+
# Normalizing RGB values to the range [0.1, 1]
|
|
140
|
+
normalized_rgb = [
|
|
141
|
+
round(r / 255 * (1 - 0.1) + 0.1, 2),
|
|
142
|
+
round(g / 255 * (1 - 0.1) + 0.1, 2),
|
|
143
|
+
round(b / 255 * (1 - 0.1) + 0.1, 2),
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
return normalized_rgb
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def assign_color_to_element(
|
|
150
|
+
model: ifcopenshell.file,
|
|
151
|
+
representation,
|
|
152
|
+
color_rgb: str | tuple[float, ...],
|
|
153
|
+
transparency: float | None,
|
|
154
|
+
) -> None:
|
|
155
|
+
"""
|
|
156
|
+
Assign a color to the IFC element representation or item.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
model (ifcopenshell.file): The IFC model instance.
|
|
160
|
+
representation: The IFC representation or representation item.
|
|
161
|
+
color_rgb (str | tuple[float, ...]): Color as RGB string or tuple.
|
|
162
|
+
transparency (float | None): Transparency value (0.0 to 1.0).
|
|
163
|
+
|
|
164
|
+
Raises:
|
|
165
|
+
TypeError: If representation is not a valid IFC representation type.
|
|
166
|
+
"""
|
|
167
|
+
value = ifc_normalise_color(color_rgb) if isinstance(color_rgb, str) else color_rgb
|
|
168
|
+
|
|
169
|
+
# Creating a new style
|
|
170
|
+
style_ifc = ifcopenshell.api.style.add_style(model, name="Style")
|
|
171
|
+
|
|
172
|
+
ifcopenshell.api.style.add_surface_style(
|
|
173
|
+
model,
|
|
174
|
+
style=style_ifc,
|
|
175
|
+
ifc_class="IfcSurfaceStyleShading",
|
|
176
|
+
attributes={
|
|
177
|
+
"SurfaceColour": {
|
|
178
|
+
"Name": None,
|
|
179
|
+
"Red": value[0],
|
|
180
|
+
"Green": value[1],
|
|
181
|
+
"Blue": value[2],
|
|
182
|
+
},
|
|
183
|
+
**({"Transparency": transparency} if transparency is not None else {}),
|
|
184
|
+
},
|
|
185
|
+
)
|
|
186
|
+
if representation.is_a("IfcRepresentation"):
|
|
187
|
+
ifcopenshell.api.style.assign_representation_styles(
|
|
188
|
+
model, shape_representation=representation, styles=[style_ifc]
|
|
189
|
+
)
|
|
190
|
+
elif representation.is_a("IfcRepresentationItem"):
|
|
191
|
+
ifcopenshell.api.style.assign_item_style(model, item=representation, style=style_ifc)
|
|
192
|
+
else:
|
|
193
|
+
raise TypeError(f"Unable to assign style to instance of type {representation.is_a()}")
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def assign_layer_to_representation(
|
|
197
|
+
model,
|
|
198
|
+
representation: ifcopenshell.entity_instance,
|
|
199
|
+
layer_name: str,
|
|
200
|
+
color: tuple = (1.0, 1.0, 1.0),
|
|
201
|
+
transparency: float = 0.0,
|
|
202
|
+
) -> None:
|
|
203
|
+
"""
|
|
204
|
+
Assign a layer to the given representation, reusing or creating IfcPresentationLayerWithStyle.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
model: The IFC model instance.
|
|
208
|
+
representation: The representation to assign to the layer.
|
|
209
|
+
layer_name: Name of the layer.
|
|
210
|
+
color: Normalized RGB tuple for the layer style (default: white).
|
|
211
|
+
transparency: Transparency for the layer style (default: 0.0).
|
|
212
|
+
"""
|
|
213
|
+
try:
|
|
214
|
+
items_to_assign = []
|
|
215
|
+
|
|
216
|
+
# Assign the representation itself if it's a geometric solid
|
|
217
|
+
if hasattr(representation, "is_a") and representation.is_a() in [
|
|
218
|
+
"IfcExtrudedAreaSolid",
|
|
219
|
+
"IfcTriangulatedFaceSet",
|
|
220
|
+
"IfcPolygonalFaceSet",
|
|
221
|
+
"IfcMappedItem",
|
|
222
|
+
"IfcSweptDiskSolid",
|
|
223
|
+
]:
|
|
224
|
+
items_to_assign.append(representation)
|
|
225
|
+
|
|
226
|
+
# Add geometry items from the representation if present
|
|
227
|
+
if hasattr(representation, "Items") and representation.Items:
|
|
228
|
+
for item in representation.Items:
|
|
229
|
+
items_to_assign.append(item)
|
|
230
|
+
if hasattr(item, "is_a") and item.is_a("IfcMappedItem"):
|
|
231
|
+
items_to_assign.append(item)
|
|
232
|
+
|
|
233
|
+
# Add the representation itself if it's a mapped representation
|
|
234
|
+
if hasattr(representation, "is_a") and representation.is_a("IfcShapeRepresentation"):
|
|
235
|
+
if getattr(representation, "RepresentationType", "") == "MappedRepresentation":
|
|
236
|
+
items_to_assign.append(representation)
|
|
237
|
+
|
|
238
|
+
# Remove duplicates
|
|
239
|
+
items_to_assign = list(dict.fromkeys(items_to_assign))
|
|
240
|
+
|
|
241
|
+
# Don't create layer if no items to assign (prevents BricsCAD errors)
|
|
242
|
+
if not items_to_assign:
|
|
243
|
+
return
|
|
244
|
+
|
|
245
|
+
# Create surface style for the layer
|
|
246
|
+
layer_color = model.create_entity(
|
|
247
|
+
"IfcColourRgb",
|
|
248
|
+
Name="LayerColor",
|
|
249
|
+
Red=color[0],
|
|
250
|
+
Green=color[1],
|
|
251
|
+
Blue=color[2],
|
|
252
|
+
)
|
|
253
|
+
surface_style_shading = model.create_entity(
|
|
254
|
+
"IfcSurfaceStyleShading",
|
|
255
|
+
SurfaceColour=layer_color,
|
|
256
|
+
Transparency=transparency,
|
|
257
|
+
)
|
|
258
|
+
surface_style = model.create_entity(
|
|
259
|
+
"IfcSurfaceStyle",
|
|
260
|
+
Name="LayerStyle",
|
|
261
|
+
Side="POSITIVE",
|
|
262
|
+
Styles=[surface_style_shading],
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
# Check if layer already exists
|
|
266
|
+
existing_layer = None
|
|
267
|
+
for layer in model.by_type("IfcPresentationLayerWithStyle"):
|
|
268
|
+
if layer.Name == layer_name:
|
|
269
|
+
existing_layer = layer
|
|
270
|
+
break
|
|
271
|
+
|
|
272
|
+
if existing_layer:
|
|
273
|
+
existing_items = list(existing_layer.AssignedItems) if existing_layer.AssignedItems else []
|
|
274
|
+
existing_items.extend(items_to_assign)
|
|
275
|
+
existing_layer.AssignedItems = list(dict.fromkeys(existing_items))
|
|
276
|
+
else:
|
|
277
|
+
model.create_entity(
|
|
278
|
+
"IfcPresentationLayerWithStyle",
|
|
279
|
+
Name=layer_name,
|
|
280
|
+
Description=None,
|
|
281
|
+
AssignedItems=items_to_assign,
|
|
282
|
+
LayerOn=True,
|
|
283
|
+
LayerFrozen=False,
|
|
284
|
+
LayerBlocked=False,
|
|
285
|
+
LayerStyles=[surface_style],
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
# Also assign mapped items to the layer
|
|
289
|
+
if hasattr(representation, "Items") and representation.Items:
|
|
290
|
+
for item in representation.Items:
|
|
291
|
+
if hasattr(item, "is_a") and item.is_a("IfcMappedItem"):
|
|
292
|
+
# Handle mapped items recursively
|
|
293
|
+
if hasattr(item, "MappingSource") and item.MappingSource:
|
|
294
|
+
mapped_rep = item.MappingSource.MappedRepresentation
|
|
295
|
+
if mapped_rep:
|
|
296
|
+
assign_layer_to_representation(model, mapped_rep, layer_name, color, transparency)
|
|
297
|
+
|
|
298
|
+
except Exception as e:
|
|
299
|
+
print(f"Warning: Could not assign layer '{layer_name}' to representation: {e}")
|