mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-07 00:11:36 +00:00
Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cb255a18b9 | |||
| 183028570e | |||
| 341cdd4ef7 | |||
| fe68e16ca3 | |||
| a8f0c90d47 | |||
| 39f550529b | |||
| 25c1a14e64 | |||
| 9f7d710119 | |||
| ea592775e4 | |||
| 42be4eb064 | |||
| 8662e20c58 | |||
| da4ce3773f | |||
| 87c9bbe2a7 | |||
| a66ee06e3d | |||
| 9969de58cb | |||
| 6995cafe63 | |||
| eaa80ad08a | |||
| d001180082 | |||
| 3884403231 | |||
| 8d4b5d83e1 | |||
| e1250f2177 | |||
| a2ee920a5f | |||
| fdbe74a432 | |||
| 4e39fb3edd | |||
| c2049246cd | |||
| 335e2b7a78 | |||
| bb8e84e5ec | |||
| 2fa30be0d0 | |||
| c50149ad87 | |||
| da1bdc802d | |||
| a9cba30da6 | |||
| 3665dda87c | |||
| f2696d5352 | |||
| 5e394e1576 | |||
| ebd03e9290 | |||
| c3bfd7354f | |||
| 1d046eaadf | |||
| 2f554db54b | |||
| 54b4cef25e | |||
| c6106e6636 | |||
| 865dbab3ec | |||
| 76e6c63a01 | |||
| 3320accc7a | |||
| ac686db298 | |||
| aca26c7597 |
@@ -13,7 +13,7 @@ readme = "README.md"
|
||||
requires-python = ">=3.8"
|
||||
keywords = ["IFC", "BCF", "BIM"]
|
||||
dependencies = [
|
||||
"xsdata",
|
||||
"xsdata>=24.4",
|
||||
"numpy",
|
||||
"ifcopenshell",
|
||||
]
|
||||
|
||||
@@ -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("xs", "http://www.w3.org/2001/XMLSchema")
|
||||
parser.register_namespace(ns_map=parser.ns_map, prefix="xs", uri="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(pretty_print=True),
|
||||
config=SerializerConfig(indent=" "),
|
||||
context=context or XmlContext(),
|
||||
)
|
||||
|
||||
@@ -36,8 +36,9 @@ class AbstractXmlParserSerializer(Protocol):
|
||||
xml: The XML file as bytes.
|
||||
clazz: The class to parse to.
|
||||
"""
|
||||
...
|
||||
|
||||
def serialize(self, obj: T, ns_map: Optional[dict[str, str]] = None) -> str:
|
||||
def serialize(self, obj: object, ns_map: Optional[dict[str, str]] = None) -> str:
|
||||
"""
|
||||
Serialize an object to XML.
|
||||
|
||||
@@ -48,6 +49,7 @@ class AbstractXmlParserSerializer(Protocol):
|
||||
Returns:
|
||||
The XML as string.
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
class XmlParserSerializer:
|
||||
@@ -68,7 +70,7 @@ class XmlParserSerializer:
|
||||
"""
|
||||
return self.parser.from_bytes(xml, clazz)
|
||||
|
||||
def serialize(self, obj: T, ns_map: Optional[dict[str, str]] = None) -> str:
|
||||
def serialize(self, obj: object, ns_map: Optional[dict[Optional[str], str]] = None) -> str:
|
||||
"""
|
||||
Serialize an object to XML.
|
||||
|
||||
@@ -79,5 +81,5 @@ class XmlParserSerializer:
|
||||
Returns:
|
||||
The XML as string.
|
||||
"""
|
||||
ns_map = ns_map or {"xs": "http://www.w3.org/2001/XMLSchema"}
|
||||
ns_map = ns_map or self.parser.ns_map or {"xs": "http://www.w3.org/2001/XMLSchema"}
|
||||
return self.serializer.render(obj, ns_map)
|
||||
|
||||
@@ -286,9 +286,9 @@ endif
|
||||
|
||||
# Required by bcf
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/ad/c7/17c9d16320d8e2cfdb27d2fd298b5e8f7a3f211025b0c1bc7a39a85bd690/xsdata-22.11.tar.gz
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/b4/ef/35d8118f903510f9e028f8a6a4edb615fa69e28a30d955593425a88e587a/xsdata-24.5.tar.gz
|
||||
cd dist/working && tar -xzvf xsdata*
|
||||
cp -r dist/working/xsdata-22.11/xsdata dist/blenderbim/libs/site/packages/
|
||||
cp -r dist/working/xsdata-24.5/xsdata dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by bcf
|
||||
|
||||
@@ -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.label(text="View the console for full logs.", icon="CONSOLE")
|
||||
layout.operator("wm.console_toggle", 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,14 +149,7 @@ if sys.modules.get("bpy", None):
|
||||
|
||||
def execute(self, context):
|
||||
info = format_debug_info(get_debug_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)
|
||||
context.window_manager.clipboard = info
|
||||
return {"FINISHED"}
|
||||
|
||||
class HiddenPanel:
|
||||
|
||||
@@ -26,6 +26,7 @@ import bmesh
|
||||
import logging
|
||||
import mathutils
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
import multiprocessing
|
||||
import ifcopenshell
|
||||
import ifcopenshell.geom
|
||||
@@ -303,22 +304,22 @@ class IfcImporter:
|
||||
self.update_progress(100)
|
||||
bpy.context.window_manager.progress_end()
|
||||
|
||||
def is_element_far_away(self, element):
|
||||
def is_element_far_away(self, element: ifcopenshell.entity_instance) -> bool:
|
||||
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:
|
||||
pass
|
||||
return False
|
||||
|
||||
def is_point_far_away(self, point, is_meters=True):
|
||||
def is_point_far_away(
|
||||
self, point: Union[ifcopenshell.entity_instance, npt.NDArray[np.float64]], is_meters: bool = True
|
||||
) -> bool:
|
||||
# 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 = point
|
||||
if hasattr(point, "Coordinates"):
|
||||
coords = point.Coordinates
|
||||
coords = getattr(point, "Coordinates", point)
|
||||
return abs(coords[0]) > limit or abs(coords[1]) > limit or abs(coords[2]) > limit
|
||||
|
||||
def process_context_filter(self):
|
||||
@@ -608,7 +609,9 @@ class IfcImporter:
|
||||
|
||||
threshold = 10000 # Just from experience.
|
||||
|
||||
faces = [len(e.CfsFaces) for e in self.file.by_type("IfcClosedShell")]
|
||||
# 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])]
|
||||
if faces and max(faces) > threshold:
|
||||
self.ifc_import_settings.should_use_native_meshes = True
|
||||
return
|
||||
@@ -616,12 +619,14 @@ class IfcImporter:
|
||||
if self.file.schema == "IFC2X3":
|
||||
return
|
||||
|
||||
faces = [len(e.Faces) for e in self.file.by_type("IfcPolygonalFaceSet")]
|
||||
# 2 IfcPolygonalFaceSet.Faces
|
||||
faces = [len(faces) for e in self.file.by_type("IfcPolygonalFaceSet") if (faces := e[2])]
|
||||
if faces and max(faces) > threshold:
|
||||
self.ifc_import_settings.should_use_native_meshes = True
|
||||
return
|
||||
|
||||
faces = [len(e.CoordIndex) for e in self.file.by_type("IfcTriangulatedFaceSet")]
|
||||
# 3 IfcTriangulatedFaceSet.CoordIndex
|
||||
faces = [len(index) for e in self.file.by_type("IfcTriangulatedFaceSet") if (index := e[3])]
|
||||
if faces and max(faces) > threshold:
|
||||
self.ifc_import_settings.should_use_native_meshes = True
|
||||
|
||||
@@ -679,7 +684,7 @@ class IfcImporter:
|
||||
props.blender_orthogonal_height = str(offset_point[2])
|
||||
props.has_blender_offset = True
|
||||
|
||||
def get_offset_point(self):
|
||||
def get_offset_point(self) -> Union[npt.NDArray[np.float64], None]:
|
||||
elements_checked = 0
|
||||
# If more than these elements aren't far away, the file probably isn't absolutely positioned
|
||||
element_checking_threshold = 10
|
||||
@@ -714,7 +719,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):
|
||||
def does_element_likely_have_geometry_far_away(self, element: ifcopenshell.entity_instance) -> bool:
|
||||
for representation in element.Representation.Representations:
|
||||
items = []
|
||||
for item in representation.Items:
|
||||
@@ -731,13 +736,14 @@ 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[0, 3], matrix[1, 3], matrix[2, 3])):
|
||||
elif self.is_point_far_away((matrix[:3, 3])):
|
||||
obj.BIMObjectProperties.blender_offset_type = "OBJECT_PLACEMENT"
|
||||
matrix = ifcopenshell.util.geolocation.global2local(
|
||||
matrix,
|
||||
@@ -750,7 +756,9 @@ class IfcImporter:
|
||||
|
||||
return mathutils.Matrix(matrix.tolist())
|
||||
|
||||
def find_decomposed_ifc_class(self, element, ifc_class):
|
||||
def find_decomposed_ifc_class(
|
||||
self, element: ifcopenshell.entity_instance, ifc_class: str
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
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(), pset=pset)
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
|
||||
|
||||
|
||||
class BIM_OT_enable_editing_aggregate(bpy.types.Operator, Operator):
|
||||
|
||||
@@ -60,13 +60,7 @@ class CopyDebugInformation(bpy.types.Operator):
|
||||
print(text)
|
||||
print("-" * 80)
|
||||
|
||||
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)
|
||||
context.window_manager.clipboard = text
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -400,7 +400,11 @@ 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):
|
||||
[
|
||||
@@ -938,7 +942,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(), pset=pset)
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=new[0],pset=pset)
|
||||
|
||||
if new[0].is_a("IfcElementAssembly"):
|
||||
linked_aggregate_group = [
|
||||
@@ -1050,7 +1054,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,6 +23,7 @@ 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
|
||||
|
||||
@@ -134,6 +135,10 @@ 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
|
||||
@@ -144,16 +149,25 @@ 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 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)
|
||||
if not element:
|
||||
continue
|
||||
|
||||
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,
|
||||
@@ -168,11 +182,13 @@ 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")
|
||||
|
||||
new_objs = tool.Misc.split_objects_with_cutter(objs, cutter)
|
||||
objs_to_cut.append(obj)
|
||||
|
||||
new_objs = tool.Misc.split_objects_with_cutter(objs_to_cut, 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:
|
||||
for obj in objs_to_cut:
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
|
||||
representation = tool.Geometry.get_active_representation(obj)
|
||||
@@ -187,6 +203,8 @@ 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"
|
||||
|
||||
@@ -193,7 +193,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(), pset=pset)
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, 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(), pset=pset)
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -931,13 +931,11 @@ 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):
|
||||
|
||||
@@ -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(), pset=pset)
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, 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(), pset=pset)
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, 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(), pset=pset)
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -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(), pset=pset)
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
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,10 +36,14 @@ 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,6 +20,7 @@ import re
|
||||
import bpy
|
||||
import json
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.selector
|
||||
@@ -214,7 +215,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)
|
||||
|
||||
@@ -367,7 +368,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,6 +44,8 @@ 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 self.props.active_container_index < len(self.props.containers):
|
||||
if 0 <= 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 self.props.active_container_index < len(self.props.containers):
|
||||
if 0 <= 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,6 +20,7 @@ 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
|
||||
|
||||
@@ -406,10 +406,11 @@ 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.label(text="View the console for full logs.", icon="CONSOLE")
|
||||
box.operator("wm.console_toggle", 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"
|
||||
|
||||
@@ -18,10 +18,11 @@
|
||||
|
||||
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(
|
||||
@@ -37,8 +38,15 @@ def edit_object_placement(
|
||||
|
||||
|
||||
def add_representation(
|
||||
ifc, geometry, style, surveyor, obj=None, context=None, ifc_representation_class=None, profile_set_usage=None
|
||||
):
|
||||
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:
|
||||
element = ifc.get_entity(obj)
|
||||
if not element:
|
||||
return
|
||||
@@ -47,7 +55,7 @@ def add_representation(
|
||||
data = geometry.get_object_data(obj)
|
||||
|
||||
if not data and ifc_representation_class != "IfcTextLiteral":
|
||||
raise IncompatibleRepresentationError()
|
||||
return
|
||||
|
||||
representation = ifc.run(
|
||||
"geometry.add_representation",
|
||||
@@ -89,15 +97,15 @@ def add_representation(
|
||||
|
||||
|
||||
def switch_representation(
|
||||
ifc,
|
||||
geometry,
|
||||
obj=None,
|
||||
representation=None,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
apply_openings=True,
|
||||
):
|
||||
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:
|
||||
"""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`;
|
||||
|
||||
@@ -16,8 +16,18 @@
|
||||
# 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
|
||||
|
||||
def copy_class(ifc, collector, geometry, root, obj=None):
|
||||
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:
|
||||
element = ifc.get_entity(obj)
|
||||
if not element:
|
||||
return
|
||||
@@ -48,16 +58,16 @@ def copy_class(ifc, collector, geometry, root, obj=None):
|
||||
|
||||
|
||||
def assign_class(
|
||||
ifc,
|
||||
collector,
|
||||
root,
|
||||
obj=None,
|
||||
ifc_class=None,
|
||||
predefined_type=None,
|
||||
should_add_representation=True,
|
||||
context=None,
|
||||
ifc_representation_class=None,
|
||||
):
|
||||
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:
|
||||
if ifc.get_entity(obj):
|
||||
return
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -24,8 +24,10 @@ 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
|
||||
@@ -319,7 +321,7 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
return new_mesh
|
||||
|
||||
@classmethod
|
||||
def get_active_representation(cls, obj):
|
||||
def get_active_representation(cls, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""< 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)
|
||||
@@ -459,7 +461,9 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
return f"{representation.ContextOfItems.id()}/{representation.id()}"
|
||||
|
||||
@classmethod
|
||||
def get_styles(cls, obj, only_assigned_to_faces=False):
|
||||
def get_styles(
|
||||
cls, obj: bpy.types.Object, only_assigned_to_faces: bool = False
|
||||
) -> list[Union[ifcopenshell.entity_instance, None]]:
|
||||
styles = [tool.Style.get_style(s.material) for s in obj.material_slots if s.material]
|
||||
if not only_assigned_to_faces:
|
||||
return styles
|
||||
@@ -467,8 +471,15 @@ 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
|
||||
|
||||
@@ -560,11 +571,11 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
new.value = element[i]
|
||||
|
||||
@classmethod
|
||||
def is_body_representation(cls, representation):
|
||||
def is_body_representation(cls, representation: ifcopenshell.entity_instance) -> bool:
|
||||
return representation.ContextOfItems.ContextIdentifier == "Body"
|
||||
|
||||
@classmethod
|
||||
def is_box_representation(cls, representation):
|
||||
def is_box_representation(cls, representation: ifcopenshell.entity_instance) -> bool:
|
||||
return representation.ContextOfItems.ContextIdentifier == "Box"
|
||||
|
||||
@classmethod
|
||||
@@ -572,11 +583,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):
|
||||
def is_mapped_representation(cls, representation: ifcopenshell.entity_instance) -> bool:
|
||||
return representation.RepresentationType == "MappedRepresentation"
|
||||
|
||||
@classmethod
|
||||
def is_meshlike(cls, representation):
|
||||
def is_meshlike(cls, representation: ifcopenshell.entity_instance) -> bool:
|
||||
if ifcopenshell.util.representation.resolve_representation(representation).RepresentationType in (
|
||||
"AdvancedBrep",
|
||||
"Annotation2D",
|
||||
@@ -656,7 +667,9 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
bpy.data.objects.remove(obj)
|
||||
|
||||
@classmethod
|
||||
def resolve_mapped_representation(cls, representation):
|
||||
def resolve_mapped_representation(
|
||||
cls, representation: ifcopenshell.entity_instance
|
||||
) -> ifcopenshell.entity_instance:
|
||||
if representation.RepresentationType == "MappedRepresentation":
|
||||
return cls.resolve_mapped_representation(representation.Items[0].MappingSource.MappedRepresentation)
|
||||
return representation
|
||||
|
||||
@@ -97,7 +97,9 @@ class Misc(blenderbim.core.tool.Misc):
|
||||
IfcStore.edited_objs.add(obj)
|
||||
|
||||
@classmethod
|
||||
def split_objects_with_cutter(cls, objs, cutter):
|
||||
def split_objects_with_cutter(
|
||||
cls, objs: list[bpy.types.Object], cutter: bpy.types.Object
|
||||
) -> list[bpy.types.Object]:
|
||||
cutter_mesh = cutter.data
|
||||
|
||||
bm = bmesh.new()
|
||||
|
||||
@@ -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(), pset=pset)
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
|
||||
|
||||
@classmethod
|
||||
def get_flow_segment_axis(cls, obj):
|
||||
|
||||
@@ -21,6 +21,7 @@ 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
|
||||
|
||||
@@ -29,6 +29,7 @@ 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):
|
||||
@@ -129,7 +130,7 @@ class Root(blenderbim.core.tool.Root):
|
||||
return ifcopenshell.util.representation.get_representation(element, context=context.ContextType)
|
||||
|
||||
@classmethod
|
||||
def get_element_type(cls, element):
|
||||
def get_element_type(cls, element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
|
||||
return ifcopenshell.util.element.get_type(element)
|
||||
|
||||
@classmethod
|
||||
@@ -282,8 +283,12 @@ class Root(blenderbim.core.tool.Root):
|
||||
|
||||
@classmethod
|
||||
def run_geometry_add_representation(
|
||||
cls, obj=None, context=None, ifc_representation_class=None, profile_set_usage=None
|
||||
):
|
||||
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:
|
||||
return blenderbim.core.geometry.add_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
|
||||
@@ -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,6 +105,7 @@ 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
|
||||
@@ -117,6 +118,7 @@ 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
|
||||
@@ -172,6 +174,7 @@ 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"
|
||||
@@ -184,6 +187,7 @@ 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,6 +19,7 @@
|
||||
import bpy
|
||||
import math
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.tool as tool
|
||||
from mathutils import Vector
|
||||
|
||||
@@ -59,7 +59,6 @@
|
||||
#include <GeomAPI_ProjectPointOnSurf.hxx>
|
||||
#include <Geom_Plane.hxx>
|
||||
#include <IntTools_FaceFace.hxx>
|
||||
#include <STEPConstruct_PointHasher.hxx>
|
||||
#include "clash_utils.h"
|
||||
|
||||
#ifdef WITH_HDF5
|
||||
|
||||
@@ -67,6 +67,20 @@ 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"
|
||||
@@ -143,6 +157,10 @@ 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"}),
|
||||
}
|
||||
|
||||
|
||||
@@ -324,8 +342,18 @@ def wrap_usecase(usecase_path, usecase):
|
||||
|
||||
try:
|
||||
result = usecase(*args, **settings)
|
||||
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."
|
||||
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."
|
||||
)
|
||||
raise TypeError(msg) from e
|
||||
|
||||
if should_run_listeners:
|
||||
|
||||
@@ -89,7 +89,6 @@ def assign_connection_geometry(
|
||||
usecase.axis = axis
|
||||
usecase.ref_direction = ref_direction
|
||||
usecase.unit_scale = unit_scale
|
||||
usecase.ifc_vertices = []
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
|
||||
@@ -110,7 +110,9 @@ class Usecase:
|
||||
"IfcCalendarDate", **ifcopenshell.util.date.datetime2ifc(edition_date, "IfcCalendarDate")
|
||||
)
|
||||
else:
|
||||
result.EditionDate = ifcopenshell.util.date.datetime2ifc(edition_date, "IfcDate")
|
||||
if edition_date:
|
||||
edition_date = ifcopenshell.util.date.datetime2ifc(edition_date, "IfcDate")
|
||||
result.EditionDate = edition_date
|
||||
|
||||
self.relate_to_project(result)
|
||||
|
||||
|
||||
@@ -16,8 +16,17 @@
|
||||
# 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, context_type=None, context_identifier=None, target_view=None, parent=None) -> None:
|
||||
|
||||
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:
|
||||
"""Adds a new geometric representation context
|
||||
|
||||
In IFC, physical objects may have zero, one, or multiple geometric
|
||||
@@ -87,7 +96,7 @@ def add_context(file, context_type=None, context_identifier=None, target_view=No
|
||||
|
||||
:param context_type: The type of the context, must be one of "Model" or
|
||||
"Plan" only.
|
||||
:type context_type: str
|
||||
:type context_type: str, optional
|
||||
: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
|
||||
@@ -104,7 +113,7 @@ def add_context(file, context_type=None, context_identifier=None, target_view=No
|
||||
:type parent: ifcopenshell.entity_instance, optional
|
||||
:return: the newly created IfcGeometricRepresentationContext or
|
||||
IfcGeometricRepresentationSubContext entity
|
||||
:rtype: ifcopenshell.entity_instance, optional
|
||||
:rtype: ifcopenshell.entity_instance
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
@@ -16,8 +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/>.
|
||||
|
||||
import ifcopenshell
|
||||
from typing import Any
|
||||
|
||||
def edit_context(file, context, attributes) -> None:
|
||||
|
||||
def edit_context(file: ifcopenshell.file, context: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
|
||||
"""Edits the attributes of an IfcGeometricRepresentationContext
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
@@ -26,7 +29,7 @@ def edit_context(file, context, attributes) -> None:
|
||||
: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, optional
|
||||
:type attributes: dict
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
@@ -44,7 +47,7 @@ def edit_context(file, context, attributes) -> None:
|
||||
ifcopenshell.api.run("context.edit_context", model,
|
||||
context=body, attributes={"ContextIdentifier": "Body"})
|
||||
"""
|
||||
settings = {"context": context, "attributes": attributes or {}}
|
||||
settings = {"context": context, "attributes": attributes}
|
||||
|
||||
for name, value in settings["attributes"].items():
|
||||
setattr(settings["context"], name, value)
|
||||
|
||||
@@ -22,7 +22,9 @@ from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def add_cost_schedule(file: ifcopenshell.file, name: Optional[str] = None, predefined_type="NOTDEFINED") -> None:
|
||||
def add_cost_schedule(
|
||||
file: ifcopenshell.file, name: Optional[str] = None, predefined_type: str = "NOTDEFINED"
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""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, Optional
|
||||
from typing import Any
|
||||
|
||||
|
||||
def edit_information(
|
||||
file: ifcopenshell.file,
|
||||
information: ifcopenshell.entity_instance,
|
||||
attributes: Optional[dict[str, Any]] = None,
|
||||
attributes: dict[str, Any],
|
||||
) -> 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, optional
|
||||
:type attributes: dict
|
||||
: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 or {}}
|
||||
settings = {"information": information, "attributes": attributes}
|
||||
|
||||
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, Optional
|
||||
from typing import Any
|
||||
|
||||
|
||||
def edit_reference(
|
||||
file: ifcopenshell.file,
|
||||
reference: ifcopenshell.entity_instance,
|
||||
attributes: Optional[dict[str, Any]] = None,
|
||||
attributes: dict[str, Any],
|
||||
) -> 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, optional
|
||||
:type attributes: dict
|
||||
: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 or {}}
|
||||
settings = {"reference": reference, "attributes": attributes}
|
||||
|
||||
for name, value in settings["attributes"].items():
|
||||
setattr(settings["reference"], name, value)
|
||||
|
||||
@@ -17,9 +17,14 @@
|
||||
# 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, context=None, axis=None) -> None:
|
||||
def add_axis_representation(
|
||||
file: ifcopenshell.file, context: ifcopenshell.entity_instance, axis: tuple[COORD, COORD]
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Adds a new axis representation
|
||||
|
||||
Certain objects are typically "axis-based", such as walls, beams,
|
||||
|
||||
@@ -16,27 +16,51 @@
|
||||
# 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
|
||||
|
||||
|
||||
def add_boolean(file, **usecase_settings) -> None:
|
||||
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
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {
|
||||
"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,
|
||||
"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,
|
||||
}
|
||||
for key, value in usecase_settings.items():
|
||||
usecase.settings[key] = value
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
|
||||
@@ -16,13 +16,15 @@
|
||||
# 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
|
||||
|
||||
import collections
|
||||
from typing import Any, Optional, Literal, Union
|
||||
import dataclasses
|
||||
|
||||
|
||||
SUPPORTED_DOOR_TYPES = (
|
||||
@@ -38,9 +40,14 @@ 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.
|
||||
@@ -69,80 +76,212 @@ def create_ifc_door_lining(
|
||||
return door_lining
|
||||
|
||||
|
||||
def create_ifc_box(builder: ShapeBuilder, size: Vector, position: Vector = V(0, 0, 0).freeze()):
|
||||
def create_ifc_box(
|
||||
builder: ShapeBuilder, size: Vector, position: Vector = V(0, 0, 0).freeze()
|
||||
) -> ifcopenshell.entity_instance:
|
||||
rect = builder.rectangle(size.xy)
|
||||
box = builder.extrude(rect, size.z, position=position, extrusion_vector=V(0, 0, 1))
|
||||
return box
|
||||
|
||||
|
||||
def add_door_representation(file, **usecase_settings) -> None:
|
||||
"""units in usecase_settings expected to be in ifc project units"""
|
||||
# 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
|
||||
|
||||
"""
|
||||
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
|
||||
usecase.settings = {"unit_scale": ifcopenshell.util.unit.calculate_unit_scale(usecase.file)}
|
||||
usecase.settings.update(
|
||||
# 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(
|
||||
{
|
||||
"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
|
||||
},
|
||||
"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,
|
||||
}
|
||||
)
|
||||
for key, value in usecase_settings.items():
|
||||
usecase.settings[key] = value
|
||||
usecase.settings = settings
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
|
||||
@@ -19,13 +19,17 @@
|
||||
import ifcopenshell.util.unit
|
||||
|
||||
|
||||
def add_footprint_representation(file, **usecase_settings) -> None:
|
||||
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:
|
||||
settings = {
|
||||
"context": None, # IfcGeometricRepresentationContext
|
||||
"curves": [], # A list of IFC curves to include in the curve set
|
||||
"context": context,
|
||||
"curves": curves,
|
||||
}
|
||||
for key, value in usecase_settings.items():
|
||||
settings[key] = value
|
||||
|
||||
return file.createIfcShapeRepresentation(
|
||||
settings["context"],
|
||||
|
||||
@@ -17,26 +17,43 @@
|
||||
# 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, **usecase_settings) -> None:
|
||||
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:
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {
|
||||
"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
|
||||
"context": context,
|
||||
"vertices": vertices,
|
||||
"edges": edges,
|
||||
"faces": faces,
|
||||
"coordinate_offset": cooridnate_offset,
|
||||
"unit_scale": unit_scale,
|
||||
"force_faceted_brep": force_faceted_brep,
|
||||
}
|
||||
for key, value in usecase_settings.items():
|
||||
usecase.settings[key] = value
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
|
||||
@@ -19,23 +19,35 @@
|
||||
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, **usecase_settings) -> None:
|
||||
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:
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {
|
||||
"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),
|
||||
"context": context,
|
||||
"profile": profile,
|
||||
"depth": depth,
|
||||
"cardinal_point": cardinal_point,
|
||||
"clippings": clippings if clippings is not None else [],
|
||||
"placement_zx_axes": placement_zx_axes,
|
||||
}
|
||||
for key, value in usecase_settings.items():
|
||||
usecase.settings[key] = value
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
|
||||
@@ -22,46 +22,100 @@ 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):
|
||||
def mm(x: float) -> float:
|
||||
"""mm to meters shortcut for readability"""
|
||||
return x / 1000
|
||||
|
||||
|
||||
def add_railing_representation(file, **usecase_settings) -> None:
|
||||
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:
|
||||
"""
|
||||
units in usecase_settings expected to be in ifc project units
|
||||
Units are expected to be in IFC project units.
|
||||
|
||||
`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
|
||||
: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 expected to be a list of Vector objects
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {"unit_scale": ifcopenshell.util.unit.calculate_unit_scale(usecase.file)}
|
||||
usecase.settings.update(
|
||||
# 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(
|
||||
{
|
||||
"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,
|
||||
"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,
|
||||
}
|
||||
)
|
||||
usecase.settings = settings
|
||||
|
||||
for key, value in usecase_settings.items():
|
||||
usecase.settings[key] = value
|
||||
|
||||
if usecase.settings["railing_type"] != "WALL_MOUNTED_HANDRAIL":
|
||||
if railing_type != "WALL_MOUNTED_HANDRAIL":
|
||||
raise Exception('Only "WALL_MOUNTED_HANDRAIL" railing_type is supported at the moment.')
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
@@ -16,11 +16,12 @@
|
||||
# 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
|
||||
import bpy.types
|
||||
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))
|
||||
@@ -28,7 +29,44 @@ X_AXIS = Vector((1, 0, 0))
|
||||
EPSILON = 1e-6
|
||||
|
||||
|
||||
def add_representation(file: ifcopenshell.file, **usecase_settings) -> ifcopenshell.entity_instance:
|
||||
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:
|
||||
# lazy import Helper to avoid circular import
|
||||
if "Helper" not in globals():
|
||||
from blenderbim.bim.module.geometry.helper import Helper
|
||||
@@ -37,34 +75,27 @@ def add_representation(file: ifcopenshell.file, **usecase_settings) -> ifcopensh
|
||||
# TODO: This usecase currently depends on Blender's data model
|
||||
usecase.file = file
|
||||
usecase.settings = {
|
||||
"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
|
||||
"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,
|
||||
}
|
||||
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 (
|
||||
@@ -473,11 +504,10 @@ class Usecase:
|
||||
geom_data = self.settings["geometry"]
|
||||
|
||||
if isinstance(geom_data, bpy.types.Mesh):
|
||||
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)
|
||||
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)
|
||||
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
@@ -17,22 +17,32 @@
|
||||
# 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, **usecase_settings) -> None:
|
||||
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:
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {
|
||||
"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
|
||||
"context": context,
|
||||
"depth": depth,
|
||||
"x_angle": x_angle,
|
||||
"clippings": clippings if clippings is not None else [],
|
||||
}
|
||||
for key, value in usecase_settings.items():
|
||||
usecase.settings[key] = value
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
|
||||
@@ -18,27 +18,39 @@
|
||||
|
||||
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, **usecase_settings) -> None:
|
||||
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:
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {
|
||||
"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
|
||||
"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 [],
|
||||
}
|
||||
for key, value in usecase_settings.items():
|
||||
usecase.settings[key] = value
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
|
||||
@@ -16,11 +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/>.
|
||||
|
||||
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 collections
|
||||
import dataclasses
|
||||
from typing import Any, Optional, Literal, Union
|
||||
|
||||
|
||||
# SCHEMAS describe panels setup
|
||||
@@ -42,6 +45,11 @@ 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()
|
||||
):
|
||||
@@ -210,71 +218,209 @@ def create_ifc_window(
|
||||
return output_items
|
||||
|
||||
|
||||
def add_window_representation(file, **usecase_settings) -> None:
|
||||
"""units in usecase_settings expected to be in ifc project units"""
|
||||
# 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
|
||||
|
||||
"""
|
||||
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
|
||||
usecase.settings = {"unit_scale": ifcopenshell.util.unit.calculate_unit_scale(usecase.file)}
|
||||
usecase.settings.update(
|
||||
# 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(
|
||||
{
|
||||
"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
|
||||
},
|
||||
],
|
||||
"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,
|
||||
}
|
||||
)
|
||||
|
||||
for key, value in usecase_settings.items():
|
||||
usecase.settings[key] = value
|
||||
usecase.settings = settings
|
||||
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, **usecase_settings) -> None:
|
||||
def assign_representation(
|
||||
file: ifcopenshell.file, product: ifcopenshell.entity_instance, representation: ifcopenshell.entity_instance
|
||||
) -> None:
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {"product": None, "representation": None}
|
||||
for key, value in usecase_settings.items():
|
||||
usecase.settings[key] = value
|
||||
usecase.settings = {"product": product, "representation": representation}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
|
||||
@@ -20,16 +20,20 @@ import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.element
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def connect_element(file, **usecase_settings) -> None:
|
||||
def connect_element(
|
||||
file: ifcopenshell.file,
|
||||
relating_element: ifcopenshell.entity_instance,
|
||||
related_element: ifcopenshell.entity_instance,
|
||||
description: Optional[str] = None,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
settings = {
|
||||
"relating_element": None,
|
||||
"related_element": None,
|
||||
"description": None,
|
||||
"relating_element": relating_element,
|
||||
"related_element": related_element,
|
||||
"description": description,
|
||||
}
|
||||
for key, value in usecase_settings.items():
|
||||
settings[key] = value
|
||||
|
||||
incompatible_connections = []
|
||||
|
||||
|
||||
@@ -20,18 +20,24 @@ import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.element
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def connect_path(file, **usecase_settings) -> None:
|
||||
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:
|
||||
settings = {
|
||||
"relating_element": None,
|
||||
"related_element": None,
|
||||
"relating_connection": "NOTDEFINED",
|
||||
"related_connection": "NOTDEFINED",
|
||||
"description": None,
|
||||
"relating_element": relating_element,
|
||||
"related_element": related_element,
|
||||
"relating_connection": relating_connection,
|
||||
"related_connection": related_connection,
|
||||
"description": description,
|
||||
}
|
||||
for key, value in usecase_settings.items():
|
||||
settings[key] = value
|
||||
|
||||
incompatible_connections = []
|
||||
for rel in settings["relating_element"].ConnectedTo:
|
||||
|
||||
@@ -22,8 +22,16 @@ import ifcopenshell.util.unit
|
||||
|
||||
|
||||
def create_2pt_wall(
|
||||
file, element=None, context=None, p1=None, p2=None, elevation=None, height=None, thickness=None, is_si=True
|
||||
) -> None:
|
||||
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:
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {
|
||||
|
||||
@@ -20,30 +20,31 @@ import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def disconnect_element(file, **usecase_settings) -> None:
|
||||
settings = {
|
||||
"relating_element": None,
|
||||
"related_element": None,
|
||||
}
|
||||
for key, value in usecase_settings.items():
|
||||
settings[key] = value
|
||||
|
||||
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
|
||||
incompatible_connections = []
|
||||
|
||||
for rel in settings["relating_element"].ConnectedTo:
|
||||
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatedElement == settings["related_element"]:
|
||||
for rel in relating_element.ConnectedTo:
|
||||
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatedElement == related_element:
|
||||
incompatible_connections.append(rel)
|
||||
|
||||
for rel in settings["relating_element"].ConnectedFrom:
|
||||
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatingElement == settings["related_element"]:
|
||||
for rel in relating_element.ConnectedFrom:
|
||||
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatingElement == related_element:
|
||||
incompatible_connections.append(rel)
|
||||
|
||||
for rel in settings["related_element"].ConnectedTo:
|
||||
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatedElement == settings["relating_element"]:
|
||||
for rel in related_element.ConnectedTo:
|
||||
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatedElement == relating_element:
|
||||
incompatible_connections.append(rel)
|
||||
|
||||
for rel in settings["related_element"].ConnectedFrom:
|
||||
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatingElement == settings["relating_element"]:
|
||||
for rel in related_element.ConnectedFrom:
|
||||
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatingElement == relating_element:
|
||||
incompatible_connections.append(rel)
|
||||
|
||||
if incompatible_connections:
|
||||
|
||||
@@ -19,33 +19,36 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
from typing import Optional
|
||||
|
||||
|
||||
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"]:
|
||||
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:
|
||||
connections = [
|
||||
r
|
||||
for r in settings["element"].ConnectedTo
|
||||
if r.is_a("IfcRelConnectsPathElements") and r.RelatingConnectionType == settings["connection_type"]
|
||||
for r in element.ConnectedTo
|
||||
if r.is_a("IfcRelConnectsPathElements") and r.RelatingConnectionType == connection_type
|
||||
] + [
|
||||
r
|
||||
for r in settings["element"].ConnectedFrom
|
||||
if r.is_a("IfcRelConnectsPathElements") and r.RelatedConnectionType == settings["connection_type"]
|
||||
for r in element.ConnectedFrom
|
||||
if r.is_a("IfcRelConnectsPathElements") and r.RelatedConnectionType == connection_type
|
||||
]
|
||||
else:
|
||||
elif related_element:
|
||||
connections = [
|
||||
r
|
||||
for r in settings["relating_element"].ConnectedTo
|
||||
if r.is_a("IfcRelConnectsPathElements") and r.RelatedElement == settings["related_element"]
|
||||
for r in relating_element.ConnectedTo
|
||||
if r.is_a("IfcRelConnectsPathElements") and r.RelatedElement == 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=True,
|
||||
should_transform_children=False,
|
||||
is_si: bool = True,
|
||||
should_transform_children: bool = False,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
|
||||
@@ -16,14 +16,15 @@
|
||||
# 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, **usecase_settings) -> None:
|
||||
|
||||
def map_representation(
|
||||
file: ifcopenshell.file, representation: ifcopenshell.entity_instance
|
||||
) -> ifcopenshell.entity_instance:
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {"representation": None}
|
||||
usecase.ifc_vertices = []
|
||||
for key, value in usecase_settings.items():
|
||||
usecase.settings[key] = value
|
||||
usecase.settings = {"representation": representation}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
|
||||
@@ -19,12 +19,10 @@
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def remove_boolean(file, **usecase_settings) -> None:
|
||||
def remove_boolean(file: ifcopenshell.file, item: ifcopenshell.entity_instance) -> None:
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {"item": None}
|
||||
for key, value in usecase_settings.items():
|
||||
usecase.settings[key] = value
|
||||
usecase.settings = {"item": item}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
|
||||
@@ -20,12 +20,12 @@ import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def unassign_representation(file, **usecase_settings) -> None:
|
||||
def unassign_representation(
|
||||
file: ifcopenshell.file, product: ifcopenshell.entity_instance, representation: ifcopenshell.entity_instance
|
||||
) -> None:
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {"product": None, "representation": None}
|
||||
for key, value in usecase_settings.items():
|
||||
usecase.settings[key] = value
|
||||
usecase.settings = {"product": product, "representation": representation}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
|
||||
@@ -16,8 +16,10 @@
|
||||
# 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) -> None:
|
||||
|
||||
def add_georeferencing(file: ifcopenshell.file) -> None:
|
||||
"""Add empty georeferencing entities to a model
|
||||
|
||||
By default, models are not georeferenced. Georeferencing requires two
|
||||
|
||||
@@ -16,8 +16,16 @@
|
||||
# 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, map_conversion=None, projected_crs=None, true_north=None) -> None:
|
||||
|
||||
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:
|
||||
"""Edits the attributes of a map conversion, projected CRS, and true north
|
||||
|
||||
Setting the correct georeferencing parameters is a complex topic and
|
||||
@@ -47,7 +55,7 @@ def edit_georeferencing(file, map_conversion=None, projected_crs=None, true_nort
|
||||
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: list[float]
|
||||
:type true_north: tuple[float, float], optional
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
@@ -101,7 +109,7 @@ class Usecase:
|
||||
self.set_true_north()
|
||||
|
||||
def set_true_north(self):
|
||||
if self.settings["true_north"] == []:
|
||||
if self.settings["true_north"] == None:
|
||||
return
|
||||
for context in self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
if context.TrueNorth:
|
||||
@@ -111,6 +119,8 @@ 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,8 +16,10 @@
|
||||
# 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) -> None:
|
||||
|
||||
def remove_georeferencing(file: ifcopenshell.file) -> None:
|
||||
"""Remove georeferencing data
|
||||
|
||||
All georeferencing parameters such as projected CRS and map conversion
|
||||
|
||||
@@ -16,13 +16,18 @@
|
||||
# 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, axis_curve=None, grid_axis=None) -> None:
|
||||
def create_axis_curve(
|
||||
file: ifcopenshell.file, axis_curve: bpy.types.Object, grid_axis: ifcopenshell.entity_instance
|
||||
) -> None:
|
||||
"""Adds curve geometry to a grid axis to represent the axis extents
|
||||
|
||||
This currently depends on the Blender geometry kernel to function.
|
||||
|
||||
@@ -15,9 +15,17 @@
|
||||
#
|
||||
# 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, axis_tag=None, same_sense=None, uvw_axes=None, grid=None) -> None:
|
||||
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:
|
||||
"""Adds a new grid axis to a grid
|
||||
|
||||
An IFC grid will typically have a minimum of two axes which will be
|
||||
@@ -66,17 +74,9 @@ def create_grid_axis(file, axis_tag=None, same_sense=None, uvw_axes=None, grid=N
|
||||
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": settings["axis_tag"], "SameSense": settings["same_sense"]}
|
||||
)
|
||||
axes = list(getattr(settings["grid"], settings["uvw_axes"]) or [])
|
||||
element = file.create_entity("IfcGridAxis", **{"AxisTag": axis_tag, "SameSense": same_sense})
|
||||
axes = list(getattr(grid, uvw_axes) or [])
|
||||
axes.append(element)
|
||||
setattr(settings["grid"], settings["uvw_axes"], axes)
|
||||
setattr(grid, uvw_axes, axes)
|
||||
return element
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def remove_grid_axis(file, axis=None) -> None:
|
||||
def remove_grid_axis(file: ifcopenshell.file, axis: ifcopenshell.entity_instance) -> None:
|
||||
"""Removes a grid axis from a grid
|
||||
|
||||
:param axis: The IfcGridAxis you want to remove.
|
||||
@@ -43,9 +43,8 @@ def remove_grid_axis(file, axis=None) -> None:
|
||||
# Let's remove it!
|
||||
ifcopenshell.api.run("grid.remove_grid_axis", model, axis=axis_2)
|
||||
"""
|
||||
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"])
|
||||
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)
|
||||
|
||||
@@ -19,9 +19,12 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.guid
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def add_group(file, Name="Unnamed", Description=None) -> None:
|
||||
def add_group(
|
||||
file: ifcopenshell.file, name: str = "Unnamed", description: Optional[str] = None
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Adds a new group
|
||||
|
||||
An IFC group is an arbitrary collection of products, which are typically
|
||||
@@ -34,8 +37,8 @@ def add_group(file, Name="Unnamed", Description=None) -> None:
|
||||
|
||||
: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
|
||||
|
||||
@@ -43,11 +46,11 @@ def add_group(file, Name="Unnamed", Description=None) -> None:
|
||||
|
||||
.. 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(
|
||||
@@ -55,7 +58,7 @@ def add_group(file, Name="Unnamed", Description=None) -> None:
|
||||
**{
|
||||
"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,9 +15,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/>.
|
||||
import ifcopenshell
|
||||
from typing import Any
|
||||
|
||||
|
||||
def edit_group(file, group=None, attributes=None) -> None:
|
||||
def edit_group(file: ifcopenshell.file, group: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
|
||||
"""Edits the attributes of an IfcGroup
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
@@ -26,7 +28,7 @@ def edit_group(file, group=None, attributes=None) -> None:
|
||||
: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, optional
|
||||
:type attributes: dict
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
@@ -34,11 +36,11 @@ def edit_group(file, group=None, attributes=None) -> None:
|
||||
|
||||
.. 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 or {}}
|
||||
settings = {"group": group, "attributes": attributes}
|
||||
|
||||
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, group=None) -> None:
|
||||
def remove_group(file: ifcopenshell.file, group: ifcopenshell.entity_instance) -> None:
|
||||
"""Removes a group
|
||||
|
||||
All products assigned to the group will remain, but the relationship to
|
||||
@@ -36,7 +36,7 @@ def remove_group(file, group=None) -> None:
|
||||
|
||||
.. 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,9 +19,12 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def update_group_products(file, group=None, products=None) -> None:
|
||||
def update_group_products(
|
||||
file: ifcopenshell.file, group: ifcopenshell.entity_instance, products: list[ifcopenshell.entity_instance]
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Sets a group products to be an explicit list of products
|
||||
|
||||
Any previous products assigned to that group will have their assignment
|
||||
@@ -38,7 +41,7 @@ def update_group_products(file, group=None, products=None) -> None:
|
||||
|
||||
.. 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)
|
||||
"""
|
||||
@@ -58,11 +61,17 @@ def update_group_products(file, group=None, products=None) -> None:
|
||||
}
|
||||
)
|
||||
else:
|
||||
# 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")]
|
||||
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:]
|
||||
|
||||
rel.RelatedObjects = settings["products"]
|
||||
for g in existing_sub_groups:
|
||||
rel.RelatedObjects.add(g)
|
||||
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]
|
||||
|
||||
@@ -15,9 +15,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/>.
|
||||
import ifcopenshell
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def add_layer(file, Name=None) -> None:
|
||||
def add_layer(file: ifcopenshell.file, name: str = "Unnamed") -> ifcopenshell.entity_instance:
|
||||
"""Adds a new layer
|
||||
|
||||
An IFC layer is like a CAD layer. Portions of an object's geometry
|
||||
@@ -32,15 +34,13 @@ def add_layer(file, Name=None) -> None:
|
||||
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")
|
||||
"""
|
||||
settings = {"Name": Name or "Unnamed"}
|
||||
|
||||
return file.create_entity("IfcPresentationLayerAssignment", Name=settings["Name"])
|
||||
return file.create_entity("IfcPresentationLayerAssignment", Name=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,9 +15,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/>.
|
||||
import ifcopenshell
|
||||
from typing import Any
|
||||
|
||||
|
||||
def edit_layer(file, layer=None, attributes=None) -> None:
|
||||
def edit_layer(file: ifcopenshell.file, layer: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
|
||||
"""Edits the attributes of an IfcPresentationLayerAssignment
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
@@ -26,7 +28,7 @@ def edit_layer(file, layer=None, attributes=None) -> None:
|
||||
: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, optional
|
||||
:type attributes: dict
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
@@ -34,11 +36,11 @@ def edit_layer(file, layer=None, attributes=None) -> None:
|
||||
|
||||
.. 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 or {}}
|
||||
settings = {"layer": layer, "attributes": attributes}
|
||||
|
||||
for name, value in settings["attributes"].items():
|
||||
setattr(settings["layer"], name, value)
|
||||
|
||||
@@ -15,9 +15,10 @@
|
||||
#
|
||||
# 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, layer=None) -> None:
|
||||
def remove_layer(file: ifcopenshell.file, layer: ifcopenshell.entity_instance) -> None:
|
||||
"""Removes a layer
|
||||
|
||||
All representation items assigned to the layer will remain, but the
|
||||
@@ -32,9 +33,7 @@ def remove_layer(file, layer=None) -> None:
|
||||
|
||||
.. 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)
|
||||
"""
|
||||
settings = {"layer": layer}
|
||||
|
||||
file.remove(settings["layer"])
|
||||
file.remove(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, name=None) -> None:
|
||||
def add_library(file: ifcopenshell.file, name: str) -> ifcopenshell.entity_instance:
|
||||
"""Adds a new library to the project
|
||||
|
||||
A library is an external data source that is related to the project. It
|
||||
@@ -60,6 +60,4 @@ def add_library(file, name=None) -> None:
|
||||
|
||||
ifcopenshell.api.run("library.add_library", model, name="Brickschema")
|
||||
"""
|
||||
settings = {"name": name}
|
||||
|
||||
return file.create_entity("IfcLibraryInformation", Name=settings["name"])
|
||||
return file.create_entity("IfcLibraryInformation", Name=name)
|
||||
|
||||
@@ -15,9 +15,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
|
||||
import ifcopenshell.util.date
|
||||
import datetime
|
||||
from typing import Any
|
||||
|
||||
|
||||
def edit_library(file, library=None, attributes=None) -> None:
|
||||
def edit_library(file: ifcopenshell.file, library: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
|
||||
"""Edits the attributes of an IfcLibraryInformation
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
@@ -26,7 +30,7 @@ def edit_library(file, library=None, attributes=None) -> None:
|
||||
: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, optional
|
||||
:type attributes: dict
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
@@ -39,7 +43,16 @@ def edit_library(file, library=None, attributes=None) -> None:
|
||||
attributes={"Description": "A Brickschema TTL including only mechanical distribution systems."})
|
||||
"""
|
||||
|
||||
settings = {"library": library, "attributes": attributes or {}}
|
||||
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
|
||||
|
||||
for name, value in settings["attributes"].items():
|
||||
setattr(settings["library"], name, value)
|
||||
for name, value in attributes.items():
|
||||
setattr(library, name, value)
|
||||
|
||||
@@ -15,9 +15,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
|
||||
|
||||
|
||||
def edit_reference(file, reference=None, attributes=None) -> None:
|
||||
def edit_reference(
|
||||
file: ifcopenshell.file, reference: ifcopenshell.entity_instance, attributes: dict[str, Any]
|
||||
) -> None:
|
||||
"""Edits the attributes of an IfcLibraryReference
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
@@ -26,7 +30,7 @@ def edit_reference(file, reference=None, attributes=None) -> None:
|
||||
: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, optional
|
||||
:type attributes: dict
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
@@ -40,7 +44,7 @@ def edit_reference(file, reference=None, attributes=None) -> None:
|
||||
ifcopenshell.api.run("library.edit_reference", model,
|
||||
reference=reference, attributes={"Identification": "http://example.org/digitaltwin#AHU01"})
|
||||
"""
|
||||
settings = {"reference": reference, "attributes": attributes or {}}
|
||||
settings = {"reference": reference, "attributes": attributes}
|
||||
|
||||
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, library=None) -> None:
|
||||
def remove_library(file: ifcopenshell.file, library: ifcopenshell.entity_instance) -> None:
|
||||
"""Removes a library
|
||||
|
||||
All references along with their relationships will also be removed. Any
|
||||
@@ -38,14 +38,23 @@ def remove_library(file, library=None) -> None:
|
||||
library = ifcopenshell.api.run("library.add_library", model, name="Brickschema")
|
||||
ifcopenshell.api.run("library.remove_library", model, library=library)
|
||||
"""
|
||||
settings = {"library": library}
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
@@ -40,11 +40,14 @@ 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)
|
||||
"""
|
||||
settings = {"reference": reference}
|
||||
if file.schema != "IFC2X3":
|
||||
rels = reference.LibraryRefForObjects
|
||||
else:
|
||||
rels = [rel for rel in file.by_type("IfcRelAssociatesLibrary") if rel.RelatingLibrary == reference]
|
||||
|
||||
for rel in settings["reference"].LibraryRefForObjects:
|
||||
for rel in rels:
|
||||
history = rel.OwnerHistory
|
||||
file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
file.remove(settings["reference"])
|
||||
file.remove(reference)
|
||||
|
||||
@@ -15,9 +15,12 @@
|
||||
#
|
||||
# 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, constituent_set=None, material=None) -> None:
|
||||
def add_constituent(
|
||||
file: ifcopenshell.file, constituent_set: ifcopenshell.entity_instance, material: ifcopenshell.entity_instance
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Adds a new constituent to a constituent set
|
||||
|
||||
A constituent describes how a portion of an object is made out of a
|
||||
|
||||
@@ -15,9 +15,12 @@
|
||||
#
|
||||
# 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, layer_set=None, material=None) -> None:
|
||||
def add_layer(
|
||||
file: ifcopenshell.file, layer_set: ifcopenshell.entity_instance, material: ifcopenshell.entity_instance
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Adds a new layer to a layer set
|
||||
|
||||
A layer represents a portion of material within a layered build up,
|
||||
|
||||
@@ -19,7 +19,9 @@
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
def add_list_item(file, material_list=None, material=None) -> None:
|
||||
def add_list_item(
|
||||
file: ifcopenshell.file, material_list: ifcopenshell.entity_instance, material: ifcopenshell.entity_instance
|
||||
) -> 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,9 +15,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 Optional
|
||||
|
||||
|
||||
def add_material(file, name=None, category=None) -> None:
|
||||
def add_material(
|
||||
file: ifcopenshell.file, name: Optional[str] = None, category: Optional[str] = None
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Adds a new material
|
||||
|
||||
A material in IFC represents a physical material, such as timber, steel,
|
||||
@@ -48,7 +52,7 @@ def add_material(file, name=None, category=None) -> None:
|
||||
|
||||
:param name: The name of the material, typically tagged in a finishes
|
||||
drawing or schedule.
|
||||
:type name: str
|
||||
:type name: str, optional
|
||||
:param category: The category of the material.
|
||||
:type category: str, optional
|
||||
:return: The newly created IfcMaterial
|
||||
|
||||
@@ -15,9 +15,12 @@
|
||||
#
|
||||
# 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, name="Unnamed", set_type="IfcMaterialConstituentSet") -> None:
|
||||
def add_material_set(
|
||||
file: ifcopenshell.file, name: str = "Unnamed", set_type: str = "IfcMaterialConstituentSet"
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Adds a new material set
|
||||
|
||||
IFC allows you to state that objects are made out of multiple materials.
|
||||
|
||||
@@ -19,7 +19,9 @@
|
||||
import ifcopenshell.util.representation
|
||||
|
||||
|
||||
def assign_profile(file, material_profile=None, profile=None) -> None:
|
||||
def assign_profile(
|
||||
file: ifcopenshell.file, material_profile: ifcopenshell.entity_instance, profile: ifcopenshell.entity_instance
|
||||
) -> 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
|
||||
@@ -94,7 +96,8 @@ def assign_profile(file, material_profile=None, profile=None) -> None:
|
||||
|
||||
|
||||
class Usecase:
|
||||
def execute(self):
|
||||
file: ifcopenshell.file
|
||||
def execute(self) -> None:
|
||||
# TODO: handle composite profiles
|
||||
old_profile = self.settings["material_profile"].Profile
|
||||
self.settings["material_profile"].Profile = self.settings["profile"]
|
||||
@@ -117,7 +120,7 @@ class Usecase:
|
||||
# TODO: check remove deep
|
||||
self.file.remove(old_profile)
|
||||
|
||||
def change_profile(self, element):
|
||||
def change_profile(self, element: ifcopenshell.entity_instance) -> None:
|
||||
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, material=None) -> None:
|
||||
def copy_material(file: ifcopenshell.file, material: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||
"""Copies a material
|
||||
|
||||
All material psets and styles are copied. The copied material is not
|
||||
@@ -48,11 +48,23 @@ def copy_material(file, material=None) -> None:
|
||||
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,9 +15,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/>.
|
||||
import ifcopenshell
|
||||
from typing import Any
|
||||
|
||||
|
||||
def edit_assigned_material(file, element=None, attributes=None) -> None:
|
||||
def edit_assigned_material(file: ifcopenshell.file, element: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
|
||||
"""Edits the attributes of an IfcMaterial
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
@@ -26,7 +28,7 @@ def edit_assigned_material(file, element=None, attributes=None) -> None:
|
||||
: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, optional
|
||||
:type attributes: dict
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
@@ -38,7 +40,7 @@ def edit_assigned_material(file, element=None, attributes=None) -> None:
|
||||
ifcopenshell.api.run("material.edit_assigned_material", model,
|
||||
element=concrete, attributes={"Description": "40MPA concrete with broom finish"})
|
||||
"""
|
||||
settings = {"element": element, "attributes": attributes or {}}
|
||||
settings = {"element": element, "attributes": attributes}
|
||||
|
||||
for name, value in settings["attributes"].items():
|
||||
setattr(settings["element"], name, value)
|
||||
|
||||
@@ -15,9 +15,16 @@
|
||||
#
|
||||
# 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, constituent=None, attributes=None, material=None) -> None:
|
||||
def edit_constituent(
|
||||
file: ifcopenshell.file,
|
||||
constituent: ifcopenshell.entity_instance,
|
||||
attributes: Optional[dict[str, Any]] = None,
|
||||
material: Optional[ifcopenshell.entity_instance] = None,
|
||||
) -> None:
|
||||
"""Edits the attributes of an IfcMaterialConstituent
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
|
||||
@@ -15,9 +15,16 @@
|
||||
#
|
||||
# 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_layer(file, layer=None, attributes=None, material=None) -> None:
|
||||
def edit_layer(
|
||||
file: ifcopenshell.file,
|
||||
layer: ifcopenshell.entity_instance,
|
||||
attributes: Optional[dict[str, Any]] = None,
|
||||
material: Optional[ifcopenshell.entity_instance] = None,
|
||||
) -> None:
|
||||
"""Edits the attributes of an IfcMaterialLayer
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
|
||||
@@ -15,9 +15,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/>.
|
||||
import ifcopenshell
|
||||
from typing import Any
|
||||
|
||||
|
||||
def edit_layer_usage(file, usage=None, attributes=None) -> None:
|
||||
def edit_layer_usage(file: ifcopenshell.file, usage: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
|
||||
"""Edits the attributes of an IfcMaterialLayerSetUsage
|
||||
|
||||
This is typically used to change the offset from the reference line to
|
||||
@@ -29,7 +31,7 @@ def edit_layer_usage(file, usage=None, attributes=None) -> None:
|
||||
:param usage: The IfcMaterialLayerSetUsage entity you want to edit
|
||||
:type usage: ifcopenshell.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict, optional
|
||||
:type attributes: dict
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
@@ -73,7 +75,7 @@ def edit_layer_usage(file, usage=None, attributes=None) -> None:
|
||||
ifcopenshell.api.run("material.edit_layer_usage", model,
|
||||
usage=rel.RelatingMaterial, attributes={"OffsetFromReferenceLine": 200})
|
||||
"""
|
||||
settings = {"usage": usage, "attributes": attributes or {}}
|
||||
settings = {"usage": usage, "attributes": attributes}
|
||||
|
||||
for name, value in settings["attributes"].items():
|
||||
setattr(settings["usage"], name, value)
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
#
|
||||
# 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_material(file, material=None, attributes=None) -> None:
|
||||
def edit_material(file: ifcopenshell.file, material: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
|
||||
"""Edits the attributes of an IfcMaterial"""
|
||||
|
||||
settings = {"material": material, "attributes": attributes or {}}
|
||||
|
||||
for name, value in settings["attributes"].items():
|
||||
setattr(settings["material"], name, value)
|
||||
for name, value in attributes.items():
|
||||
setattr(material, name, value)
|
||||
|
||||
@@ -17,7 +17,17 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
def edit_profile(file, profile=None, attributes=None, profile_def=None, material=None) -> None:
|
||||
from typing import Any, Optional
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
def edit_profile(
|
||||
file: ifcopenshell.file,
|
||||
profile: ifcopenshell.entity_instance,
|
||||
attributes: Optional[dict[str, Any]] = None,
|
||||
profile_def: Optional[ifcopenshell.entity_instance] = None,
|
||||
material: Optional[ifcopenshell.entity_instance] = None,
|
||||
) -> None:
|
||||
"""Edits the attributes of an IfcMaterialProfile
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
|
||||
@@ -15,12 +15,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.geom
|
||||
import ifcopenshell.util.representation
|
||||
from typing import Any
|
||||
|
||||
|
||||
def edit_profile_usage(file, usage=None, attributes=None) -> None:
|
||||
def edit_profile_usage(
|
||||
file: ifcopenshell.file, usage: ifcopenshell.entity_instance, attributes: dict[str, Any]
|
||||
) -> None:
|
||||
"""Edits the attributes of an IfcMaterialProfileSetUsage
|
||||
|
||||
This is typically used to change the cardinal point of the profile.
|
||||
@@ -34,7 +36,7 @@ def edit_profile_usage(file, usage=None, attributes=None) -> None:
|
||||
:param usage: The IfcMaterialProfileSetUsage entity you want to edit
|
||||
:type usage: ifcopenshell.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict, optional
|
||||
:type attributes: dict
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
@@ -91,7 +93,7 @@ def edit_profile_usage(file, usage=None, attributes=None) -> None:
|
||||
usecase = Usecase()
|
||||
|
||||
usecase.file = file
|
||||
usecase.settings = {"usage": usage, "attributes": attributes or {}}
|
||||
usecase.settings = {"usage": usage, "attributes": attributes}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user