mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-24 13:56:50 +00:00
Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4fb34c0816 | |||
| 9b562669d2 | |||
| 295d0a677e | |||
| 9aacafd575 | |||
| ce6ce1b975 | |||
| d064ef1237 | |||
| 2db9bd9c39 | |||
| 7622504d68 | |||
| 221dd9a7a2 | |||
| 1b74a99667 | |||
| 9122f006c7 | |||
| 678dbaa66a | |||
| fb88499197 | |||
| f84adf67cd | |||
| cacb38a251 | |||
| 7c6c01e6b9 | |||
| 4e462a3cf7 | |||
| e7f4c6c9cc | |||
| 7bf8c0bbfb | |||
| c4157673f5 | |||
| 16285e0625 | |||
| a07d3b9669 | |||
| 2d0b356203 | |||
| f666053b63 | |||
| 3bdc969efb | |||
| 932817877c | |||
| 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 |
@@ -36,7 +36,9 @@ if(NOT CMAKE_BUILD_TYPE)
|
||||
endif()
|
||||
|
||||
# use extra version to make pre-release using eg semver
|
||||
set(EXTRA_VERSION "-alpha.3")
|
||||
if(NOT DEFINED EXTRA_VERSION)
|
||||
set(EXTRA_VERSION "-alpha.3")
|
||||
endif()
|
||||
|
||||
option(MINIMAL_BUILD "The build is to make a minimal version of IFC converter from OCCT into IFC." OFF)
|
||||
option(WASM_BUILD "Build a WebAssembly binary." OFF)
|
||||
@@ -1147,9 +1149,10 @@ endif()
|
||||
|
||||
# Packaging
|
||||
list(APPEND CPACK_SOURCE_IGNORE_FILES
|
||||
.git
|
||||
.gitignore
|
||||
"/\\\\.git"
|
||||
"/build/"
|
||||
)
|
||||
set(CPACK_SOURCE_INSTALLED_DIRECTORIES "${CMAKE_SOURCE_DIR}/..;/")
|
||||
set(CPACK_PACKAGE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}${EXTRA_VERSION}")
|
||||
set(CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}${EXTRA_VERSION}")
|
||||
SET(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}${EXTRA_VERSION}-${CMAKE_SYSTEM_NAME}")
|
||||
|
||||
@@ -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)
|
||||
|
||||
+6
-29
@@ -277,34 +277,6 @@ endif
|
||||
cp -r dist/working/pyparsing-2.4.5/pyparsing.py dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by bcf
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/5b/ef/f97c3e1a7efa00e989a793fe15297214fc95ad7d9e3810586bd08ce9f0f3/xmlschema-2.0.2.tar.gz
|
||||
cd dist/working && tar -xzvf xmlschema*
|
||||
cp -r dist/working/xmlschema-2.0.2/xmlschema dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# 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 && tar -xzvf xsdata*
|
||||
cp -r dist/working/xsdata-22.11/xsdata dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by bcf
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/11/bc/5afb61dd5d863e5cf77cd952445c50c17e65953405986f19e97e4389692a/elementpath-3.0.2.tar.gz
|
||||
cd dist/working && tar -xzvf elementpath*
|
||||
cp -r dist/working/elementpath-3.0.2/elementpath dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by bcf
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/21/9f/b251f7f8a76dec1d6651be194dfba8fb8d7781d10ab3987190de8391d08e/six-1.14.0.tar.gz
|
||||
cd dist/working && tar -xzvf six*
|
||||
cp -r dist/working/six-1.14.0/six.py dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by IFCCSV and ifcopenshell.util.selector
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/18/4d/8d522136c37d9e1ea74062b41b8d5e1318ebf45063ae46ce72ed60af223b/lark-parser-0.8.5.tar.gz
|
||||
@@ -366,7 +338,12 @@ endif
|
||||
# Provides Brickschema functionality
|
||||
cd dist/working && . env/bin/activate && $(PIP) install "brickschema[persistence]==0.7.6a2" --target=./site-packages
|
||||
# Required for SVG to DXF conversion
|
||||
cd dist/working && . env/bin/activate && $(PIP) install "ezdxf" --target=./site-packages
|
||||
cd dist/working && . env/bin/activate && $(PIP) install ezdxf --target=./site-packages
|
||||
# Required by bcf
|
||||
cd dist/working && . env/bin/activate && $(PIP) install xsdata --target=./site-packages
|
||||
cd dist/working && . env/bin/activate && $(PIP) install xmlschema --target=./site-packages
|
||||
cd dist/working && . env/bin/activate && $(PIP) install elementpath --target=./site-packages
|
||||
cd dist/working && . env/bin/activate && $(PIP) install six --target=./site-packages
|
||||
cp -r dist/working/site-packages/* dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
|
||||
@@ -120,11 +120,16 @@ if sys.modules.get("bpy", None):
|
||||
bl_context = "scene"
|
||||
|
||||
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")
|
||||
box = layout.box()
|
||||
info = get_debug_info()
|
||||
|
||||
layout = self.layout
|
||||
layout.alert = True
|
||||
layout.label(text="BlenderBIM could not load.", icon="ERROR")
|
||||
if info["os"] == "Windows":
|
||||
layout.operator("wm.console_toggle", text="View the console for full logs.", icon="CONSOLE")
|
||||
else:
|
||||
layout.label(text="View the console for full logs.", icon="CONSOLE")
|
||||
box = layout.box()
|
||||
py = ".".join(info["python_version"].split(".")[0:2])
|
||||
b3d = ".".join(info["blender_version"].split(".")[0:2])
|
||||
box.label(text=f"Blender {b3d} {info['os']} {info['machine']}", icon="BLENDER")
|
||||
@@ -133,6 +138,21 @@ if sys.modules.get("bpy", None):
|
||||
op = layout.operator("bim.open_uri", text="How Can I Fix This?")
|
||||
op.uri = "https://docs.blenderbim.org/users/troubleshooting.html#installation-issues"
|
||||
|
||||
layout.label(text="Try Reinstalling:", icon="IMPORT")
|
||||
op = layout.operator("bim.open_uri", text="Re-download Add-on")
|
||||
bbim_date = info["blenderbim_version"].split(".")[-1]
|
||||
py_tag = py.replace(".", "")
|
||||
if "Linux" in info["os"]:
|
||||
os = "linux"
|
||||
elif "Darwin" in info["os"]:
|
||||
if "arm64" in info["machine"]:
|
||||
os = "macosm1"
|
||||
else:
|
||||
os = "macos"
|
||||
else:
|
||||
os = "win"
|
||||
op.uri = f"https://github.com/IfcOpenShell/IfcOpenShell/releases/download/blenderbim-{bbim_date}/blenderbim-{bbim_date}-py{py_tag}-{os}.zip"
|
||||
|
||||
class OpenUri(bpy.types.Operator):
|
||||
bl_idname = "bim.open_uri"
|
||||
bl_label = "Open URI"
|
||||
@@ -149,14 +169,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:
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
* Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
*
|
||||
* This file is part of BlenderBIM Add-on.
|
||||
*
|
||||
* BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY, without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* You may copy this `schedule.css` template alongside your input schedule
|
||||
* document with the same filename. For example if you have a schedule called
|
||||
* `door_types.ods`, you can create a `door_types.css` in the same folder to
|
||||
* style that schedule.
|
||||
*/
|
||||
|
||||
/**
|
||||
* If you specify a font size in CSS, such as text { font-size: 5; }, all fonts
|
||||
* will be overriden to match that size.
|
||||
*
|
||||
* If your CSS, ODS, XLSX does not specify a font size, the variables below
|
||||
* will specify the default font size.
|
||||
*
|
||||
* If your ODS, XLSX does specify a font size, they will scale linearly based
|
||||
* on the variables below.
|
||||
*/
|
||||
|
||||
:root {
|
||||
--font-size-pt: 12;
|
||||
--font-size-px: 4.13;
|
||||
--font-width: 0.45;
|
||||
}
|
||||
|
||||
text, tspan { /* 2.5mm */ fill: black; stroke: none; font-family: 'OpenGost Type B TT', 'DejaVu Sans Condensed', 'Liberation Sans', 'Arial Narrow', 'Arial'; }
|
||||
@@ -29,7 +29,7 @@ from mathutils import geometry
|
||||
from mathutils import Vector
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from typing import Optional, Callable, Any
|
||||
from typing import Optional, Callable, Any, Union
|
||||
|
||||
|
||||
def draw_attributes(props, layout, copy_operator=None, popup_active_attribute=None):
|
||||
@@ -91,7 +91,11 @@ def import_attributes(ifc_class, props, data, callback=None):
|
||||
|
||||
|
||||
# A more elegant attribute importer signature, intended to supersede import_attributes
|
||||
def import_attributes2(element, props, callback=None):
|
||||
def import_attributes2(
|
||||
element: Union[str, ifcopenshell.entity_instance],
|
||||
props: bpy.types.PropertyGroup,
|
||||
callback: Optional[Callable] = None,
|
||||
) -> None:
|
||||
if isinstance(element, str):
|
||||
attributes = tool.Ifc.schema().declaration_by_name(element).as_entity().all_attributes()
|
||||
info = {a.name(): None for a in attributes}
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -27,15 +27,12 @@ classes = (
|
||||
operator.CopyAttributeToSelection,
|
||||
prop.BIMAttributeProperties,
|
||||
ui.BIM_PT_object_attributes,
|
||||
ui.BIM_PT_material_attributes,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Object.BIMAttributeProperties = bpy.props.PointerProperty(type=prop.BIMAttributeProperties)
|
||||
bpy.types.Material.BIMAttributeProperties = bpy.props.PointerProperty(type=prop.BIMAttributeProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Object.BIMAttributeProperties
|
||||
del bpy.types.Material.BIMAttributeProperties
|
||||
|
||||
@@ -23,7 +23,6 @@ import blenderbim.tool as tool
|
||||
|
||||
def refresh():
|
||||
AttributesData.is_loaded = False
|
||||
MaterialAttributesData.is_loaded = False
|
||||
|
||||
|
||||
class AttributesData:
|
||||
@@ -51,28 +50,3 @@ class AttributesData:
|
||||
key = "STEP ID"
|
||||
results.append({"name": key, "value": str(value)})
|
||||
return results
|
||||
|
||||
|
||||
class MaterialAttributesData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"ifc_definition_id": cls.ifc_definition_id(), "attributes": cls.attributes()}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def ifc_definition_id(cls):
|
||||
return bpy.context.active_object.active_material.BIMObjectProperties.ifc_definition_id
|
||||
|
||||
@classmethod
|
||||
def attributes(cls):
|
||||
results = []
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object.active_material)
|
||||
data = element.get_info()
|
||||
for key, value in data.items():
|
||||
if value is None or isinstance(value, ifcopenshell.entity_instance) or key in ["id", "type"]:
|
||||
continue
|
||||
results.append({"name": key, "value": str(value)})
|
||||
return results
|
||||
|
||||
@@ -40,14 +40,10 @@ class EnableEditingAttributes(bpy.types.Operator):
|
||||
bl_label = "Enable Editing Attributes"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
obj_type: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
if self.obj_type == "Object":
|
||||
obj = bpy.data.objects.get(self.obj)
|
||||
elif self.obj_type == "Material":
|
||||
obj = bpy.data.materials.get(self.obj)
|
||||
obj = bpy.data.objects.get(self.obj)
|
||||
props = obj.BIMAttributeProperties
|
||||
props.attributes.clear()
|
||||
|
||||
@@ -85,13 +81,9 @@ class DisableEditingAttributes(bpy.types.Operator):
|
||||
bl_label = "Disable Editing Attributes"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
obj_type: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
if self.obj_type == "Object":
|
||||
obj = bpy.data.objects.get(self.obj)
|
||||
elif self.obj_type == "Material":
|
||||
obj = bpy.data.materials.get(self.obj)
|
||||
obj = bpy.data.objects.get(self.obj)
|
||||
props = obj.BIMAttributeProperties
|
||||
props.is_editing_attributes = False
|
||||
return {"FINISHED"}
|
||||
@@ -102,14 +94,10 @@ class EditAttributes(bpy.types.Operator, Operator):
|
||||
bl_label = "Edit Attributes"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
obj_type: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
if self.obj_type == "Object":
|
||||
obj = bpy.data.objects.get(self.obj)
|
||||
elif self.obj_type == "Material":
|
||||
obj = bpy.data.materials.get(self.obj)
|
||||
obj = bpy.data.objects.get(self.obj)
|
||||
props = obj.BIMAttributeProperties
|
||||
product = tool.Ifc.get_entity(obj)
|
||||
|
||||
@@ -126,7 +114,7 @@ class EditAttributes(bpy.types.Operator, Operator):
|
||||
|
||||
attributes = blenderbim.bim.helper.export_attributes(props.attributes, callback=callback)
|
||||
ifcopenshell.api.run("attribute.edit_attributes", self.file, product=product, attributes=attributes)
|
||||
bpy.ops.bim.disable_editing_attributes(obj=obj.name, obj_type=self.obj_type)
|
||||
bpy.ops.bim.disable_editing_attributes(obj=obj.name)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -19,28 +19,25 @@
|
||||
import blenderbim.bim.helper
|
||||
from bpy.types import Panel
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.attribute.data import AttributesData, MaterialAttributesData
|
||||
from blenderbim.bim.module.attribute.data import AttributesData
|
||||
|
||||
|
||||
def draw_ui(context, layout, obj_type, attributes):
|
||||
obj = context.active_object if obj_type == "Object" else context.active_object.active_material
|
||||
def draw_ui(context, layout, attributes):
|
||||
obj = context.active_object
|
||||
oprops = obj.BIMObjectProperties
|
||||
props = obj.BIMAttributeProperties
|
||||
|
||||
if props.is_editing_attributes:
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.edit_attributes", icon="CHECKMARK", text="Save Attributes")
|
||||
op.obj_type = obj_type
|
||||
op.obj = obj.name
|
||||
op = row.operator("bim.disable_editing_attributes", icon="CANCEL", text="")
|
||||
op.obj_type = obj_type
|
||||
op.obj = obj.name
|
||||
|
||||
blenderbim.bim.helper.draw_attributes(props.attributes, layout, copy_operator="bim.copy_attribute_to_selection")
|
||||
else:
|
||||
row = layout.row()
|
||||
op = row.operator("bim.enable_editing_attributes", icon="GREASEPENCIL", text="Edit")
|
||||
op.obj_type = obj_type
|
||||
op.obj = obj.name
|
||||
|
||||
for attribute in attributes:
|
||||
@@ -72,32 +69,4 @@ class BIM_PT_object_attributes(Panel):
|
||||
def draw(self, context):
|
||||
if not AttributesData.is_loaded:
|
||||
AttributesData.load()
|
||||
draw_ui(context, self.layout, "Object", AttributesData.data["attributes"])
|
||||
|
||||
|
||||
class BIM_PT_material_attributes(Panel):
|
||||
bl_label = "Material Attributes"
|
||||
bl_idname = "BIM_PT_material_attributes"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "material"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not IfcStore.get_file():
|
||||
return False
|
||||
try:
|
||||
return bool(context.active_object.active_material.BIMObjectProperties.ifc_definition_id)
|
||||
except:
|
||||
return False
|
||||
|
||||
def draw(self, context):
|
||||
if not MaterialAttributesData.is_loaded:
|
||||
MaterialAttributesData.load()
|
||||
elif (
|
||||
context.active_object.active_material.BIMObjectProperties.ifc_definition_id
|
||||
!= MaterialAttributesData.data["ifc_definition_id"]
|
||||
):
|
||||
MaterialAttributesData.load()
|
||||
|
||||
draw_ui(context, self.layout, "Material", MaterialAttributesData.data["attributes"])
|
||||
draw_ui(context, self.layout, AttributesData.data["attributes"])
|
||||
|
||||
@@ -19,10 +19,13 @@
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.cost
|
||||
import ifcopenshell.util.date
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.unit
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.schema
|
||||
from ifcopenshell.util.doc import get_entity_doc, get_predefined_type_doc
|
||||
from typing import Any
|
||||
|
||||
|
||||
def refresh():
|
||||
@@ -34,6 +37,7 @@ def refresh():
|
||||
class CostSchedulesData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
_cost_values: dict[int, dict[str, Any]]
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
@@ -136,7 +140,7 @@ class CostSchedulesData:
|
||||
data["UnitBasisUnitSymbol"] = "U"
|
||||
if cost_value.Category == "*":
|
||||
is_sum = True
|
||||
cost_quantity = data["TotalCostQuantity"] or 1
|
||||
cost_quantity = 1 if data["TotalCostQuantity"] is None else data["TotalCostQuantity"]
|
||||
if has_unit_basis:
|
||||
data["TotalCost"] = data["TotalAppliedValue"] * cost_quantity / data["UnitBasisValueComponent"]
|
||||
else:
|
||||
@@ -155,6 +159,7 @@ class CostSchedulesData:
|
||||
data["UnitSymbol"] = "-"
|
||||
if cost_item.CostQuantities:
|
||||
quantity = cost_item.CostQuantities[0]
|
||||
data["QuantityType"] = quantity.is_a()
|
||||
unit = ifcopenshell.util.unit.get_property_unit(quantity, tool.Ifc.get())
|
||||
if unit:
|
||||
data["UnitSymbol"] = ifcopenshell.util.unit.get_unit_symbol(unit)
|
||||
|
||||
@@ -223,7 +223,7 @@ class EditCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
class AssignCostItemType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.assign_cost_item_type"
|
||||
bl_label = "Assign Cost Item Type Product"
|
||||
bl_label = "Assign Cost Item To Product Types"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
cost_item: bpy.props.IntProperty()
|
||||
prop_name: bpy.props.StringProperty()
|
||||
@@ -509,7 +509,7 @@ class SelectCostScheduleProducts(bpy.types.Operator):
|
||||
|
||||
|
||||
class ImportCostScheduleCsv(bpy.types.Operator, ImportHelper, tool.Ifc.Operator):
|
||||
bl_idname = "import_cost_schedule_csv.bim"
|
||||
bl_idname = "bim.import_cost_schedule_csv"
|
||||
bl_label = "Import Cost Schedule CSV"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".csv"
|
||||
|
||||
@@ -21,6 +21,7 @@ import blenderbim.bim.module.cost.prop as CostProp
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.cost.data import CostSchedulesData
|
||||
from typing import Any
|
||||
|
||||
|
||||
class BIM_PT_cost_schedules(Panel):
|
||||
@@ -53,7 +54,7 @@ class BIM_PT_cost_schedules(Panel):
|
||||
row.label(text="No Cost Schedules found.", icon="TEXT")
|
||||
|
||||
row.operator("bim.add_cost_schedule", icon="ADD", text="")
|
||||
row.operator("import_cost_schedule_csv.bim",icon="IMPORT",text="")
|
||||
row.operator("bim.import_cost_schedule_csv",icon="IMPORT",text="")
|
||||
|
||||
for schedule in CostSchedulesData.data["schedules"]:
|
||||
self.draw_cost_schedule_ui(schedule)
|
||||
@@ -178,24 +179,33 @@ class BIM_PT_cost_schedules(Panel):
|
||||
"active_cost_item_index",
|
||||
)
|
||||
if self.props.active_cost_item_id:
|
||||
cost_item = CostSchedulesData.data["cost_items"][ifc_definition_id]
|
||||
if self.props.cost_item_editing_type == "ATTRIBUTES":
|
||||
self.draw_editable_cost_item_attributes_ui()
|
||||
elif self.props.cost_item_editing_type == "QUANTITIES":
|
||||
self.draw_editable_cost_item_quantities_ui()
|
||||
self.draw_editable_cost_item_quantities_ui(cost_item)
|
||||
elif self.props.cost_item_editing_type == "VALUES":
|
||||
self.draw_editable_cost_item_values_ui()
|
||||
|
||||
def draw_editable_cost_item_attributes_ui(self):
|
||||
blenderbim.bim.helper.draw_attributes(self.props.cost_item_attributes, self.layout)
|
||||
|
||||
def draw_editable_cost_item_quantities_ui(self):
|
||||
def draw_editable_cost_item_quantities_ui(self, cost_item: dict[str, Any]):
|
||||
quantities = CostSchedulesData.data["cost_quantities"]
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "quantity_types", text="")
|
||||
# In IFC, all quantities of IfcCostTime should have 1 type.
|
||||
if quantities:
|
||||
quantity_class = cost_item["QuantityType"]
|
||||
row.label(text=quantity_class)
|
||||
else:
|
||||
row.prop(self.props, "quantity_types", text="")
|
||||
quantity_class = self.props.quantity_types
|
||||
|
||||
op = row.operator("bim.add_cost_item_quantity", text="", icon="ADD")
|
||||
op.cost_item = self.props.active_cost_item_id
|
||||
op.ifc_class = self.props.quantity_types
|
||||
op.ifc_class = quantity_class
|
||||
|
||||
for quantity in CostSchedulesData.data["cost_quantities"]:
|
||||
for quantity in quantities:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=quantity["name"])
|
||||
row.label(text=quantity["value"])
|
||||
@@ -634,7 +644,7 @@ class BIM_UL_cost_items_trait:
|
||||
layout.label(text=cost_item["UnitBasisUnitSymbol"])
|
||||
|
||||
def draw_total_quantity_column(self, layout, cost_item):
|
||||
if cost_item["TotalCostQuantity"]:
|
||||
if cost_item["TotalCostQuantity"] is not None:
|
||||
label = "{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '-'}"
|
||||
layout.label(text=label)
|
||||
else:
|
||||
|
||||
@@ -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"}
|
||||
|
||||
|
||||
|
||||
@@ -159,9 +159,7 @@ class BaseDecorator:
|
||||
objecttype = "NOTDEFINED"
|
||||
|
||||
def __init__(self):
|
||||
self.font_id = blf.load(
|
||||
os.path.join(bpy.context.scene.BIMProperties.data_dir, "fonts", "OpenGost Type B TT.ttf")
|
||||
)
|
||||
self.font_id = 0 # 0 is the default font
|
||||
|
||||
# POLYLINE_UNIFORM_COLOR is good for smoothed lines since `bgl.enable(GL_LINE_SMOOTH)` is deprecated
|
||||
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
|
||||
@@ -404,7 +402,6 @@ class BaseDecorator:
|
||||
line_no += 1 if multiline_to_bottom else -1
|
||||
return
|
||||
|
||||
# 0 is the default font, but we're fancier than that
|
||||
font_id = self.font_id
|
||||
|
||||
color = context.preferences.addons["blenderbim"].preferences.decorations_colour
|
||||
@@ -2014,6 +2011,13 @@ class DecorationsHandler:
|
||||
for object_type in ("SLOPE_ANGLE", "SLOPE_FRACTION", "SLOPE_PERCENT"):
|
||||
self.decorators[object_type] = self.decorators["FALL"]
|
||||
self.decorators["MULTI_SYMBOL"] = self.decorators["SYMBOL"]
|
||||
if drawing_font := bpy.context.scene.DocProperties.drawing_font:
|
||||
drawing_font_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "fonts", drawing_font)
|
||||
if os.path.exists(drawing_font_path):
|
||||
font_id = blf.load(drawing_font_path)
|
||||
for decorator in self.decorators.values():
|
||||
decorator.font_id = font_id
|
||||
|
||||
|
||||
def get_objects_and_decorators(self, collection):
|
||||
# TODO: do it in data instead of the handler for performance?
|
||||
|
||||
@@ -315,9 +315,7 @@ class RasterStyleProperty(enum.Enum):
|
||||
SPACE_SHADING = "space.shading"
|
||||
|
||||
|
||||
RASTER_STYLE_PROPERTIES_EXCLUDE = (
|
||||
"scene.render.filepath",
|
||||
)
|
||||
RASTER_STYLE_PROPERTIES_EXCLUDE = ("scene.render.filepath",)
|
||||
|
||||
|
||||
class DocProperties(PropertyGroup):
|
||||
@@ -371,6 +369,7 @@ class DocProperties(PropertyGroup):
|
||||
default=os.path.join("drawings", "assets", "shading_styles.json"), name="Default Shading Styles"
|
||||
)
|
||||
shadingstyle_default: StringProperty(default="Blender Default", name="Default Shading Style")
|
||||
drawing_font: StringProperty(default="OpenGost Type B TT.ttf", name="Drawing Font")
|
||||
|
||||
|
||||
class BIMCameraProperties(PropertyGroup):
|
||||
|
||||
@@ -16,22 +16,21 @@
|
||||
# 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 blenderbim.bim.module.drawing.svgwriter import SvgWriter
|
||||
import os
|
||||
import re
|
||||
import bpy
|
||||
import string
|
||||
import svgwrite
|
||||
import openpyxl
|
||||
|
||||
from blenderbim.bim.module.drawing.svgwriter import SvgWriter
|
||||
from odf.opendocument import load as load_ods
|
||||
from odf.table import Table, TableRow, TableColumn, TableCell
|
||||
from odf.text import P
|
||||
from odf.style import Style
|
||||
from textwrap import wrap
|
||||
from pathlib import Path
|
||||
import string
|
||||
|
||||
FONT_SIZE = 4.13
|
||||
FONT_WIDTH = lambda size: size * 0.45
|
||||
FONT_SIZE_PT = 12
|
||||
FONT_FAMILY = "OpenGost Type B TT"
|
||||
DEBUG = False
|
||||
|
||||
|
||||
@@ -63,11 +62,29 @@ class Scheduler:
|
||||
)
|
||||
self.padding = 1
|
||||
self.margin = 1
|
||||
|
||||
self.parse_css(infile)
|
||||
if infile.endswith("ods"):
|
||||
self.schedule_ods(infile, outfile)
|
||||
elif infile.endswith("xlsx"):
|
||||
self.schedule_xlsx(infile, outfile)
|
||||
|
||||
def parse_css(self, infile):
|
||||
stylesheet_path = os.path.splitext(infile)[0] + ".css"
|
||||
if not os.path.exists(stylesheet_path):
|
||||
stylesheet_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "assets", "schedule.css")
|
||||
with open(stylesheet_path, "r") as stylesheet:
|
||||
css = stylesheet.read()
|
||||
|
||||
matches = re.search("--font-size-pt:\s*([0-9.]+);", css)
|
||||
self.font_size_pt = float(matches.groups()[0]) if matches else 12 # Default to 12pt
|
||||
matches = re.search("--font-size-px:\s*([0-9.]+);", css)
|
||||
self.font_size_px = float(matches.groups()[0]) if matches else 4.13 # Magic number 4.13px ~= 12pt
|
||||
matches = re.search("--font-width:\s*([0-9.]+);", css)
|
||||
self.font_width = float(matches.groups()[0]) if matches else 0.45 # A magic number for OpenGost
|
||||
|
||||
self.svg.defs.add(self.svg.style(css))
|
||||
|
||||
def schedule_xlsx(self, infile, outfile):
|
||||
workbook = openpyxl.open(infile, data_only=True)
|
||||
sheet = workbook.active
|
||||
@@ -144,8 +161,8 @@ class Scheduler:
|
||||
x += unmerged_width
|
||||
continue
|
||||
|
||||
font_size = cell.font.size or 11 # 11pt default
|
||||
font_size = font_size / FONT_SIZE_PT * FONT_SIZE # Magic?
|
||||
font_size = cell.font.size or self.font_size_pt # 12pt default
|
||||
font_size = font_size / self.font_size_pt * self.font_size_px # Magic?
|
||||
|
||||
text_position = [0.0, 0.0]
|
||||
if cell.alignment.horizontal == "left":
|
||||
@@ -424,9 +441,9 @@ class Scheduler:
|
||||
italic_text = final_cell_style.get("font-style", None) == "italic"
|
||||
# NOTE: very naive since we're scaling text proportionally
|
||||
font_size = (
|
||||
float(final_cell_style.get("font-size", f"{FONT_SIZE_PT}pt")[:-2])
|
||||
/ FONT_SIZE_PT
|
||||
* FONT_SIZE
|
||||
float(final_cell_style.get("font-size", f"{self.font_size_pt}pt")[:-2])
|
||||
/ self.font_size_pt
|
||||
* self.font_size_px
|
||||
)
|
||||
|
||||
if p_tags:
|
||||
@@ -523,10 +540,7 @@ class Scheduler:
|
||||
"""
|
||||
text_lines = [str(p) for p in p_tags]
|
||||
box_alignment_params = SvgWriter.get_box_alignment_parameters(box_alignment)
|
||||
text_params = {
|
||||
"font-size": font_size,
|
||||
"font-family": FONT_FAMILY,
|
||||
}
|
||||
text_params = {"font-size": font_size}
|
||||
if bold:
|
||||
text_params["font-weight"] = "bold"
|
||||
if italic:
|
||||
@@ -546,13 +560,13 @@ class Scheduler:
|
||||
|
||||
text_tag = self.svg.text("", **(text_params | {"font-size": "0"} | box_alignment_params))
|
||||
|
||||
# TODO: should be done in less naive way
|
||||
# without using magic number for FONT_WIDTH
|
||||
# currently it might not work for all fonts and font sizes
|
||||
# TODO: Should be done without using magic number for self.font_width
|
||||
if wrap_text:
|
||||
wrapped_lines = []
|
||||
for line in text_lines:
|
||||
wrapped_line = wrap(line, width=int(cell_width // FONT_WIDTH(font_size)), break_long_words=False)
|
||||
wrapped_line = wrap(
|
||||
line, width=int(cell_width // (font_size * self.font_width)), break_long_words=False
|
||||
)
|
||||
wrapped_lines.extend(wrapped_line)
|
||||
else:
|
||||
wrapped_lines = text_lines
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -83,17 +83,8 @@ class EnableEditingArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Array", "Data"))[self.item]
|
||||
props = obj.BIMArrayProperties
|
||||
|
||||
relating_obj = props.relating_array_object
|
||||
|
||||
if relating_obj:
|
||||
element = tool.Ifc.get_entity(relating_obj)
|
||||
parent_globalid = ifcopenshell.util.element.get_pset(element, "BBIM_Array", "Parent")
|
||||
parent_element = tool.Ifc.get().by_guid(parent_globalid)
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(parent_element, "BBIM_Array", "Data"))[self.item]
|
||||
else:
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Array", "Data"))[self.item]
|
||||
props.count = data["count"]
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
props.x = data["x"] * si_conversion
|
||||
@@ -102,9 +93,7 @@ class EnableEditingArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
props.use_local_space = data.get("use_local_space", False)
|
||||
props.sync_children = data.get("sync_children", False)
|
||||
props.method = data.get("method", "OFFSET")
|
||||
|
||||
props.is_editing = self.item
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -148,10 +137,6 @@ class EditArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
tool.Blender.Modifier.Array.set_children_lock_state(element, self.item, True)
|
||||
tool.Blender.Modifier.Array.constrain_children_to_parent(element)
|
||||
|
||||
#clears the relating_array_object so it doesn't show again next time
|
||||
props.relating_array_object = None
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -208,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))
|
||||
|
||||
@@ -53,7 +53,7 @@ class AuthoringData:
|
||||
cls.data["ifc_element_type"] = cls.ifc_element_type
|
||||
cls.data["ifc_classes"] = cls.ifc_classes()
|
||||
cls.data["relating_type_id"] = cls.relating_type_id() # only after .ifc_classes()
|
||||
cls.data["predefined_type"] = cls.predefined_type()
|
||||
cls.data["predefined_type"] = cls.predefined_type() # only after .relating_type_id()
|
||||
cls.data["type_class"] = cls.type_class()
|
||||
|
||||
# only after .type_class()
|
||||
@@ -251,6 +251,8 @@ class AuthoringData:
|
||||
|
||||
@classmethod
|
||||
def predefined_type(cls):
|
||||
if not tool.Blender.enum_property_has_valid_index(cls.props, "relating_type_id", cls.data["relating_type_id"]):
|
||||
return
|
||||
relating_type_id = cls.props.relating_type_id
|
||||
if not relating_type_id:
|
||||
return
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -44,6 +44,7 @@ from bpy.types import SpaceView3D
|
||||
from bpy.props import FloatProperty
|
||||
from bpy_extras.object_utils import AddObjectHelper, object_data_add
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
from typing import Union, Optional, Any
|
||||
|
||||
|
||||
class AddFilledOpening(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -59,9 +60,15 @@ class AddFilledOpening(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
|
||||
class FilledOpeningGenerator:
|
||||
def generate(self, filling_obj, voided_obj, target=None):
|
||||
def generate(
|
||||
self,
|
||||
filling_obj: Union[bpy.types.Object, None],
|
||||
voided_obj: Union[bpy.types.Object, None],
|
||||
target: Optional[Vector] = None,
|
||||
) -> None:
|
||||
props = bpy.context.scene.BIMModelProperties
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
opening_thickness_si = None
|
||||
|
||||
filling = tool.Ifc.get_entity(filling_obj)
|
||||
element = tool.Ifc.get_entity(voided_obj)
|
||||
@@ -92,6 +99,7 @@ class FilledOpeningGenerator:
|
||||
# In this prototype, we assume openings are only added to axis-based elements
|
||||
layers = tool.Model.get_material_layer_parameters(element)
|
||||
if layers["layer_set_direction"] == "AXIS2":
|
||||
opening_thickness_si = layers["thickness"] * 2
|
||||
axis = tool.Model.get_wall_axis(voided_obj, layers=layers)["base"]
|
||||
new_matrix = voided_obj.matrix_world.copy()
|
||||
point_on_axis = tool.Cad.point_on_edge(target, axis)
|
||||
@@ -145,7 +153,9 @@ class FilledOpeningGenerator:
|
||||
"geometry.assign_representation", tool.Ifc.get(), product=opening, representation=mapped_representation
|
||||
)
|
||||
else:
|
||||
representation = self.generate_opening_from_filling(filling, filling_obj)
|
||||
representation = self.generate_opening_from_filling(
|
||||
filling, filling_obj, opening_thickness_si=opening_thickness_si
|
||||
)
|
||||
opening = ifcopenshell.api.run(
|
||||
"root.create_entity", tool.Ifc.get(), ifc_class="IfcOpeningElement", predefined_type="OPENING"
|
||||
)
|
||||
@@ -187,7 +197,7 @@ class FilledOpeningGenerator:
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
def regenerate_from_type(self, usecase_path, ifc_file, settings):
|
||||
def regenerate_from_type(self, usecase_path: str, ifc_file: ifcopenshell.file, settings: dict[str, Any]) -> None:
|
||||
relating_type = settings["relating_type"]
|
||||
|
||||
for related_object in settings["related_objects"]:
|
||||
@@ -252,10 +262,15 @@ class FilledOpeningGenerator:
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
def generate_opening_from_filling(self, filling, filling_obj):
|
||||
def generate_opening_from_filling(
|
||||
self,
|
||||
filling: ifcopenshell.entity_instance,
|
||||
filling_obj: bpy.types.Object,
|
||||
opening_thickness_si: Optional[float] = None,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
# Since openings are reused later, we give a default thickness of 1.2m
|
||||
# which should cover the majority of curved, or super thick walls.
|
||||
thickness = 1.2
|
||||
thickness = 1.2 if opening_thickness_si is None else opening_thickness_si
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
shape_builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
|
||||
|
||||
@@ -346,7 +361,9 @@ class FilledOpeningGenerator:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_existing_opening_occurrence_if_any(self, filling):
|
||||
def get_existing_opening_occurrence_if_any(
|
||||
self, filling: ifcopenshell.entity_instance
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
filling_type = ifcopenshell.util.element.get_type(filling)
|
||||
if filling_type:
|
||||
filling_occurrences = ifcopenshell.util.element.get_types(filling_type)
|
||||
|
||||
@@ -138,7 +138,10 @@ class AddConstrTypeInstance(bpy.types.Operator):
|
||||
if not relating_type_id:
|
||||
return {"FINISHED"}
|
||||
|
||||
if self.from_invoke:
|
||||
# Check relating_type_id enum_items since it's possible
|
||||
# that we're adding e.g. IfcRoofType being in a Slab Tool
|
||||
# and roof type id won't be present in the relating_type_id enum.
|
||||
if self.from_invoke and str(self.relating_type_id) in AuthoringData.data["relating_type_id"]:
|
||||
props.relating_type_id = str(self.relating_type_id)
|
||||
|
||||
relating_type = tool.Ifc.get().by_id(int(relating_type_id))
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -87,21 +87,6 @@ def update_type_page(self, context):
|
||||
AuthoringData.data["paginated_relating_types"] = AuthoringData.paginated_relating_types()
|
||||
|
||||
|
||||
def update_relating_array_from_object(self, context):
|
||||
bpy.ops.bim.enable_editing_array(item=self.is_editing)
|
||||
return
|
||||
|
||||
|
||||
def is_object_array_applicable(self, obj):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
return False
|
||||
return ifcopenshell.util.element.get_pset(element, "BBIM_Array")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class BIMModelProperties(PropertyGroup):
|
||||
ifc_class: bpy.props.EnumProperty(items=get_ifc_class, name="Construction Class", update=update_ifc_class)
|
||||
relating_type_id: bpy.props.EnumProperty(
|
||||
@@ -218,14 +203,6 @@ class BIMArrayProperties(PropertyGroup):
|
||||
description="Regenerate all children based on the parent object",
|
||||
default=False,
|
||||
)
|
||||
relating_array_object: bpy.props.PointerProperty(
|
||||
type=bpy.types.Object,
|
||||
name="Copy Array Properties",
|
||||
update=update_relating_array_from_object,
|
||||
poll=is_object_array_applicable,
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
class BIMStairProperties(PropertyGroup):
|
||||
|
||||
@@ -535,5 +535,5 @@ class RemoveRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
obj.BIMRailingProperties.is_editing = False
|
||||
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Railing")
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), 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"}
|
||||
|
||||
@@ -223,8 +223,6 @@ class BIM_PT_array(bpy.types.Panel):
|
||||
row = col.row(align=True)
|
||||
row.prop(props, "z")
|
||||
row.operator("bim.input_cursor_z_array", icon="CURSOR", text="")
|
||||
row = col.row(align=True)
|
||||
row.prop(props, "relating_array_object", icon="COPYDOWN")
|
||||
else:
|
||||
row = box.row(align=True)
|
||||
name = f"{array['count']} Items ({array.get('method', 'OFFSET').capitalize()})"
|
||||
|
||||
@@ -590,6 +590,6 @@ class RemoveWindow(bpy.types.Operator, tool.Ifc.Operator):
|
||||
obj.BIMWindowProperties.is_editing = False
|
||||
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Window")
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -36,8 +36,8 @@ classes = (
|
||||
operator.EnableCulling,
|
||||
operator.EnableEditingHeader,
|
||||
operator.ExportIFC,
|
||||
operator.ExportIFCDeprecated,
|
||||
operator.FlipClippingPlane,
|
||||
operator.ImportIFC,
|
||||
operator.LinkIfc,
|
||||
operator.LoadLink,
|
||||
operator.LoadLinkedProject,
|
||||
@@ -97,7 +97,7 @@ def register():
|
||||
addon_keymaps.append((km, kmi))
|
||||
|
||||
km = wm.keyconfigs.addon.keymaps.new(name="Window", space_type="EMPTY")
|
||||
kmi = km.keymap_items.new("export_ifc.bim", "S", "PRESS", ctrl=True)
|
||||
kmi = km.keymap_items.new("bim.export_ifc", "S", "PRESS", ctrl=True)
|
||||
kmi.properties.should_save_as = False
|
||||
addon_keymaps.append((km, kmi))
|
||||
|
||||
|
||||
@@ -1106,8 +1106,8 @@ class ToggleLinkVisibility(bpy.types.Operator):
|
||||
]
|
||||
|
||||
|
||||
class ExportIFC(bpy.types.Operator):
|
||||
bl_idname = "export_ifc.bim"
|
||||
class ExportIFCBase:
|
||||
bl_idname = "bim.export_ifc"
|
||||
bl_label = "Save IFC"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".ifc"
|
||||
@@ -1224,14 +1224,20 @@ class ExportIFC(bpy.types.Operator):
|
||||
return "Save the IFC file. Will save both .IFC/.BLEND files if synced together"
|
||||
|
||||
|
||||
class ImportIFC(bpy.types.Operator):
|
||||
bl_idname = "import_ifc.bim"
|
||||
bl_label = "Import IFC"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
class ExportIFC(ExportIFCBase, bpy.types.Operator):
|
||||
bl_idname = "bim.export_ifc"
|
||||
|
||||
|
||||
# TODO: remove as deprecated, better wait couple releases since
|
||||
# this operator is used for saving IFC files in user scripts.
|
||||
class ExportIFCDeprecated(ExportIFCBase, bpy.types.Operator):
|
||||
bl_idname = "export_ifc.bim"
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.bim.load_project("INVOKE_DEFAULT")
|
||||
return {"FINISHED"}
|
||||
msg = f"'{ExportIFCDeprecated.bl_idname}' operator name is deprecated, use '{ExportIFC.bl_idname}'."
|
||||
self.report({"WARNING"}, msg)
|
||||
print(msg)
|
||||
return super().execute(context)
|
||||
|
||||
|
||||
class LoadLinkedProject(bpy.types.Operator):
|
||||
|
||||
@@ -68,9 +68,9 @@ def file_menu(self, context):
|
||||
op = self.layout.operator("bim.load_project", text="Open IFC Project", icon="FILEBROWSER")
|
||||
op.should_start_fresh_session = True
|
||||
self.layout.separator()
|
||||
op = self.layout.operator("export_ifc.bim", icon="FILE_TICK", text="Save IFC Project")
|
||||
op = self.layout.operator("bim.export_ifc", icon="FILE_TICK", text="Save IFC Project")
|
||||
op.should_save_as = False
|
||||
op = self.layout.operator("export_ifc.bim", text="Save IFC Project As...")
|
||||
op = self.layout.operator("bim.export_ifc", text="Save IFC Project As...")
|
||||
op.should_save_as = True
|
||||
self.layout.separator()
|
||||
self.layout.operator("bim.revert_project")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -21,6 +21,7 @@ import bpy
|
||||
import pathlib
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.attribute
|
||||
import ifcopenshell.util.doc
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
@@ -36,10 +37,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()
|
||||
|
||||
|
||||
@@ -328,7 +328,7 @@ class EditResourceQuantity(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
|
||||
class ImportResources(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
|
||||
bl_idname = "import_resources.bim"
|
||||
bl_idname = "bim.import_resources"
|
||||
bl_label = "Import Resources"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".csv"
|
||||
|
||||
@@ -51,7 +51,7 @@ class BIM_PT_resources(Panel):
|
||||
row.operator("bim.disable_resource_editing_ui", text="", icon="CANCEL")
|
||||
else:
|
||||
row.operator("bim.load_resources", text="", icon="GREASEPENCIL")
|
||||
row.operator("import_resources.bim", text="", icon="IMPORT")
|
||||
row.operator("bim.import_resources", text="", icon="IMPORT")
|
||||
if not self.props.is_editing:
|
||||
return
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -628,7 +628,7 @@ class DisableEditingWorkCalendar(bpy.types.Operator):
|
||||
|
||||
|
||||
class ImportCSV(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
|
||||
bl_idname = "import_csv.bim"
|
||||
bl_idname = "bim.import_csv"
|
||||
bl_label = "Import CSV"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".csv"
|
||||
@@ -653,7 +653,7 @@ class ImportCSV(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
|
||||
|
||||
|
||||
class ImportP6(bpy.types.Operator, ImportHelper):
|
||||
bl_idname = "import_p6.bim"
|
||||
bl_idname = "bim.import_p6"
|
||||
bl_label = "Import P6"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".xml"
|
||||
@@ -679,7 +679,7 @@ class ImportP6(bpy.types.Operator, ImportHelper):
|
||||
|
||||
|
||||
class ImportP6XER(bpy.types.Operator, ImportHelper):
|
||||
bl_idname = "import_p6xer.bim"
|
||||
bl_idname = "bim.import_p6xer"
|
||||
bl_label = "Import P6 XER"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".xer"
|
||||
@@ -705,7 +705,7 @@ class ImportP6XER(bpy.types.Operator, ImportHelper):
|
||||
|
||||
|
||||
class ImportPP(bpy.types.Operator, ImportHelper):
|
||||
bl_idname = "import_pp.bim"
|
||||
bl_idname = "bim.import_pp"
|
||||
bl_label = "Import Powerproject .pp"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".pp"
|
||||
@@ -731,7 +731,7 @@ class ImportPP(bpy.types.Operator, ImportHelper):
|
||||
|
||||
|
||||
class ImportMSP(bpy.types.Operator, ImportHelper):
|
||||
bl_idname = "import_msp.bim"
|
||||
bl_idname = "bim.import_msp"
|
||||
bl_label = "Import MSP"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".xml"
|
||||
@@ -757,7 +757,7 @@ class ImportMSP(bpy.types.Operator, ImportHelper):
|
||||
|
||||
|
||||
class ExportMSP(bpy.types.Operator, ImportHelper):
|
||||
bl_idname = "export_msp.bim"
|
||||
bl_idname = "bim.export_msp"
|
||||
bl_label = "Export MSP"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".xml"
|
||||
@@ -787,7 +787,7 @@ class ExportMSP(bpy.types.Operator, ImportHelper):
|
||||
|
||||
|
||||
class ExportP6(bpy.types.Operator, ImportHelper):
|
||||
bl_idname = "export_p6.bim"
|
||||
bl_idname = "bim.export_p6"
|
||||
bl_label = "Export P6"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".xml"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -512,10 +512,7 @@ class RemoveType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def _execute(self, context):
|
||||
element = tool.Ifc.get().by_id(self.element)
|
||||
obj = tool.Ifc.get_object(element)
|
||||
ifcopenshell.api.run("root.remove_product", tool.Ifc.get(), product=element)
|
||||
if obj:
|
||||
tool.Ifc.unlink(obj=obj)
|
||||
bpy.data.objects.remove(obj)
|
||||
tool.Geometry.delete_ifc_object(obj)
|
||||
|
||||
|
||||
class RenameType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import os
|
||||
import bpy
|
||||
import addon_utils
|
||||
import platform
|
||||
from pathlib import Path
|
||||
from bpy.types import Panel
|
||||
from bpy.props import StringProperty, IntProperty, BoolProperty
|
||||
@@ -290,8 +291,6 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
row = layout.row()
|
||||
row.prop(self, "spatial_elements_unselectable")
|
||||
|
||||
|
||||
|
||||
row = layout.row()
|
||||
row.prop(context.scene.BIMProjectProperties, "should_disable_undo_on_save")
|
||||
row = layout.row()
|
||||
@@ -320,28 +319,30 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
row.prop(context.scene.BIMProperties, "data_dir")
|
||||
row.operator("bim.select_data_dir", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.BIMProperties, "pset_dir")
|
||||
row = self.layout.row(align=True)
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.DocProperties, "sheets_dir")
|
||||
row = self.layout.row(align=True)
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.DocProperties, "layouts_dir")
|
||||
row = self.layout.row(align=True)
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.DocProperties, "titleblocks_dir")
|
||||
row = self.layout.row(align=True)
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.DocProperties, "drawings_dir")
|
||||
row = self.layout.row(align=True)
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.DocProperties, "stylesheet_path")
|
||||
row = self.layout.row(align=True)
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.DocProperties, "markers_path")
|
||||
row = self.layout.row(align=True)
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.DocProperties, "symbols_path")
|
||||
row = self.layout.row(align=True)
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.DocProperties, "patterns_path")
|
||||
row = self.layout.row(align=True)
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.DocProperties, "shadingstyles_path")
|
||||
row = self.layout.row(align=True)
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.DocProperties, "shadingstyle_default")
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.DocProperties, "drawing_font")
|
||||
|
||||
|
||||
# Scene panel groups
|
||||
@@ -406,10 +407,14 @@ 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")
|
||||
if platform.system() == "Windows":
|
||||
box.operator("wm.console_toggle", text="View the console for full logs.", icon="CONSOLE")
|
||||
else:
|
||||
box.label(text="View the console for full logs.", icon="CONSOLE")
|
||||
box.operator("bim.copy_debug_information", text="Copy Error Message To Clipboard")
|
||||
op = box.operator("bim.open_uri", text="How Can I Fix This?")
|
||||
op.uri = "https://docs.blenderbim.org/users/troubleshooting.html"
|
||||
|
||||
@@ -1,88 +1,117 @@
|
||||
def add_cost_schedule(ifc, name, predefined_type):
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
def add_cost_schedule(ifc: tool.Ifc, name, predefined_type):
|
||||
ifc.run("cost.add_cost_schedule", name=name, predefined_type=predefined_type)
|
||||
|
||||
|
||||
def edit_cost_schedule(ifc, cost, cost_schedule):
|
||||
def edit_cost_schedule(ifc: tool.Ifc, cost: tool.Cost, cost_schedule: ifcopenshell.entity_instance):
|
||||
attributes = cost.get_cost_schedule_attributes()
|
||||
ifc.run("cost.edit_cost_schedule", cost_schedule=cost_schedule, attributes=attributes)
|
||||
cost.disable_editing_cost_schedule()
|
||||
|
||||
|
||||
def disable_editing_cost_schedule(cost):
|
||||
def disable_editing_cost_schedule(cost: tool.Cost):
|
||||
cost.disable_editing_cost_schedule()
|
||||
|
||||
|
||||
def remove_cost_schedule(ifc, cost_schedule):
|
||||
def remove_cost_schedule(ifc: tool.Ifc, cost_schedule: ifcopenshell.entity_instance):
|
||||
ifc.run("cost.remove_cost_schedule", cost_schedule=cost_schedule)
|
||||
|
||||
|
||||
def enable_editing_cost_schedule_attributes(cost, cost_schedule):
|
||||
def enable_editing_cost_schedule_attributes(cost: tool.Cost, cost_schedule: ifcopenshell.entity_instance):
|
||||
cost.load_cost_schedule_attributes(cost_schedule)
|
||||
cost.enable_editing_cost_schedule_attributes(cost_schedule)
|
||||
|
||||
|
||||
def enable_editing_cost_items(cost, cost_schedule):
|
||||
def enable_editing_cost_items(cost: tool.Cost, cost_schedule: ifcopenshell.entity_instance):
|
||||
cost.enable_editing_cost_items(cost_schedule)
|
||||
cost.load_cost_schedule_tree()
|
||||
cost.play_sound()
|
||||
|
||||
|
||||
def add_summary_cost_item(ifc, cost, cost_schedule):
|
||||
def add_summary_cost_item(ifc: tool.Ifc, cost: tool.Cost, cost_schedule: ifcopenshell.entity_instance):
|
||||
ifc.run("cost.add_cost_item", cost_schedule=cost_schedule)
|
||||
cost.load_cost_schedule_tree()
|
||||
# cost.play_sound()
|
||||
|
||||
|
||||
def add_cost_item(ifc, cost, cost_item):
|
||||
def add_cost_item(ifc: tool.Ifc, cost: tool.Cost, cost_item: ifcopenshell.entity_instance):
|
||||
ifc.run("cost.add_cost_item", cost_item=cost_item)
|
||||
cost.load_cost_schedule_tree()
|
||||
# cost.enable_editing_cost_schedule_attributes(cost_schedule)
|
||||
|
||||
|
||||
def expand_cost_item(cost, cost_item):
|
||||
def expand_cost_item(cost: tool.Cost, cost_item: ifcopenshell.entity_instance):
|
||||
cost.expand_cost_item(cost_item)
|
||||
cost.load_cost_schedule_tree()
|
||||
|
||||
|
||||
def expand_cost_items(cost):
|
||||
def expand_cost_items(cost: tool.Cost):
|
||||
cost.expand_cost_items()
|
||||
cost.load_cost_schedule_tree()
|
||||
|
||||
|
||||
def contract_cost_item(cost, cost_item):
|
||||
def contract_cost_item(cost: tool.Cost, cost_item: ifcopenshell.entity_instance):
|
||||
cost.contract_cost_item(cost_item)
|
||||
cost.load_cost_schedule_tree()
|
||||
|
||||
|
||||
def contract_cost_items(cost):
|
||||
def contract_cost_items(cost: tool.Cost):
|
||||
cost.contract_cost_items()
|
||||
cost.load_cost_schedule_tree()
|
||||
|
||||
|
||||
def remove_cost_item(ifc, cost, cost_item_id):
|
||||
def remove_cost_item(ifc: tool.Ifc, cost: tool.Cost, cost_item_id: int):
|
||||
cost_item = ifc.get().by_id(cost_item_id)
|
||||
ifc.run("cost.remove_cost_item", cost_item=cost_item)
|
||||
cost.clean_up_cost_item_tree(cost_item_id)
|
||||
cost.load_cost_schedule_tree()
|
||||
|
||||
|
||||
def enable_editing_cost_item_attributes(cost, cost_item):
|
||||
def enable_editing_cost_item_attributes(cost: tool.Cost, cost_item: ifcopenshell.entity_instance):
|
||||
cost.enable_editing_cost_item_attributes(cost_item)
|
||||
cost.load_cost_item_attributes(cost_item)
|
||||
|
||||
|
||||
def disable_editing_cost_item(cost):
|
||||
def disable_editing_cost_item(cost: tool.Cost):
|
||||
cost.disable_editing_cost_item()
|
||||
|
||||
|
||||
def edit_cost_item(ifc, cost):
|
||||
def edit_cost_item(ifc: tool.Ifc, cost: tool.Cost):
|
||||
attributes = cost.get_cost_item_attributes()
|
||||
ifc.run("cost.edit_cost_item", cost_item=cost.get_active_cost_item(), attributes=attributes)
|
||||
cost.disable_editing_cost_item()
|
||||
cost.load_cost_schedule_tree()
|
||||
|
||||
|
||||
def assign_cost_item_type(ifc, cost, spatial, cost_item, prop_name):
|
||||
def assign_cost_item_type(
|
||||
ifc: tool.Ifc, cost: tool.Cost, spatial: tool.Spatial, cost_item: ifcopenshell.entity_instance, prop_name
|
||||
):
|
||||
product_types = spatial.get_selected_product_types()
|
||||
[
|
||||
ifc.run("control.assign_control", relating_control=cost_item, related_object=product_type)
|
||||
@@ -91,7 +120,9 @@ def assign_cost_item_type(ifc, cost, spatial, cost_item, prop_name):
|
||||
cost.load_cost_item_types(cost_item)
|
||||
|
||||
|
||||
def unassign_cost_item_type(ifc, cost, spatial, cost_item, product_types):
|
||||
def unassign_cost_item_type(
|
||||
ifc: tool.Ifc, cost: tool.Cost, spatial: tool.Spatial, cost_item: ifcopenshell.entity_instance, product_types
|
||||
):
|
||||
if not product_types:
|
||||
product_types = spatial.get_selected_product_types()
|
||||
[
|
||||
@@ -101,83 +132,85 @@ def unassign_cost_item_type(ifc, cost, spatial, cost_item, product_types):
|
||||
cost.load_cost_item_types(cost_item)
|
||||
|
||||
|
||||
def load_cost_item_types(cost):
|
||||
def load_cost_item_types(cost: tool.Cost):
|
||||
cost_item = cost.get_active_cost_item()
|
||||
cost.load_cost_item_types(cost_item)
|
||||
|
||||
|
||||
def assign_cost_item_quantity(ifc, cost, cost_item, related_object_type, prop_name):
|
||||
def assign_cost_item_quantity(
|
||||
ifc: tool.Ifc, cost: tool.Cost, cost_item: ifcopenshell.entity_instance, related_object_type, prop_name
|
||||
):
|
||||
products = cost.get_products(related_object_type)
|
||||
if products:
|
||||
ifc.run("cost.assign_cost_item_quantity", cost_item=cost_item, products=products, prop_name=prop_name)
|
||||
cost.load_cost_item_quantity_assignments(cost_item, related_object_type=related_object_type)
|
||||
|
||||
|
||||
def load_cost_item_quantities(cost):
|
||||
def load_cost_item_quantities(cost: tool.Cost):
|
||||
cost.load_cost_item_quantities()
|
||||
|
||||
|
||||
def load_cost_item_element_quantities(cost):
|
||||
def load_cost_item_element_quantities(cost: tool.Cost):
|
||||
cost_item = cost.get_highlighted_cost_item()
|
||||
cost.load_cost_item_quantity_assignments(cost_item, related_object_type="PRODUCT")
|
||||
|
||||
|
||||
def load_cost_item_task_quantities(cost):
|
||||
def load_cost_item_task_quantities(cost: tool.Cost):
|
||||
cost_item = cost.get_highlighted_cost_item()
|
||||
cost.load_cost_item_quantity_assignments(cost_item, related_object_type="PROCESS")
|
||||
|
||||
|
||||
def load_cost_item_resource_quantities(cost):
|
||||
def load_cost_item_resource_quantities(cost: tool.Cost):
|
||||
cost_item = cost.get_highlighted_cost_item()
|
||||
cost.load_cost_item_quantity_assignments(cost_item, related_object_type="RESOURCE")
|
||||
|
||||
|
||||
def assign_cost_value(ifc, cost_item, cost_rate):
|
||||
def assign_cost_value(ifc: tool.Ifc, cost_item: ifcopenshell.entity_instance, cost_rate):
|
||||
ifc.run("cost.assign_cost_value", cost_item=cost_item, cost_rate=cost_rate)
|
||||
|
||||
|
||||
def load_schedule_of_rates(cost, schedule_of_rates):
|
||||
def load_schedule_of_rates(cost: tool.Cost, schedule_of_rates):
|
||||
cost.load_schedule_of_rates_tree(schedule_of_rates)
|
||||
|
||||
|
||||
def unassign_cost_item_quantity(ifc, cost, cost_item, products):
|
||||
def unassign_cost_item_quantity(ifc: tool.Ifc, cost: tool.Cost, cost_item: ifcopenshell.entity_instance, products):
|
||||
ifc.run("cost.unassign_cost_item_quantity", cost_item=cost_item, products=products)
|
||||
cost.load_cost_item_quantities()
|
||||
|
||||
|
||||
def enable_editing_cost_item_quantities(cost, cost_item):
|
||||
def enable_editing_cost_item_quantities(cost: tool.Cost, cost_item: ifcopenshell.entity_instance):
|
||||
cost.enable_editing_cost_item_quantities(cost_item)
|
||||
|
||||
|
||||
def enable_editing_cost_item_values(cost, cost_item):
|
||||
def enable_editing_cost_item_values(cost: tool.Cost, cost_item: ifcopenshell.entity_instance):
|
||||
cost.enable_editing_cost_item_values(cost_item)
|
||||
|
||||
|
||||
def add_cost_item_quantity(ifc, cost_item, ifc_class):
|
||||
def add_cost_item_quantity(ifc: tool.Ifc, cost_item: ifcopenshell.entity_instance, ifc_class):
|
||||
ifc.run("cost.add_cost_item_quantity", cost_item=cost_item, ifc_class=ifc_class)
|
||||
|
||||
|
||||
def remove_cost_item_quantity(ifc, cost_item, physical_quantity):
|
||||
def remove_cost_item_quantity(ifc: tool.Ifc, cost_item: ifcopenshell.entity_instance, physical_quantity):
|
||||
ifc.run("cost.remove_cost_item_quantity", cost_item=cost_item, physical_quantity=physical_quantity)
|
||||
|
||||
|
||||
def enable_editing_cost_item_quantity(cost, physical_quantity):
|
||||
def enable_editing_cost_item_quantity(cost: tool.Cost, physical_quantity):
|
||||
cost.load_cost_item_quantity_attributes(physical_quantity)
|
||||
cost.enable_editing_cost_item_quantity(physical_quantity)
|
||||
|
||||
|
||||
def disable_editing_cost_item_quantity(cost):
|
||||
def disable_editing_cost_item_quantity(cost: tool.Cost):
|
||||
cost.disable_editing_cost_item_quantity()
|
||||
|
||||
|
||||
def edit_cost_item_quantity(ifc, cost, physical_quantity):
|
||||
def edit_cost_item_quantity(ifc: tool.Ifc, cost: tool.Cost, physical_quantity):
|
||||
attributes = cost.get_cost_item_quantity_attributes()
|
||||
ifc.run("cost.edit_cost_item_quantity", physical_quantity=physical_quantity, attributes=attributes)
|
||||
cost.disable_editing_cost_item_quantity()
|
||||
cost.load_cost_item_quantities()
|
||||
|
||||
|
||||
def add_cost_value(ifc, cost, parent, cost_type, cost_category):
|
||||
def add_cost_value(ifc: tool.Ifc, cost: tool.Cost, parent, cost_type, cost_category):
|
||||
value = ifc.run("cost.add_cost_value", parent=parent)
|
||||
ifc.run(
|
||||
"cost.edit_cost_value",
|
||||
@@ -186,82 +219,84 @@ def add_cost_value(ifc, cost, parent, cost_type, cost_category):
|
||||
)
|
||||
|
||||
|
||||
def remove_cost_value(ifc, parent, cost_value):
|
||||
def remove_cost_value(ifc: tool.Ifc, parent, cost_value):
|
||||
ifc.run("cost.remove_cost_value", parent=parent, cost_value=cost_value)
|
||||
|
||||
|
||||
def enable_editing_cost_item_value(cost, cost_value):
|
||||
def enable_editing_cost_item_value(cost: tool.Cost, cost_value):
|
||||
cost.load_cost_item_value_attributes(cost_value)
|
||||
cost.enable_editing_cost_item_value(cost_value)
|
||||
|
||||
|
||||
def disable_editing_cost_item_value(cost):
|
||||
def disable_editing_cost_item_value(cost: tool.Cost):
|
||||
cost.disable_editing_cost_item_value()
|
||||
|
||||
|
||||
def enable_editing_cost_item_value_formula(cost, cost_value):
|
||||
def enable_editing_cost_item_value_formula(cost: tool.Cost, cost_value):
|
||||
cost.load_cost_item_value_formula_attributes(cost_value)
|
||||
cost.enable_editing_cost_item_value_formula(cost_value)
|
||||
|
||||
|
||||
def edit_cost_item_value_formula(ifc, cost, cost_value):
|
||||
def edit_cost_item_value_formula(ifc: tool.Ifc, cost: tool.Cost, cost_value):
|
||||
formula = cost.get_cost_item_value_formula()
|
||||
ifc.run("cost.edit_cost_value_formula", cost_value=cost_value, formula=formula)
|
||||
cost.disable_editing_cost_item_value()
|
||||
|
||||
|
||||
def edit_cost_value(ifc, cost, cost_value):
|
||||
def edit_cost_value(ifc: tool.Ifc, cost: tool.Cost, cost_value):
|
||||
attributes = cost.get_cost_value_attributes()
|
||||
ifc.run("cost.edit_cost_value", cost_value=cost_value, attributes=attributes)
|
||||
cost.disable_editing_cost_item_value()
|
||||
# cost.load_cost_item_values(cost.get_highlighted_cost_item())
|
||||
|
||||
|
||||
def copy_cost_item_values(ifc, cost, source, destination):
|
||||
def copy_cost_item_values(ifc: tool.Ifc, cost: tool.Cost, source, destination):
|
||||
ifc.run("cost.copy_cost_item_values", source=source, destination=destination)
|
||||
|
||||
|
||||
def select_cost_item_products(cost, spatial, cost_item):
|
||||
def select_cost_item_products(cost: tool.Cost, spatial: tool.Spatial, cost_item: ifcopenshell.entity_instance):
|
||||
is_deep = cost.show_nested_cost_item_elements()
|
||||
products = cost.get_cost_item_products(cost_item, is_deep)
|
||||
spatial.select_products(products)
|
||||
|
||||
|
||||
def select_cost_schedule_products(cost, spatial, cost_schedule):
|
||||
def select_cost_schedule_products(cost: tool.Cost, spatial: tool.Spatial, cost_schedule: ifcopenshell.entity_instance):
|
||||
products = cost.get_cost_schedule_products(cost_schedule)
|
||||
spatial.select_products(products)
|
||||
|
||||
|
||||
def import_cost_schedule_csv(cost, file_path, is_schedule_of_rates):
|
||||
def import_cost_schedule_csv(cost: tool.Cost, file_path, is_schedule_of_rates):
|
||||
cost.import_cost_schedule_csv(file_path, is_schedule_of_rates)
|
||||
|
||||
|
||||
def add_cost_column(cost, name):
|
||||
def add_cost_column(cost: tool.Cost, name):
|
||||
cost.add_cost_column(name)
|
||||
|
||||
|
||||
def remove_cost_column(cost, name):
|
||||
def remove_cost_column(cost: tool.Cost, name):
|
||||
cost.remove_cost_column(name)
|
||||
|
||||
|
||||
def expand_cost_item_rate(cost, cost_item):
|
||||
def expand_cost_item_rate(cost: tool.Cost, cost_item: ifcopenshell.entity_instance):
|
||||
cost.expand_cost_item_rate(cost_item)
|
||||
|
||||
|
||||
def contract_cost_item_rate(cost, cost_item):
|
||||
def contract_cost_item_rate(cost: tool.Cost, cost_item: ifcopenshell.entity_instance):
|
||||
cost.contract_cost_item_rate(cost_item)
|
||||
|
||||
|
||||
def calculate_cost_item_resource_value(ifc, cost_item):
|
||||
def calculate_cost_item_resource_value(ifc: tool.Ifc, cost_item: ifcopenshell.entity_instance):
|
||||
ifc.run("cost.calculate_cost_item_resource_value", cost_item=cost_item)
|
||||
|
||||
|
||||
def export_cost_schedules(cost, filepath, format, cost_schedule=None):
|
||||
def export_cost_schedules(cost: tool.Cost, filepath, format, cost_schedule=None):
|
||||
cost.play_sound()
|
||||
return cost.export_cost_schedules(filepath, format, cost_schedule)
|
||||
|
||||
|
||||
def clear_cost_item_assignments(ifc, cost, cost_item, related_object_type):
|
||||
def clear_cost_item_assignments(
|
||||
ifc: tool.Ifc, cost: tool.Cost, cost_item: ifcopenshell.entity_instance, related_object_type
|
||||
):
|
||||
products = cost.get_cost_item_assignments(cost_item, filter_by_type=related_object_type)
|
||||
if products:
|
||||
ifc.run("cost.unassign_cost_item_quantity", cost_item=cost_item, products=products)
|
||||
@@ -269,7 +304,7 @@ def clear_cost_item_assignments(ifc, cost, cost_item, related_object_type):
|
||||
cost.load_cost_schedule_tree()
|
||||
|
||||
|
||||
def select_unassigned_products(ifc, cost, spatial):
|
||||
def select_unassigned_products(ifc: tool.Ifc, cost: tool.Cost, spatial: tool.Spatial):
|
||||
spatial.deselect_objects()
|
||||
products = ifc.get().by_type("IfcElement")
|
||||
cost_schedule = cost.get_active_cost_schedule()
|
||||
@@ -277,11 +312,11 @@ def select_unassigned_products(ifc, cost, spatial):
|
||||
spatial.select_products(selection)
|
||||
|
||||
|
||||
def load_product_cost_items(cost, product):
|
||||
def load_product_cost_items(cost: tool.Cost, product):
|
||||
cost.load_product_cost_items(product)
|
||||
|
||||
|
||||
def highlight_product_cost_item(spatial, cost, cost_item):
|
||||
def highlight_product_cost_item(spatial: tool.Spatial, cost: tool.Cost, cost_item: ifcopenshell.entity_instance):
|
||||
cost_schedule = cost.get_cost_schedule(cost_item)
|
||||
is_cost_schedule_active = cost.is_cost_schedule_active(cost_schedule)
|
||||
if is_cost_schedule_active:
|
||||
@@ -290,7 +325,7 @@ def highlight_product_cost_item(spatial, cost, cost_item):
|
||||
return "Cost schedule is not active"
|
||||
|
||||
|
||||
def change_parent_cost_item(ifc, cost, new_parent):
|
||||
def change_parent_cost_item(ifc: tool.Ifc, cost: tool.Cost, new_parent):
|
||||
cost_item = cost.get_active_cost_item()
|
||||
if cost_item and cost.is_root_cost_item(cost_item):
|
||||
return "Cannot change root cost item"
|
||||
@@ -300,7 +335,7 @@ def change_parent_cost_item(ifc, cost, new_parent):
|
||||
cost.load_cost_schedule_tree()
|
||||
|
||||
|
||||
def copy_cost_item(ifc, cost):
|
||||
def copy_cost_item(ifc: tool.Ifc, cost: tool.Cost):
|
||||
cost_item = cost.get_highlighted_cost_item()
|
||||
if cost_item:
|
||||
cost_item = ifc.run("cost.copy_cost_item", cost_item=cost_item)
|
||||
@@ -308,7 +343,7 @@ def copy_cost_item(ifc, cost):
|
||||
cost.load_cost_schedule_tree()
|
||||
|
||||
|
||||
def add_currency(ifc, cost):
|
||||
def add_currency(ifc: tool.Ifc, cost: tool.Cost):
|
||||
unit = ifc.run("unit.add_monetary_unit")
|
||||
attributes = cost.get_currency_attributes()
|
||||
ifc.run("unit.edit_monetary_unit", unit=unit, attributes=attributes)
|
||||
|
||||
@@ -16,36 +16,44 @@
|
||||
# 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 pathlib import Path
|
||||
import ifcopenshell
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
def enable_editing_text(drawing, obj=None):
|
||||
def enable_editing_text(drawing: tool.Drawing, obj: bpy.types.Object) -> None:
|
||||
drawing.enable_editing_text(obj)
|
||||
drawing.import_text_attributes(obj)
|
||||
|
||||
|
||||
def disable_editing_text(drawing, obj=None):
|
||||
def disable_editing_text(drawing: tool.Drawing, obj: bpy.types.Object) -> None:
|
||||
drawing.disable_editing_text(obj)
|
||||
|
||||
|
||||
def edit_text(drawing, obj=None):
|
||||
def edit_text(drawing: tool.Drawing, obj: bpy.types.Object) -> None:
|
||||
drawing.synchronise_ifc_and_text_attributes(obj)
|
||||
drawing.update_text_size_pset(obj)
|
||||
drawing.update_text_value(obj)
|
||||
drawing.disable_editing_text(obj)
|
||||
|
||||
|
||||
def enable_editing_assigned_product(drawing, obj=None):
|
||||
def enable_editing_assigned_product(drawing: tool.Drawing, obj: bpy.types.Object) -> None:
|
||||
drawing.enable_editing_assigned_product(obj)
|
||||
drawing.import_assigned_product(obj)
|
||||
|
||||
|
||||
def disable_editing_assigned_product(drawing, obj=None):
|
||||
def disable_editing_assigned_product(drawing: tool.Drawing, obj: bpy.types.Object) -> None:
|
||||
drawing.disable_editing_assigned_product(obj)
|
||||
|
||||
|
||||
def edit_assigned_product(ifc, drawing, obj=None, product=None):
|
||||
def edit_assigned_product(
|
||||
ifc: tool.Ifc, drawing: tool.Drawing, obj: bpy.types.Object, product: Optional[ifcopenshell.entity_instance] = None
|
||||
) -> None:
|
||||
element = ifc.get_entity(obj)
|
||||
existing_product = drawing.get_assigned_product(element)
|
||||
if existing_product != product:
|
||||
@@ -58,16 +66,16 @@ def edit_assigned_product(ifc, drawing, obj=None, product=None):
|
||||
drawing.disable_editing_assigned_product(obj)
|
||||
|
||||
|
||||
def load_sheets(drawing):
|
||||
def load_sheets(drawing: tool.Drawing) -> None:
|
||||
drawing.import_sheets()
|
||||
drawing.enable_editing_sheets()
|
||||
|
||||
|
||||
def disable_editing_sheets(drawing):
|
||||
def disable_editing_sheets(drawing: tool.Drawing) -> None:
|
||||
drawing.disable_editing_sheets()
|
||||
|
||||
|
||||
def add_sheet(ifc, drawing, titleblock: ifcopenshell.entity_instance):
|
||||
def add_sheet(ifc: tool.Ifc, drawing, titleblock: ifcopenshell.entity_instance) -> None:
|
||||
sheet = ifc.run("document.add_information")
|
||||
layout = ifc.run("document.add_reference", information=sheet)
|
||||
titleblock_reference = ifc.run("document.add_reference", information=sheet)
|
||||
@@ -93,7 +101,7 @@ def add_sheet(ifc, drawing, titleblock: ifcopenshell.entity_instance):
|
||||
drawing.import_sheets()
|
||||
|
||||
|
||||
def regenerate_sheet(drawing, sheet=None):
|
||||
def regenerate_sheet(drawing: tool.Drawing, sheet: ifcopenshell.entity_instance) -> None:
|
||||
titleblock_uri = drawing.get_document_uri(sheet, "TITLEBLOCK")
|
||||
drawing.create_svg_sheet(sheet, drawing.sanitise_filename(Path(titleblock_uri).stem))
|
||||
try:
|
||||
@@ -104,11 +112,11 @@ def regenerate_sheet(drawing, sheet=None):
|
||||
drawing.delete_file(path_layout)
|
||||
|
||||
|
||||
def open_sheet(drawing, sheet=None):
|
||||
def open_sheet(drawing: tool.Drawing, sheet: ifcopenshell.entity_instance) -> None:
|
||||
drawing.open_layout_svg(drawing.get_document_uri(sheet, "LAYOUT"))
|
||||
|
||||
|
||||
def remove_sheet(ifc, drawing, sheet=None):
|
||||
def remove_sheet(ifc: tool.Ifc, drawing: tool.Drawing, sheet: ifcopenshell.entity_instance) -> None:
|
||||
for reference in drawing.get_document_references(sheet):
|
||||
if drawing.get_reference_description(reference) in ("LAYOUT", "SHEET", "REVISION", "RASTER"):
|
||||
uri = ifc.resolve_uri(drawing.get_document_uri(reference))
|
||||
@@ -118,7 +126,7 @@ def remove_sheet(ifc, drawing, sheet=None):
|
||||
drawing.import_sheets()
|
||||
|
||||
|
||||
def rename_sheet(ifc, drawing, sheet: ifcopenshell.entity_instance, identification: str, name: str) -> None:
|
||||
def rename_sheet(ifc: tool.Ifc, drawing, sheet: ifcopenshell.entity_instance, identification: str, name: str) -> None:
|
||||
if ifc.get_schema() == "IFC2X3":
|
||||
attributes = {"DocumentId": identification, "Name": name}
|
||||
else:
|
||||
@@ -144,30 +152,32 @@ def rename_sheet(ifc, drawing, sheet: ifcopenshell.entity_instance, identificati
|
||||
drawing.move_file(old_location, ifc.resolve_uri(new_location))
|
||||
|
||||
|
||||
def rename_reference(ifc, drawing, reference=None, identification=None):
|
||||
def rename_reference(
|
||||
ifc: tool.Ifc, drawing: tool.Drawing, reference: ifcopenshell.entity_instance, identification: str
|
||||
) -> None:
|
||||
attributes = drawing.generate_reference_attributes(reference, Identification=identification)
|
||||
ifc.run("document.edit_reference", reference=reference, attributes=attributes)
|
||||
|
||||
|
||||
def load_schedules(drawing):
|
||||
def load_schedules(drawing: tool.Drawing) -> None:
|
||||
drawing.import_documents("SCHEDULE")
|
||||
drawing.enable_editing_schedules()
|
||||
|
||||
|
||||
def load_references(drawing):
|
||||
def load_references(drawing: tool.Drawing) -> None:
|
||||
drawing.import_documents("REFERENCE")
|
||||
drawing.enable_editing_references()
|
||||
|
||||
|
||||
def disable_editing_schedules(drawing):
|
||||
def disable_editing_schedules(drawing: tool.Drawing) -> None:
|
||||
drawing.disable_editing_schedules()
|
||||
|
||||
|
||||
def disable_editing_references(drawing):
|
||||
def disable_editing_references(drawing: tool.Drawing) -> None:
|
||||
drawing.disable_editing_references()
|
||||
|
||||
|
||||
def add_document(ifc, drawing, document_type, uri=None):
|
||||
def add_document(ifc: tool.Ifc, drawing: tool.Drawing, document_type: tool.Drawing.DOCUMENT_TYPE, uri: str) -> None:
|
||||
document = ifc.run("document.add_information")
|
||||
reference = ifc.run("document.add_reference", information=document)
|
||||
name = drawing.get_path_filename(uri)
|
||||
@@ -180,34 +190,43 @@ def add_document(ifc, drawing, document_type, uri=None):
|
||||
drawing.import_documents(document_type)
|
||||
|
||||
|
||||
def remove_document(ifc, drawing, document_type, document=None):
|
||||
def remove_document(
|
||||
ifc: tool.Ifc,
|
||||
drawing: tool.Drawing,
|
||||
document_type: tool.Drawing.DOCUMENT_TYPE,
|
||||
document: ifcopenshell.entity_instance,
|
||||
) -> None:
|
||||
ifc.run("document.remove_information", information=document)
|
||||
drawing.import_documents(document_type)
|
||||
|
||||
|
||||
def open_schedule(drawing, schedule=None):
|
||||
def open_schedule(drawing: tool.Drawing, schedule: ifcopenshell.entity_instance) -> None:
|
||||
drawing.open_spreadsheet(drawing.get_document_uri(schedule))
|
||||
|
||||
|
||||
def open_reference(drawing, reference=None):
|
||||
def open_reference(drawing: tool.Drawing, reference: ifcopenshell.entity_instance) -> None:
|
||||
drawing.open_svg(drawing.get_document_uri(reference))
|
||||
|
||||
|
||||
def update_document_name(ifc, drawing, document=None, name=None):
|
||||
def update_document_name(
|
||||
ifc: tool.Ifc, drawing: tool.Drawing, document: ifcopenshell.entity_instance, name=None
|
||||
) -> None:
|
||||
if drawing.get_name(document) != name:
|
||||
ifc.run("document.edit_information", information=document, attributes={"Name": name})
|
||||
|
||||
|
||||
def load_drawings(drawing):
|
||||
def load_drawings(drawing: tool.Drawing) -> None:
|
||||
drawing.import_drawings()
|
||||
drawing.enable_editing_drawings()
|
||||
|
||||
|
||||
def disable_editing_drawings(drawing):
|
||||
def disable_editing_drawings(drawing: tool.Drawing) -> None:
|
||||
drawing.disable_editing_drawings()
|
||||
|
||||
|
||||
def add_drawing(ifc, collector, drawing, target_view=None, location_hint=None):
|
||||
def add_drawing(
|
||||
ifc: tool.Ifc, collector: tool.Collector, drawing: tool.Drawing, target_view=None, location_hint=None
|
||||
) -> None:
|
||||
drawing_name = drawing.ensure_unique_drawing_name(drawing.generate_drawing_name(target_view, location_hint))
|
||||
drawing_matrix = drawing.generate_drawing_matrix(target_view, location_hint)
|
||||
camera = drawing.create_camera(drawing_name, drawing_matrix, location_hint)
|
||||
@@ -265,7 +284,12 @@ def add_drawing(ifc, collector, drawing, target_view=None, location_hint=None):
|
||||
drawing.import_drawings()
|
||||
|
||||
|
||||
def duplicate_drawing(ifc, drawing_tool, drawing=None, should_duplicate_annotations=False):
|
||||
def duplicate_drawing(
|
||||
ifc: tool.Ifc,
|
||||
drawing_tool: tool.Drawing,
|
||||
drawing: ifcopenshell.entity_instance,
|
||||
should_duplicate_annotations: bool = False,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
drawing_name = drawing_tool.ensure_unique_drawing_name(drawing_tool.get_name(drawing))
|
||||
new_drawing = ifc.run("root.copy_class", product=drawing)
|
||||
drawing_tool.copy_representation(drawing, new_drawing)
|
||||
@@ -302,7 +326,7 @@ def duplicate_drawing(ifc, drawing_tool, drawing=None, should_duplicate_annotati
|
||||
return new_drawing
|
||||
|
||||
|
||||
def remove_drawing(ifc, drawing_tool, drawing=None):
|
||||
def remove_drawing(ifc: tool.Ifc, drawing_tool: tool.Drawing, drawing: ifcopenshell.entity_instance) -> None:
|
||||
if drawing_tool.is_active_drawing(drawing):
|
||||
drawing_tool.run_drawing_activate_model()
|
||||
|
||||
@@ -330,7 +354,9 @@ def remove_drawing(ifc, drawing_tool, drawing=None):
|
||||
drawing_tool.import_drawings()
|
||||
|
||||
|
||||
def update_drawing_name(ifc, drawing_tool, drawing=None, name=None):
|
||||
def update_drawing_name(
|
||||
ifc: tool.Ifc, drawing_tool: tool.Drawing, drawing: ifcopenshell.entity_instance, name=None
|
||||
) -> None:
|
||||
if drawing_tool.get_name(drawing) != name:
|
||||
ifc.run("attribute.edit_attributes", product=drawing, attributes={"Name": name})
|
||||
group = drawing_tool.get_drawing_group(drawing)
|
||||
@@ -364,7 +390,13 @@ def update_drawing_name(ifc, drawing_tool, drawing=None, name=None):
|
||||
drawing_tool.import_sheets()
|
||||
|
||||
|
||||
def add_annotation(ifc, collector, drawing_tool, drawing=None, object_type=None):
|
||||
def add_annotation(
|
||||
ifc: tool.Ifc,
|
||||
collector: tool.Collector,
|
||||
drawing_tool: tool.Drawing,
|
||||
drawing: ifcopenshell.entity_instance,
|
||||
object_type: str,
|
||||
) -> None:
|
||||
target_view = drawing_tool.get_drawing_target_view(drawing)
|
||||
context = drawing_tool.get_annotation_context(target_view, object_type)
|
||||
if not context:
|
||||
@@ -387,12 +419,14 @@ def add_annotation(ifc, collector, drawing_tool, drawing=None, object_type=None)
|
||||
drawing_tool.enable_editing(obj)
|
||||
|
||||
|
||||
def build_schedule(drawing, schedule=None):
|
||||
def build_schedule(drawing: tool.Drawing, schedule: ifcopenshell.entity_instance) -> None:
|
||||
drawing.create_svg_schedule(schedule)
|
||||
drawing.open_svg(drawing.get_path_with_ext(drawing.get_document_uri(schedule), "svg"))
|
||||
|
||||
|
||||
def sync_references(ifc, collector, drawing_tool, drawing=None):
|
||||
def sync_references(
|
||||
ifc: tool.Ifc, collector: tool.Collector, drawing_tool: tool.Drawing, drawing: ifcopenshell.entity_instance
|
||||
) -> None:
|
||||
if not drawing_tool.has_annotation(drawing):
|
||||
return
|
||||
|
||||
@@ -437,11 +471,13 @@ def sync_references(ifc, collector, drawing_tool, drawing=None):
|
||||
drawing_tool.sync_object_representation(reference_obj)
|
||||
|
||||
|
||||
def select_assigned_product(drawing, context):
|
||||
def select_assigned_product(drawing: tool.Drawing, context: bpy.types.Context) -> None:
|
||||
drawing.select_assigned_product(context)
|
||||
|
||||
|
||||
def activate_drawing_view(ifc, blender, drawing_tool, drawing):
|
||||
def activate_drawing_view(
|
||||
ifc: tool.Ifc, blender: tool.Blender, drawing_tool: tool.Drawing, drawing: ifcopenshell.entity_instance
|
||||
) -> None:
|
||||
camera = ifc.get_object(drawing)
|
||||
if not camera:
|
||||
camera = drawing_tool.import_drawing(drawing)
|
||||
|
||||
@@ -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`;
|
||||
|
||||
@@ -85,6 +85,7 @@ def enable_editing_material(material_tool, material):
|
||||
def edit_material(ifc, material_tool, material):
|
||||
attributes = material_tool.get_material_attributes()
|
||||
ifc.run("material.edit_material", material=material, attributes=attributes)
|
||||
material_tool.sync_blender_material_name(material)
|
||||
material_tool.disable_editing_material()
|
||||
material_type = material_tool.get_active_material_type()
|
||||
material_tool.import_material_definitions(material_type)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -487,12 +487,12 @@ class Material:
|
||||
def disable_editing_materials(cls): pass
|
||||
def enable_editing_material(cls, material): pass
|
||||
def enable_editing_materials(cls): pass
|
||||
def get_active_material_type(cls): pass
|
||||
def get_active_material(cls): pass
|
||||
def get_active_material_type(cls): pass
|
||||
def get_active_object_material(cls, obj): pass
|
||||
def get_elements_by_material(cls, material): pass
|
||||
def get_material_attributes(cls): pass
|
||||
def get_material(cls, element, should_inherit): pass
|
||||
def get_material_attributes(cls): pass
|
||||
def get_name(cls, obj): pass
|
||||
def get_type(cls, element): pass
|
||||
def has_material_profile(cls, element): pass
|
||||
@@ -503,6 +503,7 @@ class Material:
|
||||
def is_material_used_in_sets(cls, material): pass
|
||||
def load_material_attributes(cls, material): pass
|
||||
def replace_material_with_material_profile(cls, element): pass
|
||||
def sync_blender_material_name(cls, material): pass
|
||||
|
||||
|
||||
@interface
|
||||
|
||||
@@ -468,15 +468,20 @@ class Blender(blenderbim.core.tool.Blender):
|
||||
active_object.select_set(True)
|
||||
|
||||
@classmethod
|
||||
def enum_property_has_valid_index(cls, props, prop_name: str, enum_items: tuple) -> bool:
|
||||
def enum_property_has_valid_index(cls, props: bpy.types.PropertyGroup, prop_name: str, enum_items: tuple) -> bool:
|
||||
"""method created for readibility and to avoid console warnings like
|
||||
`pyrna_enum_to_py: current value '17' matches no enum in 'BIMModelProperties', '', 'relating_type_id'`
|
||||
"""
|
||||
items_amount = len(enum_items)
|
||||
# If enum has no items it seems to always produce a warning.
|
||||
# E.g. if you try to get it's value directly: `BIMModelProperties.relating_type_id`.
|
||||
if items_amount == 0:
|
||||
return False
|
||||
current_value_index = props.get(prop_name, None)
|
||||
# assuming the default value is fine
|
||||
if current_value_index is None:
|
||||
return True
|
||||
return current_value_index < len(enum_items)
|
||||
return current_value_index < items_amount
|
||||
|
||||
@classmethod
|
||||
def append_data_block(cls, filepath: str, data_block_type: str, name: str, link=False, relative=False) -> dict:
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import os
|
||||
import bpy
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell.util.date
|
||||
import ifcopenshell.util.cost
|
||||
import ifcopenshell.util.unit
|
||||
import blenderbim.bim.helper
|
||||
import json
|
||||
from typing import Optional, Any, Generator
|
||||
|
||||
|
||||
class Cost(blenderbim.core.tool.Cost):
|
||||
@@ -36,7 +38,7 @@ class Cost(blenderbim.core.tool.Cost):
|
||||
blenderbim.bim.helper.import_attributes2(cost_schedule, props.cost_schedule_attributes, callback=special_import)
|
||||
|
||||
@classmethod
|
||||
def enable_editing_cost_items(cls, cost_schedule):
|
||||
def enable_editing_cost_items(cls, cost_schedule: ifcopenshell.entity_instance) -> None:
|
||||
props = bpy.context.scene.BIMCostProperties
|
||||
props.active_cost_schedule_id = cost_schedule.id()
|
||||
props.is_editing = "COST_ITEMS"
|
||||
@@ -163,7 +165,7 @@ class Cost(blenderbim.core.tool.Cost):
|
||||
return
|
||||
|
||||
@classmethod
|
||||
def load_cost_item_types(cls, cost_item=None):
|
||||
def load_cost_item_types(cls, cost_item: Optional[ifcopenshell.entity_instance] = None) -> None:
|
||||
if not cost_item:
|
||||
cost_item = cls.get_highlighted_cost_item()
|
||||
if not cost_item:
|
||||
@@ -259,7 +261,7 @@ class Cost(blenderbim.core.tool.Cost):
|
||||
blenderbim.bim.helper.import_attributes2(physical_quantity, props.quantity_attributes)
|
||||
|
||||
@classmethod
|
||||
def enable_editing_cost_item_values(cls, cost_item=None):
|
||||
def enable_editing_cost_item_values(cls, cost_item: ifcopenshell.entity_instance) -> None:
|
||||
props = bpy.context.scene.BIMCostProperties
|
||||
props.active_cost_item_id = cost_item.id()
|
||||
props.cost_item_editing_type = "VALUES"
|
||||
@@ -287,7 +289,7 @@ class Cost(blenderbim.core.tool.Cost):
|
||||
return attributes
|
||||
|
||||
@classmethod
|
||||
def load_cost_item_value_attributes(cls, cost_value=None):
|
||||
def load_cost_item_value_attributes(cls, cost_value: ifcopenshell.entity_instance) -> None:
|
||||
def import_attributes(name, prop, data, cost_value, is_rates, props_collection):
|
||||
if name == "AppliedValue":
|
||||
# TODO: for now, only support simple IfcValues (which are effectively IfcMonetaryMeasure)
|
||||
@@ -334,36 +336,38 @@ class Cost(blenderbim.core.tool.Cost):
|
||||
blenderbim.bim.helper.import_attributes2(cost_value, props.cost_value_attributes, callback=callback)
|
||||
|
||||
@classmethod
|
||||
def calculate_applied_value(cls, cost_item, cost_value):
|
||||
def calculate_applied_value(
|
||||
cls, cost_item: ifcopenshell.entity_instance, cost_value: ifcopenshell.entity_instance
|
||||
) -> float:
|
||||
return ifcopenshell.util.cost.calculate_applied_value(cost_item, cost_value)
|
||||
|
||||
@classmethod
|
||||
def is_active_schedule_of_rates(cls):
|
||||
def is_active_schedule_of_rates(cls) -> bool:
|
||||
return (
|
||||
tool.Ifc.get().by_id(bpy.context.scene.BIMCostProperties.active_cost_schedule_id).PredefinedType
|
||||
== "SCHEDULEOFRATES"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def enable_editing_cost_item_value(cls, cost_value=None):
|
||||
def enable_editing_cost_item_value(cls, cost_value: ifcopenshell.entity_instance) -> None:
|
||||
props = bpy.context.scene.BIMCostProperties
|
||||
props.active_cost_value_id = cost_value.id()
|
||||
props.cost_value_editing_type = "ATTRIBUTES"
|
||||
|
||||
@classmethod
|
||||
def disable_editing_cost_item_value(cls):
|
||||
def disable_editing_cost_item_value(cls) -> None:
|
||||
props = bpy.context.scene.BIMCostProperties
|
||||
props.active_cost_value_id = 0
|
||||
props.cost_value_editing_type = ""
|
||||
|
||||
@classmethod
|
||||
def load_cost_item_value_formula_attributes(cls, cost_value=None):
|
||||
def load_cost_item_value_formula_attributes(cls, cost_value: ifcopenshell.entity_instance) -> None:
|
||||
props = bpy.context.scene.BIMCostProperties
|
||||
props.cost_value_attributes.clear()
|
||||
bpy.context.scene.BIMCostProperties.cost_value_formula = ifcopenshell.util.cost.serialise_cost_value(cost_value)
|
||||
|
||||
@classmethod
|
||||
def enable_editing_cost_item_value_formula(cls, cost_value=None):
|
||||
def enable_editing_cost_item_value_formula(cls, cost_value: ifcopenshell.entity_instance) -> None:
|
||||
props = bpy.context.scene.BIMCostProperties
|
||||
props.active_cost_value_id = cost_value.id()
|
||||
props.cost_value_editing_type = "FORMULA"
|
||||
@@ -373,7 +377,7 @@ class Cost(blenderbim.core.tool.Cost):
|
||||
return bpy.context.scene.BIMCostProperties.cost_value_formula
|
||||
|
||||
@classmethod
|
||||
def get_cost_value_attributes(cls):
|
||||
def get_cost_value_attributes(cls) -> dict[str, Any]:
|
||||
def export_attributes(attributes, prop):
|
||||
if prop.name == "UnitBasisValue":
|
||||
if prop.is_null:
|
||||
@@ -392,13 +396,18 @@ class Cost(blenderbim.core.tool.Cost):
|
||||
return blenderbim.bim.helper.export_attributes(props.cost_value_attributes, callback)
|
||||
|
||||
@classmethod
|
||||
def get_cost_value_unit_component(cls):
|
||||
def get_cost_value_unit_component(cls) -> ifcopenshell.entity_instance:
|
||||
return tool.Ifc.get().by_id(
|
||||
int(bpy.context.scene.BIMCostProperties.cost_value_attributes.get("UnitBasisUnit").enum_value)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_cost_item_assignments(cls, cost_item, filter_by_type=None, is_deep=False):
|
||||
def get_cost_item_assignments(
|
||||
cls,
|
||||
cost_item: ifcopenshell.entity_instance,
|
||||
filter_by_type: Optional[ifcopenshell.util.cost.FILTER_BY_TYPE] = None,
|
||||
is_deep: bool = False,
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
return ifcopenshell.util.cost.get_cost_item_assignments(
|
||||
cost_item, filter_by_type=filter_by_type, is_deep=is_deep
|
||||
)
|
||||
@@ -408,30 +417,40 @@ class Cost(blenderbim.core.tool.Cost):
|
||||
return bpy.context.scene.BIMCostProperties.show_nested_elements
|
||||
|
||||
@classmethod
|
||||
def get_cost_item_products(cls, cost_item, is_deep=False):
|
||||
def get_cost_item_products(
|
||||
cls, cost_item: ifcopenshell.entity_instance, is_deep: bool = False
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
return cls.get_cost_item_assignments(cost_item, filter_by_type="PRODUCT", is_deep=is_deep)
|
||||
|
||||
@classmethod
|
||||
def get_cost_item_resources(cls, cost_item, is_deep=False):
|
||||
def get_cost_item_resources(
|
||||
cls, cost_item: ifcopenshell.entity_instance, is_deep: bool = False
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
return cls.get_cost_item_assignments(cost_item, filter_by_type="RESOURCE", is_deep=is_deep)
|
||||
|
||||
@classmethod
|
||||
def get_cost_item_processes(cls, cost_item, is_deep=False):
|
||||
def get_cost_item_processes(
|
||||
cls, cost_item: ifcopenshell.entity_instance, is_deep: bool = False
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
return cls.get_cost_item_assignments(cost_item, filter_by_type="PROCESS", is_deep=is_deep)
|
||||
|
||||
@classmethod
|
||||
def get_schedule_cost_items(cls, cost_schedule):
|
||||
def get_schedule_cost_items(
|
||||
cls, cost_schedule: ifcopenshell.entity_instance
|
||||
) -> Generator[ifcopenshell.entity_instance, None, None]:
|
||||
return ifcopenshell.util.cost.get_schedule_cost_items(cost_schedule)
|
||||
|
||||
@classmethod
|
||||
def get_cost_schedule_products(cls, cost_schedule):
|
||||
def get_cost_schedule_products(
|
||||
cls, cost_schedule: ifcopenshell.entity_instance
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
products = []
|
||||
for cost_item in ifcopenshell.util.cost.get_schedule_cost_items(cost_schedule):
|
||||
products.extend(cls.get_cost_item_products(cost_item))
|
||||
return products
|
||||
|
||||
@classmethod
|
||||
def import_cost_schedule_csv(cls, file_path=None, is_schedule_of_rates=False):
|
||||
def import_cost_schedule_csv(cls, file_path: Optional[str] = None, is_schedule_of_rates: bool = False) -> None:
|
||||
if not file_path:
|
||||
return
|
||||
from ifc5d.csv2ifc import Csv2Ifc
|
||||
@@ -473,7 +492,9 @@ class Cost(blenderbim.core.tool.Cost):
|
||||
cls.load_schedule_of_rates_tree(schedule_of_rates=tool.Ifc.get().by_id(int(props.schedule_of_rates)))
|
||||
|
||||
@classmethod
|
||||
def create_new_cost_item_li(cls, props_collection, cost_item, level_index, type="cost_rate"):
|
||||
def create_new_cost_item_li(
|
||||
cls, props_collection, cost_item: ifcopenshell.entity_instance, level_index: int, type: str = "cost_rate"
|
||||
) -> None:
|
||||
new = props_collection.add()
|
||||
new.ifc_definition_id = cost_item.id()
|
||||
new.name = cost_item.Name or "Unnamed"
|
||||
|
||||
@@ -51,11 +51,14 @@ from blenderbim.bim.module.drawing.prop import get_diagram_scales, BOX_ALIGNMENT
|
||||
from lxml import etree
|
||||
from mathutils import Vector, Matrix
|
||||
from fractions import Fraction
|
||||
from typing import Optional, Union, Iterable, Any
|
||||
from typing import Optional, Union, Iterable, Any, Literal
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class Drawing(blenderbim.core.tool.Drawing):
|
||||
ANNOTATION_DATA_TYPE = Literal["empty", "curve", "mesh"]
|
||||
DOCUMENT_TYPE = Literal["SCHEDULE", "REFERENCE"]
|
||||
|
||||
@classmethod
|
||||
def canonicalise_class_name(cls, name):
|
||||
return re.sub("[^0-9a-zA-Z]+", "", name)
|
||||
@@ -68,11 +71,11 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_annotation_data_type(cls, object_type):
|
||||
def get_annotation_data_type(cls, object_type: str) -> ANNOTATION_DATA_TYPE:
|
||||
return ANNOTATION_TYPES_DATA[object_type][3]
|
||||
|
||||
@classmethod
|
||||
def create_annotation_object(cls, drawing, object_type):
|
||||
def create_annotation_object(cls, drawing: ifcopenshell.entity_instance, object_type: str) -> bpy.types.Object:
|
||||
data_type = cls.get_annotation_data_type(object_type)
|
||||
obj = annotation.Annotator.get_annotation_obj(drawing, object_type, data_type)
|
||||
if object_type == "FILL_AREA":
|
||||
@@ -298,15 +301,15 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
bpy.context.scene.DocProperties.is_editing_sheets = False
|
||||
|
||||
@classmethod
|
||||
def disable_editing_text(cls, obj):
|
||||
def disable_editing_text(cls, obj: bpy.types.Object) -> None:
|
||||
obj.BIMTextProperties.is_editing = False
|
||||
|
||||
@classmethod
|
||||
def disable_editing_assigned_product(cls, obj):
|
||||
def disable_editing_assigned_product(cls, obj: bpy.types.Object) -> None:
|
||||
obj.BIMAssignedProductProperties.is_editing_product = False
|
||||
|
||||
@classmethod
|
||||
def enable_editing(cls, obj):
|
||||
def enable_editing(cls, obj: bpy.types.Object) -> None:
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
@@ -330,11 +333,11 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
bpy.context.scene.DocProperties.is_editing_sheets = True
|
||||
|
||||
@classmethod
|
||||
def enable_editing_text(cls, obj):
|
||||
def enable_editing_text(cls, obj: bpy.types.Object) -> None:
|
||||
obj.BIMTextProperties.is_editing = True
|
||||
|
||||
@classmethod
|
||||
def enable_editing_assigned_product(cls, obj):
|
||||
def enable_editing_assigned_product(cls, obj: bpy.types.Object) -> None:
|
||||
obj.BIMAssignedProductProperties.is_editing_product = True
|
||||
|
||||
@classmethod
|
||||
@@ -428,7 +431,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return location
|
||||
|
||||
@classmethod
|
||||
def get_path_filename(cls, path):
|
||||
def get_path_filename(cls, path: str) -> str:
|
||||
return os.path.splitext(os.path.basename(path))[0]
|
||||
|
||||
@classmethod
|
||||
@@ -483,7 +486,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return ""
|
||||
|
||||
@classmethod
|
||||
def get_name(cls, element):
|
||||
def get_name(cls, element: ifcopenshell.entity_instance) -> Union[str, None]:
|
||||
return element.Name
|
||||
|
||||
@classmethod
|
||||
@@ -563,7 +566,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
ifcopenshell.util.element.remove_deep2(ifc_file, literal)
|
||||
|
||||
@classmethod
|
||||
def synchronise_ifc_and_text_attributes(cls, obj):
|
||||
def synchronise_ifc_and_text_attributes(cls, obj: bpy.types.Object) -> None:
|
||||
literals = cls.get_text_literal(obj, return_list=True)
|
||||
literals_attributes = cls.export_text_literal_attributes(obj)
|
||||
defined_ifc_ids = [l.ifc_definition_id for l in obj.BIMTextProperties.literals]
|
||||
@@ -784,7 +787,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
new.ifc_definition_id = drawing.id() # Last, to prevent unnecessary prop callbacks
|
||||
|
||||
@classmethod
|
||||
def import_documents(cls, document_type):
|
||||
def import_documents(cls, document_type: DOCUMENT_TYPE) -> None:
|
||||
dprops = bpy.context.scene.DocProperties
|
||||
if document_type == "SCHEDULE":
|
||||
documents_collection = dprops.schedules
|
||||
@@ -845,7 +848,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return next(s for s in props.sheets[: props.active_sheet_index + 1][::-1] if s.is_sheet)
|
||||
|
||||
@classmethod
|
||||
def import_text_attributes(cls, obj):
|
||||
def import_text_attributes(cls, obj: bpy.types.Object) -> None:
|
||||
props = obj.BIMTextProperties
|
||||
props.literals.clear()
|
||||
|
||||
@@ -863,7 +866,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
props.font_size = str(text_data["FontSize"])
|
||||
|
||||
@classmethod
|
||||
def import_assigned_product(cls, obj):
|
||||
def import_assigned_product(cls, obj: bpy.types.Object) -> None:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
product = cls.get_assigned_product(element)
|
||||
if product:
|
||||
@@ -934,38 +937,16 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
bpy.context.scene.DocProperties.should_draw_decorations = True
|
||||
|
||||
@classmethod
|
||||
def update_text_value(cls, obj):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element.is_a("IfcTypeProduct"):
|
||||
objs = [obj]
|
||||
for occurrence in ifcopenshell.util.element.get_types(element):
|
||||
obj = tool.Ifc.get_object(occurrence)
|
||||
if obj:
|
||||
objs.append(obj)
|
||||
else:
|
||||
objs = []
|
||||
element_type = ifcopenshell.util.element.get_type(element)
|
||||
if element_type and element_type.RepresentationMaps:
|
||||
obj = tool.Ifc.get_object(element_type)
|
||||
if obj:
|
||||
objs.append(obj)
|
||||
for occurrence in ifcopenshell.util.element.get_types(element_type):
|
||||
obj = tool.Ifc.get_object(occurrence)
|
||||
if obj:
|
||||
objs.append(obj)
|
||||
else:
|
||||
objs = [obj]
|
||||
|
||||
for obj in objs:
|
||||
props = obj.BIMTextProperties
|
||||
literals = cls.get_text_literal(obj, return_list=True)
|
||||
cls.import_text_attributes(obj)
|
||||
for i, literal in enumerate(literals):
|
||||
product = cls.get_assigned_product(tool.Ifc.get_entity(obj)) or tool.Ifc.get_entity(obj)
|
||||
props.literals[i].value = cls.replace_text_literal_variables(literal.Literal, product)
|
||||
def update_text_value(cls, obj: bpy.types.Object) -> None:
|
||||
props = obj.BIMTextProperties
|
||||
literals = cls.get_text_literal(obj, return_list=True)
|
||||
cls.import_text_attributes(obj)
|
||||
for i, literal in enumerate(literals):
|
||||
product = cls.get_assigned_product(tool.Ifc.get_entity(obj)) or tool.Ifc.get_entity(obj)
|
||||
props.literals[i].value = cls.replace_text_literal_variables(literal.Literal, product)
|
||||
|
||||
@classmethod
|
||||
def update_text_size_pset(cls, obj):
|
||||
def update_text_size_pset(cls, obj: bpy.types.Object) -> None:
|
||||
"""updates pset `EPset_Annotation.Classes` value
|
||||
based on current font size from `obj.BIMTextProperties.font_size`
|
||||
"""
|
||||
@@ -1544,7 +1525,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
tool.Geometry.record_object_position(obj)
|
||||
|
||||
@classmethod
|
||||
def get_document_references(cls, document):
|
||||
def get_document_references(cls, document: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
return document.DocumentReferences or []
|
||||
return document.HasDocumentReferences or []
|
||||
@@ -1575,7 +1556,9 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return reference.Description
|
||||
|
||||
@classmethod
|
||||
def generate_reference_attributes(cls, reference: ifcopenshell.entity_instance, **attributes: Any) -> dict[str, Any]:
|
||||
def generate_reference_attributes(
|
||||
cls, reference: ifcopenshell.entity_instance, **attributes: Any
|
||||
) -> dict[str, Any]:
|
||||
"""will automatically convert attributes below for IFC2X3 compatibility:
|
||||
|
||||
- Identification -> ItemReference
|
||||
@@ -1657,23 +1640,22 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
base_elements = set(ifc_file.by_type("IfcElement") + ifc_file.by_type("IfcSpatialElement"))
|
||||
elements = {e for e in (elements & base_elements) if e.is_a() != "IfcSpace"}
|
||||
|
||||
|
||||
updated_set = set()
|
||||
|
||||
for i in elements:
|
||||
# exclude annotations to avoid including annotations from other drawings
|
||||
if not i.is_a("IfcAnnotation"):
|
||||
if not i.is_a("IfcAnnotation"):
|
||||
updated_set.add(i)
|
||||
#add aggregate too, if element is host by one
|
||||
# add aggregate too, if element is host by one
|
||||
if i.Decomposes:
|
||||
aggregate = i.Decomposes[0].RelatingObject
|
||||
#remove IfcProject for class iterator. See https://github.com/IfcOpenShell/IfcOpenShell/issues/4361#issuecomment-2081223615
|
||||
if not aggregate.is_a("IfcProject"):
|
||||
# remove IfcProject for class iterator. See https://github.com/IfcOpenShell/IfcOpenShell/issues/4361#issuecomment-2081223615
|
||||
if not aggregate.is_a("IfcProject"):
|
||||
updated_set.add(aggregate)
|
||||
|
||||
# After the iteration is complete, update elements with updated set
|
||||
# After the iteration is complete, update elements with updated set
|
||||
elements.update(updated_set)
|
||||
|
||||
|
||||
# add annotations from the current drawing
|
||||
annotations = tool.Drawing.get_group_elements(tool.Drawing.get_drawing_group(drawing))
|
||||
elements.update(annotations)
|
||||
@@ -1716,7 +1698,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return reference.ReferencedDocument
|
||||
|
||||
@classmethod
|
||||
def select_assigned_product(cls, context):
|
||||
def select_assigned_product(cls, context: bpy.types.Context) -> None:
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
product = cls.get_assigned_product(element)
|
||||
@@ -1735,7 +1717,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return True if (camera and camera.data.type == "ORTHO") else False
|
||||
|
||||
@classmethod
|
||||
def is_active_drawing(cls, drawing):
|
||||
def is_active_drawing(cls, drawing: ifcopenshell.entity_instance) -> bool:
|
||||
return drawing.id() == bpy.context.scene.DocProperties.active_drawing_id
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -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
|
||||
@@ -88,7 +90,7 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
bpy.data.meshes.remove(data)
|
||||
|
||||
@classmethod
|
||||
def delete_ifc_object(cls, obj):
|
||||
def delete_ifc_object(cls, obj: bpy.types.Object) -> None:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
return
|
||||
@@ -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
|
||||
|
||||
@@ -242,3 +242,16 @@ class Material(blenderbim.core.tool.Material):
|
||||
meshes_to_objects[mesh] = obj
|
||||
for obj in meshes_to_objects.values():
|
||||
tool.Geometry.reload_representation(obj)
|
||||
|
||||
@classmethod
|
||||
def sync_blender_material_name(cls, material):
|
||||
name = material.Name or "Unnamed"
|
||||
obj = tool.Ifc.get_object(material)
|
||||
if obj:
|
||||
obj.name = name
|
||||
style = tool.Style.get_style(obj)
|
||||
if style:
|
||||
style.Name = name
|
||||
obj = tool.Ifc.get_object(style)
|
||||
if obj:
|
||||
obj.name = name
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -23,6 +23,7 @@ import collections
|
||||
import collections.abc
|
||||
import numpy as np
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.representation
|
||||
@@ -468,7 +469,7 @@ class Model(blenderbim.core.tool.Model):
|
||||
has_deleted_opening = True
|
||||
|
||||
@classmethod
|
||||
def get_material_layer_parameters(cls, element):
|
||||
def get_material_layer_parameters(cls, element: ifcopenshell.entity_instance) -> dict[str, Any]:
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
layer_set_direction = "AXIS2"
|
||||
offset = 0.0
|
||||
@@ -551,7 +552,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
|
||||
|
||||
@@ -31,6 +31,7 @@ import json
|
||||
from math import pi
|
||||
from mathutils import Vector, Matrix
|
||||
from shapely import Polygon, MultiPolygon
|
||||
from typing import Generator
|
||||
|
||||
|
||||
class Spatial(blenderbim.core.tool.Spatial):
|
||||
@@ -187,14 +188,14 @@ class Spatial(blenderbim.core.tool.Spatial):
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def get_selected_products(cls):
|
||||
def get_selected_products(cls) -> Generator[ifcopenshell.entity_instance, None, None]:
|
||||
for obj in bpy.context.selected_objects:
|
||||
entity = tool.Ifc.get_entity(obj)
|
||||
if entity and entity.is_a("IfcProduct"):
|
||||
yield entity
|
||||
|
||||
@classmethod
|
||||
def get_selected_product_types(cls):
|
||||
def get_selected_product_types(cls) -> Generator[ifcopenshell.entity_instance, None, None]:
|
||||
for obj in bpy.context.selected_objects:
|
||||
entity = tool.Ifc.get_entity(obj)
|
||||
if entity and entity.is_a("IfcTypeProduct"):
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -42,7 +42,7 @@ Installation
|
||||
|
||||
You do not need to unzip the add-on file. You should install it as a zipped file.
|
||||
|
||||
You should now see **Import-Export: BlenderBIM** available in your add-ons list. Enable the add-on by pressing the checkbox.
|
||||
You should now see **System: BlenderBIM** available in your add-ons list. Enable the add-on by pressing the checkbox.
|
||||
|
||||
.. image:: images/install-blenderbim-3.png
|
||||
|
||||
|
||||
@@ -960,7 +960,7 @@ def run_test_code():
|
||||
def saving_sample_test_files(and_open_in_blender=None):
|
||||
filepath = f"{variables['cwd']}/test/files/temp/sample_test_file"
|
||||
blend_filepath = f"{filepath}.blend"
|
||||
bpy.ops.export_ifc.bim(filepath=f"{filepath}.ifc", should_save_as=True)
|
||||
bpy.ops.bim.export_ifc(filepath=f"{filepath}.ifc", should_save_as=True)
|
||||
bpy.ops.wm.save_as_mainfile(filepath=f"{filepath}.blend")
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+33
-15
@@ -23,22 +23,25 @@ import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.selector
|
||||
import ifcopenshell.util.element
|
||||
import locale
|
||||
from typing import Any, Optional
|
||||
|
||||
CostItem = dict[str, Any]
|
||||
|
||||
|
||||
class Csv2Ifc:
|
||||
def __init__(self):
|
||||
self.csv = None
|
||||
self.file = None
|
||||
self.cost_items = []
|
||||
self.cost_schedule = None
|
||||
self.is_schedule_of_rates = False
|
||||
self.units = {}
|
||||
self.csv: str = None
|
||||
self.file: ifcopenshell.file = None
|
||||
self.cost_items: list[CostItem] = []
|
||||
self.cost_schedule: ifcopenshell.entity_instance = None
|
||||
self.is_schedule_of_rates: bool = False
|
||||
self.units: dict[str, ifcopenshell.entity_instance] = {}
|
||||
|
||||
def execute(self):
|
||||
def execute(self) -> None:
|
||||
self.parse_csv()
|
||||
self.create_ifc()
|
||||
|
||||
def parse_csv(self):
|
||||
def parse_csv(self) -> None:
|
||||
self.parents = {}
|
||||
self.headers = {}
|
||||
locale.setlocale(locale.LC_ALL, "") # set the system locale
|
||||
@@ -47,6 +50,7 @@ class Csv2Ifc:
|
||||
for row in reader:
|
||||
if not row[0]:
|
||||
continue
|
||||
# parse header
|
||||
if row[0] == "Hierarchy":
|
||||
self.has_categories = True
|
||||
for i, col in enumerate(row):
|
||||
@@ -55,6 +59,17 @@ class Csv2Ifc:
|
||||
if col == "Value":
|
||||
self.has_categories = False
|
||||
self.headers[col] = i
|
||||
|
||||
# validate header
|
||||
mandatory_fields = {"Name", "Quantity", "Unit"}
|
||||
if not self.is_schedule_of_rates:
|
||||
mandatory_fields.update({"Property", "Query"})
|
||||
available_fields = set(self.headers.keys())
|
||||
if not mandatory_fields.issubset(available_fields):
|
||||
raise Exception(
|
||||
f"Missing mandatory fields in CSV header: {', '.join(mandatory_fields-available_fields)}"
|
||||
)
|
||||
|
||||
continue
|
||||
cost_data = self.get_row_cost_data(row)
|
||||
hierarchy_key = int(row[0])
|
||||
@@ -64,7 +79,7 @@ class Csv2Ifc:
|
||||
self.parents[hierarchy_key - 1]["children"].append(cost_data)
|
||||
self.parents[hierarchy_key] = cost_data
|
||||
|
||||
def get_row_cost_data(self, row):
|
||||
def get_row_cost_data(self, row: list[str]) -> CostItem:
|
||||
name = row[self.headers["Name"]]
|
||||
identification = row[self.headers["Identification"]] if "Identification" in self.headers else None
|
||||
quantity = row[self.headers["Quantity"]]
|
||||
@@ -99,7 +114,7 @@ class Csv2Ifc:
|
||||
"children": [],
|
||||
}
|
||||
|
||||
def create_ifc(self):
|
||||
def create_ifc(self) -> None:
|
||||
if not self.file:
|
||||
self.create_boilerplate_ifc()
|
||||
if not self.cost_schedule:
|
||||
@@ -108,11 +123,13 @@ class Csv2Ifc:
|
||||
self.cost_schedule.PredefinedType = "SCHEDULEOFRATES"
|
||||
self.create_cost_items(self.cost_items)
|
||||
|
||||
def create_cost_items(self, cost_items, parent=None):
|
||||
def create_cost_items(
|
||||
self, cost_items: list[CostItem], parent: Optional[ifcopenshell.entity_instance] = None
|
||||
) -> None:
|
||||
for cost_item in cost_items:
|
||||
self.create_cost_item(cost_item, parent)
|
||||
|
||||
def create_cost_item(self, cost_item, parent):
|
||||
def create_cost_item(self, cost_item: CostItem, parent: Optional[ifcopenshell.entity_instance] = None) -> None:
|
||||
if parent is None:
|
||||
cost_item["ifc"] = ifcopenshell.api.run("cost.add_cost_item", self.file, cost_schedule=self.cost_schedule)
|
||||
else:
|
||||
@@ -161,6 +178,7 @@ class Csv2Ifc:
|
||||
quantity = ifcopenshell.api.run(
|
||||
"cost.add_cost_item_quantity", self.file, cost_item=cost_item["ifc"], ifc_class=quantity_class
|
||||
)
|
||||
# 3 IfcPhysicalSimpleQuantity Value
|
||||
quantity[3] = cost_item["Quantity"]
|
||||
|
||||
if cost_item["assignments"]["Query"]:
|
||||
@@ -184,7 +202,7 @@ class Csv2Ifc:
|
||||
|
||||
self.create_cost_items(cost_item["children"], cost_item["ifc"])
|
||||
|
||||
def create_unit(self, symbol):
|
||||
def create_unit(self, symbol) -> ifcopenshell.entity_instance:
|
||||
unit = self.units.get(symbol, None)
|
||||
if unit:
|
||||
return unit
|
||||
@@ -194,12 +212,12 @@ class Csv2Ifc:
|
||||
self.units[symbol] = unit
|
||||
return unit
|
||||
|
||||
def create_boilerplate_ifc(self):
|
||||
def create_boilerplate_ifc(self) -> None:
|
||||
self.file = ifcopenshell.file(schema="IFC4")
|
||||
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
|
||||
|
||||
|
||||
def has_property(self, product, property_name):
|
||||
def has_property(self, product, property_name) -> bool:
|
||||
if not property_name:
|
||||
return True
|
||||
qtos = ifcopenshell.util.element.get_psets(product, qtos_only=True)
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user