Compare commits

..

9 Commits

Author SHA1 Message Date
Ryan Schultz 0f835ca739 removed unnecessary code 2024-05-28 18:43:13 -05:00
Ryan Schultz 87442f549f Update src/blenderbim/blenderbim/bim/module/model/prop.py
Co-authored-by: Bruno Perdigão <57102715+brunoperdigao@users.noreply.github.com>
2024-05-28 11:02:23 -05:00
Ryan Schultz bf27e41275 Update src/blenderbim/blenderbim/bim/module/model/array.py
Co-authored-by: Bruno Perdigão <57102715+brunoperdigao@users.noreply.github.com>
2024-05-28 11:02:09 -05:00
Ryan Schultz 66cf06c990 Update src/blenderbim/blenderbim/bim/module/model/array.py
Co-authored-by: Bruno Perdigão <57102715+brunoperdigao@users.noreply.github.com>
2024-05-27 16:10:20 -05:00
Ryan Schultz 5757a62b6f Update src/blenderbim/blenderbim/bim/module/model/prop.py
Co-authored-by: Bruno Perdigão <57102715+brunoperdigao@users.noreply.github.com>
2024-05-27 16:09:28 -05:00
Ryan Schultz e2552e3d46 Update src/blenderbim/blenderbim/bim/module/model/prop.py
Co-authored-by: Bruno Perdigão <57102715+brunoperdigao@users.noreply.github.com>
2024-05-27 16:09:22 -05:00
Ryan Schultz 1b91540c96 Update src/blenderbim/blenderbim/bim/module/model/prop.py
Co-authored-by: Bruno Perdigão <57102715+brunoperdigao@users.noreply.github.com>
2024-05-27 16:09:15 -05:00
Ryan Schultz 274b12e5a0 Update src/blenderbim/blenderbim/bim/module/model/prop.py
Co-authored-by: Bruno Perdigão <57102715+brunoperdigao@users.noreply.github.com>
2024-05-27 16:09:01 -05:00
Ryan Schultz 496ba6f22d fix #3802 - Copy array attributes from another array 2024-05-26 13:56:52 -05:00
208 changed files with 1102 additions and 2305 deletions
+3 -6
View File
@@ -36,9 +36,7 @@ if(NOT CMAKE_BUILD_TYPE)
endif()
# use extra version to make pre-release using eg semver
if(NOT DEFINED EXTRA_VERSION)
set(EXTRA_VERSION "-alpha.3")
endif()
set(EXTRA_VERSION "-alpha.3")
option(MINIMAL_BUILD "The build is to make a minimal version of IFC converter from OCCT into IFC." OFF)
option(WASM_BUILD "Build a WebAssembly binary." OFF)
@@ -1149,10 +1147,9 @@ endif()
# Packaging
list(APPEND CPACK_SOURCE_IGNORE_FILES
"/\\\\.git"
"/build/"
.git
.gitignore
)
set(CPACK_SOURCE_INSTALLED_DIRECTORIES "${CMAKE_SOURCE_DIR}/..;/")
set(CPACK_PACKAGE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}${EXTRA_VERSION}")
set(CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}${EXTRA_VERSION}")
SET(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}${EXTRA_VERSION}-${CMAKE_SYSTEM_NAME}")
+1 -1
View File
@@ -13,7 +13,7 @@ readme = "README.md"
requires-python = ">=3.8"
keywords = ["IFC", "BCF", "BIM"]
dependencies = [
"xsdata>=24.4",
"xsdata",
"numpy",
"ifcopenshell",
]
+5 -7
View File
@@ -10,14 +10,14 @@ from xsdata.formats.dataclass.serializers.config import SerializerConfig
def build_xml_parser(context: Optional[XmlContext] = None) -> XmlParser:
"""Return a parser for an XML file."""
parser = XmlParser(context=context or XmlContext())
parser.register_namespace(ns_map=parser.ns_map, prefix="xs", uri="http://www.w3.org/2001/XMLSchema")
parser.register_namespace("xs", "http://www.w3.org/2001/XMLSchema")
return parser
def build_serializer(context: Optional[XmlContext] = None) -> XmlSerializer:
"""Return a serializer for an XML file."""
return XmlSerializer(
config=SerializerConfig(indent=" "),
config=SerializerConfig(pretty_print=True),
context=context or XmlContext(),
)
@@ -36,9 +36,8 @@ class AbstractXmlParserSerializer(Protocol):
xml: The XML file as bytes.
clazz: The class to parse to.
"""
...
def serialize(self, obj: object, ns_map: Optional[dict[str, str]] = None) -> str:
def serialize(self, obj: T, ns_map: Optional[dict[str, str]] = None) -> str:
"""
Serialize an object to XML.
@@ -49,7 +48,6 @@ class AbstractXmlParserSerializer(Protocol):
Returns:
The XML as string.
"""
...
class XmlParserSerializer:
@@ -70,7 +68,7 @@ class XmlParserSerializer:
"""
return self.parser.from_bytes(xml, clazz)
def serialize(self, obj: object, ns_map: Optional[dict[Optional[str], str]] = None) -> str:
def serialize(self, obj: T, ns_map: Optional[dict[str, str]] = None) -> str:
"""
Serialize an object to XML.
@@ -81,5 +79,5 @@ class XmlParserSerializer:
Returns:
The XML as string.
"""
ns_map = ns_map or self.parser.ns_map or {"xs": "http://www.w3.org/2001/XMLSchema"}
ns_map = ns_map or {"xs": "http://www.w3.org/2001/XMLSchema"}
return self.serializer.render(obj, ns_map)
+2 -2
View File
@@ -286,9 +286,9 @@ endif
# Required by bcf
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/b4/ef/35d8118f903510f9e028f8a6a4edb615fa69e28a30d955593425a88e587a/xsdata-24.5.tar.gz
cd dist/working && wget https://files.pythonhosted.org/packages/ad/c7/17c9d16320d8e2cfdb27d2fd298b5e8f7a3f211025b0c1bc7a39a85bd690/xsdata-22.11.tar.gz
cd dist/working && tar -xzvf xsdata*
cp -r dist/working/xsdata-24.5/xsdata dist/blenderbim/libs/site/packages/
cp -r dist/working/xsdata-22.11/xsdata dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Required by bcf
+9 -2
View File
@@ -122,7 +122,7 @@ if sys.modules.get("bpy", None):
def draw(self, context):
layout = self.layout
layout.label(text="BlenderBIM could not load.", icon="ERROR")
layout.operator("wm.console_toggle", text="View the console for full logs.", icon="CONSOLE")
layout.label(text="View the console for full logs.", icon="CONSOLE")
box = layout.box()
info = get_debug_info()
py = ".".join(info["python_version"].split(".")[0:2])
@@ -149,7 +149,14 @@ if sys.modules.get("bpy", None):
def execute(self, context):
info = format_debug_info(get_debug_info())
context.window_manager.clipboard = info
if platform.system() == "Windows":
command = "echo | set /p nul=" + info
elif platform.system() == "Darwin": # for MacOS
command = 'printf "' + info.replace("\n", "\\n").replace('"', "") + '" | pbcopy'
else: # Linux
command = 'printf "' + info.replace("\n", "\\n").replace('"', "") + '" | xclip -selection clipboard'
subprocess.run(command, shell=True, check=True)
return {"FINISHED"}
class HiddenPanel:
+13 -21
View File
@@ -26,7 +26,6 @@ import bmesh
import logging
import mathutils
import numpy as np
import numpy.typing as npt
import multiprocessing
import ifcopenshell
import ifcopenshell.geom
@@ -304,22 +303,22 @@ class IfcImporter:
self.update_progress(100)
bpy.context.window_manager.progress_end()
def is_element_far_away(self, element: ifcopenshell.entity_instance) -> bool:
def is_element_far_away(self, element):
try:
placement = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
point = placement[:, 3][0:3]
return self.is_point_far_away(point, is_meters=False)
except:
return False
pass
def is_point_far_away(
self, point: Union[ifcopenshell.entity_instance, npt.NDArray[np.float64]], is_meters: bool = True
) -> bool:
def is_point_far_away(self, point, is_meters=True):
# Locations greater than 1km are not considered "small sites" according to the georeferencing guide
# Users can configure this if they have to handle larger sites but beware of surveying precision
limit = self.ifc_import_settings.distance_limit
limit = limit if is_meters else (limit / self.unit_scale)
coords = getattr(point, "Coordinates", point)
coords = point
if hasattr(point, "Coordinates"):
coords = point.Coordinates
return abs(coords[0]) > limit or abs(coords[1]) > limit or abs(coords[2]) > limit
def process_context_filter(self):
@@ -609,9 +608,7 @@ class IfcImporter:
threshold = 10000 # Just from experience.
# The check for CfsFaces/Faces/CoordIndex accommodates invalid data from Cadwork
# 0 IfcClosedShell.CfsFaces
faces = [len(faces) for e in self.file.by_type("IfcClosedShell") if (faces := e[0])]
faces = [len(e.CfsFaces) for e in self.file.by_type("IfcClosedShell")]
if faces and max(faces) > threshold:
self.ifc_import_settings.should_use_native_meshes = True
return
@@ -619,14 +616,12 @@ class IfcImporter:
if self.file.schema == "IFC2X3":
return
# 2 IfcPolygonalFaceSet.Faces
faces = [len(faces) for e in self.file.by_type("IfcPolygonalFaceSet") if (faces := e[2])]
faces = [len(e.Faces) for e in self.file.by_type("IfcPolygonalFaceSet")]
if faces and max(faces) > threshold:
self.ifc_import_settings.should_use_native_meshes = True
return
# 3 IfcTriangulatedFaceSet.CoordIndex
faces = [len(index) for e in self.file.by_type("IfcTriangulatedFaceSet") if (index := e[3])]
faces = [len(e.CoordIndex) for e in self.file.by_type("IfcTriangulatedFaceSet")]
if faces and max(faces) > threshold:
self.ifc_import_settings.should_use_native_meshes = True
@@ -684,7 +679,7 @@ class IfcImporter:
props.blender_orthogonal_height = str(offset_point[2])
props.has_blender_offset = True
def get_offset_point(self) -> Union[npt.NDArray[np.float64], None]:
def get_offset_point(self):
elements_checked = 0
# If more than these elements aren't far away, the file probably isn't absolutely positioned
element_checking_threshold = 10
@@ -719,7 +714,7 @@ class IfcImporter:
if self.is_point_far_away(point, is_meters=False):
return point
def does_element_likely_have_geometry_far_away(self, element: ifcopenshell.entity_instance) -> bool:
def does_element_likely_have_geometry_far_away(self, element):
for representation in element.Representation.Representations:
items = []
for item in representation.Items:
@@ -736,14 +731,13 @@ class IfcImporter:
if subelement.is_a("IfcCartesianPoint"):
if len(subelement.Coordinates) == 3 and self.is_point_far_away(subelement, is_meters=False):
return True
return False
def apply_blender_offset_to_matrix_world(self, obj: bpy.types.Object, matrix: np.ndarray) -> mathutils.Matrix:
props = bpy.context.scene.BIMGeoreferenceProperties
if props.has_blender_offset:
if obj.data and obj.data.get("has_cartesian_point_offset", None):
obj.BIMObjectProperties.blender_offset_type = "CARTESIAN_POINT"
elif self.is_point_far_away((matrix[:3, 3])):
elif self.is_point_far_away((matrix[0, 3], matrix[1, 3], matrix[2, 3])):
obj.BIMObjectProperties.blender_offset_type = "OBJECT_PLACEMENT"
matrix = ifcopenshell.util.geolocation.global2local(
matrix,
@@ -756,9 +750,7 @@ class IfcImporter:
return mathutils.Matrix(matrix.tolist())
def find_decomposed_ifc_class(
self, element: ifcopenshell.entity_instance, ifc_class: str
) -> Union[ifcopenshell.entity_instance, None]:
def find_decomposed_ifc_class(self, element, ifc_class):
if element.is_a(ifc_class):
return element
rel_aggregates = element.IsDecomposedBy
@@ -100,7 +100,7 @@ class BIM_OT_aggregate_unassign_object(bpy.types.Operator, Operator):
pset = ifcopenshell.util.element.get_pset(element, 'BBIM_Linked_Aggregate')
if pset:
pset = tool.Ifc.get().by_id(pset["id"])
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
class BIM_OT_enable_editing_aggregate(bpy.types.Operator, Operator):
@@ -60,7 +60,13 @@ class CopyDebugInformation(bpy.types.Operator):
print(text)
print("-" * 80)
context.window_manager.clipboard = text
if platform.system() == "Windows":
command = "echo | set /p nul=" + text
elif platform.system() == "Darwin": # for MacOS
command = 'printf "' + text.replace("\n", "\\n").replace('"', "") + '" | pbcopy'
else: # Linux
command = 'printf "' + text.replace("\n", "\\n").replace('"', "") + '" | xclip -selection clipboard'
subprocess.run(command, shell=True, check=True)
return {"FINISHED"}
@@ -159,7 +159,9 @@ class BaseDecorator:
objecttype = "NOTDEFINED"
def __init__(self):
self.font_id = 0 # 0 is the default font
self.font_id = blf.load(
os.path.join(bpy.context.scene.BIMProperties.data_dir, "fonts", "OpenGost Type B TT.ttf")
)
# POLYLINE_UNIFORM_COLOR is good for smoothed lines since `bgl.enable(GL_LINE_SMOOTH)` is deprecated
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
@@ -402,6 +404,7 @@ class BaseDecorator:
line_no += 1 if multiline_to_bottom else -1
return
# 0 is the default font, but we're fancier than that
font_id = self.font_id
color = context.preferences.addons["blenderbim"].preferences.decorations_colour
@@ -2011,13 +2014,6 @@ class DecorationsHandler:
for object_type in ("SLOPE_ANGLE", "SLOPE_FRACTION", "SLOPE_PERCENT"):
self.decorators[object_type] = self.decorators["FALL"]
self.decorators["MULTI_SYMBOL"] = self.decorators["SYMBOL"]
if drawing_font := bpy.context.scene.DocProperties.drawing_font:
drawing_font_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "fonts", drawing_font)
if os.path.exists(drawing_font_path):
font_id = blf.load(drawing_font_path)
for decorator in self.decorators.values():
decorator.font_id = font_id
def get_objects_and_decorators(self, collection):
# TODO: do it in data instead of the handler for performance?
@@ -315,7 +315,9 @@ class RasterStyleProperty(enum.Enum):
SPACE_SHADING = "space.shading"
RASTER_STYLE_PROPERTIES_EXCLUDE = ("scene.render.filepath",)
RASTER_STYLE_PROPERTIES_EXCLUDE = (
"scene.render.filepath",
)
class DocProperties(PropertyGroup):
@@ -369,7 +371,6 @@ class DocProperties(PropertyGroup):
default=os.path.join("drawings", "assets", "shading_styles.json"), name="Default Shading Styles"
)
shadingstyle_default: StringProperty(default="Blender Default", name="Default Shading Style")
drawing_font: StringProperty(default="OpenGost Type B TT.ttf", name="Drawing Font")
class BIMCameraProperties(PropertyGroup):
@@ -400,11 +400,7 @@ class UpdateRepresentation(bpy.types.Operator, Operator):
representation_data["profile_set_usage"] = tool.Geometry.get_profile_set_usage(product)
representation_data["text_literal"] = tool.Geometry.get_text_literal(old_representation)
# TODO: replace with core.add_representation?
new_representation = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data)
if new_representation is None:
self.report({"ERROR"}, "Error creating representation for Blender object.")
return {"CANCELLED"}
if tool.Geometry.is_body_representation(new_representation):
[
@@ -942,7 +938,7 @@ class OverrideDuplicateMove(bpy.types.Operator):
pset = ifcopenshell.util.element.get_pset(new[0], "BBIM_Linked_Aggregate")
if pset:
pset = tool.Ifc.get().by_id(pset["id"])
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=new[0],pset=pset)
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
if new[0].is_a("IfcElementAssembly"):
linked_aggregate_group = [
@@ -1054,7 +1050,7 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
if self.group_name in product_groups_name:
return
linked_aggregate_group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), name=self.group_name)
linked_aggregate_group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), Name=self.group_name)
ifcopenshell.api.run("group.assign_group", tool.Ifc.get(), products=[element], group=linked_aggregate_group)
def custom_incremental_naming_for_element_assembly(old_to_new):
@@ -23,7 +23,6 @@ import blenderbim.bim.handler
import blenderbim.tool as tool
import blenderbim.core.misc as core
import blenderbim.core.geometry as core_geometry
import blenderbim.core.root
from blenderbim.bim.ifc import IfcStore
from mathutils import Vector, Matrix, Euler
@@ -135,10 +134,6 @@ class ResizeToStorey(bpy.types.Operator, Operator):
class SplitAlongEdge(bpy.types.Operator, Operator):
bl_idname = "bim.split_along_edge"
bl_label = "Split Along Edge"
bl_description = (
"Active object is considered to be a cutting object."
"Will unassign element from a type if type has a representation."
)
bl_options = {"REGISTER", "UNDO"}
@classmethod
@@ -149,25 +144,16 @@ class SplitAlongEdge(bpy.types.Operator, Operator):
cutter = context.active_object
objs = [o for o in context.selected_objects if o != cutter]
objs_to_cut = []
# Splitting only works on meshes
for obj in objs:
# You cannot split meshes if the representation is mapped.
element = tool.Ifc.get_entity(obj)
if not element:
continue
if element:
relating_type = tool.Root.get_element_type(element)
if relating_type and tool.Root.does_type_have_representations(relating_type):
bpy.ops.bim.unassign_type(related_object=obj.name)
relating_type = tool.Root.get_element_type(element)
if relating_type and tool.Root.does_type_have_representations(relating_type):
bpy.ops.bim.unassign_type(related_object=obj.name)
# refresh representation
representation = tool.Geometry.get_active_representation(obj)
# skip empty objects that might get in the way
if not representation:
continue
core_geometry.switch_representation(
tool.Ifc,
tool.Geometry,
@@ -182,13 +168,11 @@ class SplitAlongEdge(bpy.types.Operator, Operator):
if not tool.Geometry.is_meshlike(representation):
bpy.ops.bim.update_representation(obj=obj.name, ifc_representation_class="IfcTessellatedFaceSet")
objs_to_cut.append(obj)
new_objs = tool.Misc.split_objects_with_cutter(objs_to_cut, cutter)
new_objs = tool.Misc.split_objects_with_cutter(objs, cutter)
for obj in new_objs:
blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=obj)
bpy.ops.bim.update_representation(obj=obj.name)
for obj in objs_to_cut:
for obj in objs:
bpy.ops.bim.update_representation(obj=obj.name)
representation = tool.Geometry.get_active_representation(obj)
@@ -203,8 +187,6 @@ class SplitAlongEdge(bpy.types.Operator, Operator):
apply_openings=True,
)
self.report({"INFO"}, f"Splitting finished, {len(new_objs)} new objects created.")
class GetConnectedSystemElements(bpy.types.Operator, Operator):
bl_idname = "bim.get_connected_system_elements"
@@ -83,8 +83,17 @@ class EnableEditingArray(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
element = tool.Ifc.get_entity(obj)
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Array", "Data"))[self.item]
props = obj.BIMArrayProperties
relating_obj = props.relating_array_object
if relating_obj:
element = tool.Ifc.get_entity(relating_obj)
parent_globalid = ifcopenshell.util.element.get_pset(element, "BBIM_Array", "Parent")
parent_element = tool.Ifc.get().by_guid(parent_globalid)
data = json.loads(ifcopenshell.util.element.get_pset(parent_element, "BBIM_Array", "Data"))[self.item]
else:
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Array", "Data"))[self.item]
props.count = data["count"]
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
props.x = data["x"] * si_conversion
@@ -93,7 +102,9 @@ class EnableEditingArray(bpy.types.Operator, tool.Ifc.Operator):
props.use_local_space = data.get("use_local_space", False)
props.sync_children = data.get("sync_children", False)
props.method = data.get("method", "OFFSET")
props.is_editing = self.item
return {"FINISHED"}
@@ -137,6 +148,10 @@ class EditArray(bpy.types.Operator, tool.Ifc.Operator):
tool.Blender.Modifier.Array.set_children_lock_state(element, self.item, True)
tool.Blender.Modifier.Array.constrain_children_to_parent(element)
#clears the relating_array_object so it doesn't show again next time
props.relating_array_object = None
return {"FINISHED"}
@@ -193,7 +208,7 @@ class RemoveArray(bpy.types.Operator, tool.Ifc.Operator):
pset = tool.Ifc.get().by_id(pset["id"])
if len(data) == 1:
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
else:
del data[self.item]
data = tool.Ifc.get().createIfcText(json.dumps(data))
@@ -644,6 +644,6 @@ class RemoveDoor(bpy.types.Operator, tool.Ifc.Operator):
obj.BIMDoorProperties.is_editing = False
pset = tool.Pset.get_element_pset(element, "BBIM_Door")
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
return {"FINISHED"}
@@ -931,11 +931,13 @@ class PatchNonParametricMepSegment(bpy.types.Operator, tool.Ifc.Operator):
return context.active_object
def _execute(self, context):
styles = tool.Geometry.get_styles(context.active_object)
blenderbim.core.material.patch_non_parametric_mep_segment(
tool.Ifc, tool.Material, tool.Profile, obj=context.active_object
)
bpy.ops.bim.enable_editing_extrusion_axis()
bpy.ops.bim.edit_extrusion_axis()
styles = tool.Geometry.get_styles(context.active_object)
class EnableEditingExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
@@ -87,6 +87,21 @@ def update_type_page(self, context):
AuthoringData.data["paginated_relating_types"] = AuthoringData.paginated_relating_types()
def update_relating_array_from_object(self, context):
bpy.ops.bim.enable_editing_array(item=self.is_editing)
return
def is_object_array_applicable(self, obj):
element = tool.Ifc.get_entity(obj)
if not element:
return False
return ifcopenshell.util.element.get_pset(element, "BBIM_Array")
class BIMModelProperties(PropertyGroup):
ifc_class: bpy.props.EnumProperty(items=get_ifc_class, name="Construction Class", update=update_ifc_class)
relating_type_id: bpy.props.EnumProperty(
@@ -203,6 +218,14 @@ class BIMArrayProperties(PropertyGroup):
description="Regenerate all children based on the parent object",
default=False,
)
relating_array_object: bpy.props.PointerProperty(
type=bpy.types.Object,
name="Copy Array Properties",
update=update_relating_array_from_object,
poll=is_object_array_applicable,
)
class BIMStairProperties(PropertyGroup):
@@ -535,5 +535,5 @@ class RemoveRailing(bpy.types.Operator, tool.Ifc.Operator):
obj.BIMRailingProperties.is_editing = False
pset = tool.Pset.get_element_pset(element, "BBIM_Railing")
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
return {"FINISHED"}
@@ -757,7 +757,7 @@ class RemoveRoof(bpy.types.Operator, tool.Ifc.Operator):
obj.BIMRoofProperties.is_editing = False
pset = tool.Pset.get_element_pset(element, "BBIM_Roof")
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
return {"FINISHED"}
@@ -337,6 +337,6 @@ class RemoveStair(bpy.types.Operator, tool.Ifc.Operator):
obj.BIMStairProperties.is_editing = False
pset = tool.Pset.get_element_pset(element, "BBIM_Stair")
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
return {"FINISHED"}
@@ -223,6 +223,8 @@ class BIM_PT_array(bpy.types.Panel):
row = col.row(align=True)
row.prop(props, "z")
row.operator("bim.input_cursor_z_array", icon="CURSOR", text="")
row = col.row(align=True)
row.prop(props, "relating_array_object", icon="COPYDOWN")
else:
row = box.row(align=True)
name = f"{array['count']} Items ({array.get('method', 'OFFSET').capitalize()})"
@@ -590,6 +590,6 @@ class RemoveWindow(bpy.types.Operator, tool.Ifc.Operator):
obj.BIMWindowProperties.is_editing = False
pset = tool.Pset.get_element_pset(element, "BBIM_Window")
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
return {"FINISHED"}
@@ -19,7 +19,6 @@
import bpy
import blenderbim.bim.schema
import ifcopenshell
import ifcopenshell.util.attribute
import ifcopenshell.util.element
import blenderbim.tool as tool
from blenderbim.bim.prop import Attribute, StrProperty
@@ -36,14 +36,10 @@ class PsetTemplatesData:
@classmethod
def load(cls):
cls.is_loaded = True
cls.data["pset_template_files"] = cls.pset_template_files()
# after pset_template_files
cls.data["pset_templates"] = cls.pset_templates()
# after pset_template_files because it loads IfcStore.pset_template_file
cls.data["primary_measure_type"] = cls.primary_measure_type()
cls.data["property_template_type"] = cls.property_template_type()
cls.data["pset_template_files"] = cls.pset_template_files()
cls.data["pset_templates"] = cls.pset_templates()
cls.data["pset_template"] = cls.pset_template()
cls.data["prop_templates"] = cls.prop_templates()
@@ -20,7 +20,6 @@ import re
import bpy
import json
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.guid
import ifcopenshell.util.element
import ifcopenshell.util.selector
@@ -215,7 +214,7 @@ class SaveSearch(Operator, tool.Ifc.Operator):
group = group[0]
group.Description = description
else:
group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), name=self.name, description=description)
group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), Name=self.name, Description=description)
if results:
ifcopenshell.api.run("group.assign_group", tool.Ifc.get(), products=list(results), group=group)
@@ -368,7 +367,7 @@ class SaveColourscheme(Operator, tool.Ifc.Operator):
description = json.dumps(
{"type": "BBIM_Search", "colourscheme": colourscheme, "colourscheme_query": query}
)
group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), name=self.name, description=description)
group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), Name=self.name, Description=description)
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
@@ -44,8 +44,6 @@ def update_elevation(self, context):
def update_active_container_index(self, context):
if self.active_container_index < 0:
return
self.active_container_id = self.containers[self.active_container_index].ifc_definition_id
self.container_name = self.containers[self.active_container_index].name
self.elevation = self.containers[self.active_container_index].elevation
@@ -116,7 +116,7 @@ class BIM_PT_SpatialManager(Panel):
self.props = context.scene.BIMSpatialManagerProperties
row = self.layout.row()
row.operator("bim.load_container_manager", icon="FILE_REFRESH", text="Load Spatial Structure")
if 0 <= self.props.active_container_index < len(self.props.containers):
if self.props.active_container_index < len(self.props.containers):
ifc_definition_id = self.props.containers[self.props.active_container_index].ifc_definition_id
row = self.layout.row()
row.alignment = "RIGHT"
@@ -134,7 +134,7 @@ class BIM_PT_SpatialManager(Panel):
"active_container_index",
)
row = self.layout.row()
if 0 <= self.props.active_container_index < len(self.props.containers):
if self.props.active_container_index < len(self.props.containers):
row.prop(self.props, "container_name", text="")
row.prop(self.props, "elevation", text="")
op = row.operator("bim.edit_container_attributes", icon="CHECKMARK", text="Apply")
@@ -20,7 +20,6 @@ import bpy
import json
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.attribute
import blenderbim.bim.helper
import blenderbim.bim.handler
import blenderbim.core.structural as core
+14 -15
View File
@@ -290,6 +290,8 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
row = layout.row()
row.prop(self, "spatial_elements_unselectable")
row = layout.row()
row.prop(context.scene.BIMProjectProperties, "should_disable_undo_on_save")
row = layout.row()
@@ -318,30 +320,28 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
row.prop(context.scene.BIMProperties, "data_dir")
row.operator("bim.select_data_dir", icon="FILE_FOLDER", text="")
row = self.layout.row()
row = self.layout.row(align=True)
row.prop(context.scene.BIMProperties, "pset_dir")
row = self.layout.row()
row = self.layout.row(align=True)
row.prop(context.scene.DocProperties, "sheets_dir")
row = self.layout.row()
row = self.layout.row(align=True)
row.prop(context.scene.DocProperties, "layouts_dir")
row = self.layout.row()
row = self.layout.row(align=True)
row.prop(context.scene.DocProperties, "titleblocks_dir")
row = self.layout.row()
row = self.layout.row(align=True)
row.prop(context.scene.DocProperties, "drawings_dir")
row = self.layout.row()
row = self.layout.row(align=True)
row.prop(context.scene.DocProperties, "stylesheet_path")
row = self.layout.row()
row = self.layout.row(align=True)
row.prop(context.scene.DocProperties, "markers_path")
row = self.layout.row()
row = self.layout.row(align=True)
row.prop(context.scene.DocProperties, "symbols_path")
row = self.layout.row()
row = self.layout.row(align=True)
row.prop(context.scene.DocProperties, "patterns_path")
row = self.layout.row()
row = self.layout.row(align=True)
row.prop(context.scene.DocProperties, "shadingstyles_path")
row = self.layout.row()
row = self.layout.row(align=True)
row.prop(context.scene.DocProperties, "shadingstyle_default")
row = self.layout.row()
row.prop(context.scene.DocProperties, "drawing_font")
# Scene panel groups
@@ -406,11 +406,10 @@ class BIM_PT_tabs(Panel):
if blenderbim.last_error:
box = self.layout.box()
box.alert=True
row = box.row(align=True)
row.label(text="BlenderBIM experienced an error :(", icon="ERROR")
row.operator("bim.close_error", text="", icon="CANCEL")
box.operator("wm.console_toggle", text="View the console for full logs.", icon="CONSOLE")
box.label(text="View the console for full logs.", icon="CONSOLE")
box.operator("bim.copy_debug_information", text="Copy Error Message To Clipboard")
op = box.operator("bim.open_uri", text="How Can I Fix This?")
op.uri = "https://docs.blenderbim.org/users/troubleshooting.html"
+13 -21
View File
@@ -18,11 +18,10 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
import blenderbim.core.tool as tool
if TYPE_CHECKING:
import bpy
import ifcopenshell
import blenderbim.tool as tool
def edit_object_placement(
@@ -38,15 +37,8 @@ def edit_object_placement(
def add_representation(
ifc: tool.Ifc,
geometry: tool.Geometry,
style: tool.Style,
surveyor: tool.Surveyor,
obj: bpy.types.Object,
context: ifcopenshell.entity_instance,
ifc_representation_class: Optional[str] = None,
profile_set_usage: Optional[ifcopenshell.entity_instance] = None,
) -> ifcopenshell.entity_instance:
ifc, geometry, style, surveyor, obj=None, context=None, ifc_representation_class=None, profile_set_usage=None
):
element = ifc.get_entity(obj)
if not element:
return
@@ -55,7 +47,7 @@ def add_representation(
data = geometry.get_object_data(obj)
if not data and ifc_representation_class != "IfcTextLiteral":
return
raise IncompatibleRepresentationError()
representation = ifc.run(
"geometry.add_representation",
@@ -97,15 +89,15 @@ def add_representation(
def switch_representation(
ifc: tool.Ifc,
geometry: tool.Geometry,
obj: bpy.types.Object,
representation: ifcopenshell.entity_instance,
should_reload: bool = True,
is_global: bool = True,
should_sync_changes_first: bool = False,
apply_openings: bool = True,
) -> None:
ifc,
geometry,
obj=None,
representation=None,
should_reload=True,
is_global=True,
should_sync_changes_first=False,
apply_openings=True,
):
"""Function can switch to representation that wasn't yet assigned to that object. See #2766.
`should_sync_changes_first` - sync ifc representation with current state of `obj.data`;
+11 -21
View File
@@ -16,18 +16,8 @@
# 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/>.
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
if TYPE_CHECKING:
import bpy
import ifcopenshell
import blenderbim.tool as tool
def copy_class(
ifc: tool.Ifc, collector: tool.Collector, geometry: tool.Geometry, root: tool.Root, obj: bpy.types.Object
) -> ifcopenshell.entity_instance:
def copy_class(ifc, collector, geometry, root, obj=None):
element = ifc.get_entity(obj)
if not element:
return
@@ -58,16 +48,16 @@ def copy_class(
def assign_class(
ifc: tool.Ifc,
collector: tool.Collector,
root: tool.Root,
obj: bpy.types.Object,
ifc_class: str,
context: ifcopenshell.entity_instance,
predefined_type: Optional[str] = None,
should_add_representation: bool = True,
ifc_representation_class: Optional[str] = None,
) -> ifcopenshell.entity_instance:
ifc,
collector,
root,
obj=None,
ifc_class=None,
predefined_type=None,
should_add_representation=True,
context=None,
ifc_representation_class=None,
):
if ifc.get_entity(obj):
return
+1 -1
View File
@@ -18,11 +18,11 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional, Union
import blenderbim.core.tool as tool
if TYPE_CHECKING:
import bpy
import ifcopenshell
import blenderbim.tool as tool
def reference_structure(
+7 -20
View File
@@ -24,10 +24,8 @@ import logging
import numpy as np
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.geom
import ifcopenshell.guid
import ifcopenshell.util.element
import ifcopenshell.util.representation
import ifcopenshell.util.system
import blenderbim.core.tool
import blenderbim.core.drawing
@@ -321,7 +319,7 @@ class Geometry(blenderbim.core.tool.Geometry):
return new_mesh
@classmethod
def get_active_representation(cls, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
def get_active_representation(cls, obj):
"""< IfcShapeRepresentation or None"""
if obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.ifc_definition_id:
return tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
@@ -461,9 +459,7 @@ class Geometry(blenderbim.core.tool.Geometry):
return f"{representation.ContextOfItems.id()}/{representation.id()}"
@classmethod
def get_styles(
cls, obj: bpy.types.Object, only_assigned_to_faces: bool = False
) -> list[Union[ifcopenshell.entity_instance, None]]:
def get_styles(cls, obj, only_assigned_to_faces=False):
styles = [tool.Style.get_style(s.material) for s in obj.material_slots if s.material]
if not only_assigned_to_faces:
return styles
@@ -471,15 +467,8 @@ class Geometry(blenderbim.core.tool.Geometry):
usage_count = [0] * len(obj.material_slots)
if not usage_count: # if there are no materials, polygons will still use index 0
return []
for poly in obj.data.polygons:
usage_count[poly.material_index] += 1
# remove usages for empty material slots
for i, slot in reversed(list(enumerate(obj.material_slots))):
if not slot.material:
del usage_count[i]
styles = [style for style, usage in zip(styles, usage_count, strict=True) if usage > 0]
return styles
@@ -571,11 +560,11 @@ class Geometry(blenderbim.core.tool.Geometry):
new.value = element[i]
@classmethod
def is_body_representation(cls, representation: ifcopenshell.entity_instance) -> bool:
def is_body_representation(cls, representation):
return representation.ContextOfItems.ContextIdentifier == "Body"
@classmethod
def is_box_representation(cls, representation: ifcopenshell.entity_instance) -> bool:
def is_box_representation(cls, representation):
return representation.ContextOfItems.ContextIdentifier == "Box"
@classmethod
@@ -583,11 +572,11 @@ class Geometry(blenderbim.core.tool.Geometry):
return not all([tool.Cad.is_x(o, 1.0) for o in obj.scale]) or obj in IfcStore.edited_objs
@classmethod
def is_mapped_representation(cls, representation: ifcopenshell.entity_instance) -> bool:
def is_mapped_representation(cls, representation):
return representation.RepresentationType == "MappedRepresentation"
@classmethod
def is_meshlike(cls, representation: ifcopenshell.entity_instance) -> bool:
def is_meshlike(cls, representation):
if ifcopenshell.util.representation.resolve_representation(representation).RepresentationType in (
"AdvancedBrep",
"Annotation2D",
@@ -667,9 +656,7 @@ class Geometry(blenderbim.core.tool.Geometry):
bpy.data.objects.remove(obj)
@classmethod
def resolve_mapped_representation(
cls, representation: ifcopenshell.entity_instance
) -> ifcopenshell.entity_instance:
def resolve_mapped_representation(cls, representation):
if representation.RepresentationType == "MappedRepresentation":
return cls.resolve_mapped_representation(representation.Items[0].MappingSource.MappedRepresentation)
return representation
+1 -3
View File
@@ -97,9 +97,7 @@ class Misc(blenderbim.core.tool.Misc):
IfcStore.edited_objs.add(obj)
@classmethod
def split_objects_with_cutter(
cls, objs: list[bpy.types.Object], cutter: bpy.types.Object
) -> list[bpy.types.Object]:
def split_objects_with_cutter(cls, objs, cutter):
cutter_mesh = cutter.data
bm = bmesh.new()
+1 -1
View File
@@ -551,7 +551,7 @@ class Model(blenderbim.core.tool.Model):
data = tool.Ifc.get().createIfcText(json.dumps(data))
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": data})
else:
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
@classmethod
def get_flow_segment_axis(cls, obj):
@@ -21,7 +21,6 @@ import bpy
import ifcopenshell
import ifcopenshell.util.unit
import blenderbim.core.tool
import blenderbim.core.root
import blenderbim.bim.schema
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
+3 -8
View File
@@ -29,7 +29,6 @@ import blenderbim.core.style
import blenderbim.tool as tool
from mathutils import Vector
from blenderbim.bim.module.model.opening import FilledOpeningGenerator
from typing import Union, Optional
class Root(blenderbim.core.tool.Root):
@@ -130,7 +129,7 @@ class Root(blenderbim.core.tool.Root):
return ifcopenshell.util.representation.get_representation(element, context=context.ContextType)
@classmethod
def get_element_type(cls, element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
def get_element_type(cls, element):
return ifcopenshell.util.element.get_type(element)
@classmethod
@@ -283,12 +282,8 @@ class Root(blenderbim.core.tool.Root):
@classmethod
def run_geometry_add_representation(
cls,
obj: bpy.types.Object,
context: ifcopenshell.entity_instance,
ifc_representation_class: Optional[str] = None,
profile_set_usage: Optional[ifcopenshell.entity_instance] = None,
) -> ifcopenshell.entity_instance:
cls, obj=None, context=None, ifc_representation_class=None, profile_set_usage=None
):
return blenderbim.core.geometry.add_representation(
tool.Ifc,
tool.Geometry,
+1 -1
View File
@@ -1630,7 +1630,7 @@ class Sequence(blenderbim.core.tool.Sequence):
group.Description = json.dumps(description)
else:
description = json.dumps({"type": "BBIM_AnimationColorScheme", "colourscheme": colour_scheme})
group = tool.Ifc.run("group.add_group", name=name, description=description)
group = tool.Ifc.run("group.add_group", Name=name, Description=description)
return group[0]
@classmethod
@@ -105,7 +105,6 @@ For Linux or Mac:
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifccsv.py
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifcdiff.py
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/bsdd.py
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/bcf
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifc4d
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifc5d
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifccityjson
@@ -118,7 +117,6 @@ For Linux or Mac:
$ ln -s $PWD/src/ifccsv/ifccsv.py $BLENDER_ADDON_PATH/libs/site/packages/ifccsv.py
$ ln -s $PWD/src/ifcdiff/ifcdiff.py $BLENDER_ADDON_PATH/libs/site/packages/ifcdiff.py
$ ln -s $PWD/src/bsdd/bsdd.py $BLENDER_ADDON_PATH/libs/site/packages/bsdd.py
$ ln -s $PWD/src/bcf/src/bcf $BLENDER_ADDON_PATH/libs/site/packages/bcf
$ ln -s $PWD/src/ifc4d/ifc4d $BLENDER_ADDON_PATH/libs/site/packages/ifc4d
$ ln -s $PWD/src/ifc5d/ifc5d $BLENDER_ADDON_PATH/libs/site/packages/ifc5d
$ ln -s $PWD/src/ifccityjson/ifccityjson $BLENDER_ADDON_PATH/libs/site/packages/ifccityjson
@@ -174,7 +172,6 @@ Before running it follow the instructions descibed after `rem` tags.
del "%blenderbim%\libs\site\packages\ifccsv.py"
del "%blenderbim%\libs\site\packages\ifcdiff.py"
del "%blenderbim%\libs\site\packages\bsdd.py"
rd /S /Q "%blenderbim%\libs\site\packages\bcf"
rd /S /Q "%blenderbim%\libs\site\packages\ifc4d"
rd /S /Q "%blenderbim%\libs\site\packages\ifc5d"
rd /S /Q "%blenderbim%\libs\site\packages\ifccityjson"
@@ -187,7 +184,6 @@ Before running it follow the instructions descibed after `rem` tags.
mklink "%blenderbim%\libs\site\packages\ifccsv.py" "%cd%\src\ifccsv\ifccsv.py"
mklink "%blenderbim%\libs\site\packages\ifcdiff.py" "%cd%\src\ifcdiff\ifcdiff.py"
mklink "%blenderbim%\libs\site\packages\bsdd.py" "%cd%\src\bsdd\bsdd.py"
mklink /D "%blenderbim%\libs\site\packages\bcf" "%cd%\src\bcf\src\bcf"
mklink /D "%blenderbim%\libs\site\packages\ifc4d" "%cd%\src\ifc4d\ifc4d"
mklink /D "%blenderbim%\libs\site\packages\ifc5d" "%cd%\src\ifc5d\ifc5d"
mklink /D "%blenderbim%\libs\site\packages\ifccityjson" "%cd%\src\ifccityjson\ifccityjson"
@@ -19,7 +19,6 @@
import bpy
import math
import ifcopenshell
import ifcopenshell.api
import blenderbim.core.tool
import blenderbim.tool as tool
from mathutils import Vector
@@ -59,6 +59,7 @@
#include <GeomAPI_ProjectPointOnSurf.hxx>
#include <Geom_Plane.hxx>
#include <IntTools_FaceFace.hxx>
#include <STEPConstruct_PointHasher.hxx>
#include "clash_utils.h"
#ifdef WITH_HDF5
@@ -67,20 +67,6 @@ def batching_argument_deprecation(
return (replace_usecase or usecase_path, settings)
def renamed_arguments_deprecation(
usecase_path: str, settings: dict, arguments_remapped: dict[str, str]
) -> tuple[str, dict]:
for prev_argument, new_argument in arguments_remapped.items():
if prev_argument in settings:
print(
f"WARNING. `{prev_argument}` argument is deprecated for API method "
f'"{usecase_path}" and should be replaced with `{new_argument}`.'
)
settings = settings | {new_argument: settings[prev_argument]}
settings.pop(prev_argument)
return (usecase_path, settings)
ARGUMENTS_DEPRECATION = {
"spatial.assign_container": partial(
batching_argument_deprecation, prev_argument="product", new_argument="products"
@@ -157,10 +143,6 @@ ARGUMENTS_DEPRECATION = {
"project.unassign_declaration": partial(
batching_argument_deprecation, prev_argument="definition", new_argument="definitions"
),
"group.add_group": partial(
renamed_arguments_deprecation, arguments_remapped={"Name": "name", "Description": "description"}
),
"layer.add_layer": partial(renamed_arguments_deprecation, arguments_remapped={"Name": "name"}),
}
@@ -342,18 +324,8 @@ def wrap_usecase(usecase_path, usecase):
try:
result = usecase(*args, **settings)
except NotImplementedError as e:
if not e.args[0].startswith(f"{usecase.__name__}()"):
# signature errors typically start with function name
# e.g. "TypeError: edit_library() got an unexpected keyword argument 'test'"
# otherwise it's an error inside api call and we shouldn't get in the way
raise e
msg = (
f"Incorrect function arguments provided for {usecase_path}\n{str(e)}. "
f"You specified args {args} and settings {settings}\n\n"
f"Correct signature is {inspect.signature(usecase)}\n"
f"See help(ifcopenshell.api.{usecase_path}) for documentation."
)
except TypeError as e:
msg = f"Incorrect function arguments provided for {usecase_path}\n{str(e)}. You specified args {args} and settings {settings}\n\nCorrect signature is {inspect.signature(usecase)}\nSee help(ifcopenshell.api.{usecase_path}) for documentation."
raise TypeError(msg) from e
if should_run_listeners:
@@ -89,6 +89,7 @@ def assign_connection_geometry(
usecase.axis = axis
usecase.ref_direction = ref_direction
usecase.unit_scale = unit_scale
usecase.ifc_vertices = []
return usecase.execute()
@@ -110,9 +110,7 @@ class Usecase:
"IfcCalendarDate", **ifcopenshell.util.date.datetime2ifc(edition_date, "IfcCalendarDate")
)
else:
if edition_date:
edition_date = ifcopenshell.util.date.datetime2ifc(edition_date, "IfcDate")
result.EditionDate = edition_date
result.EditionDate = ifcopenshell.util.date.datetime2ifc(edition_date, "IfcDate")
self.relate_to_project(result)
@@ -16,17 +16,8 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
from typing import Optional, Literal
def add_context(
file: ifcopenshell.file,
context_type: Optional[Literal["Model", "Plan"]] = None,
context_identifier: Optional[str] = None,
target_view: Optional[str] = None,
parent: Optional[ifcopenshell.entity_instance] = None,
) -> ifcopenshell.entity_instance:
def add_context(file, context_type=None, context_identifier=None, target_view=None, parent=None) -> None:
"""Adds a new geometric representation context
In IFC, physical objects may have zero, one, or multiple geometric
@@ -96,7 +87,7 @@ def add_context(
:param context_type: The type of the context, must be one of "Model" or
"Plan" only.
:type context_type: str, optional
:type context_type: str
:param context_identifier: The identifier of the context, chosen from
one of the common identifiers above or consult the IFC documentation
(under the IfcShapeRepresentation page) for more details. Optional
@@ -113,7 +104,7 @@ def add_context(
:type parent: ifcopenshell.entity_instance, optional
:return: the newly created IfcGeometricRepresentationContext or
IfcGeometricRepresentationSubContext entity
:rtype: ifcopenshell.entity_instance
:rtype: ifcopenshell.entity_instance, optional
Example:
@@ -16,11 +16,8 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
from typing import Any
def edit_context(file: ifcopenshell.file, context: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
def edit_context(file, context, attributes) -> None:
"""Edits the attributes of an IfcGeometricRepresentationContext
For more information about the attributes and data types of an
@@ -29,7 +26,7 @@ def edit_context(file: ifcopenshell.file, context: ifcopenshell.entity_instance,
:param context: The IfcGeometricRepresentationContext entity you want to edit
:type context: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values.
:type attributes: dict
:type attributes: dict, optional
:return: None
:rtype: None
@@ -47,7 +44,7 @@ def edit_context(file: ifcopenshell.file, context: ifcopenshell.entity_instance,
ifcopenshell.api.run("context.edit_context", model,
context=body, attributes={"ContextIdentifier": "Body"})
"""
settings = {"context": context, "attributes": attributes}
settings = {"context": context, "attributes": attributes or {}}
for name, value in settings["attributes"].items():
setattr(settings["context"], name, value)
@@ -22,9 +22,7 @@ from datetime import datetime
from typing import Optional
def add_cost_schedule(
file: ifcopenshell.file, name: Optional[str] = None, predefined_type: str = "NOTDEFINED"
) -> ifcopenshell.entity_instance:
def add_cost_schedule(file: ifcopenshell.file, name: Optional[str] = None, predefined_type="NOTDEFINED") -> None:
"""Add a new cost schedule
A cost schedule is a group of cost items which typically represent a
@@ -16,13 +16,13 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
from typing import Any
from typing import Any, Optional
def edit_information(
file: ifcopenshell.file,
information: ifcopenshell.entity_instance,
attributes: dict[str, Any],
attributes: Optional[dict[str, Any]] = None,
) -> None:
"""Edits the attributes of an IfcDocumentInformation
@@ -32,7 +32,7 @@ def edit_information(
:param reference: The IfcDocumentInformation entity you want to edit
:type reference: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values.
:type attributes: dict
:type attributes: dict, optional
:return: None
:rtype: None
@@ -46,7 +46,7 @@ def edit_information(
attributes={"Identification": "A-GA-6100", "Name": "Overall Plan",
"Location": "A-GA-6100 - Overall Plan.pdf"})
"""
settings = {"information": information, "attributes": attributes}
settings = {"information": information, "attributes": attributes or {}}
for name, value in settings["attributes"].items():
setattr(settings["information"], name, value)
@@ -16,13 +16,13 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
from typing import Any
from typing import Any, Optional
def edit_reference(
file: ifcopenshell.file,
reference: ifcopenshell.entity_instance,
attributes: dict[str, Any],
attributes: Optional[dict[str, Any]] = None,
) -> None:
"""Edits the attributes of an IfcDocumentReference
@@ -32,7 +32,7 @@ def edit_reference(
:param reference: The IfcDocumentReference entity you want to edit
:type reference: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values.
:type attributes: dict
:type attributes: dict, optional
:return: None
:rtype: None
@@ -49,7 +49,7 @@ def edit_reference(
ifcopenshell.api.run("document.edit_reference", model,
reference=reference, attributes={"Identification": "2.1.15"})
"""
settings = {"reference": reference, "attributes": attributes}
settings = {"reference": reference, "attributes": attributes or {}}
for name, value in settings["attributes"].items():
setattr(settings["reference"], name, value)
@@ -17,14 +17,9 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell.util.unit
from typing import Union
COORD = Union[tuple[float, float], tuple[float, float, float]]
def add_axis_representation(
file: ifcopenshell.file, context: ifcopenshell.entity_instance, axis: tuple[COORD, COORD]
) -> ifcopenshell.entity_instance:
def add_axis_representation(file, context=None, axis=None) -> None:
"""Adds a new axis representation
Certain objects are typically "axis-based", such as walls, beams,
@@ -16,51 +16,27 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import ifcopenshell.util.unit
import numpy as np
import numpy.typing as npt
from typing import Optional, TYPE_CHECKING, Literal
if TYPE_CHECKING:
import bpy.types
NPArrayOfFloats = npt.NDArray[np.float64]
def add_boolean(
file: ifcopenshell.file,
representation: ifcopenshell.entity_instance,
# A matrix to define a clipping Ifchalfspacesolid.
# The XY plane is the clipping boundary and +Z is removed.
operator: str = "DIFFERENCE",
# IfcHalfSpaceSolid, Mesh
type: Literal["IfcHalfSpaceSolid", "Mesh"] = "IfcHalfSpaceSolid",
matrix: Optional[NPArrayOfFloats] = None,
# A Blender OBJ to define the voided OBJ for a "Mesh" type
blender_obj: Optional[bpy.types.Object] = None,
# A Blender OBJ to define the void OBJ for a "Mesh" type
blender_void: Optional[bpy.types.Object] = None,
should_force_faceted_brep: bool = False,
should_force_triangulation: bool = False,
) -> list[ifcopenshell.entity_instance]:
"""For `type` values:
- "IfcHalfSpaceSolid" - `matrix` is not optional.
- "Mesh" - `blender_obj` and `blender_void` are not optional
"""
def add_boolean(file, **usecase_settings) -> None:
usecase = Usecase()
usecase.file = file
usecase.settings = {
"representation": representation,
"operator": operator,
"type": type,
"matrix": matrix,
"blender_obj": blender_obj,
"blender_void": blender_void,
"should_force_faceted_brep": should_force_faceted_brep,
"should_force_triangulation": should_force_triangulation,
"representation": None,
"operator": "DIFFERENCE",
# IfcHalfSpaceSolid, Mesh
"type": "IfcHalfSpaceSolid",
# The XY plane is the clipping boundary and +Z is removed.
"matrix": None, # A matrix to define a clipping Ifchalfspacesolid.
"blender_obj": None, # A Blender OBJ to define the voided OBJ for a "Mesh" type
"blender_void": None, # A Blender OBJ to define the void OBJ for a "Mesh" type
"should_force_faceted_brep": False,
"should_force_triangulation": False,
}
for key, value in usecase_settings.items():
usecase.settings[key] = value
return usecase.execute()
@@ -16,15 +16,13 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import collections.abc
import ifcopenshell.util.unit
from ifcopenshell.util.shape_builder import ShapeBuilder, V
from ifcopenshell.api.geometry.add_window_representation import create_ifc_window
from mathutils import Vector
from math import cos, radians
from typing import Any, Optional, Literal, Union
import dataclasses
import collections
SUPPORTED_DOOR_TYPES = (
@@ -40,14 +38,9 @@ SUPPORTED_DOOR_TYPES = (
)
def mm(x: float) -> float:
"""mm to meters shortcut for readability"""
return x / 1000
def create_ifc_door_lining(
builder: ShapeBuilder, size: Vector, thickness: list, position: Vector = V(0, 0, 0).freeze()
) -> ifcopenshell.entity_instance:
):
"""`thickness` of the profile is defined as list in the following order: `(SIDE, TOP)`
`thickness` can be also defined just as 1 float value.
@@ -76,212 +69,80 @@ def create_ifc_door_lining(
return door_lining
def create_ifc_box(
builder: ShapeBuilder, size: Vector, position: Vector = V(0, 0, 0).freeze()
) -> ifcopenshell.entity_instance:
def create_ifc_box(builder: ShapeBuilder, size: Vector, position: Vector = V(0, 0, 0).freeze()):
rect = builder.rectangle(size.xy)
box = builder.extrude(rect, size.z, position=position, extrusion_vector=V(0, 0, 1))
return box
# we use dataclass as we need default values for arguments
# it's okay to use slots since we don't need dynamic attributes
@dataclasses.dataclass(slots=True)
class DoorLiningProperties:
LiningDepth: Optional[float] = None
"""Optional, defaults to 50mm."""
LiningThickness: Optional[float] = None
"""Optional, defaults to 50mm."""
LiningOffset: Optional[float] = None
"""Offset from the outer side of the wall (by Y-axis). Optional, defaults to 0.0."""
LiningToPanelOffsetX: Optional[float] = None
"""Offset from the wall. Optional, defaults to 25mm."""
LiningToPanelOffsetY: Optional[float] = None
"""Offset from the X-axis (unlike windows). Optional, defaults to 25mm."""
TransomThickness: Optional[float] = None
"""Vertical distance between door and window panels. Optional, defaults to 0.0."""
TransomOffset: Optional[float] = None
"""Distance from the bottom door opening
to the beginning of the transom
unlike windows TransomOffset which goes to the center of the transom.
Optional, defaults 1.525m."""
ShapeAspectStyle: None = None
"""Optional. Deprecated argument."""
CasingDepth: Optional[float] = None
"""Casing cover wall faces around the opening
on the left, right and upper sides
Casing should be either on both sides of the wall or no casing
If `LiningOffset` is present then therefore casing is not possible on outer wall
therefore there will be no casing on inner wall either. Optional, defaults to 5mm."""
CasingThickness: Optional[float] = None
"""Casing thickness by Z-axis. Optional, defaults to 75mm."""
ThresholdDepth: Optional[float] = None
"""Threshold covers the bottom side of the opening. Optional, defaults to 100mm."""
ThresholdThickness: Optional[float] = None
"""Theshold thickness by Z-axis. Optional, defaults to 25mm."""
ThresholdOffset: Optional[float] = None
"""Threshold offset by Y-axis. Optional, defaults to 0.0."""
def initialize_properties(self, unit_scale: float) -> None:
# in meters
# fmt: off
default_values: dict[str, float] = dict(
LiningDepth = mm(50),
LiningThickness = mm(50),
LiningOffset = 0.0,
LiningToPanelOffsetX = mm(25),
LiningToPanelOffsetY = mm(25),
TransomThickness = 0.0,
TransomOffset = mm(1525),
CasingDepth = mm(5),
CasingThickness = mm(75),
ThresholdDepth = mm(100),
ThresholdThickness = mm(25),
ThresholdOffset = 0.0,
)
# fmt: on
si_conversion = 1 / unit_scale
for attr, default_value in default_values.items():
if getattr(self, attr) is not None:
continue
setattr(self, attr, default_value * si_conversion)
@dataclasses.dataclass(slots=True)
class DoorPanelProperties:
PanelDepth: Optional[float] = None
"""Frame thickness by Y axis. Optional, defaults to 35 mm."""
PanelWidth: float = 1.0
"""Ratio to the clear door opening. Optional, defaults to 1.0."""
FrameDepth: Optional[float] = None
"""Frame thickness by Y axis. Optional, defaults to 35 mm."""
FrameThickness: Optional[float] = None
"""Frame thickness by X axis. Optional, defaults to 35 mm."""
PanelPosition: None = None
"""Optional, value is never used"""
PanelOperation: None = None
"""Optional, value is never used.
Defines the basic ways to describe how door panels operate."""
ShapeAspectStyle: None = None
"""Optional. Deprecated argument."""
def initialize_properties(self, unit_scale: float) -> None:
# in meters
# fmt: off
default_values: dict[str, float] = dict(
PanelDepth = mm(35),
FrameDepth = mm(35),
FrameThickness = mm(35),
)
# fmt: on
si_conversion = 1 / unit_scale
for attr, default_value in default_values.items():
if getattr(self, attr) is not None:
continue
setattr(self, attr, default_value * si_conversion)
def add_door_representation(
file: ifcopenshell.file,
*, # keywords only as this API implementation is probably not final
context: ifcopenshell.entity_instance,
overall_height: Optional[float] = None,
overall_width: Optional[float] = None,
# door type
# http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcDoorTypeOperationEnum.htm
operation_type: Literal[
"SINGLE_SWING_LEFT",
"SINGLE_SWING_RIGHT",
"DOUBLE_SWING_RIGHT",
"DOUBLE_SWING_LEFT",
"DOUBLE_DOOR_SINGLE_SWING",
"DOUBLE_DOOR_DOUBLE_SWING",
"SLIDING_TO_LEFT",
"SLIDING_TO_RIGHT",
"DOUBLE_DOOR_SLIDING",
] = "SINGLE_SWING_LEFT",
lining_properties: Optional[Union[DoorLiningProperties, dict[str, Any]]] = None,
panel_properties: Optional[Union[DoorPanelProperties, dict[str, Any]]] = None,
unit_scale: Optional[float] = None,
) -> ifcopenshell.entity_instance:
"""units in usecase_settings expected to be in ifc project units
:param context: IfcGeometricRepresentationContext for the representation.
:type context: ifcopenshell.entity_instance
:param overall_height: Overall door height. Defaults to 2m.
:type overall_height: float, optional
:param overall_width: Overall door width. Defaults to 0.9m.
:type overall_width: float, optional
:param operation_type: Type of the door. Defaults to SINGLE_SWING_LEFT.
:type operation_type: str, optional
:param lining_properties: DoorLiningProperties or a dictionary to create one.
See DoorLiningProperties description for details.
:type lining_properties: Union[DoorLiningProperties, dict[str, Any]]]
:param panel_properties: DoorPanelProperties or a dictionary to create one.
See DoorPanelProperties description for details.
:type panel_properties: Union[DoorPanelProperties, dict[str, Any]]]
:param unit_scale: The unit scale as calculated by
ifcopenshell.util.unit.calculate_unit_scale. If not provided, it
will be automatically calculated for you.
:type unit_scale: float, optional
:return: IfcShapeRepresentation for a door.
:rtype: ifcopenshell.entity_instance
"""
def add_door_representation(file, **usecase_settings) -> None:
"""units in usecase_settings expected to be in ifc project units"""
usecase = Usecase()
usecase.file = file
# http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcDoor.htm
# http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcDoorTypeOperationEnum.htm
# http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcDoorLiningProperties.htm
# http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcDoorPanelProperties.htm
# define unit_scale first as it's going to be used setting default arguments
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file) if unit_scale is None else unit_scale
settings: dict[str, Any] = {"unit_scale": unit_scale}
if lining_properties is None:
lining_properties = DoorLiningProperties()
elif not isinstance(lining_properties, DoorLiningProperties):
lining_properties = DoorLiningProperties(**lining_properties)
lining_properties.initialize_properties(unit_scale)
lining_properties = dataclasses.asdict(lining_properties)
if panel_properties is None:
panel_properties = DoorPanelProperties()
elif not isinstance(panel_properties, DoorPanelProperties):
panel_properties = DoorPanelProperties(**panel_properties)
panel_properties.initialize_properties(unit_scale)
panel_properties = dataclasses.asdict(panel_properties)
settings.update(
usecase.settings = {"unit_scale": ifcopenshell.util.unit.calculate_unit_scale(usecase.file)}
usecase.settings.update(
{
"context": context,
"overall_height": overall_height if overall_height is not None else usecase.convert_si_to_unit(2.0),
"overall_width": overall_width if overall_width is not None else usecase.convert_si_to_unit(0.9),
"operation_type": operation_type,
"lining_properties": lining_properties,
"panel_properties": panel_properties,
"context": None, # IfcGeometricRepresentationContext
"overall_height": usecase.convert_si_to_unit(2.0),
"overall_width": usecase.convert_si_to_unit(0.9),
# DOUBLE_DOOR_DOUBLE_SWING, DOUBLE_DOOR_FOLDING, DOUBLE_DOOR_LIFTING_VERTICAL,
# DOUBLE_DOOR_SINGLE_SWING, DOUBLE_DOOR_SINGLE_SWING_OPPOSITE_LEFT,
# DOUBLE_DOOR_SINGLE_SWING_OPPOSITE_RIGHT, DOUBLE_DOOR_SLIDING,
# DOUBLE_SWING_LEFT, DOUBLE_SWING_RIGHT, FOLDING_TO_LEFT,
# FOLDING_TO_RIGHT, LIFTING_HORIZONTAL, LIFTING_VERTICAL_LEFT,
# LIFTING_VERTICAL_RIGHT, REVOLVING, REVOLVING_VERTICAL,
# ROLLINGUP, SINGLE_SWING_LEFT, SINGLE_SWING_RIGHT, SLIDING_TO_LEFT,
# SLIDING_TO_RIGHT, SWING_FIXED_LEFT, SWING_FIXED_RIGHT
"operation_type": "SINGLE_SWING_LEFT", # door type
"lining_properties": {
"LiningDepth": usecase.convert_si_to_unit(0.050),
"LiningThickness": usecase.convert_si_to_unit(0.050),
# offset from the outer side of the wall (by Y-axis)
"LiningOffset": usecase.convert_si_to_unit(0.0),
# offset from the wall
"LiningToPanelOffsetX": usecase.convert_si_to_unit(0.025),
# offset from the X-axis (unlike windows)
"LiningToPanelOffsetY": usecase.convert_si_to_unit(0.025),
# transom - vertical distance between door and window panels
"TransomThickness": usecase.convert_si_to_unit(0.000),
# TransomOffset - distance from the bottom door opening
# to the beginning of the transom
# unlike windows TransomOffset which goes to the center of the transom
"TransomOffset": usecase.convert_si_to_unit(1.525),
"ShapeAspectStyle": None, # DEPRECATED
# Casing cover wall faces around the opening
# on the left, right and upper sides
# Casing should be either on both sides of the wall or no casing
# If `LiningOffset` is present then therefore casing is not possible on outer wall
# therefore there will be no casing on inner wall either
"CasingDepth": usecase.convert_si_to_unit(0.005),
"CasingThickness": usecase.convert_si_to_unit(0.075), # by Z-axis
# Threshold covers the bottom side of the opening
"ThresholdDepth": usecase.convert_si_to_unit(0.1),
"ThresholdThickness": usecase.convert_si_to_unit(0.025), # by Z-axis
# offset by Y-axis
"ThresholdOffset": usecase.convert_si_to_unit(0.000),
},
"panel_properties": {
"PanelDepth": usecase.convert_si_to_unit(0.035), # by Y
"PanelWidth": 1.0, # as ratio to the clear door opening
"FrameDepth": usecase.convert_si_to_unit(0.035), # by Y
"FrameThickness": usecase.convert_si_to_unit(0.035), # by X
# LEFT, MIDDLE, RIGHT, NOTDEFINED
"PanelPosition": ..., # NEVER USED
# defines the basic ways to describe how door panels operate
# basically how it opens
"PanelOperation": None, # NEVER USED
"ShapeAspectStyle": None, # DEPRECATED
},
}
)
usecase.settings = settings
for key, value in usecase_settings.items():
usecase.settings[key] = value
return usecase.execute()
@@ -19,17 +19,13 @@
import ifcopenshell.util.unit
def add_footprint_representation(
file,
# IfcGeometricRepresentationContext
context: ifcopenshell.entity_instance,
# A list of IFC curves to include in the curve set
curves: list[ifcopenshell.entity_instance],
) -> ifcopenshell.entity_instance:
def add_footprint_representation(file, **usecase_settings) -> None:
settings = {
"context": context,
"curves": curves,
"context": None, # IfcGeometricRepresentationContext
"curves": [], # A list of IFC curves to include in the curve set
}
for key, value in usecase_settings.items():
settings[key] = value
return file.createIfcShapeRepresentation(
settings["context"],
@@ -17,43 +17,26 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell.util.unit
from typing import Optional
COORD_3D = tuple[float, float, float]
def add_mesh_representation(
file: ifcopenshell.file,
# IfcGeometricRepresentationContext
context: ifcopenshell.entity_instance,
# Vertices, edges, and faces are given in the form of: [item1, item2, item3, ...]
# A list of coordinates
# ... where itemN = [(0., 0., 0.), (1., 1., 1.), (x, y, z), ...]
vertices: list[COORD_3D],
# A list of edges, represented by vertex index pairs
# ... where itemN = [(0, 1), (1, 2), (v1, v2), ...]
edges: list[tuple[int, int]],
# A list of polygons, represented by vertex indices
# ... where itemN = [(0, 1, 2), (5, 4, 2, 3), (v1, v2, v3, ... vN), ...]
faces: list[list[int]],
# Optionally apply a vector offset to all coordinates
cooridnate_offset: Optional[COORD_3D] = None,
# A scale factor to apply for all vectors in case the unit is different
unit_scale: Optional[float] = None,
# Force using IfcFacetedBreps instead of IfcPolygonalFaceSets
force_faceted_brep: bool = False,
) -> ifcopenshell.entity_instance:
def add_mesh_representation(file: ifcopenshell.file, **usecase_settings) -> None:
usecase = Usecase()
usecase.file = file
usecase.settings = {
"context": context,
"vertices": vertices,
"edges": edges,
"faces": faces,
"coordinate_offset": cooridnate_offset,
"unit_scale": unit_scale,
"force_faceted_brep": force_faceted_brep,
"context": None, # IfcGeometricRepresentationContext
# Vertices, edges, and faces are given in the form of: [item1, item2, item3, ...]
# ... where itemN = [(0., 0., 0.), (1., 1., 1.), (x, y, z), ...]
"vertices": None, # A list of coordinates
# ... where itemN = [(0, 1), (1, 2), (v1, v2), ...]
"edges": None, # A list of edges, represented by vertex index pairs
# ... where itemN = [(0, 1, 2), (5, 4, 2, 3), (v1, v2, v3, ... vN), ...]
"faces": None, # A list of polygons, represented by vertex indices
"coordinate_offset": None, # Optionally apply a vector offset to all coordinates
"unit_scale": None, # A scale factor to apply for all vectors in case the unit is different
"force_faceted_brep": False, # Force using IfcFacetedBreps instead of IfcPolygonalFaceSets
}
for key, value in usecase_settings.items():
usecase.settings[key] = value
return usecase.execute()
@@ -19,35 +19,23 @@
import ifcopenshell.geom
import ifcopenshell.util.unit
from ifcopenshell.util.data import Clipping
from typing import Any, Union, Optional, Literal
VECTOR_3D = tuple[float, float, float]
def add_profile_representation(
file: ifcopenshell.file,
# IfcGeometricRepresentationContext
context: ifcopenshell.entity_instance,
profile: ifcopenshell.entity_instance,
# in meters
depth: float = 1.0,
cardinal_point: Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] = 5,
# A list of planes that define clipping half space solids
# Planes are defined either by Clipping objects
# or by dictionaries of arguments for `Clipping.parse`
clippings: Optional[list[Union[Clipping, dict[str, Any]]]] = None,
placement_zx_axes: tuple[Union[VECTOR_3D, None], Union[VECTOR_3D, None]] = (None, None),
) -> None:
def add_profile_representation(file, **usecase_settings) -> None:
usecase = Usecase()
usecase.file = file
usecase.settings = {
"context": context,
"profile": profile,
"depth": depth,
"cardinal_point": cardinal_point,
"clippings": clippings if clippings is not None else [],
"placement_zx_axes": placement_zx_axes,
"context": None, # IfcGeometricRepresentationContext
"profile": None,
"depth": 1.0,
"cardinal_point": 5,
# Planes are defined either by Clipping objects
# or by dictionaries of arguments for `Clipping.parse`
"clippings": [], # A list of planes that define clipping half space solids
"placement_zx_axes": (None, None),
}
for key, value in usecase_settings.items():
usecase.settings[key] = value
return usecase.execute()
@@ -22,100 +22,46 @@ from itertools import chain
from mathutils import Vector, Matrix
import collections
import mathutils
from pprint import pprint
from math import pi, cos, sin, tan, radians
from typing import Literal, Optional, Any
def mm(x: float) -> float:
def mm(x):
"""mm to meters shortcut for readability"""
return x / 1000
def add_railing_representation(
file: ifcopenshell.file,
*, # keywords only as this API implementation is probably not final
# IfcGeometricRepresentationContext
context: ifcopenshell.entity_instance,
railing_type: Literal["WALL_MOUNTED_HANDRAIL"] = "WALL_MOUNTED_HANDRAIL",
railing_path: list[Vector],
use_manual_supports: bool = False,
support_spacing: Optional[float] = None,
railing_diameter: Optional[float] = None,
clear_width: Optional[float] = None,
terminal_type: Literal[
"180",
"TO_END_POST",
"TO_WALL",
"TO_FLOOR",
"TO_END_POST_AND_FLOOR",
] = "180",
height: Optional[float] = None,
looped_path: bool = False,
unit_scale: Optional[float] = None,
) -> ifcopenshell.entity_instance:
def add_railing_representation(file, **usecase_settings) -> None:
"""
Units are expected to be in IFC project units.
units in usecase_settings expected to be in ifc project units
:param context: IfcGeometricRepresentationContext for the representation.
:type context: ifcopenshell.entity_instance
:param railing_type: Type of the railing. Defaults to "WALL_MOUNTED_HANDRAIL".
:type railing_type: Literal["WALL_MOUNTED_HANDRAIL"], optional
:param railing_path: A list of points coordinates for the railing path,
coordinates are expected to be at the top of the railing, not at the center.
If not provided, default path [(0, 0, 1), (1, 0, 1), (2, 0, 1)] (in meters) will be used
:type railing_path: list[Vector], optional.
:param use_manual_supports: If enabled, supports are added on every vertex on the edges of the railing path.
If disabled, supports are added automatically based on the support spacing. Default to False.
:type use_manual_supports: bool, optional
:param support_spacing: Distance between supports if automatic supports are used. Defaults to 1m.
:type support_spacing: float, optional
:param railing_diameter: Railing diameter. Defaults to 50mm.
:type railing_diameter: float, optional
:param clear_width: Clear width between the railing and the wall. Defaults to 40mm.
:type clear_width: float, optional
:param terminal_type: type of the cap. Defaults to "180".
:type terminal_type: Literal["180","TO_END_POST","TO_WALL","TO_FLOOR","TO_END_POST_AND_FLOOR"], optional
:param height: defaults to 1m
:type height: float, optional
:param looped_path: Whether to end the railing on the first point of `railing_path`. Defaults to False.
:type looped_path: bool, optional
:param unit_scale: The unit scale as calculated by
ifcopenshell.util.unit.calculate_unit_scale. If not provided, it
will be automatically calculated for you.
:type unit_scale: float, optional
:return: IfcShapeRepresentation for a railing.
:rtype: ifcopenshell.entity_instance
`railing_path` is a list of point coordinates for the railing path,
coordinates are expected to be at the top of the railing, not at the center
`railing_path` is expected to be a list of Vector objects
"""
usecase = Usecase()
usecase.file = file
# define unit_scale first as it's going to be used setting default arguments
settings: dict[str, Any] = {
"unit_scale": ifcopenshell.util.unit.calculate_unit_scale(file) if unit_scale is None else unit_scale,
}
settings.update(
usecase.settings = {"unit_scale": ifcopenshell.util.unit.calculate_unit_scale(usecase.file)}
usecase.settings.update(
{
"context": context,
"railing_type": railing_path,
"railing_path": (
railing_path
if railing_path is not None
else usecase.path_si_to_units([V(0, 0, 1), V(1, 0, 1), V(2, 0, 1)])
),
"use_manual_supports": use_manual_supports,
"support_spacing": support_spacing if support_spacing is not None else usecase.convert_si_to_unit(mm(1000)),
"railing_diameter": (
railing_diameter if railing_diameter is not None else usecase.convert_si_to_unit(mm(50))
),
"clear_width": clear_width if clear_width is not None else usecase.convert_si_to_unit(mm(40)),
"terminal_type": terminal_type,
"height": height if height is not None else usecase.convert_si_to_unit(mm(1000)),
"looped_path": looped_path,
"context": None, # IfcGeometricRepresentationContext
"railing_type": "WALL_MOUNTED_HANDRAIL",
"railing_path": usecase.path_si_to_units([V(0, 0, 1), V(1, 0, 1), V(2, 0, 1)]),
"use_manual_supports": False,
"support_spacing": usecase.convert_si_to_unit(mm(1000)),
"railing_diameter": usecase.convert_si_to_unit(mm(50)),
"clear_width": usecase.convert_si_to_unit(mm(40)),
"terminal_type": "180",
"height": usecase.convert_si_to_unit(mm(1000)),
"looped_path": False,
}
)
usecase.settings = settings
if railing_type != "WALL_MOUNTED_HANDRAIL":
for key, value in usecase_settings.items():
usecase.settings[key] = value
if usecase.settings["railing_type"] != "WALL_MOUNTED_HANDRAIL":
raise Exception('Only "WALL_MOUNTED_HANDRAIL" railing_type is supported at the moment.')
return usecase.execute()
@@ -16,12 +16,11 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import bpy.types
import bpy
import math
import bmesh
import ifcopenshell.util.unit
from mathutils import Vector, Matrix
from typing import Union, Optional, Literal, Any
Z_AXIS = Vector((0, 0, 1))
@@ -29,44 +28,7 @@ X_AXIS = Vector((1, 0, 0))
EPSILON = 1e-6
def add_representation(
file: ifcopenshell.file,
*, # keywords only as this API implementation is probably not final
# IfcGeometricRepresentationContext
context: ifcopenshell.entity_instance,
# This is (currently) a Blender object, hence this depends on Blender now
blender_object: bpy.types.Object,
# This is (currently) a Blender data object, hence this depends on Blender now
geometry: Union[bpy.types.Mesh, bpy.types.Curve],
# Optionally apply a vector offset to all coordinates
coordinate_offset: Optional[Vector] = None,
# How many representation items to create
total_items: int = 1,
# A scale factor to apply for all vectors in case the unit is different
unit_scale: Optional[float] = None,
# If we should force faceted breps for meshes
should_force_faceted_brep: bool = False,
# If we should force triangulation for meshes
should_force_triangulation: bool = False,
# If UV coordinates should also be generated
should_generate_uvs: bool = False,
# Whether to cast a mesh into a particular class
ifc_representation_class: Optional[
Literal[
"IfcExtrudedAreaSolid/IfcRectangleProfileDef",
"IfcExtrudedAreaSolid/IfcCircleProfileDef",
"IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef",
"IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids",
"IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage",
"IfcGeometricCurveSet/IfcTextLiteral",
"IfcTextLiteral",
]
] = None,
# The material profile set if the extrusion requires it
profile_set_usage: Optional[ifcopenshell.entity_instance] = None,
# The text literal if the representation requires it
text_literal: Optional[ifcopenshell.entity_instance] = None,
) -> ifcopenshell.entity_instance:
def add_representation(file: ifcopenshell.file, **usecase_settings) -> ifcopenshell.entity_instance:
# lazy import Helper to avoid circular import
if "Helper" not in globals():
from blenderbim.bim.module.geometry.helper import Helper
@@ -75,27 +37,34 @@ def add_representation(
# TODO: This usecase currently depends on Blender's data model
usecase.file = file
usecase.settings = {
"context": context,
"blender_object": blender_object,
"geometry": geometry,
"coordinate_offset": coordinate_offset,
"total_items": total_items,
"unit_scale": unit_scale,
"should_force_faceted_brep": should_force_faceted_brep,
"should_force_triangulation": should_force_triangulation,
"should_generate_uvs": should_generate_uvs,
"ifc_representation_class": ifc_representation_class,
"profile_set_usage": profile_set_usage,
"text_literal": text_literal,
"context": None, # IfcGeometricRepresentationContext
"blender_object": None, # This is (currently) a Blender object, hence this depends on Blender now
"geometry": None, # This is (currently) a Blender data object, hence this depends on Blender now
"coordinate_offset": None, # Optionally apply a vector offset to all coordinates
"total_items": 1, # How many representation items to create
"unit_scale": None, # A scale factor to apply for all vectors in case the unit is different
"should_force_faceted_brep": False, # If we should force faceted breps for meshes
"should_force_triangulation": False, # If we should force triangulation for meshes
"should_generate_uvs": False, # If UV coordinates should also be generated
# Possible IFC representation classes:
# IfcExtrudedAreaSolid/IfcRectangleProfileDef
# IfcExtrudedAreaSolid/IfcCircleProfileDef
# IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef
# IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids
# IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage
# IfcGeometricCurveSet/IfcTextLiteral
# IfcTextLiteral
"ifc_representation_class": None, # Whether to cast a mesh into a particular class
"profile_set_usage": None, # The material profile set if the extrusion requires it
"text_literal": None, # The text literal if the representation requires it
}
usecase.ifc_vertices = []
for key, value in usecase_settings.items():
usecase.settings[key] = value
return usecase.execute()
class Usecase:
file: ifcopenshell.file
settings: dict[str, Any]
def execute(self):
self.is_manifold = None
if (
@@ -504,10 +473,11 @@ class Usecase:
geom_data = self.settings["geometry"]
if isinstance(geom_data, bpy.types.Mesh):
if self.is_mesh_curve_consecutive(geom_data):
if self.file.schema == "IFC2X3":
return self.create_curves_from_mesh_ifc2x3(should_exclude_faces=should_exclude_faces, is_2d=is_2d)
return self.create_curves_from_mesh(should_exclude_faces=should_exclude_faces, is_2d=is_2d)
if not self.is_mesh_curve_consecutive(geom_data):
return
if self.file.schema == "IFC2X3":
return self.create_curves_from_mesh_ifc2x3(should_exclude_faces=should_exclude_faces, is_2d=is_2d)
return self.create_curves_from_mesh(should_exclude_faces=should_exclude_faces, is_2d=is_2d)
import blenderbim.tool as tool
@@ -17,32 +17,22 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell.util.unit
from ifcopenshell.util.data import Clipping
from math import sin, cos
from typing import Any, Optional, Union
def add_slab_representation(
file,
# IfcGeometricRepresentationContext
context: ifcopenshell.entity_instance,
# in meters
depth: float = 0.2,
# in radians
x_angle: float = 0.0,
# A list of planes that define clipping half space solids
# Planes are defined either by Clipping objects
# or by dictionaries of arguments for `Clipping.parse`
clippings: Optional[list[Union[Clipping, dict[str, Any]]]] = None,
) -> ifcopenshell.entity_instance:
def add_slab_representation(file, **usecase_settings) -> None:
usecase = Usecase()
usecase.file = file
usecase.settings = {
"context": context,
"depth": depth,
"x_angle": x_angle,
"clippings": clippings if clippings is not None else [],
"context": None, # IfcGeometricRepresentationContext
"depth": 0.2,
"x_angle": 0, # Radians
# Planes are defined either by Clipping objects
# or by dictionaries of arguments for `Clipping.parse`
"clippings": [], # A list of planes that define clipping half space solids
}
for key, value in usecase_settings.items():
usecase.settings[key] = value
return usecase.execute()
@@ -18,39 +18,27 @@
import ifcopenshell.util.unit
from math import sin, cos
from typing import Optional, Union, Any
from ifcopenshell.util.data import Clipping
def add_wall_representation(
file: ifcopenshell.file,
context: ifcopenshell.entity_instance, # IfcGeometricRepresentationContext
# all lengths are in meters
length: float = 1.0,
height: float = 3.0,
offset: float = 0.0,
thickness: float = 0.2,
# Sloped walls along the wall's X axis, provided in radians
x_angle: float = 0.0,
# A list of planes that define clipping half space solids
# Planes are defined either by Clipping objects
# or by dictionaries of arguments for `Clipping.parse`
clippings: Optional[list[Union[Clipping, dict[str, Any]]]] = None,
# Any existing IfcBooleanResults
booleans: Optional[list[ifcopenshell.entity_instance]] = None,
) -> ifcopenshell.entity_instance:
def add_wall_representation(file, **usecase_settings) -> None:
usecase = Usecase()
usecase.file = file
usecase.settings = {
"context": context,
"length": length,
"height": height,
"offset": offset,
"thickness": thickness,
"x_angle": x_angle,
"clippings": clippings if clippings is not None else [],
"booleans": booleans if booleans is not None else [],
"context": None, # IfcGeometricRepresentationContext
"length": 1.0,
"height": 3.0,
"offset": 0.0,
"thickness": 0.2,
# Sloped walls along the wall's X axis, provided in radians
"x_angle": 0,
# Planes are defined either by Clipping objects
# or by dictionaries of arguments for `Clipping.parse`
"clippings": [], # A list of planes that define clipping half space solids
"booleans": [], # Any existing IfcBooleanResults
}
for key, value in usecase_settings.items():
usecase.settings[key] = value
return usecase.execute()
@@ -16,14 +16,11 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import collections.abc
import ifcopenshell.util.unit
from ifcopenshell.util.shape_builder import ShapeBuilder, V
from itertools import chain
from mathutils import Vector
import dataclasses
from typing import Any, Optional, Literal, Union
import collections
# SCHEMAS describe panels setup
@@ -45,11 +42,6 @@ DEFAULT_PANEL_SCHEMAS = {
}
def mm(x: float) -> float:
"""mm to meters shortcut for readability"""
return x / 1000
def create_ifc_window_frame_simple(
builder: ShapeBuilder, size: Vector, thickness: list, position: Vector = V(0, 0, 0).freeze()
):
@@ -218,209 +210,71 @@ def create_ifc_window(
return output_items
# we use dataclass as we need default values for arguments
# it's okay to use slots since we don't need dynamic attributes
@dataclasses.dataclass(slots=True)
class WindowLiningProperties:
LiningDepth: Optional[float] = None
"""Optional, defaults to 50mm."""
LiningThickness: Optional[float] = None
"""Optional, defaults to 50mm."""
LiningOffset: Optional[float] = None
"""Offset to the wall. Optional, defaults to 50mm."""
LiningToPanelOffsetX: Optional[float] = None
"""Offset from the wall. Optional, defaults to 25mm."""
# that way it allows you to define overall_depth constant between all panels
# and still have panels with different size:
# overall_depth = lining_depth + offset_y
# full offset from X axis = overall_depth - frame_depth.
LiningToPanelOffsetY: Optional[float] = None
"""Offset from the lining. Optional, defaults to 25mm."""
MullionThickness: Optional[float] = None
"""Mullion thickness (horizontal distance between panels).
Applies to windows of types: DoublePanelVertical, TriplePanelBottom, TriplePanelTop,
TriplePanelLeft, TriplePanelRight.
Optional, defaults to 50mm."""
FirstMullionOffset: Optional[float] = None
"""Distance from the first lining to the mullion center. Optional, defaults to 300mm."""
SecondMullionOffset: Optional[float] = None
"""Distance from the first lining to the second mullion center.
Applies to windows of type: TriplePanelVertical.
Optional, defaults to 450mm."""
TransomThickness: Optional[float] = None
"""Transom thickness (vertical distance between panels), works similar way to mullions.
Applies to windows of types:DoublePanelHorizontal, TriplePanelBottom, TriplePanelTop,
TriplePanelLeft, TriplePanelRight.
Optional, defaults to 50mm."""
FirstTransomOffset: Optional[float] = None
"""Optional, defaults to 300mm."""
SecondTransomOffset: Optional[float] = None
"""
Applies to windows of type: TriplePanelHorizontal.
Optional, defaults to 600mm."""
ShapeAspectStyle: None = None
"""Optional. Deprecated argument."""
def initialize_properties(self, unit_scale: float) -> None:
# in meters
# fmt: off
default_values: dict[str, float] = dict(
LiningDepth = mm(50),
LiningThickness = mm(50),
LiningOffset = mm(50),
LiningToPanelOffsetX = mm(25),
LiningToPanelOffsetY = mm(25),
MullionThickness = mm(50),
FirstMullionOffset = mm(300),
SecondMullionOffset = mm(450),
TransomThickness = mm(50),
FirstTransomOffset = mm(300),
SecondTransomOffset = mm(600),
)
# fmt: on
si_conversion = 1 / unit_scale
for attr, default_value in default_values.items():
if getattr(self, attr) is not None:
continue
setattr(self, attr, default_value * si_conversion)
@dataclasses.dataclass(slots=True)
class WindowPanelProperties:
FrameDepth: Optional[float] = None
"""Frame thickness by Y axis. Optional, defaults to 35 mm."""
FrameThickness: Optional[float] = None
"""Frame thickness by X axis. Optional, defaults to 35 mm."""
PanelPosition: None = None
"""Optional, value is never used"""
PanelOperation: None = None
"""Optional, value is never used.
Defines the basic ways to describe how window panels operate."""
ShapeAspectStyle: None = None
"""Optional. Deprecated argument."""
def initialize_properties(self, unit_scale: float) -> None:
# in meters
# fmt: off
default_values: dict[str, float] = dict(
FrameDepth = mm(35),
FrameThickness = mm(35),
)
# fmt: on
si_conversion = 1 / unit_scale
for attr, default_value in default_values.items():
if getattr(self, attr) is not None:
continue
setattr(self, attr, default_value * si_conversion)
def add_window_representation(
file: ifcopenshell.file,
*, # keywords only as this API implementation is probably not final
context: ifcopenshell.entity_instance,
overall_height: Optional[float] = None,
overall_width: Optional[float] = None,
partition_type: Literal[
"SINGLE_PANEL",
"DOUBLE_PANEL_HORIZONTAL",
"DOUBLE_PANEL_VERTICAL",
"TRIPLE_PANEL_BOTTOM",
"TRIPLE_PANEL_HORIZONTAL",
"TRIPLE_PANEL_LEFT",
"TRIPLE_PANEL_RIGHT",
"TRIPLE_PANEL_TOP",
"TRIPLE_PANEL_VERTICAL",
] = "SINGLE_PANEL",
lining_properties: Optional[Union[WindowLiningProperties, dict[str, Any]]] = None,
panel_properties: Optional[list[Union[WindowPanelProperties, dict[str, Any]]]] = None,
unit_scale: Optional[float] = None,
) -> ifcopenshell.entity_instance:
"""units in usecase_settings expected to be in ifc project units
:param context: IfcGeometricRepresentationContext for the representation.
:type context: ifcopenshell.entity_instance
:param overall_height: Overall window height. Defaults to 0.9m.
:type overall_height: float, optional
:param overall_width: Overall window width. Defaults to 0.6m.
:type overall_width: float, optional
:param partition_type: Type of the window. Defaults to SINGLE_PANEL.
:type partition_type: str, optional
:param lining_properties: WindowLiningProperties or a dictionary to create one.
See WindowLiningProperties description for details.
:type lining_properties: Union[WindowLiningProperties, dict[str, Any]]]
:param panel_properties: A list of WindowPanelProperties or dictionaries to create one.
See WindowPanelProperties description for details.
:type panel_properties: list[Union[WindowPanelProperties, dict[str, Any]]]]
:param unit_scale: The unit scale as calculated by
ifcopenshell.util.unit.calculate_unit_scale. If not provided, it
will be automatically calculated for you.
:type unit_scale: float, optional
:return: IfcShapeRepresentation for a window.
:rtype: ifcopenshell.entity_instance
"""
def add_window_representation(file, **usecase_settings) -> None:
"""units in usecase_settings expected to be in ifc project units"""
usecase = Usecase()
usecase.file = file
# http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcWindow.htm
# http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcWindowTypePartitioningEnum.htm
# http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcWindowLiningProperties.htm
# http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcWindowPanelProperties.htm
# define unit_scale first as it's going to be used setting default arguments
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file) if unit_scale is None else unit_scale
settings: dict[str, Any] = {"unit_scale": unit_scale}
if lining_properties is None:
lining_properties = WindowLiningProperties()
elif not isinstance(lining_properties, WindowLiningProperties):
lining_properties = WindowLiningProperties(**lining_properties)
lining_properties.initialize_properties(unit_scale)
lining_properties = dataclasses.asdict(lining_properties)
if panel_properties is None:
panel_properties = [WindowPanelProperties()]
for i in range(len(panel_properties)):
properties = panel_properties[i]
if not isinstance(properties, WindowPanelProperties):
properties = WindowPanelProperties(**properties)
properties.initialize_properties(unit_scale)
panel_properties[i] = dataclasses.asdict(properties)
settings.update(
usecase.settings = {"unit_scale": ifcopenshell.util.unit.calculate_unit_scale(usecase.file)}
usecase.settings.update(
{
"context": context,
"overall_height": overall_height if overall_height is not None else usecase.convert_si_to_unit(0.9),
"overall_width": overall_width if overall_width is not None else usecase.convert_si_to_unit(0.6),
"partition_type": partition_type,
"lining_properties": lining_properties,
"panel_properties": panel_properties,
"context": None, # IfcGeometricRepresentationContext
# SINGLE_PANEL, DOUBLE_PANEL_HORIZONTAL, DOUBLE_PANEL_VERTICAL,
# TRIPLE_PANEL_BOTTOM, TRIPLE_PANEL_HORIZONTAL, TRIPLE_PANEL_LEFT,
# TRIPLE_PANEL_RIGHT, TRIPLE_PANEL_TOP, TRIPLE_PANEL_VERTICAL
"partition_type": "SINGLE_PANEL",
"overall_height": usecase.convert_si_to_unit(0.9),
"overall_width": usecase.convert_si_to_unit(0.6),
"lining_properties": {
"LiningDepth": usecase.convert_si_to_unit(0.050),
"LiningThickness": usecase.convert_si_to_unit(0.050),
"LiningOffset": usecase.convert_si_to_unit(0.050), # offset to the wall
# offset from the wall
"LiningToPanelOffsetX": usecase.convert_si_to_unit(0.025),
# offset from the lining
# that way it allows you to define overall_depth constant between all panels
# and still have panels with different size:
# overall_depth = lining_depth + offset_y
# full offset from X axis = overall_depth - frame_depth
"LiningToPanelOffsetY": usecase.convert_si_to_unit(0.025),
# applies to DoublePanelVertical, TriplePanelBottom, TriplePanelTop,
# TriplePanelLeft, TriplePanelRight
# mullion - horizontal distance between panels
"MullionThickness": usecase.convert_si_to_unit(0.050),
# distance from the first lining to the mullion center
"FirstMullionOffset": usecase.convert_si_to_unit(0.3),
# applies to TriplePanelVertical
# distance from the first lining to the second mullion center
"SecondMullionOffset": usecase.convert_si_to_unit(0.45),
# applies to DoublePanelHorizontal, TriplePanelBottom, TriplePanelTop,
# TriplePanelLeft, TriplePanelRight
# works similar way to mullion
"TransomThickness": usecase.convert_si_to_unit(0.050),
"FirstTransomOffset": usecase.convert_si_to_unit(0.3),
# applies to TriplePanelHorizontal
"SecondTransomOffset": usecase.convert_si_to_unit(0.6),
"ShapeAspectStyle": None, # DEPRECATED
},
"panel_properties": [
{
"FrameDepth": usecase.convert_si_to_unit(0.035), # by Y
"FrameThickness": usecase.convert_si_to_unit(0.035), # by X
# BOTTOM, LEFT, MIDDLE, RIGHT, TOP
"PanelPosition": ..., # NEVER USED
# defines the basic ways to describe how window panels operate
# how it's hanged, how it opens
"OperationType": None, # NEVER USED
"ShapeAspectStyle": None, # DEPRECATED
},
],
}
)
usecase.settings = settings
for key, value in usecase_settings.items():
usecase.settings[key] = value
usecase.settings["panel_schema"] = DEFAULT_PANEL_SCHEMAS[usecase.settings["partition_type"]]
return usecase.execute()
@@ -20,12 +20,12 @@ import ifcopenshell.api
import ifcopenshell.util.element
def assign_representation(
file: ifcopenshell.file, product: ifcopenshell.entity_instance, representation: ifcopenshell.entity_instance
) -> None:
def assign_representation(file, **usecase_settings) -> None:
usecase = Usecase()
usecase.file = file
usecase.settings = {"product": product, "representation": representation}
usecase.settings = {"product": None, "representation": None}
for key, value in usecase_settings.items():
usecase.settings[key] = value
return usecase.execute()
@@ -20,20 +20,16 @@ import ifcopenshell
import ifcopenshell.api
import ifcopenshell.guid
import ifcopenshell.util.element
from typing import Optional
def connect_element(
file: ifcopenshell.file,
relating_element: ifcopenshell.entity_instance,
related_element: ifcopenshell.entity_instance,
description: Optional[str] = None,
) -> ifcopenshell.entity_instance:
def connect_element(file, **usecase_settings) -> None:
settings = {
"relating_element": relating_element,
"related_element": related_element,
"description": description,
"relating_element": None,
"related_element": None,
"description": None,
}
for key, value in usecase_settings.items():
settings[key] = value
incompatible_connections = []
@@ -20,24 +20,18 @@ import ifcopenshell
import ifcopenshell.api
import ifcopenshell.guid
import ifcopenshell.util.element
from typing import Optional
def connect_path(
file: ifcopenshell.file,
relating_element: ifcopenshell.entity_instance,
related_element: ifcopenshell.entity_instance,
relating_connection: str = "NOTDEFINED",
related_connection: str = "NOTDEFINED",
description: Optional[str] = None,
) -> ifcopenshell.entity_instance:
def connect_path(file, **usecase_settings) -> None:
settings = {
"relating_element": relating_element,
"related_element": related_element,
"relating_connection": relating_connection,
"related_connection": related_connection,
"description": description,
"relating_element": None,
"related_element": None,
"relating_connection": "NOTDEFINED",
"related_connection": "NOTDEFINED",
"description": None,
}
for key, value in usecase_settings.items():
settings[key] = value
incompatible_connections = []
for rel in settings["relating_element"].ConnectedTo:
@@ -22,16 +22,8 @@ import ifcopenshell.util.unit
def create_2pt_wall(
file: ifcopenshell.file,
element: ifcopenshell.entity_instance,
context: ifcopenshell.entity_instance,
p1: tuple[float, float],
p2: tuple[float, float],
elevation: float,
height: float,
thickness: float,
is_si: bool = True,
) -> ifcopenshell.entity_instance:
file, element=None, context=None, p1=None, p2=None, elevation=None, height=None, thickness=None, is_si=True
) -> None:
usecase = Usecase()
usecase.file = file
usecase.settings = {
@@ -20,31 +20,30 @@ import ifcopenshell
import ifcopenshell.util.element
def disconnect_element(
file: ifcopenshell.file,
relating_element: ifcopenshell.entity_instance,
related_element: ifcopenshell.entity_instance,
) -> None:
# TODO: arguments relating_element, related_element probably
# should be renamed to element1, element2
# as api call doesn't really treat them as "relating" and "related"
# and just purging all connections between them
def disconnect_element(file, **usecase_settings) -> None:
settings = {
"relating_element": None,
"related_element": None,
}
for key, value in usecase_settings.items():
settings[key] = value
incompatible_connections = []
for rel in relating_element.ConnectedTo:
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatedElement == related_element:
for rel in settings["relating_element"].ConnectedTo:
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatedElement == settings["related_element"]:
incompatible_connections.append(rel)
for rel in relating_element.ConnectedFrom:
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatingElement == related_element:
for rel in settings["relating_element"].ConnectedFrom:
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatingElement == settings["related_element"]:
incompatible_connections.append(rel)
for rel in related_element.ConnectedTo:
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatedElement == relating_element:
for rel in settings["related_element"].ConnectedTo:
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatedElement == settings["relating_element"]:
incompatible_connections.append(rel)
for rel in related_element.ConnectedFrom:
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatingElement == relating_element:
for rel in settings["related_element"].ConnectedFrom:
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatingElement == settings["relating_element"]:
incompatible_connections.append(rel)
if incompatible_connections:
@@ -19,36 +19,33 @@
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
from typing import Optional
def disconnect_path(
file: ifcopenshell.file,
element: Optional[ifcopenshell.entity_instance] = None,
connection_type: Optional[str] = None,
relating_element: Optional[ifcopenshell.entity_instance] = None,
related_element: Optional[ifcopenshell.entity_instance] = None,
) -> None:
"""There are two options to use this API method:
- provide `element` (connected from) and `connection_type` that should be disconnected.
- provide connected elements to disconnect explicitly:
`relating_element` (connected from) and `related_element` (connected to)
"""
if connection_type and element:
def disconnect_path(file, **usecase_settings) -> None:
settings = {
"relating_element": None,
"related_element": None,
"element": None,
"connection_type": None,
}
for key, value in usecase_settings.items():
settings[key] = value
if settings["connection_type"] and settings["element"]:
connections = [
r
for r in element.ConnectedTo
if r.is_a("IfcRelConnectsPathElements") and r.RelatingConnectionType == connection_type
for r in settings["element"].ConnectedTo
if r.is_a("IfcRelConnectsPathElements") and r.RelatingConnectionType == settings["connection_type"]
] + [
r
for r in element.ConnectedFrom
if r.is_a("IfcRelConnectsPathElements") and r.RelatedConnectionType == connection_type
for r in settings["element"].ConnectedFrom
if r.is_a("IfcRelConnectsPathElements") and r.RelatedConnectionType == settings["connection_type"]
]
elif related_element:
else:
connections = [
r
for r in relating_element.ConnectedTo
if r.is_a("IfcRelConnectsPathElements") and r.RelatedElement == related_element
for r in settings["relating_element"].ConnectedTo
if r.is_a("IfcRelConnectsPathElements") and r.RelatedElement == settings["related_element"]
]
for connection in set(connections):
@@ -31,8 +31,8 @@ def edit_object_placement(
file: ifcopenshell.file,
product: ifcopenshell.entity_instance,
matrix: Optional[NPArrayOfFloats] = None,
is_si: bool = True,
should_transform_children: bool = False,
is_si=True,
should_transform_children=False,
) -> ifcopenshell.entity_instance:
usecase = Usecase()
usecase.file = file
@@ -16,15 +16,14 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
def map_representation(
file: ifcopenshell.file, representation: ifcopenshell.entity_instance
) -> ifcopenshell.entity_instance:
def map_representation(file, **usecase_settings) -> None:
usecase = Usecase()
usecase.file = file
usecase.settings = {"representation": representation}
usecase.settings = {"representation": None}
usecase.ifc_vertices = []
for key, value in usecase_settings.items():
usecase.settings[key] = value
return usecase.execute()
@@ -19,10 +19,12 @@
import ifcopenshell.util.element
def remove_boolean(file: ifcopenshell.file, item: ifcopenshell.entity_instance) -> None:
def remove_boolean(file, **usecase_settings) -> None:
usecase = Usecase()
usecase.file = file
usecase.settings = {"item": item}
usecase.settings = {"item": None}
for key, value in usecase_settings.items():
usecase.settings[key] = value
return usecase.execute()
@@ -20,12 +20,12 @@ import ifcopenshell.api
import ifcopenshell.util.element
def unassign_representation(
file: ifcopenshell.file, product: ifcopenshell.entity_instance, representation: ifcopenshell.entity_instance
) -> None:
def unassign_representation(file, **usecase_settings) -> None:
usecase = Usecase()
usecase.file = file
usecase.settings = {"product": product, "representation": representation}
usecase.settings = {"product": None, "representation": None}
for key, value in usecase_settings.items():
usecase.settings[key] = value
return usecase.execute()
@@ -16,10 +16,8 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
def add_georeferencing(file: ifcopenshell.file) -> None:
def add_georeferencing(file) -> None:
"""Add empty georeferencing entities to a model
By default, models are not georeferenced. Georeferencing requires two
@@ -16,16 +16,8 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
from typing import Optional, Any
def edit_georeferencing(
file: ifcopenshell.file,
map_conversion: Optional[dict[str, Any]] = None,
projected_crs: Optional[dict[str, Any]] = None,
true_north: Optional[tuple[float, float]] = None,
) -> None:
def edit_georeferencing(file, map_conversion=None, projected_crs=None, true_north=None) -> None:
"""Edits the attributes of a map conversion, projected CRS, and true north
Setting the correct georeferencing parameters is a complex topic and
@@ -55,7 +47,7 @@ def edit_georeferencing(
names and values you want to edit.
:type projected_crs: dict, optional
:param true_north: A unitised 2D vector, where each ordinate is a float
:type true_north: tuple[float, float], optional
:type true_north: list[float]
:return: None
:rtype: None
@@ -109,7 +101,7 @@ class Usecase:
self.set_true_north()
def set_true_north(self):
if self.settings["true_north"] == None:
if self.settings["true_north"] == []:
return
for context in self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
if context.TrueNorth:
@@ -119,8 +111,6 @@ class Usecase:
context.TrueNorth = self.file.create_entity("IfcDirection")
direction = context.TrueNorth
if self.settings["true_north"] is None:
# TODO: code will never be executed since None value
# is substituted by an empty list
context.TrueNorth = self.settings["true_north"]
elif context.CoordinateSpaceDimension == 2:
direction.DirectionRatios = self.settings["true_north"][0:2]
@@ -16,10 +16,8 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
def remove_georeferencing(file: ifcopenshell.file) -> None:
def remove_georeferencing(file) -> None:
"""Remove georeferencing data
All georeferencing parameters such as projected CRS and map conversion
@@ -16,18 +16,13 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import ifcopenshell
import ifcopenshell.util.element
import ifcopenshell.util.unit
import ifcopenshell.util.placement
from mathutils import Matrix # For now, we depend on Blender
import bpy.types
def create_axis_curve(
file: ifcopenshell.file, axis_curve: bpy.types.Object, grid_axis: ifcopenshell.entity_instance
) -> None:
def create_axis_curve(file, axis_curve=None, grid_axis=None) -> None:
"""Adds curve geometry to a grid axis to represent the axis extents
This currently depends on the Blender geometry kernel to function.
@@ -15,17 +15,9 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
from typing import Optional, Literal
def create_grid_axis(
file: ifcopenshell.file,
grid: ifcopenshell.entity_instance,
axis_tag: str = "A",
same_sense: bool = True,
uvw_axes: Literal["UAxes", "VAxes", "WAxes"] = "UAxes",
) -> ifcopenshell.entity_instance:
def create_grid_axis(file, axis_tag=None, same_sense=None, uvw_axes=None, grid=None) -> None:
"""Adds a new grid axis to a grid
An IFC grid will typically have a minimum of two axes which will be
@@ -74,9 +66,17 @@ def create_grid_axis(
axis_1 = ifcopenshell.api.run("grid.create_grid_axis", model,
axis_tag="1", uvw_axes="VAxes", grid=grid)
"""
settings = {
"axis_tag": axis_tag or "A",
"same_sense": same_sense or True,
"uvw_axes": uvw_axes or "UAxes", # Choose which axes
"grid": grid,
}
element = file.create_entity("IfcGridAxis", **{"AxisTag": axis_tag, "SameSense": same_sense})
axes = list(getattr(grid, uvw_axes) or [])
element = file.create_entity(
"IfcGridAxis", **{"AxisTag": settings["axis_tag"], "SameSense": settings["same_sense"]}
)
axes = list(getattr(settings["grid"], settings["uvw_axes"]) or [])
axes.append(element)
setattr(grid, uvw_axes, axes)
setattr(settings["grid"], settings["uvw_axes"], axes)
return element
@@ -19,7 +19,7 @@
import ifcopenshell.util.element
def remove_grid_axis(file: ifcopenshell.file, axis: ifcopenshell.entity_instance) -> None:
def remove_grid_axis(file, axis=None) -> None:
"""Removes a grid axis from a grid
:param axis: The IfcGridAxis you want to remove.
@@ -43,8 +43,9 @@ def remove_grid_axis(file: ifcopenshell.file, axis: ifcopenshell.entity_instance
# Let's remove it!
ifcopenshell.api.run("grid.remove_grid_axis", model, axis=axis_2)
"""
axis_curve = axis.AxisCurve
if len(file.get_inverse(axis_curve)) == 1:
ifcopenshell.util.element.remove_deep(file, axis_curve)
file.remove(axis_curve)
file.remove(axis)
settings = {"axis": axis}
if len(file.get_inverse(settings["axis"].AxisCurve)) == 1:
ifcopenshell.util.element.remove_deep(file, settings["axis"].AxisCurve)
file.remove(settings["axis"].AxisCurve)
file.remove(settings["axis"])
@@ -19,12 +19,9 @@
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.guid
from typing import Optional
def add_group(
file: ifcopenshell.file, name: str = "Unnamed", description: Optional[str] = None
) -> ifcopenshell.entity_instance:
def add_group(file, Name="Unnamed", Description=None) -> None:
"""Adds a new group
An IFC group is an arbitrary collection of products, which are typically
@@ -37,8 +34,8 @@ def add_group(
:param Name: The name of the group. Defaults to "Unnamed"
:type Name: str, optional
:param description: The description of the purpose of the group.
:type description: str, optional
:param Description: The description of the purpose of the group.
:type Description: str, optional
:return: The newly created IfcGroup
:rtype: ifcopenshell.entity_instance
@@ -46,11 +43,11 @@ def add_group(
.. code:: python
ifcopenshell.api.run("group.add_group", model, name="Unit 1A")
ifcopenshell.api.run("group.add_group", model, Name="Unit 1A")
"""
settings = {
"name": name or "Unnamed",
"description": description,
"Name": Name or "Unnamed",
"Description": Description,
}
return file.create_entity(
@@ -58,7 +55,7 @@ def add_group(
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", file),
"Name": settings["name"],
"Description": settings["description"],
"Name": settings["Name"],
"Description": settings["Description"],
}
)
@@ -42,7 +42,7 @@ def assign_group(
.. code:: python
group = ifcopenshell.api.run("group.add_group", model, name="Furniture")
group = ifcopenshell.api.run("group.add_group", model, Name="Furniture")
ifcopenshell.api.run("group.assign_group", model,
products=model.by_type("IfcFurniture"), group=group)
"""
@@ -15,11 +15,9 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
from typing import Any
def edit_group(file: ifcopenshell.file, group: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
def edit_group(file, group=None, attributes=None) -> None:
"""Edits the attributes of an IfcGroup
For more information about the attributes and data types of an
@@ -28,7 +26,7 @@ def edit_group(file: ifcopenshell.file, group: ifcopenshell.entity_instance, att
:param group: The IfcGroup entity you want to edit
:type group: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values.
:type attributes: dict
:type attributes: dict, optional
:return: None
:rtype: None
@@ -36,11 +34,11 @@ def edit_group(file: ifcopenshell.file, group: ifcopenshell.entity_instance, att
.. code:: python
group = ifcopenshell.api.run("group.add_group", model, name="Unit 1A")
group = ifcopenshell.api.run("group.add_group", model, Name="Unit 1A")
ifcopenshell.api.run("group.edit_group", model,
group=group, attributes={"Description": "All furniture and joinery included in the unit"})
"""
settings = {"group": group, "attributes": attributes}
settings = {"group": group, "attributes": attributes or {}}
for name, value in settings["attributes"].items():
setattr(settings["group"], name, value)
@@ -21,7 +21,7 @@ import ifcopenshell.api
import ifcopenshell.util.element
def remove_group(file: ifcopenshell.file, group: ifcopenshell.entity_instance) -> None:
def remove_group(file, group=None) -> None:
"""Removes a group
All products assigned to the group will remain, but the relationship to
@@ -36,7 +36,7 @@ def remove_group(file: ifcopenshell.file, group: ifcopenshell.entity_instance) -
.. code:: python
group = ifcopenshell.api.run("group.add_group", model, name="Unit 1A")
group = ifcopenshell.api.run("group.add_group", model, Name="Unit 1A")
ifcopenshell.api.run("group.remove_group", model, group=group)
"""
settings = {"group": group}
@@ -39,7 +39,7 @@ def unassign_group(
.. code:: python
group = ifcopenshell.api.run("group.add_group", model, name="Furniture")
group = ifcopenshell.api.run("group.add_group", model, Name="Furniture")
furniture = model.by_type("IfcFurniture")
ifcopenshell.api.run("group.assign_group", model, products=furniture, group=group)
@@ -19,12 +19,9 @@
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.guid
import ifcopenshell.util.element
def update_group_products(
file: ifcopenshell.file, group: ifcopenshell.entity_instance, products: list[ifcopenshell.entity_instance]
) -> ifcopenshell.entity_instance:
def update_group_products(file, group=None, products=None) -> None:
"""Sets a group products to be an explicit list of products
Any previous products assigned to that group will have their assignment
@@ -41,7 +38,7 @@ def update_group_products(
.. code:: python
group = ifcopenshell.api.run("group.add_group", model, name="Furniture")
group = ifcopenshell.api.run("group.add_group", model, Name="Furniture")
ifcopenshell.api.run("group.update_group_products", model,
products=model.by_type("IfcFurniture"), group=group)
"""
@@ -61,17 +58,11 @@ def update_group_products(
}
)
else:
rels = settings["group"].IsGroupedBy
objects = set(settings["products"])
for rel in rels:
objects.update([g for g in rel.RelatedObjects if g.is_a("IfcGroup")])
to_purge = rels[1:]
# assumes 1:1 cardinality, will need to be updated to reflect IFC4 changes
# where the cardinality is 0:? - vulevukusej
rel = settings["group"].IsGroupedBy[0]
existing_sub_groups = [g for g in rel.RelatedObjects if g.is_a("IfcGroup")]
for rel in to_purge:
history = rel.OwnerHistory
file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
rels[0].RelatedObjects = list(objects)
return rels[0]
rel.RelatedObjects = settings["products"]
for g in existing_sub_groups:
rel.RelatedObjects.add(g)
@@ -15,11 +15,9 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
from typing import Optional
def add_layer(file: ifcopenshell.file, name: str = "Unnamed") -> ifcopenshell.entity_instance:
def add_layer(file, Name=None) -> None:
"""Adds a new layer
An IFC layer is like a CAD layer. Portions of an object's geometry
@@ -34,13 +32,15 @@ def add_layer(file: ifcopenshell.file, name: str = "Unnamed") -> ifcopenshell.en
Some software that are still based on layers, such as Tekla or ArchiCAD
may also use this layer information for filtering.
:param name: The name of the layer. Defaults to "Unnamed".
:type name: str, optional
:param Name: The name of the layer. Defaults to "Unnamed".
:type Name: str, optional
:return: The newly created IfcPresentationLayerAssignment element
:rtype: ifcopenshell.entity_instance
Example:
ifcopenshell.api.run("layer.add_layer", model, name="AI-WALL-FULL-DIMS-N")
ifcopenshell.api.run("layer.add_layer", model, Name="AI-WALL-FULL-DIMS-N")
"""
return file.create_entity("IfcPresentationLayerAssignment", Name=name)
settings = {"Name": Name or "Unnamed"}
return file.create_entity("IfcPresentationLayerAssignment", Name=settings["Name"])
@@ -59,7 +59,7 @@ def assign_layer(
ifcopenshell.api.run("geometry.edit_object_placement", model, product=wall)
# Now let's create a layer that contains walls
layer = ifcopenshell.api.run("layer.add_layer", model, name="AI-WALL")
layer = ifcopenshell.api.run("layer.add_layer", model, Name="AI-WALL")
# And assign our wall representation item (in this example, there is
# only one item) to the layer.
@@ -15,11 +15,9 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
from typing import Any
def edit_layer(file: ifcopenshell.file, layer: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
def edit_layer(file, layer=None, attributes=None) -> None:
"""Edits the attributes of an IfcPresentationLayerAssignment
For more information about the attributes and data types of an
@@ -28,7 +26,7 @@ def edit_layer(file: ifcopenshell.file, layer: ifcopenshell.entity_instance, att
:param layer: The IfcPresentationLayerAssignment entity you want to edit
:type layer: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values.
:type attributes: dict
:type attributes: dict, optional
:return: None
:rtype: None
@@ -36,11 +34,11 @@ def edit_layer(file: ifcopenshell.file, layer: ifcopenshell.entity_instance, att
.. code:: python
layer = ifcopenshell.api.run("layer.add_layer", model, name="AI-WALL")
layer = ifcopenshell.api.run("layer.add_layer", model, Name="AI-WALL")
ifcopenshell.api.run("layer.edit_layer", model,
layer=layer, attributes={"Description": "All walls, based on the AIA standard."})
"""
settings = {"layer": layer, "attributes": attributes}
settings = {"layer": layer, "attributes": attributes or {}}
for name, value in settings["attributes"].items():
setattr(settings["layer"], name, value)
@@ -15,10 +15,9 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
def remove_layer(file: ifcopenshell.file, layer: ifcopenshell.entity_instance) -> None:
def remove_layer(file, layer=None) -> None:
"""Removes a layer
All representation items assigned to the layer will remain, but the
@@ -33,7 +32,9 @@ def remove_layer(file: ifcopenshell.file, layer: ifcopenshell.entity_instance) -
.. code:: python
layer = ifcopenshell.api.run("layer.add_layer", model, name="AI-WALL")
layer = ifcopenshell.api.run("layer.add_layer", model, Name="AI-WALL")
ifcopenshell.api.run("layer.remove_layer", model, layer=layer)
"""
file.remove(layer)
settings = {"layer": layer}
file.remove(settings["layer"])
@@ -56,7 +56,7 @@ def unassign_layer(
ifcopenshell.api.run("geometry.edit_object_placement", model, product=wall)
# Now let's create a layer that contains walls
layer = ifcopenshell.api.run("layer.add_layer", model, name="AI-WALL")
layer = ifcopenshell.api.run("layer.add_layer", model, Name="AI-WALL")
# And assign our wall representation item (in this example, there is
# only one item) to the layer.
@@ -21,7 +21,7 @@ import ifcopenshell.util.schema
import ifcopenshell.util.date
def add_library(file: ifcopenshell.file, name: str) -> ifcopenshell.entity_instance:
def add_library(file, name=None) -> None:
"""Adds a new library to the project
A library is an external data source that is related to the project. It
@@ -60,4 +60,6 @@ def add_library(file: ifcopenshell.file, name: str) -> ifcopenshell.entity_insta
ifcopenshell.api.run("library.add_library", model, name="Brickschema")
"""
return file.create_entity("IfcLibraryInformation", Name=name)
settings = {"name": name}
return file.create_entity("IfcLibraryInformation", Name=settings["name"])
@@ -15,13 +15,9 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.util.date
import datetime
from typing import Any
def edit_library(file: ifcopenshell.file, library: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
def edit_library(file, library=None, attributes=None) -> None:
"""Edits the attributes of an IfcLibraryInformation
For more information about the attributes and data types of an
@@ -30,7 +26,7 @@ def edit_library(file: ifcopenshell.file, library: ifcopenshell.entity_instance,
:param library: The IfcLibraryInformation entity you want to edit
:type library: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values.
:type attributes: dict
:type attributes: dict, optional
:return: None
:rtype: None
@@ -43,16 +39,7 @@ def edit_library(file: ifcopenshell.file, library: ifcopenshell.entity_instance,
attributes={"Description": "A Brickschema TTL including only mechanical distribution systems."})
"""
if "VersionDate" in attributes:
dt = attributes["VersionDate"]
if isinstance(dt, datetime.datetime):
if file.schema != "IFC2X3":
dt = ifcopenshell.util.date.datetime2ifc(dt, "IfcDateTime")
else:
calendar_date = ifcopenshell.util.date.datetime2ifc(dt, "IfcCalendarDate")
dt = file.create_entity("IfcCalendarDate", **calendar_date)
attributes = attributes.copy()
attributes["VersionDate"] = dt
settings = {"library": library, "attributes": attributes or {}}
for name, value in attributes.items():
setattr(library, name, value)
for name, value in settings["attributes"].items():
setattr(settings["library"], name, value)
@@ -15,13 +15,9 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
from typing import Any
def edit_reference(
file: ifcopenshell.file, reference: ifcopenshell.entity_instance, attributes: dict[str, Any]
) -> None:
def edit_reference(file, reference=None, attributes=None) -> None:
"""Edits the attributes of an IfcLibraryReference
For more information about the attributes and data types of an
@@ -30,7 +26,7 @@ def edit_reference(
:param reference: The IfcLibraryReference entity you want to edit
:type reference: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values.
:type attributes: dict
:type attributes: dict, optional
:return: None
:rtype: None
@@ -44,7 +40,7 @@ def edit_reference(
ifcopenshell.api.run("library.edit_reference", model,
reference=reference, attributes={"Identification": "http://example.org/digitaltwin#AHU01"})
"""
settings = {"reference": reference, "attributes": attributes}
settings = {"reference": reference, "attributes": attributes or {}}
for name, value in settings["attributes"].items():
setattr(settings["reference"], name, value)
@@ -20,7 +20,7 @@ import ifcopenshell
import ifcopenshell.util.element
def remove_library(file: ifcopenshell.file, library: ifcopenshell.entity_instance) -> None:
def remove_library(file, library=None) -> None:
"""Removes a library
All references along with their relationships will also be removed. Any
@@ -38,23 +38,14 @@ def remove_library(file: ifcopenshell.file, library: ifcopenshell.entity_instanc
library = ifcopenshell.api.run("library.add_library", model, name="Brickschema")
ifcopenshell.api.run("library.remove_library", model, library=library)
"""
settings = {"library": library}
if file.schema != "IFC2X3":
rels = []
for reference in set(library.HasLibraryReferences):
rels.extend(reference.LibraryRefForObjects)
file.remove(reference)
rels.extend(library.LibraryInfoForObjects)
file.remove(library)
else:
for reference in set(library.LibraryReference or []):
file.remove(reference)
file.remove(library)
# RelatingLibrary could either be library itself or library reference we removed
rels = [rel for rel in file.by_type("IfcRelAssociatesLibrary") if rel.RelatingLibrary is None]
for rel in rels:
history = rel.OwnerHistory
file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
for reference in set(settings["library"].HasLibraryReferences or []):
file.remove(reference)
file.remove(settings["library"])
for rel in file.by_type("IfcRelAssociatesLibrary"):
if not rel.RelatingLibrary:
history = rel.OwnerHistory
file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
@@ -40,14 +40,11 @@ def remove_reference(file: ifcopenshell.file, reference: ifcopenshell.entity_ins
# Let's change our mind and remove it.
ifcopenshell.api.run("library.remove_reference", model, reference=reference)
"""
if file.schema != "IFC2X3":
rels = reference.LibraryRefForObjects
else:
rels = [rel for rel in file.by_type("IfcRelAssociatesLibrary") if rel.RelatingLibrary == reference]
settings = {"reference": reference}
for rel in rels:
for rel in settings["reference"].LibraryRefForObjects:
history = rel.OwnerHistory
file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
file.remove(reference)
file.remove(settings["reference"])
@@ -15,12 +15,9 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
def add_constituent(
file: ifcopenshell.file, constituent_set: ifcopenshell.entity_instance, material: ifcopenshell.entity_instance
) -> ifcopenshell.entity_instance:
def add_constituent(file, constituent_set=None, material=None) -> None:
"""Adds a new constituent to a constituent set
A constituent describes how a portion of an object is made out of a
@@ -15,12 +15,9 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
def add_layer(
file: ifcopenshell.file, layer_set: ifcopenshell.entity_instance, material: ifcopenshell.entity_instance
) -> ifcopenshell.entity_instance:
def add_layer(file, layer_set=None, material=None) -> None:
"""Adds a new layer to a layer set
A layer represents a portion of material within a layered build up,
@@ -19,9 +19,7 @@
import ifcopenshell
def add_list_item(
file: ifcopenshell.file, material_list: ifcopenshell.entity_instance, material: ifcopenshell.entity_instance
) -> None:
def add_list_item(file, material_list=None, material=None) -> None:
"""Adds a new material in a list of materials
In IFC2X3, if you wanted an object to have multiple materials (i.e. a
@@ -15,13 +15,9 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
from typing import Optional
def add_material(
file: ifcopenshell.file, name: Optional[str] = None, category: Optional[str] = None
) -> ifcopenshell.entity_instance:
def add_material(file, name=None, category=None) -> None:
"""Adds a new material
A material in IFC represents a physical material, such as timber, steel,
@@ -52,7 +48,7 @@ def add_material(
:param name: The name of the material, typically tagged in a finishes
drawing or schedule.
:type name: str, optional
:type name: str
:param category: The category of the material.
:type category: str, optional
:return: The newly created IfcMaterial
@@ -15,12 +15,9 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
def add_material_set(
file: ifcopenshell.file, name: str = "Unnamed", set_type: str = "IfcMaterialConstituentSet"
) -> ifcopenshell.entity_instance:
def add_material_set(file, name="Unnamed", set_type="IfcMaterialConstituentSet") -> None:
"""Adds a new material set
IFC allows you to state that objects are made out of multiple materials.
@@ -19,9 +19,7 @@
import ifcopenshell.util.representation
def assign_profile(
file: ifcopenshell.file, material_profile: ifcopenshell.entity_instance, profile: ifcopenshell.entity_instance
) -> None:
def assign_profile(file, material_profile=None, profile=None) -> None:
"""Changes the profile curve of a material profile item in a profile set
In addition to changing the profile curve, it will also change the
@@ -96,8 +94,7 @@ def assign_profile(
class Usecase:
file: ifcopenshell.file
def execute(self) -> None:
def execute(self):
# TODO: handle composite profiles
old_profile = self.settings["material_profile"].Profile
self.settings["material_profile"].Profile = self.settings["profile"]
@@ -120,7 +117,7 @@ class Usecase:
# TODO: check remove deep
self.file.remove(old_profile)
def change_profile(self, element: ifcopenshell.entity_instance) -> None:
def change_profile(self, element):
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if not representation:
return
@@ -20,7 +20,7 @@ import ifcopenshell
import ifcopenshell.util.element
def copy_material(file: ifcopenshell.file, material: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
def copy_material(file, material=None) -> None:
"""Copies a material
All material psets and styles are copied. The copied material is not
@@ -48,23 +48,11 @@ def copy_material(file: ifcopenshell.file, material: ifcopenshell.entity_instanc
if inverse.is_a("IfcMaterialProperties"):
# Properties must not be shared between objects for convenience of authoring
inverse = ifcopenshell.util.element.copy(file, inverse)
properties = []
for pset in inverse.Properties:
properties.append(ifcopenshell.util.element.copy_deep(file, pset))
inverse.Properties = properties
inverse.Material = new
props_attribute = "Properties"
if file.schema == "IFC2X3":
if not inverse.is_a("IfcExtendedMaterialProperties"):
continue
props_attribute = "ExtendedProperties"
props = getattr(inverse, props_attribute)
if not props:
continue
copied_props = []
for pset in props:
copied_props.append(ifcopenshell.util.element.copy_deep(file, pset))
setattr(inverse, props_attribute, copied_props)
elif inverse.is_a("IfcMaterialDefinitionRepresentation"):
inverse = ifcopenshell.util.element.copy_deep(
file, inverse, exclude=["IfcRepresentationContext", "IfcMaterial"]
@@ -15,11 +15,9 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
from typing import Any
def edit_assigned_material(file: ifcopenshell.file, element: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
def edit_assigned_material(file, element=None, attributes=None) -> None:
"""Edits the attributes of an IfcMaterial
For more information about the attributes and data types of an
@@ -28,7 +26,7 @@ def edit_assigned_material(file: ifcopenshell.file, element: ifcopenshell.entity
:param element: The IfcMaterial entity you want to edit
:type element: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values.
:type attributes: dict
:type attributes: dict, optional
:return: None
:rtype: None
@@ -40,7 +38,7 @@ def edit_assigned_material(file: ifcopenshell.file, element: ifcopenshell.entity
ifcopenshell.api.run("material.edit_assigned_material", model,
element=concrete, attributes={"Description": "40MPA concrete with broom finish"})
"""
settings = {"element": element, "attributes": attributes}
settings = {"element": element, "attributes": attributes or {}}
for name, value in settings["attributes"].items():
setattr(settings["element"], name, value)
@@ -15,16 +15,9 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
from typing import Optional, Any
def edit_constituent(
file: ifcopenshell.file,
constituent: ifcopenshell.entity_instance,
attributes: Optional[dict[str, Any]] = None,
material: Optional[ifcopenshell.entity_instance] = None,
) -> None:
def edit_constituent(file, constituent=None, attributes=None, material=None) -> None:
"""Edits the attributes of an IfcMaterialConstituent
For more information about the attributes and data types of an

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