mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-12 10:33:20 +00:00
IFC4 Landscape Library
Created IFC library with simple trees, shrubs and hedges - with both 2d and 3d representations. Example - https://i.imgur.com/pOjh1si.png Now it's available along with the furniture library when creating new project - https://i.imgur.com/Ho0B5Gn.png
This commit is contained in:
@@ -25,18 +25,16 @@ from ifcopenshell.util.shape_builder import ShapeBuilder, V
|
||||
|
||||
|
||||
class LibraryGenerator:
|
||||
def generate(self, output_filename="IFC4 EU Steel.ifc"):
|
||||
def generate(self, library_name, output_filename="IFC4 EU Steel.ifc"):
|
||||
ifcopenshell.api.pre_listeners = {}
|
||||
ifcopenshell.api.post_listeners = {}
|
||||
|
||||
self.materials = {}
|
||||
|
||||
self.file = ifcopenshell.api.run("project.create_file")
|
||||
self.project = ifcopenshell.api.run(
|
||||
"root.create_entity", self.file, ifc_class="IfcProject", name=f"Non-structural assets library"
|
||||
)
|
||||
self.project = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject", name=library_name)
|
||||
self.library = ifcopenshell.api.run(
|
||||
"root.create_entity", self.file, ifc_class="IfcProjectLibrary", name=f"Non-structural assets library"
|
||||
"root.create_entity", self.file, ifc_class="IfcProjectLibrary", name=library_name
|
||||
)
|
||||
ifcopenshell.api.run(
|
||||
"project.assign_declaration", self.file, definition=self.library, relating_context=self.project
|
||||
@@ -1876,4 +1874,6 @@ class LibraryGenerator:
|
||||
|
||||
if __name__ == "__main__":
|
||||
path = Path(__file__).parents[1] / "blenderbim/bim/data/libraries"
|
||||
LibraryGenerator().generate(output_filename=str(path / "IFC4 Furniture Library.ifc"))
|
||||
LibraryGenerator().generate(
|
||||
"Non-structural assets library", output_filename=str(path / "IFC4 Furniture Library.ifc")
|
||||
)
|
||||
|
||||
@@ -0,0 +1,403 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2024 @Andrej730
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on 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.
|
||||
#
|
||||
# BlenderBIM Add-on 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 BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import random
|
||||
from math import cos, sin, tan, pi
|
||||
from pathlib import Path
|
||||
from itertools import chain
|
||||
from ifcopenshell.util.shape_builder import ShapeBuilder, V
|
||||
from collections import namedtuple
|
||||
from mathutils import Vector, Matrix
|
||||
from random import uniform
|
||||
|
||||
|
||||
SimpleTreeParams = namedtuple("SimpleTreeParams", "plant_height crown_diameter trunk_diameter")
|
||||
LowPolyTreeParams = namedtuple(
|
||||
"LowPolyTreeParams", "plant_height crown_diameter crown_max_loc crown_taper trunk_height trunk_diameter random_seed"
|
||||
)
|
||||
PalmTreeParams = namedtuple("PalmTreeParams", "plant_height crown_diameter trunk_diameter")
|
||||
TreePresetData = namedtuple("TreePresetData", "preset_class generator")
|
||||
|
||||
|
||||
GeneratedGeometry = namedtuple("GeneratedGeometry", "items_2d items_3d")
|
||||
|
||||
|
||||
def generate_low_poly_tree(
|
||||
builder: ShapeBuilder,
|
||||
crown_taper=0.5,
|
||||
crown_max_loc=0.3,
|
||||
plant_height=12.0,
|
||||
trunk_height=2.0,
|
||||
crown_diameter=4,
|
||||
trunk_diameter=0.3,
|
||||
random_seed=10,
|
||||
) -> GeneratedGeometry:
|
||||
unit_conversion = 1 / ifcopenshell.util.unit.calculate_unit_scale(builder.file)
|
||||
random.seed(random_seed)
|
||||
res = 6
|
||||
plant_height *= unit_conversion
|
||||
trunk_height *= unit_conversion
|
||||
crown_diameter *= unit_conversion
|
||||
trunk_diameter *= unit_conversion
|
||||
crown_height = plant_height - trunk_height
|
||||
|
||||
def circle(center, r, res):
|
||||
points = []
|
||||
for i in range(res):
|
||||
# fmt: off
|
||||
vert = (
|
||||
cos(i * 2 * pi / res) * r + center[0],
|
||||
sin(i * 2 * pi / res) * r + center[1],
|
||||
0.0 + center[2]
|
||||
)
|
||||
# fmt: on
|
||||
points.append(vert)
|
||||
return points
|
||||
|
||||
def merge_lists(lists):
|
||||
merged_list = []
|
||||
for i in range(len(lists)):
|
||||
merged_list = merged_list + lists[i]
|
||||
return merged_list
|
||||
|
||||
def XY_scale(vert, scale):
|
||||
vert_scaled = (vert[0] * scale, vert[1] * scale, vert[2])
|
||||
return vert_scaled
|
||||
|
||||
def rand_add(vert, amp):
|
||||
# fmt: off
|
||||
vert_new = (
|
||||
vert[0] + uniform(-amp, amp),
|
||||
vert[1] + uniform(-amp, amp),
|
||||
vert[2] + uniform(-amp, amp)
|
||||
)
|
||||
# fmt: on
|
||||
return vert_new
|
||||
|
||||
height_segments_trunk = 2
|
||||
height_segments_crown = 7
|
||||
height_segments_total = height_segments_trunk + height_segments_crown
|
||||
|
||||
verts = []
|
||||
|
||||
# Trunk
|
||||
for i in range(height_segments_trunk):
|
||||
points = circle((0.0, 0.0, i * trunk_height), trunk_diameter / 2, res)
|
||||
verts.append(points)
|
||||
|
||||
# Crown
|
||||
h_crown_max_loc = int((height_segments_crown - 1) * crown_max_loc)
|
||||
crown_indices = []
|
||||
for i in range(0, h_crown_max_loc):
|
||||
crown_indices.append(i)
|
||||
for i in range(0, height_segments_crown - h_crown_max_loc):
|
||||
crown_indices.append(h_crown_max_loc - i)
|
||||
|
||||
my_min_val = min(crown_indices)
|
||||
my_max_val = max(crown_indices)
|
||||
|
||||
n_crown_taper = crown_taper * 0.8
|
||||
|
||||
crown_diameters = []
|
||||
for x in crown_indices:
|
||||
crown_diameters.append(((x - my_min_val) / (my_max_val - my_min_val)) * n_crown_taper + (1 - n_crown_taper))
|
||||
|
||||
randomize_amplitude = plant_height / 50
|
||||
|
||||
crown_segment_height = crown_height / height_segments_crown
|
||||
for i in range(0, height_segments_crown - 1):
|
||||
points = circle(
|
||||
(0.0, 0.0, trunk_height + (i + 1) * crown_segment_height),
|
||||
crown_diameter * crown_diameters[i] / 2,
|
||||
res,
|
||||
)
|
||||
for i in range(0, len(points)):
|
||||
points[i] = rand_add(points[i], randomize_amplitude)
|
||||
verts.append(points)
|
||||
|
||||
# Top
|
||||
verts.append(circle((0.0, 0.0, plant_height), 0.05, res))
|
||||
|
||||
edge_loops = []
|
||||
for i in range(height_segments_total):
|
||||
edge_loops.append(range(i * res, (i + 1) * res))
|
||||
|
||||
verts = merge_lists(verts)
|
||||
|
||||
faces = []
|
||||
bottom = [*edge_loops[0]]
|
||||
faces.append(bottom)
|
||||
for i in range(height_segments_total - 1):
|
||||
for j in range(res):
|
||||
face = (edge_loops[i][j - 1], edge_loops[i][j], edge_loops[i + 1][j], edge_loops[i + 1][j - 1])
|
||||
faces.append(face)
|
||||
top = [*edge_loops[-1]]
|
||||
faces.append(top)
|
||||
|
||||
verts_3D = [verts]
|
||||
faces_3D = [faces]
|
||||
|
||||
verts_2D = [
|
||||
[
|
||||
(-0.5865642428398132, 0.8647078275680542, 0.0),
|
||||
(-0.7583824992179871, 0.6572927236557007, 0.0),
|
||||
(-0.7234422564506531, 0.5865941047668457, 0.0),
|
||||
(-0.8714070916175842, 0.46631893515586853, 0.0),
|
||||
(-0.9203394055366516, 0.15792329609394073, 0.0),
|
||||
(-0.6487269997596741, 0.11522211134433746, 0.0),
|
||||
(-0.9273074269294739, 0.0903107076883316, 0.0),
|
||||
(-0.9606701731681824, -0.2852219343185425, 0.0),
|
||||
(-0.8060722351074219, -0.35055163502693176, 0.0),
|
||||
(-0.8271104693412781, -0.4677186608314514, 0.0),
|
||||
(-0.6899875998497009, -0.6695915460586548, 0.0),
|
||||
(-0.6215555667877197, -0.6767659187316895, 0.0),
|
||||
(-0.4315463900566101, -0.8824808597564697, 0.0),
|
||||
(-0.30316293239593506, -0.9023936986923218, 0.0),
|
||||
(-0.32218849658966064, -0.8037619590759277, 0.0),
|
||||
(-0.21792533993721008, -0.9530860185623169, 0.0),
|
||||
(0.36917445063591003, -0.877334475517273, 0.0),
|
||||
(0.30831706523895264, -0.685136079788208, 0.0),
|
||||
(0.4778200685977936, -0.8094909191131592, 0.0),
|
||||
(0.9163193106651306, -0.368840754032135, 0.0),
|
||||
(0.827597439289093, -0.29377281665802, 0.0),
|
||||
(0.9478662610054016, -0.245104119181633, 0.0),
|
||||
(0.9667968153953552, 0.08458180725574493, 0.0),
|
||||
(0.7529001832008362, 0.05212751030921936, 0.0),
|
||||
(0.9102436900138855, 0.19098728895187378, 0.0),
|
||||
(0.8279229998588562, 0.5522171258926392, 0.0),
|
||||
(0.6635335087776184, 0.6461355686187744, 0.0),
|
||||
(0.5753684639930725, 0.8170149326324463, 0.0),
|
||||
(0.3976244330406189, 0.8779193162918091, 0.0),
|
||||
(0.18733686208724976, 0.7513588666915894, 0.0),
|
||||
(0.338344544172287, 0.910933256149292, 0.0),
|
||||
(-0.013130240142345428, 0.9798095226287842, 0.0),
|
||||
(-0.18649885058403015, 0.9414206743240356, 0.0),
|
||||
(-0.3193224370479584, 0.8835403919219971, 0.0),
|
||||
]
|
||||
]
|
||||
edges_2D = []
|
||||
faces_2D = []
|
||||
|
||||
verts_2D_new = []
|
||||
for vert in verts_2D[0]:
|
||||
verts_2D_new.append(XY_scale(vert, crown_diameter / 2))
|
||||
|
||||
range_of_verts = range(0, len(verts_2D[0]))
|
||||
for i in range_of_verts:
|
||||
edges_2D.append([range_of_verts[i - 1], i])
|
||||
faces_2D.append(i)
|
||||
|
||||
verts_2D[0] = verts_2D_new
|
||||
edges_2D = [edges_2D]
|
||||
faces_2D = [[faces_2D]]
|
||||
|
||||
verts_2D = [Vector(v).to_2d() for v in verts_2D[0]]
|
||||
shape_2d = builder.polyline(points=verts_2D, closed=True)
|
||||
shape_3d = builder.polygonal_face_set(points=verts_3D[0], faces=faces_3D[0])
|
||||
|
||||
return GeneratedGeometry((shape_2d,), (shape_3d,))
|
||||
|
||||
|
||||
def generate_simple_tree(
|
||||
builder: ShapeBuilder, plant_height=2, crown_diameter=2, trunk_diameter=0.1
|
||||
) -> GeneratedGeometry:
|
||||
unit_conversion = 1 / ifcopenshell.util.unit.calculate_unit_scale(builder.file)
|
||||
plant_height *= unit_conversion
|
||||
crown_diameter *= unit_conversion
|
||||
trunk_diameter *= unit_conversion
|
||||
|
||||
crown_radius = crown_diameter / 2
|
||||
|
||||
# 3d shape
|
||||
trunk_base = builder.circle(radius=trunk_diameter / 2)
|
||||
trunk = builder.extrude(trunk_base, magnitude=plant_height - crown_diameter)
|
||||
crown_position = (0.0, 0.0, (plant_height - crown_radius))
|
||||
crown = builder.sphere(radius=crown_radius, center=crown_position)
|
||||
|
||||
# 2d shape
|
||||
shape_2D = builder.circle(radius=crown_radius)
|
||||
|
||||
return GeneratedGeometry((shape_2D,), (trunk, crown))
|
||||
|
||||
|
||||
def generate_palm_tree(
|
||||
builder: ShapeBuilder, plant_height=2, crown_diameter=5.8, trunk_diameter=0.1
|
||||
) -> GeneratedGeometry:
|
||||
unit_conversion = 1 / ifcopenshell.util.unit.calculate_unit_scale(builder.file)
|
||||
|
||||
palm_verts = [
|
||||
Vector((-3.6415634155273438, -3.8275668621063232, -1.0948246717453003)),
|
||||
Vector((-0.3698049783706665, -1.805966854095459, 0.493910551071167)),
|
||||
Vector((-1.6397790908813477, -0.5056067705154419, 0.04353363811969757)),
|
||||
Vector((0.0, 0.0, 0.0)),
|
||||
Vector((5.023162364959717, -1.6531240940093994, -1.0948246717453003)),
|
||||
Vector((1.8090602159500122, 0.4589407444000244, 0.493910551071167)),
|
||||
Vector((1.1666079759597778, -1.2413609027862549, 0.04353363811969757)),
|
||||
Vector((1.6942416429519653, 5.038582801818848, -1.0948246717453003)),
|
||||
Vector((-0.43215513229370117, 1.833944320678711, 0.493910551071167)),
|
||||
Vector((1.2652605772018433, 1.1839056015014648, 0.04353363811969757)),
|
||||
Vector((-4.577561378479004, 2.689336061477661, -1.0948246717453003)),
|
||||
Vector((-1.8646581172943115, -0.0367276668548584, 0.493910551071167)),
|
||||
Vector((-0.8873085975646973, 1.4957730770111084, 0.04353363811969757)),
|
||||
]
|
||||
palm_faces = [(2, 1, 0), (2, 3, 1), (6, 5, 4), (6, 3, 5), (9, 8, 7), (9, 3, 8), (12, 11, 10), (12, 3, 11)]
|
||||
# each segment starts with the last point from the previous
|
||||
palm_segments_2d_ifc = [(3, 11, 10, 12, 3), (3, 1, 0, 2, 3), (3, 5, 4, 6, 3), (3, 8, 7, 9, 3)]
|
||||
palm_segments_2d_ifc = [[i + 1 for i in segment] for segment in palm_segments_2d_ifc] # IFC indices start with 1
|
||||
|
||||
scale = Vector()
|
||||
scale.xyz = crown_diameter / 11.6
|
||||
leaves_height = 0.493910551071167 * scale.z
|
||||
plant_height -= leaves_height
|
||||
for v in palm_verts:
|
||||
v *= scale
|
||||
v.z += plant_height
|
||||
v *= unit_conversion
|
||||
|
||||
plant_height *= unit_conversion
|
||||
crown_diameter *= unit_conversion
|
||||
trunk_diameter *= unit_conversion
|
||||
|
||||
# 3d
|
||||
leaves = builder.polygonal_face_set(palm_verts, palm_faces)
|
||||
trunk_base = builder.circle(radius=trunk_diameter / 2)
|
||||
trunk = builder.extrude(trunk_base, magnitude=plant_height)
|
||||
|
||||
# 2d
|
||||
remove_z = Vector((1, 1, 0))
|
||||
verts_2D = [v * remove_z for v in palm_verts]
|
||||
ifc_points = builder.file.createIfcCartesianPointList2D(verts_2D)
|
||||
ifc_segments = []
|
||||
for segment in palm_segments_2d_ifc:
|
||||
ifc_segments.append(builder.file.createIfcLineIndex(segment))
|
||||
shape_2d = builder.file.createIfcIndexedPolyCurve(Points=ifc_points, Segments=ifc_segments)
|
||||
return GeneratedGeometry((shape_2d,), (leaves, trunk))
|
||||
|
||||
|
||||
tree_presets = {
|
||||
"simple": TreePresetData(SimpleTreeParams, generate_simple_tree),
|
||||
"low_poly": TreePresetData(LowPolyTreeParams, generate_low_poly_tree),
|
||||
"palm_tree": TreePresetData(PalmTreeParams, generate_palm_tree),
|
||||
}
|
||||
|
||||
|
||||
class LibraryGenerator:
|
||||
def generate(self, library_name, output_filename="IFC4 EU Steel.ifc"):
|
||||
ifcopenshell.api.pre_listeners = {}
|
||||
ifcopenshell.api.post_listeners = {}
|
||||
|
||||
self.materials = {}
|
||||
|
||||
self.file = ifcopenshell.api.run("project.create_file")
|
||||
self.project = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject", name=library_name)
|
||||
self.library = ifcopenshell.api.run(
|
||||
"root.create_entity", self.file, ifc_class="IfcProjectLibrary", name=library_name
|
||||
)
|
||||
ifcopenshell.api.run(
|
||||
"project.assign_declaration", self.file, definition=self.library, relating_context=self.project
|
||||
)
|
||||
unit = ifcopenshell.api.run("unit.add_si_unit", self.file, unit_type="LENGTHUNIT", prefix="MILLI")
|
||||
ifcopenshell.api.run("unit.assign_unit", self.file, units=[unit])
|
||||
|
||||
model = ifcopenshell.api.run("context.add_context", self.file, context_type="Model")
|
||||
plan = ifcopenshell.api.run("context.add_context", self.file, context_type="Plan")
|
||||
self.representations = {
|
||||
"model_body": ifcopenshell.api.run(
|
||||
"context.add_context",
|
||||
self.file,
|
||||
context_type="Model",
|
||||
context_identifier="Body",
|
||||
target_view="MODEL_VIEW",
|
||||
parent=model,
|
||||
),
|
||||
"plan_body": ifcopenshell.api.run(
|
||||
"context.add_context",
|
||||
self.file,
|
||||
context_type="Plan",
|
||||
context_identifier="Body",
|
||||
target_view="PLAN_VIEW",
|
||||
parent=plan,
|
||||
),
|
||||
}
|
||||
|
||||
self.builder = ShapeBuilder(self.file)
|
||||
builder = self.builder
|
||||
|
||||
TreeData = namedtuple("TreeData", "tree_name preset_name preset_data")
|
||||
trees = [
|
||||
TreeData("Cone Tree (1.8m)", "low_poly", (1.8, 1.13, 0.0, 1.0, 0.28, 0.16, 10)),
|
||||
TreeData("Cone Tree (15m)", "low_poly", (15.0, 4.37, 0.0, 1.0, 0.74, 0.44, 10)),
|
||||
TreeData("Cone Tree (5m)", "low_poly", (5.0, 1.7, 0.0, 1.0, 0.32, 0.22, 10)),
|
||||
TreeData("Hedge Small (0.4m)", "low_poly", (0.4, 0.4, 0.49, 0.15, 0.0, 0.0, 10)),
|
||||
TreeData("Hedge Medium (0.8m)", "low_poly", (0.8, 0.8, 0.49, 0.15, 0.0, 0.0, 10)),
|
||||
TreeData("Hedge Big (1.8m)", "low_poly", (1.8, 1.8, 0.49, 0.15, 0.0, 0.0, 10)),
|
||||
TreeData("Palm Tree (5m)", "palm_tree", (5.0, 8.02, 0.2)),
|
||||
TreeData("Palm Tree (15m)", "palm_tree", (15.0, 16.64, 0.4)),
|
||||
TreeData("Shrub Small (0.4m)", "low_poly", (0.4, 0.4, 0.535, 0.524, 0.0, 0.0, 10)),
|
||||
TreeData("Shrum Medium (0.8m)", "low_poly", (0.8, 0.8, 0.535, 0.524, 0.0, 0.0, 10)),
|
||||
TreeData("Shrub Big (1.8m)", "low_poly", (1.8, 1.8, 0.535, 0.524, 0.0, 0.0, 10)),
|
||||
TreeData("Simple Tree (1.8m)", "simple", (1.8, 1.34, 0.2)),
|
||||
TreeData("Simple Tree (5m)", "simple", (5.0, 3.29, 0.2)),
|
||||
TreeData("Simple Tree (15m)", "simple", (15.0, 9.21, 0.44)),
|
||||
TreeData("Tree Small (1.8m)", "low_poly", (1.8, 1.7, 0.5, 0.36, 0.28, 0.16, 10)),
|
||||
TreeData("Tree Big (15m)", "low_poly", (15.0, 10.0, 0.5, 0.36, 1.83, 0.44, 10)),
|
||||
TreeData("Tree Medium (5m)", "low_poly", (5.0, 5.0, 0.5, 0.36, 0.84, 0.22, 10)),
|
||||
]
|
||||
|
||||
for tree_data in trees:
|
||||
preset = tree_presets.get(tree_data.preset_name)
|
||||
preset_data = preset.preset_class(*tree_data.preset_data)._asdict()
|
||||
tree_geometry = preset.generator(builder, **preset_data)
|
||||
self.create_explicit_type(
|
||||
"IfcGeographicElementType", tree_data.tree_name, **self.get_representations(tree_geometry)
|
||||
)
|
||||
|
||||
self.file.write(output_filename)
|
||||
|
||||
def get_representations(self, generated_geometry: GeneratedGeometry):
|
||||
representation_2d = self.builder.get_representation(
|
||||
self.representations["plan_body"], items=generated_geometry.items_2d
|
||||
)
|
||||
representation_3d = self.builder.get_representation(
|
||||
self.representations["model_body"], items=generated_geometry.items_3d
|
||||
)
|
||||
return {
|
||||
"representation_3d": representation_3d,
|
||||
"representation_2d": representation_2d,
|
||||
}
|
||||
|
||||
def create_explicit_type(self, ifc_class, name, representation_3d, representation_2d, **params):
|
||||
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=ifc_class, name=name)
|
||||
for param, value in params.items():
|
||||
setattr(element, param, value)
|
||||
|
||||
ifcopenshell.api.run(
|
||||
"geometry.assign_representation", self.file, product=element, representation=representation_3d
|
||||
)
|
||||
ifcopenshell.api.run(
|
||||
"geometry.assign_representation", self.file, product=element, representation=representation_2d
|
||||
)
|
||||
ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library)
|
||||
return element
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
path = Path(__file__).parents[1] / "blenderbim/bim/data/libraries"
|
||||
LibraryGenerator().generate("Landscape Assets Library", output_filename=str(path / "IFC4 Landscape Library.ifc"))
|
||||
Reference in New Issue
Block a user