Merge branch 'v0.7.0' of https://github.com/IfcOpenShell/IfcOpenShell into pr-daily-builds

 Conflicts:
	cmake/CMakeLists.txt
This commit is contained in:
krande
2022-04-07 18:53:01 +02:00
133 changed files with 4420 additions and 1000 deletions
+2
View File
@@ -82,6 +82,8 @@ jobs:
sudo /usr/bin/python -m pip install src/bcf sudo /usr/bin/python -m pip install src/bcf
sudo /usr/bin/python -m pip install pytest sudo /usr/bin/python -m pip install pytest
sudo /usr/bin/python -m pip install isodate sudo /usr/bin/python -m pip install isodate
sudo /usr/bin/python -m pip install lark
sudo /usr/bin/python -m pip install networkx
- name: Test - name: Test
run: | run: |
+4 -4
View File
@@ -62,6 +62,10 @@ endif()
OPTION(USERSPACE_PYTHON_PREFIX "Installs IfcPython for the current user only instead of system-wide." OFF) OPTION(USERSPACE_PYTHON_PREFIX "Installs IfcPython for the current user only instead of system-wide." OFF)
OPTION(ADD_COMMIT_SHA "Add commit sha and branch in version number, warning results in many rebuilds, requires git" OFF) OPTION(ADD_COMMIT_SHA "Add commit sha and branch in version number, warning results in many rebuilds, requires git" OFF)
IF(NOT CMAKE_BUILD_TYPE)
SET(CMAKE_BUILD_TYPE "Release")
ENDIF()
# TODO QtViewer is deprecated ATM as it uses the 0.4 API # TODO QtViewer is deprecated ATM as it uses the 0.4 API
# OPTION(BUILD_QTVIEWER "Build IfcOpenShell Qt GUI Viewer (requires Qt 4 framework)." OFF) # OPTION(BUILD_QTVIEWER "Build IfcOpenShell Qt GUI Viewer (requires Qt 4 framework)." OFF)
include(GNUInstallDirs) include(GNUInstallDirs)
@@ -478,10 +482,6 @@ if(HDF5_SUPPORT)
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_HDF5) set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_HDF5)
endif() endif()
IF(NOT CMAKE_BUILD_TYPE)
SET(CMAKE_BUILD_TYPE "Release")
ENDIF()
if(ENABLE_BUILD_OPTIMIZATIONS) if(ENABLE_BUILD_OPTIMIZATIONS)
if(MSVC) if(MSVC)
# NOTE: RelWithDebInfo and Release use O2 (= /Ox /Gl /Gy/ = Og /Oi /Ot /Oy /Ob2 /Gs /GF /Gy) by default, # NOTE: RelWithDebInfo and Release use O2 (= /Ox /Gl /Gy/ = Og /Oi /Ot /Oy /Ob2 /Gs /GF /Gy) by default,
+4 -11
View File
@@ -46,8 +46,7 @@ BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost
LXML_URL:=https://files.pythonhosted.org/packages/19/d9/a69c6aff5673554df48120565a14a50eaa41d29ae03b02faa0b023666318/lxml-4.6.3-cp39-cp39-manylinux2014_x86_64.whl LXML_URL:=https://files.pythonhosted.org/packages/19/d9/a69c6aff5673554df48120565a14a50eaa41d29ae03b02faa0b023666318/lxml-4.6.3-cp39-cp39-manylinux2014_x86_64.whl
endif endif
ifeq ($(PYVERSION), py310) ifeq ($(PYVERSION), py310)
# https://github.com/humanoid-path-planner/hpp-fcl/issues/272 HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/linux-64/hpp-fcl-1.8.0-py310hdaf7e41_1.tar.bz2
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/linux-64/hpp-fcl-1.7.5-py39hbcdfc36_0.tar.bz2
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/linux-64/eigenpy-2.6.11-py310hf3e5c9c_0.tar.bz2 EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/linux-64/eigenpy-2.6.11-py310hf3e5c9c_0.tar.bz2
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py310h7c3ba0c_5.tar.bz2 BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py310h7c3ba0c_5.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/25/1e/19b46d8e8881fe0df2e20945d51919eeb1817836d62a90efa8506530e45c/lxml-4.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl LXML_URL:=https://files.pythonhosted.org/packages/25/1e/19b46d8e8881fe0df2e20945d51919eeb1817836d62a90efa8506530e45c/lxml-4.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl
@@ -71,8 +70,7 @@ BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1
LXML_URL:=https://files.pythonhosted.org/packages/b8/74/a71f7ad72e8db54ce899efab84507b801660750cbbfa6a39e6717557d36a/lxml-4.6.3-cp39-cp39-macosx_10_9_x86_64.whl LXML_URL:=https://files.pythonhosted.org/packages/b8/74/a71f7ad72e8db54ce899efab84507b801660750cbbfa6a39e6717557d36a/lxml-4.6.3-cp39-cp39-macosx_10_9_x86_64.whl
endif endif
ifeq ($(PYVERSION), py310) ifeq ($(PYVERSION), py310)
# https://github.com/humanoid-path-planner/hpp-fcl/issues/272 HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/osx-64/hpp-fcl-1.8.0-py310h651ac30_1.tar.bz2
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-64/hpp-fcl-1.7.5-py39h1e32b98_0.tar.bz2
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/osx-64/eigenpy-2.6.11-py310hc03097c_0.tar.bz2 EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/osx-64/eigenpy-2.6.11-py310hc03097c_0.tar.bz2
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py310h509978a_5.tar.bz2 BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py310h509978a_5.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/a1/44/17b7dac7a18807d30e2fe10c3328c152808f5464565e230bfd0e77f178c6/lxml-4.8.0-cp310-cp310-macosx_10_15_x86_64.whl LXML_URL:=https://files.pythonhosted.org/packages/a1/44/17b7dac7a18807d30e2fe10c3328c152808f5464565e230bfd0e77f178c6/lxml-4.8.0-cp310-cp310-macosx_10_15_x86_64.whl
@@ -96,8 +94,7 @@ BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/win-64/boost-1
LXML_URL:=https://files.pythonhosted.org/packages/72/d4/426ecb8849c47c3e370c87aa0ac05d85768df917ffea27fcd6686a5e6495/lxml-4.6.3-cp39-cp39-win_amd64.whl LXML_URL:=https://files.pythonhosted.org/packages/72/d4/426ecb8849c47c3e370c87aa0ac05d85768df917ffea27fcd6686a5e6495/lxml-4.6.3-cp39-cp39-win_amd64.whl
endif endif
ifeq ($(PYVERSION), py310) ifeq ($(PYVERSION), py310)
# https://github.com/humanoid-path-planner/hpp-fcl/issues/272 HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/win-64/hpp-fcl-1.8.0-py310hc5a3c62_1.tar.bz2
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/win-64/hpp-fcl-1.7.5-py39h2e7c763_0.tar.bz2
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/win-64/eigenpy-2.6.11-py310hbd43d28_0.tar.bz2 EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/win-64/eigenpy-2.6.11-py310hbd43d28_0.tar.bz2
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/win-64/boost-1.74.0-py310hc781a3c_5.tar.bz2 BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/win-64/boost-1.74.0-py310hc781a3c_5.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/f6/71/65c80a4caa1617a4c6e8fe1500cebb179db96232e2f623bfe6a1f4294e39/lxml-4.8.0-cp310-cp310-win_amd64.whl LXML_URL:=https://files.pythonhosted.org/packages/f6/71/65c80a4caa1617a4c6e8fe1500cebb179db96232e2f623bfe6a1f4294e39/lxml-4.8.0-cp310-cp310-win_amd64.whl
@@ -118,7 +115,7 @@ endif
cp -r blenderbim/* dist/blenderbim/ cp -r blenderbim/* dist/blenderbim/
# Provides IfcOpenShell Python functionality # Provides IfcOpenShell Python functionality
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-883b8a5-$(PLATFORM)64.zip cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-2985bba-$(PLATFORM)64.zip
cd dist/working && unzip ifcopenshell-python* cd dist/working && unzip ifcopenshell-python*
cp -r dist/working/ifcopenshell dist/blenderbim/libs/site/packages/ cp -r dist/working/ifcopenshell dist/blenderbim/libs/site/packages/
@@ -292,8 +289,6 @@ endif
rm -rf dist/working rm -rf dist/working
# Required by IFCClash # Required by IFCClash
# https://github.com/humanoid-path-planner/hpp-fcl/issues/272
ifneq ($(PYVERSION), py310)
mkdir dist/working mkdir dist/working
cd dist/working && wget $(HPPFCL_URL) cd dist/working && wget $(HPPFCL_URL)
cd dist/working && tar -xf hpp-fcl* cd dist/working && tar -xf hpp-fcl*
@@ -385,8 +380,6 @@ ifeq ($(PLATFORM), win)
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/ cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
endif endif
rm -rf dist/working rm -rf dist/working
# https://github.com/humanoid-path-planner/hpp-fcl/issues/272
endif
# Required by BIMTester # Required by BIMTester
mkdir dist/working mkdir dist/working
+9 -8
View File
@@ -79,17 +79,18 @@ for name in modules.keys():
classes = [ classes = [
operator.OpenUri, operator.AddIfcFile,
operator.SelectDataDir,
operator.SelectSchemaDir,
operator.SelectIfcFile,
operator.OpenUpstream,
operator.BIM_OT_add_section_plane, operator.BIM_OT_add_section_plane,
operator.BIM_OT_remove_section_plane, operator.BIM_OT_remove_section_plane,
operator.ReloadIfcFile,
operator.AddIfcFile,
operator.RemoveIfcFile,
operator.ConfigureVisibility, operator.ConfigureVisibility,
operator.OpenUpstream,
operator.OpenUri,
operator.ReloadIfcFile,
operator.RemoveIfcFile,
operator.SelectDataDir,
operator.SelectIfcFile,
operator.SelectSchemaDir,
operator.SelectURIAttribute,
prop.StrProperty, prop.StrProperty,
prop.ObjProperty, prop.ObjProperty,
prop.Attribute, prop.Attribute,
@@ -38,7 +38,7 @@
<script type="text/javascript"> <script type="text/javascript">
var g = new JSGantt.GanttChart(document.getElementById('GanttChartDIV'), 'day'); var g = new JSGantt.GanttChart(document.getElementById('GanttChartDIV'), 'day');
g.setOptions({ g.setOptions({
vCaptionType: 'Complete', // Set to Show Caption : None,Caption,Resource,Duration,Complete, vCaptionType: 'Caption', // Set to Show Caption : None,Caption,Resource,Duration,Complete,
vQuarterColWidth: 36, vQuarterColWidth: 36,
vDateTaskDisplayFormat: 'day dd month yyyy', // Shown in tool tip box vDateTaskDisplayFormat: 'day dd month yyyy', // Shown in tool tip box
vDayMajorDateDisplayFormat: 'mon yyyy - Week ww',// Set format to dates in the "Major" header of the "Day" view vDayMajorDateDisplayFormat: 'mon yyyy - Week ww',// Set format to dates in the "Major" header of the "Day" view
@@ -50,6 +50,8 @@ g.setOptions({
vFormatArr: ['Day', 'Week', 'Month', 'Quarter'], // Even with setUseSingleCell using Hour format on such a large chart can cause issues in some browsers, vFormatArr: ['Day', 'Week', 'Month', 'Quarter'], // Even with setUseSingleCell using Hour format on such a large chart can cause issues in some browsers,
vShowRes: false, // Disable the resource column. vShowRes: false, // Disable the resource column.
vShowComp: false, // Disable the completion column. vShowComp: false, // Disable the completion column.
vShowDur: false, // Disable the duration column, because jsgantt doesn't calculate durations the way we want.
vAdditionalHeaders: {ifcduration: {title: 'Duration'}},
vUseToolTip: false, // Disable tooltips. vUseToolTip: false, // Disable tooltips.
vTotalHeight: 900, vTotalHeight: 900,
}); });
@@ -5,6 +5,20 @@
</g> </g>
<g id="door-tag"> <g id="door-tag">
<circle cy="1.75" r="5" fill="white" stroke="black" style="stroke-width: 0.25;" /> <circle cy="1.75" r="5" fill="white" stroke="black" style="stroke-width: 0.25;" />
<line x1="-5" y1="1.75" x2="5" y2="1.75" style="stroke:black; stroke-width: 0.25;" /> <line x1="-5" y1="1.75" x2="5" y2="1.75" style="stroke: black; stroke-width: 0.25;" />
</g>
<g id="elevation-arrow">
<path style="fill: black;" d="M -7.07106 0 0 -7.07106 7.07106 0" />
</g>
<g id="elevation-tag">
<circle r="5" fill="white" stroke="black" style="stroke-width: 0.25;" />
<line x1="-5" y1="0" x2="5" y2="0" style="stroke: black; stroke-width: 0.25;" />
</g>
<g id="section-arrow">
<path style="fill: black;" d="M -7.07106 0 0 -7.07106 7.07106 0" />
</g>
<g id="section-tag">
<circle r="5" fill="white" stroke="black" style="stroke-width: 0.25;" />
<line x1="-5" y1="0" x2="5" y2="0" style="stroke: black; stroke-width: 0.25;" />
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 572 B

After

Width:  |  Height:  |  Size: 1.2 KiB

+3 -1
View File
@@ -28,6 +28,7 @@ import ifcopenshell
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.util.placement import ifcopenshell.util.placement
import blenderbim.tool as tool import blenderbim.tool as tool
import blenderbim.core.geometry
import blenderbim.core.aggregate import blenderbim.core.aggregate
import blenderbim.core.spatial import blenderbim.core.spatial
import blenderbim.core.style import blenderbim.core.style
@@ -47,7 +48,7 @@ class IfcExporter:
self.sync_deletions() self.sync_deletions()
self.sync_all_objects() self.sync_all_objects()
self.sync_edited_objects() self.sync_edited_objects()
extension = self.ifc_export_settings.output_file.split(".")[-1] extension = self.ifc_export_settings.output_file.split(".")[-1].lower()
if extension == "ifczip": if extension == "ifczip":
with tempfile.TemporaryDirectory() as unzipped_path: with tempfile.TemporaryDirectory() as unzipped_path:
filename, ext = os.path.splitext(os.path.basename(self.ifc_export_settings.output_file)) filename, ext = os.path.splitext(os.path.basename(self.ifc_export_settings.output_file))
@@ -179,6 +180,7 @@ class IfcExporter:
self.sync_object_placement(grid_obj) self.sync_object_placement(grid_obj)
if grid_obj.matrix_world != obj.matrix_world: if grid_obj.matrix_world != obj.matrix_world:
bpy.ops.bim.update_representation(obj=obj.name) bpy.ops.bim.update_representation(obj=obj.name)
tool.Geometry.record_object_position(obj)
def get_application_name(self): def get_application_name(self):
return "BlenderBIM" return "BlenderBIM"
+2 -5
View File
@@ -26,9 +26,7 @@ from blenderbim.bim.module.drawing.prop import RasterStyleProperty
from bpy.app.handlers import persistent from bpy.app.handlers import persistent
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.owner.prop import get_user_person, get_user_organisation from blenderbim.bim.module.owner.prop import get_user_person, get_user_organisation
from ifcopenshell.api.attribute.data import Data as AttributeData
from ifcopenshell.api.material.data import Data as MaterialData from ifcopenshell.api.material.data import Data as MaterialData
from ifcopenshell.api.style.data import Data as StyleData
from ifcopenshell.api.type.data import Data as TypeData from ifcopenshell.api.type.data import Data as TypeData
@@ -68,11 +66,10 @@ def name_callback(obj, data):
if isinstance(obj, bpy.types.Material): if isinstance(obj, bpy.types.Material):
if obj.BIMObjectProperties.ifc_definition_id: if obj.BIMObjectProperties.ifc_definition_id:
IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id).Name = obj.name IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id).Name = obj.name
AttributeData.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
MaterialData.load_materials() MaterialData.load_materials()
if obj.BIMMaterialProperties.ifc_style_id: if obj.BIMMaterialProperties.ifc_style_id:
IfcStore.get_file().by_id(obj.BIMMaterialProperties.ifc_style_id).Name = obj.name IfcStore.get_file().by_id(obj.BIMMaterialProperties.ifc_style_id).Name = obj.name
StyleData.load(IfcStore.get_file(), obj.BIMMaterialProperties.ifc_style_id) refresh_ui_data()
return return
if not obj.BIMObjectProperties.ifc_definition_id or "/" not in obj.name: if not obj.BIMObjectProperties.ifc_definition_id or "/" not in obj.name:
@@ -96,7 +93,7 @@ def name_callback(obj, data):
if element.is_a("IfcTypeProduct"): if element.is_a("IfcTypeProduct"):
TypeData.purge() TypeData.purge()
element.Name = "/".join(obj.name.split("/")[1:]) element.Name = "/".join(obj.name.split("/")[1:])
AttributeData.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id) refresh_ui_data()
def color_callback(obj, data): def color_callback(obj, data):
+5
View File
@@ -48,6 +48,9 @@ def draw_attribute(attribute, layout, copy_operator=None):
if copy_operator: if copy_operator:
op = layout.operator(f"{copy_operator}", text="", icon="COPYDOWN") op = layout.operator(f"{copy_operator}", text="", icon="COPYDOWN")
op.name = attribute.name op.name = attribute.name
if attribute.is_uri:
op = layout.operator("bim.select_uri_attribute", text="", icon="FILE_FOLDER")
op.data_path = attribute.path_from_id("string_value")
def import_attributes(ifc_class, props, data, callback=None): def import_attributes(ifc_class, props, data, callback=None):
@@ -78,6 +81,8 @@ def import_attribute(attribute, props, data, callback=None):
props.remove(len(props) - 1) props.remove(len(props) - 1)
elif data_type == "string": elif data_type == "string":
new.string_value = "" if new.is_null else data[attribute.name()] new.string_value = "" if new.is_null else data[attribute.name()]
if attribute.type_of_attribute().declared_type().name() == "IfcURIReference":
new.is_uri = True
elif data_type == "boolean": elif data_type == "boolean":
new.bool_value = False if new.is_null else data[attribute.name()] new.bool_value = False if new.is_null else data[attribute.name()]
elif data_type == "integer": elif data_type == "integer":
+1
View File
@@ -86,6 +86,7 @@ class IfcStore:
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest() ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
IfcStore.cache_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "cache", f"{ifc_hash}.h5") IfcStore.cache_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "cache", f"{ifc_hash}.h5")
cache_settings = ifcopenshell.geom.settings() cache_settings = ifcopenshell.geom.settings()
cache_settings.set(cache_settings.STRICT_TOLERANCE, True)
try: try:
IfcStore.cache = ifcopenshell.geom.serializers.hdf5(IfcStore.cache_path, cache_settings) IfcStore.cache = ifcopenshell.geom.serializers.hdf5(IfcStore.cache_path, cache_settings)
except: except:
+15 -15
View File
@@ -163,6 +163,7 @@ class IfcImporter:
self.settings = ifcopenshell.geom.settings() self.settings = ifcopenshell.geom.settings()
self.settings.set_deflection_tolerance(self.ifc_import_settings.deflection_tolerance) self.settings.set_deflection_tolerance(self.ifc_import_settings.deflection_tolerance)
self.settings.set_angular_tolerance(self.ifc_import_settings.angular_tolerance) self.settings.set_angular_tolerance(self.ifc_import_settings.angular_tolerance)
self.settings.set(self.settings.STRICT_TOLERANCE, True)
self.settings_native = ifcopenshell.geom.settings() self.settings_native = ifcopenshell.geom.settings()
self.settings_native.set(self.settings_native.INCLUDE_CURVES, True) self.settings_native.set(self.settings_native.INCLUDE_CURVES, True)
self.settings_2d = ifcopenshell.geom.settings() self.settings_2d = ifcopenshell.geom.settings()
@@ -283,11 +284,13 @@ class IfcImporter:
if c.ContextIdentifier in ["Body", "Facetation"] if c.ContextIdentifier in ["Body", "Facetation"]
] ]
# Ideally, all representations should be in a subcontext, but some BIM programs don't do this correctly # Ideally, all representations should be in a subcontext, but some BIM programs don't do this correctly
self.body_contexts.extend([ self.body_contexts.extend(
c.id() [
for c in self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False) c.id()
if c.ContextType == "Model" for c in self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False)
]) if c.ContextType == "Model"
]
)
if self.body_contexts: if self.body_contexts:
self.settings.set_context_ids(self.body_contexts) self.settings.set_context_ids(self.body_contexts)
# Annotation is to accommodate broken Revit files # Annotation is to accommodate broken Revit files
@@ -363,7 +366,7 @@ class IfcImporter:
def is_native_swept_disk_solid(self, representations): def is_native_swept_disk_solid(self, representations):
for representation in representations: for representation in representations:
items = representation["raw"].Items or [] # Be forgiving of invalid IFCs because Revit :( items = representation["raw"].Items or [] # Be forgiving of invalid IFCs because Revit :(
if len(items) == 1 and items[0].is_a("IfcSweptDiskSolid"): if len(items) == 1 and items[0].is_a("IfcSweptDiskSolid"):
return True return True
return False return False
@@ -1295,10 +1298,7 @@ class IfcImporter:
name = style.Name or str(style.id()) name = style.Name or str(style.id())
blender_material = bpy.data.materials.new(name) blender_material = bpy.data.materials.new(name)
old_definition_id = blender_material.BIMObjectProperties.ifc_definition_id self.link_element(style, blender_material)
if not old_definition_id:
self.link_element(style, blender_material)
blender_material.BIMObjectProperties.ifc_definition_id = old_definition_id
blender_material.BIMMaterialProperties.ifc_style_id = style.id() blender_material.BIMMaterialProperties.ifc_style_id = style.id()
self.material_creator.styles[style.id()] = blender_material self.material_creator.styles[style.id()] = blender_material
@@ -1352,12 +1352,12 @@ class IfcImporter:
elif surface_style.ReflectanceMethod == "FLAT": elif surface_style.ReflectanceMethod == "FLAT":
blender_material.use_nodes = True blender_material.use_nodes = True
output = {n.type: n for n in self.settings["material"].node_tree.nodes}.get("OUTPUT_MATERIAL", None) output = {n.type: n for n in blender_material.node_tree.nodes}.get("OUTPUT_MATERIAL", None)
bsdf = blender_material.node_tree.nodes["Principled BSDF"] bsdf = blender_material.node_tree.nodes["Principled BSDF"]
mix = blender_material.node_tree.nodes.new(type="ShaderNodeMixShader") mix = blender_material.node_tree.nodes.new(type="ShaderNodeMixShader")
mix.location = bsdf.location mix.location = bsdf.location
blender_material.node_tree.links.new(lightpath.outputs[0], output.inputs["Surface"]) blender_material.node_tree.links.new(mix.outputs[0], output.inputs["Surface"])
blender_material.node_tree.nodes.remove(bsdf) blender_material.node_tree.nodes.remove(bsdf)
@@ -1722,10 +1722,10 @@ class IfcImporter:
): ):
verts = [None] * len(geometry.verts) verts = [None] * len(geometry.verts)
for i in range(0, len(geometry.verts), 3): for i in range(0, len(geometry.verts), 3):
verts[i], verts[i+1], verts[i+2] = ifcopenshell.util.geolocation.enh2xyz( verts[i], verts[i + 1], verts[i + 2] = ifcopenshell.util.geolocation.enh2xyz(
geometry.verts[i], geometry.verts[i],
geometry.verts[i+1], geometry.verts[i + 1],
geometry.verts[i+2], geometry.verts[i + 2],
float(props.blender_eastings) * self.unit_scale, float(props.blender_eastings) * self.unit_scale,
float(props.blender_northings) * self.unit_scale, float(props.blender_northings) * self.unit_scale,
float(props.blender_orthogonal_height) * self.unit_scale, float(props.blender_orthogonal_height) * self.unit_scale,
@@ -19,7 +19,7 @@
import os import os
from pathlib import Path from pathlib import Path
from blenderbim.bim.prop import StrProperty from blenderbim.bim.prop import StrProperty
from blenderbim.bim.module.root.prop import getIfcClasses from blenderbim.bim.module.root.prop import get_ifc_classes
from bpy.types import PropertyGroup from bpy.types import PropertyGroup
from bpy.props import ( from bpy.props import (
PointerProperty, PointerProperty,
@@ -33,14 +33,11 @@ from bpy.props import (
) )
scenarios_enum = [] scenarios_enum = []
classes_enum = []
def purge(): def purge():
global scenarios_enum global scenarios_enum
global classes_enum
scenarios_enum = [] scenarios_enum = []
classes_enum = []
def getScenarios(self, context): def getScenarios(self, context):
@@ -68,7 +65,7 @@ class BimTesterProperties(PropertyGroup):
feature: StringProperty(default="", name="Feature / IDS", update=refreshScenarios) feature: StringProperty(default="", name="Feature / IDS", update=refreshScenarios)
steps: StringProperty(default="", name="Custom Steps") steps: StringProperty(default="", name="Custom Steps")
ifc_file: StringProperty(default="", name="IFC File") ifc_file: StringProperty(default="", name="IFC File")
audit_ifc_class: EnumProperty(items=getIfcClasses, name="Audit Class") audit_ifc_class: EnumProperty(items=get_ifc_classes, name="Audit Class")
qa_reject_element_reason: StringProperty(name="Element Rejection Reason") qa_reject_element_reason: StringProperty(name="Element Rejection Reason")
scenario: EnumProperty(items=getScenarios, name="Scenario") scenario: EnumProperty(items=getScenarios, name="Scenario")
should_load_from_memory: BoolProperty(default=False, name="Load from Memory") should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
@@ -44,6 +44,19 @@ class Loader:
self.load_settings() self.load_settings()
self.load_importer() self.load_importer()
def create_mesh(self, boundary):
# ConnectionGeometry is optional in IFC schema for some reasons.
if not boundary.ConnectionGeometry:
return None
surface = boundary.ConnectionGeometry.SurfaceOnRelatingElement
# workaround for unvalid geometry provided by Revit. See https://github.com/IfcOpenShell/IfcOpenShell/issues/635#issuecomment-770366838
if surface.is_a("IfcCurveBoundedPlane") and not getattr(surface, "InnerBoundaries", None):
surface.InnerBoundaries = ()
shape = ifcopenshell.geom.create_shape(self.settings, surface)
mesh = self.ifc_importer.create_mesh(None, shape)
self.ifc_importer.link_mesh(shape, mesh)
return mesh
def load_settings(self): def load_settings(self):
self.settings = ifcopenshell.geom.settings() self.settings = ifcopenshell.geom.settings()
self.settings.set(self.settings.EXCLUDE_SOLIDS_AND_SURFACES, False) self.settings.set(self.settings.EXCLUDE_SOLIDS_AND_SURFACES, False)
@@ -61,13 +74,7 @@ class Loader:
obj = tool.Ifc.get_object(boundary) obj = tool.Ifc.get_object(boundary)
if obj: if obj:
return obj return obj
surface = boundary.ConnectionGeometry.SurfaceOnRelatingElement mesh = self.create_mesh(boundary)
# workaround for unvalid geometry provided by Revit. See https://github.com/IfcOpenShell/IfcOpenShell/issues/635#issuecomment-770366838
if surface.is_a("IfcCurveBoundedPlane") and not getattr(surface, "InnerBoundaries", None):
surface.InnerBoundaries = ()
shape = ifcopenshell.geom.create_shape(self.settings, surface)
mesh = self.ifc_importer.create_mesh(None, shape)
self.ifc_importer.link_mesh(shape, mesh)
obj = bpy.data.objects.new(f"{boundary.is_a()}/{boundary.Name}", mesh) obj = bpy.data.objects.new(f"{boundary.is_a()}/{boundary.Name}", mesh)
obj.matrix_world = blender_space.matrix_world obj.matrix_world = blender_space.matrix_world
boundaries_collection = get_boundaries_collection(blender_space) boundaries_collection = get_boundaries_collection(blender_space)
@@ -20,35 +20,29 @@ import bpy
from . import ui, prop, operator from . import ui, prop, operator
classes = ( classes = (
operator.LoadInformation,
operator.LoadDocumentReferences,
operator.DisableDocumentEditingUI,
operator.EnableEditingDocument,
operator.DisableEditingDocument,
operator.AddInformation,
operator.AddDocumentReference, operator.AddDocumentReference,
operator.EditInformation, operator.AddInformation,
operator.EditDocumentReference,
operator.RemoveDocument,
operator.EnableAssigningDocument,
operator.DisableAssigningDocument,
operator.AssignDocument, operator.AssignDocument,
operator.DisableDocumentEditingUI,
operator.DisableEditingDocument,
operator.EditDocument,
operator.EnableEditingDocument,
operator.LoadDocument,
operator.LoadParentDocument,
operator.LoadProjectDocuments,
operator.RemoveDocument,
operator.UnassignDocument, operator.UnassignDocument,
prop.Document, prop.Document,
prop.BIMDocumentProperties, prop.BIMDocumentProperties,
prop.BIMObjectDocumentProperties,
ui.BIM_PT_documents, ui.BIM_PT_documents,
ui.BIM_PT_object_documents, ui.BIM_PT_object_documents,
ui.BIM_UL_documents, ui.BIM_UL_documents,
ui.BIM_UL_object_documents,
) )
def register(): def register():
bpy.types.Scene.BIMDocumentProperties = bpy.props.PointerProperty(type=prop.BIMDocumentProperties) bpy.types.Scene.BIMDocumentProperties = bpy.props.PointerProperty(type=prop.BIMDocumentProperties)
bpy.types.Object.BIMObjectDocumentProperties = bpy.props.PointerProperty(type=prop.BIMObjectDocumentProperties)
def unregister(): def unregister():
del bpy.types.Scene.BIMDocumentProperties del bpy.types.Scene.BIMDocumentProperties
del bpy.types.Object.BIMObjectDocumentProperties
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License # 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/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import os
import bpy import bpy
import ifcopenshell import ifcopenshell
import ifcopenshell.util.schema import ifcopenshell.util.schema
@@ -35,17 +36,29 @@ class DocumentData:
def load(cls): def load(cls):
cls.data = { cls.data = {
"total_information": cls.total_information(), "total_information": cls.total_information(),
"total_references": cls.total_references(), "parent_document": cls.parent_document(),
} }
cls.is_loaded = True cls.is_loaded = True
@classmethod @classmethod
def total_information(cls): def total_information(cls):
return len(tool.Ifc.get().by_type("IfcDocumentInformation")) return len(
[
rel
for rel in tool.Ifc.get().by_type("IfcProject")[0].HasAssociations or []
if rel.is_a("IfcRelAssociatesDocument") and rel.RelatingDocument.is_a("IfcDocumentInformation")
]
)
@classmethod @classmethod
def total_references(cls): def parent_document(cls):
return len(tool.Ifc.get().by_type("IfcDocumentReference")) props = bpy.context.scene.BIMDocumentProperties
if len(props.breadcrumbs):
parent = tool.Ifc.get().by_id(int(props.breadcrumbs[-1].name))
if tool.Ifc.get_schema() == "IFC2X3":
return str(parent.DocumentId)
return str(parent.Identification)
return ""
class ObjectDocumentData: class ObjectDocumentData:
@@ -69,16 +82,35 @@ class ObjectDocumentData:
if rel.is_a("IfcRelAssociatesDocument"): if rel.is_a("IfcRelAssociatesDocument"):
if not rel.RelatingDocument.is_a("IfcDocumentReference"): if not rel.RelatingDocument.is_a("IfcDocumentReference"):
continue continue
name = rel.RelatingDocument.Name
if not name and rel.RelatingDocument.ReferencedDocument:
name = rel.RelatingDocument.ReferencedDocument.Name
if tool.Ifc.get_schema() == "IFC2X3": if tool.Ifc.get_schema() == "IFC2X3":
identification = rel.RelatingDocument.ItemReference identification = rel.RelatingDocument.ItemReference
if not identification and rel.RelatingDocument.ReferencedDocument:
identification = rel.RelatingDocument.ReferencedDocument.DocumentId
else: else:
identification = rel.RelatingDocument.Identification identification = rel.RelatingDocument.Identification
if not identification and rel.RelatingDocument.ReferencedDocument:
identification = rel.RelatingDocument.ReferencedDocument.Identification
location = rel.RelatingDocument.Location
if location is None and rel.RelatingDocument.ReferencedDocument:
location = rel.RelatingDocument.ReferencedDocument.Location
if location:
if not "://" in location:
if not os.path.isabs(location):
location = os.path.abspath(os.path.join(os.path.dirname(tool.Ifc.get_path()), location))
location = "file://" + location
results.append( results.append(
{ {
"type": rel.RelatingDocument.is_a(),
"id": rel.RelatingDocument.id(), "id": rel.RelatingDocument.id(),
"identification": identification, "identification": identification,
"name": rel.RelatingDocument.Name, "name": name,
"location": location,
} }
) )
return results return results
@@ -26,22 +26,32 @@ from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.document.data import Data from ifcopenshell.api.document.data import Data
class LoadInformation(bpy.types.Operator, tool.Ifc.Operator): class LoadProjectDocuments(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.load_information" bl_idname = "bim.load_project_documents"
bl_label = "Load Information" bl_label = "Load Project Documents"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def _execute(self, context): def _execute(self, context):
core.load_information(tool.Document) core.load_project_documents(tool.Document)
class LoadDocumentReferences(bpy.types.Operator, tool.Ifc.Operator): class LoadDocument(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.load_document_references" bl_idname = "bim.load_document"
bl_label = "Load Document References" bl_label = "Load Document"
bl_options = {"REGISTER", "UNDO"}
document: bpy.props.IntProperty()
def _execute(self, context):
core.load_document(tool.Document, document=tool.Ifc.get().by_id(self.document))
class LoadParentDocument(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.load_parent_document"
bl_label = "Load Parent Document"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def _execute(self, context): def _execute(self, context):
core.load_references(tool.Document) core.load_parent_document(tool.Document)
class DisableDocumentEditingUI(bpy.types.Operator, tool.Ifc.Operator): class DisableDocumentEditingUI(bpy.types.Operator, tool.Ifc.Operator):
@@ -90,24 +100,14 @@ class AddDocumentReference(bpy.types.Operator, tool.Ifc.Operator):
core.add_reference(tool.Ifc, tool.Document) core.add_reference(tool.Ifc, tool.Document)
class EditInformation(bpy.types.Operator, tool.Ifc.Operator): class EditDocument(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_information" bl_idname = "bim.edit_document"
bl_label = "Edit Information" bl_label = "Edit Information"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def _execute(self, context): def _execute(self, context):
props = context.scene.BIMDocumentProperties props = context.scene.BIMDocumentProperties
core.edit_information(tool.Ifc, tool.Document, information=tool.Ifc.get().by_id(props.active_document_id)) core.edit_document(tool.Ifc, tool.Document, document=tool.Ifc.get().by_id(props.active_document_id))
class EditDocumentReference(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_document_reference"
bl_label = "Edit Document Reference"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMDocumentProperties
core.edit_reference(tool.Ifc, tool.Document, reference=tool.Ifc.get().by_id(props.active_document_id))
class RemoveDocument(bpy.types.Operator, tool.Ifc.Operator): class RemoveDocument(bpy.types.Operator, tool.Ifc.Operator):
@@ -120,28 +120,6 @@ class RemoveDocument(bpy.types.Operator, tool.Ifc.Operator):
core.remove_document(tool.Ifc, tool.Document, document=tool.Ifc.get().by_id(self.document)) core.remove_document(tool.Ifc, tool.Document, document=tool.Ifc.get().by_id(self.document))
class EnableAssigningDocument(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.enable_assigning_document"
bl_label = "Enable Assigning Document"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
core.enable_assigning_document(tool.Document, obj)
class DisableAssigningDocument(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.disable_assigning_document"
bl_label = "Disable Assigning Document"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
core.enable_assigning_document(tool.Document, obj)
class AssignDocument(bpy.types.Operator, tool.Ifc.Operator): class AssignDocument(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.assign_document" bl_idname = "bim.assign_document"
bl_label = "Assign Document" bl_label = "Assign Document"
@@ -34,6 +34,7 @@ from bpy.props import (
class Document(PropertyGroup): class Document(PropertyGroup):
name: StringProperty(name="Name") name: StringProperty(name="Name")
identification: StringProperty(name="Identification") identification: StringProperty(name="Identification")
is_information: BoolProperty(name="Is Information")
ifc_definition_id: IntProperty(name="IFC Definition ID") ifc_definition_id: IntProperty(name="IFC Definition ID")
@@ -41,12 +42,6 @@ class BIMDocumentProperties(PropertyGroup):
document_attributes: CollectionProperty(name="Document Attributes", type=Attribute) document_attributes: CollectionProperty(name="Document Attributes", type=Attribute)
active_document_id: IntProperty(name="Active Document Id") active_document_id: IntProperty(name="Active Document Id")
documents: CollectionProperty(name="Documents", type=Document) documents: CollectionProperty(name="Documents", type=Document)
breadcrumbs: CollectionProperty(name="Breadcrumbs", type=StrProperty)
active_document_index: IntProperty(name="Active Document Index") active_document_index: IntProperty(name="Active Document Index")
is_editing: StringProperty(name="Is Editing") is_editing: BoolProperty(name="Is Editing", default=False)
class BIMObjectDocumentProperties(PropertyGroup):
is_adding: StringProperty(name="Is Adding")
available_document_types: EnumProperty(
items=[(d, d, "") for d in ["IfcDocumentInformation", "IfcDocumentReference"]], name="Available Document Types"
)
@@ -29,7 +29,7 @@ class BIM_PT_documents(Panel):
bl_space_type = "PROPERTIES" bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW" bl_region_type = "WINDOW"
bl_context = "scene" bl_context = "scene"
bl_parent_id = "BIM_PT_collaboration" bl_parent_id = "BIM_PT_project_setup"
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
@@ -41,34 +41,38 @@ class BIM_PT_documents(Panel):
self.props = context.scene.BIMDocumentProperties self.props = context.scene.BIMDocumentProperties
if not self.props.is_editing or self.props.is_editing == "information": row = self.layout.row(align=True)
row = self.layout.row(align=True) row.label(text="{} Documents Found".format(DocumentData.data["total_information"]), icon="FILE")
row.label(text="{} Documents Found".format(DocumentData.data["total_information"]), icon="FILE")
if self.props.is_editing == "information":
row.operator("bim.add_information", text="", icon="ADD")
row.operator("bim.disable_document_editing_ui", text="", icon="CANCEL")
else:
row.operator("bim.load_information", text="", icon="IMPORT")
if not self.props.is_editing or self.props.is_editing == "reference":
row = self.layout.row(align=True)
row.label(text="{} References Found".format(DocumentData.data["total_references"]), icon="FILE_HIDDEN")
if self.props.is_editing == "reference":
row.operator("bim.add_document_reference", text="", icon="ADD")
row.operator("bim.disable_document_editing_ui", text="", icon="CANCEL")
else:
row.operator("bim.load_document_references", text="", icon="IMPORT")
if self.props.is_editing: if self.props.is_editing:
self.layout.template_list( row.operator("bim.disable_document_editing_ui", text="", icon="CANCEL")
"BIM_UL_documents", "", self.props, "documents", self.props, "active_document_index" else:
) row.operator("bim.load_project_documents", text="", icon="IMPORT")
if not self.props.is_editing:
return
row = self.layout.row(align=True)
if self.props.breadcrumbs:
row.operator("bim.load_parent_document", text="", icon="FRAME_PREV")
row.label(text=DocumentData.data["parent_document"])
else:
row.alignment = "RIGHT"
row.operator("bim.add_information", text="", icon="ADD")
if self.props.breadcrumbs:
row.operator("bim.add_document_reference", text="", icon="FILE_HIDDEN")
if self.props.active_document_id: if self.props.active_document_id:
self.draw_editable_ui(context) row.operator("bim.edit_document", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_document", text="", icon="CANCEL")
elif self.props.documents and self.props.active_document_index < len(self.props.documents):
ifc_definition_id = self.props.documents[self.props.active_document_index].ifc_definition_id
row.operator("bim.enable_editing_document", text="", icon="GREASEPENCIL").document = ifc_definition_id
row.operator("bim.remove_document", text="", icon="X").document = ifc_definition_id
def draw_editable_ui(self, context): self.layout.template_list("BIM_UL_documents", "", self.props, "documents", self.props, "active_document_index")
draw_attributes(self.props.document_attributes, self.layout)
if self.props.active_document_id:
draw_attributes(self.props.document_attributes, self.layout)
class BIM_PT_object_documents(Panel): class BIM_PT_object_documents(Panel):
@@ -94,8 +98,7 @@ class BIM_PT_object_documents(Panel):
obj = context.active_object obj = context.active_object
self.oprops = obj.BIMObjectProperties self.oprops = obj.BIMObjectProperties
self.sprops = context.scene.BIMDocumentProperties self.props = context.scene.BIMDocumentProperties
self.props = obj.BIMObjectDocumentProperties
self.file = IfcStore.get_file() self.file = IfcStore.get_file()
self.draw_add_ui() self.draw_add_ui()
@@ -108,52 +111,46 @@ class BIM_PT_object_documents(Panel):
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.label(text=document["identification"] or "*", icon="FILE") row.label(text=document["identification"] or "*", icon="FILE")
row.label(text=document["name"] or "Unnamed") row.label(text=document["name"] or "Unnamed")
if document["location"]:
row.operator("bim.open_uri", icon="URL", text="").uri = document["location"]
row.operator("bim.unassign_document", text="", icon="X").document = document["id"] row.operator("bim.unassign_document", text="", icon="X").document = document["id"]
def draw_add_ui(self): def draw_add_ui(self):
if self.props.is_adding: if not self.props.is_editing:
row = self.layout.row(align=True) row = self.layout.row(align=True)
icon = "FILE" if self.props.is_adding == "IfcDocumentInformation" else "FILE_HIDDEN" row.operator("bim.load_project_documents", text="Assign Document References", icon="ADD")
row.label(text="Adding {}".format(self.props.is_adding), icon=icon) return
row.operator("bim.disable_assigning_document", text="", icon="CANCEL")
self.layout.template_list( row = self.layout.row(align=True)
"BIM_UL_object_documents", if self.props.breadcrumbs:
"", row.operator("bim.load_parent_document", text="", icon="FRAME_PREV")
self.sprops, row.label(text=DocumentData.data["parent_document"])
"documents",
self.sprops,
"active_document_index",
)
else: else:
row = self.layout.row(align=True) row.alignment = "RIGHT"
row.prop(self.props, "available_document_types", text="")
row.operator("bim.enable_assigning_document", text="", icon="ADD") if self.props.documents and self.props.active_document_index < len(self.props.documents):
document = self.props.documents[self.props.active_document_index]
if not document.is_information:
row.operator("bim.assign_document", text="", icon="ADD").document = document.ifc_definition_id
row.operator("bim.disable_document_editing_ui", text="", icon="CANCEL")
self.layout.template_list("BIM_UL_documents", "", self.props, "documents", self.props, "active_document_index")
class BIM_UL_documents(UIList): class BIM_UL_documents(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname): def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item: if item:
row = layout.row(align=True) row = layout.row(align=True)
row.label(text=item.identification)
row.label(text=item.name) if item.is_information:
if context.scene.BIMDocumentProperties.active_document_id == item.ifc_definition_id: op = row.operator("bim.load_document", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT")
if context.scene.BIMDocumentProperties.is_editing == "information":
row.operator("bim.edit_information", text="", icon="CHECKMARK")
elif context.scene.BIMDocumentProperties.is_editing == "reference":
row.operator("bim.edit_document_reference", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_document", text="", icon="CANCEL")
elif context.scene.BIMDocumentProperties.active_document_id:
row.operator("bim.remove_document", text="", icon="X").document = item.ifc_definition_id
else:
op = row.operator("bim.enable_editing_document", text="", icon="GREASEPENCIL")
op.document = item.ifc_definition_id op.document = item.ifc_definition_id
row.operator("bim.remove_document", text="", icon="X").document = item.ifc_definition_id row.label(text="", icon="FILE")
else:
row.label(text="", icon="BLANK1")
row.label(text="", icon="FILE_HIDDEN")
split1 = row.split(factor=0.1)
class BIM_UL_object_documents(UIList): split1.label(text=item.identification)
def draw_item(self, context, layout, data, item, icon, active_data, active_propname): split2 = split1.split(factor=0.9)
if item: split2.label(text=item.name)
row = layout.row(align=True)
row.label(text=item.identification)
row.label(text=item.name)
row.operator("bim.assign_document", text="", icon="ADD").document = item.ifc_definition_id
@@ -33,6 +33,7 @@ classes = (
operator.AddSheet, operator.AddSheet,
operator.BuildSchedule, operator.BuildSchedule,
operator.CleanWireframes, operator.CleanWireframes,
operator.ContractSheet,
operator.CopyGrid, operator.CopyGrid,
operator.CreateDrawing, operator.CreateDrawing,
operator.CreateSheets, operator.CreateSheets,
@@ -45,12 +46,14 @@ classes = (
operator.EditVectorStyle, operator.EditVectorStyle,
operator.EnableEditingText, operator.EnableEditingText,
operator.EnableEditingTextProduct, operator.EnableEditingTextProduct,
operator.ExpandSheet,
operator.GenerateReferences, operator.GenerateReferences,
operator.LoadDrawings, operator.LoadDrawings,
operator.LoadSheets, operator.LoadSheets,
operator.OpenSheet, operator.OpenSheet,
operator.OpenView, operator.OpenView,
operator.RemoveDrawing, operator.RemoveDrawing,
operator.RemoveDrawingFromSheet,
operator.RemoveDrawingStyle, operator.RemoveDrawingStyle,
operator.RemoveDrawingStyleAttribute, operator.RemoveDrawingStyleAttribute,
operator.RemoveSchedule, operator.RemoveSchedule,
@@ -40,7 +40,7 @@ class BaseDecorator:
DEF_GLSL = """ DEF_GLSL = """
#define PI 3.141592653589793 #define PI 3.141592653589793
#define MAX_POINTS 32 #define MAX_POINTS 64
#define CIRCLE_SEGS 12 #define CIRCLE_SEGS 12
""" """
@@ -1125,15 +1125,15 @@ class GridDecorator(BaseDecorator):
self.draw_label(context, text, p1, dir, vcenter=True, gap=0) self.draw_label(context, text, p1, dir, vcenter=True, gap=0)
class SectionViewDecorator(LevelDecorator): class ElevationDecorator(LevelDecorator):
objecttype = "SECTION" objecttype = "ELEVATION"
DEF_GLSL = ( DEF_GLSL = (
BaseDecorator.DEF_GLSL BaseDecorator.DEF_GLSL
+ """ + """
#define CIRCLE_SIZE 8.0 #define CIRCLE_SIZE 8.0
#define TRIANGLE_L 32.0 #define TRIANGLE_L 22.63
#define TRIANGLE_W 16.0 #define TRIANGLE_W 11.31
""" """
) )
@@ -1144,12 +1144,14 @@ class SectionViewDecorator(LevelDecorator):
layout(lines) in; layout(lines) in;
layout(line_strip, max_vertices=MAX_POINTS) out; layout(line_strip, max_vertices=MAX_POINTS) out;
void triangle_head(in vec4 dir, in vec4 side, in float length, in float width, out vec4 head[3]) { void triangle_head(in vec4 side, in vec4 dir, in float length, in float width, in float radius, out vec4 head[5]) {
vec4 nose = dir * length; vec4 nose = side * length;
vec4 ear = side * width; vec4 ear = dir * width;
head[0] = vec4(0); head[0] = side * -radius;
head[1] = nose * .5 + ear; head[1] = nose * -.5;
head[2] = nose; head[2] = vec4(0) + ear;
head[3] = nose * .5;
head[4] = side * radius;
} }
void main() { void main() {
@@ -1160,75 +1162,206 @@ class SectionViewDecorator(LevelDecorator):
vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1); vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1);
vec4 edge = p1w - p0w, dir = normalize(edge); vec4 edge = p1w - p0w, dir = normalize(edge);
vec4 gap = dir * viewportDrawingScale * TRIANGLE_L * .5;
vec4 side = vec4(cross(vec3(dir.xy, 0), vec3(0, 0, 1)).xy, 0, 0); vec4 side = vec4(cross(vec3(dir.xy, 0), vec3(0, 0, 1)).xy, 0, 0);
vec4 p; vec4 p;
vec4 head[CIRCLE_SEGS]; vec4 head[CIRCLE_SEGS];
circle_head(viewportDrawingScale * CIRCLE_SIZE, head); circle_head(viewportDrawingScale * CIRCLE_SIZE, head);
vec4 head3[3]; vec4 head5[5];
// start edge circle // start edge circle
for(int i=0; i<CIRCLE_SEGS; i++) { for(int i=0; i<CIRCLE_SEGS; i++) {
p = p0w + gap + head[i]; p = p0w + head[i];
gl_Position = WIN2CLIP(p); gl_Position = WIN2CLIP(p);
EmitVertex(); EmitVertex();
} }
p = p0w + gap + head[0]; p = p0w + head[0];
gl_Position = WIN2CLIP(p); gl_Position = WIN2CLIP(p);
EmitVertex(); EmitVertex();
EndPrimitive(); EndPrimitive();
// start edge triangle // edge triangle
triangle_head(dir, -side, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, head3); triangle_head(side, dir, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, viewportDrawingScale * CIRCLE_SIZE, head5);
p = p0w + head3[0]; p = p0w + head5[0];
gl_Position = WIN2CLIP(p); gl_Position = WIN2CLIP(p);
EmitVertex(); EmitVertex();
p = p0w + head3[1]; p = p0w + head5[1];
gl_Position = WIN2CLIP(p); gl_Position = WIN2CLIP(p);
EmitVertex(); EmitVertex();
p = p0w + head3[2]; p = p0w + head5[2];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head5[3];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head5[4];
gl_Position = WIN2CLIP(p); gl_Position = WIN2CLIP(p);
EmitVertex(); EmitVertex();
EndPrimitive(); EndPrimitive();
// end edge circle // reference divider
for(int i=0; i<CIRCLE_SEGS; i++) { p = p0w + normalize(vec4(-1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE;
p = p1w - gap + head[i];
gl_Position = WIN2CLIP(p);
EmitVertex();
}
p = p1w - gap + head[0];
gl_Position = WIN2CLIP(p); gl_Position = WIN2CLIP(p);
EmitVertex(); EmitVertex();
EndPrimitive(); p = p0w + normalize(vec4(1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE;
// end edge triangle
triangle_head(-dir, -side, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, head3);
p = p1w + head3[0];
gl_Position = WIN2CLIP(p); gl_Position = WIN2CLIP(p);
EmitVertex(); EmitVertex();
p = p1w + head3[1];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + head3[2];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// stem
gl_Position = p0;
EmitVertex();
gl_Position = p1;
EmitVertex();
EndPrimitive(); EndPrimitive();
} }
""" """
def decorate(self, context, obj): def decorate(self, context, obj):
verts, _, _ = self.get_path_geom(obj, topo=False) center = obj.matrix_world.translation
self.draw_lines(context, obj, verts, [(0, 1)]) v1 = obj.matrix_world @ Vector((0, 0, 0))
v2 = obj.matrix_world @ Vector((0, 0, -1))
self.draw_lines(context, obj, [v1, v2], [(0, 1)])
class SectionDecorator(LevelDecorator):
objecttype = "SECTION"
DEF_GLSL = (
BaseDecorator.DEF_GLSL
+ """
#define CIRCLE_SIZE 8.0
#define TRIANGLE_L 22.63
#define TRIANGLE_W 11.31
"""
)
GEOM_GLSL = """
uniform vec2 winsize;
uniform float viewportDrawingScale;
layout(lines) in;
layout(line_strip, max_vertices=MAX_POINTS) out;
void triangle_head(in vec4 side, in vec4 dir, in float length, in float width, in float radius, out vec4 head[5]) {
vec4 nose = side * length;
vec4 ear = dir * width;
head[0] = side * -radius;
head[1] = nose * -.5;
head[2] = vec4(0) + ear;
head[3] = nose * .5;
head[4] = side * radius;
}
void main() {
vec4 clip2win = matCLIP2WIN();
vec4 win2clip = matWIN2CLIP();
vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position;
vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1);
vec4 edge = p1w - p0w, side = normalize(edge);
vec4 gap = side * viewportDrawingScale * CIRCLE_SIZE;
vec4 dir = vec4(cross(vec3(side.xy, 0), vec3(0, 0, 1)).xy, 0, 0);
vec4 p;
vec4 head[CIRCLE_SEGS];
circle_head(viewportDrawingScale * CIRCLE_SIZE, head);
vec4 head5[5];
// start edge circle
for(int i=0; i<CIRCLE_SEGS; i++) {
p = p0w + head[i];
gl_Position = WIN2CLIP(p);
EmitVertex();
}
p = p0w + head[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// start edge triangle
triangle_head(side, dir, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, viewportDrawingScale * CIRCLE_SIZE, head5);
p = p0w + head5[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head5[1];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head5[2];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head5[3];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head5[4];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// start reference divider
p = p0w + normalize(vec4(-1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE;
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + normalize(vec4(1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE;
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// end edge circle
for(int i=0; i<CIRCLE_SEGS; i++) {
p = p1w - head[i];
gl_Position = WIN2CLIP(p);
EmitVertex();
}
p = p1w - head[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// end edge triangle
triangle_head(side, dir, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, viewportDrawingScale * CIRCLE_SIZE, head5);
p = p1w + head5[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + head5[1];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + head5[2];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + head5[3];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + head5[4];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// end reference divider
p = p1w + normalize(vec4(-1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE;
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + normalize(vec4(1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE;
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// stem
p = p0w + gap;
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w - gap;
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
}
"""
def decorate(self, context, obj):
if obj.data.is_editmode:
verts, idxs = self.get_editmesh_geom(obj)
else:
verts, idxs = self.get_mesh_geom(obj)
vert_map = {v.freeze(): (obj.matrix_world.inverted() @ v).x for v in verts}
verts = sorted(verts, key=lambda v: vert_map[v], reverse=True)
self.draw_lines(context, obj, verts, idxs)
class TextDecorator(BaseDecorator): class TextDecorator(BaseDecorator):
@@ -1327,7 +1460,8 @@ class DecorationsHandler:
SectionLevelDecorator, SectionLevelDecorator,
StairDecorator, StairDecorator,
BreakDecorator, BreakDecorator,
SectionViewDecorator, SectionDecorator,
ElevationDecorator,
TextDecorator, TextDecorator,
] ]
@@ -260,14 +260,11 @@ def format_distance(value, isArea=False, hide_units=True):
def get_active_drawing(scene): def get_active_drawing(scene):
"""Get active drawing collection and camera""" """Get active drawing collection and camera"""
props = scene.DocProperties props = scene.DocProperties
if props.active_drawing_index is None or len(props.drawings) == 0:
return None, None
try: try:
drawing = props.active_drawing camera = tool.Ifc.get_object(tool.Ifc.get().by_id(props.active_drawing_id))
camera = tool.Ifc.get_object(tool.Ifc.get().by_id(drawing.ifc_definition_id))
return camera.users_collection[0], camera return camera.users_collection[0], camera
except (KeyError, IndexError): except:
raise RuntimeError("missing drawing collection") return None, None
def get_project_collection(scene): def get_project_collection(scene):
@@ -82,6 +82,11 @@ class AddDrawing(bpy.types.Operator, Operator):
target_view=self.props.target_view, target_view=self.props.target_view,
location_hint=hint, location_hint=hint,
) )
try:
drawing = tool.Ifc.get().by_id(self.props.active_drawing_id)
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=drawing)
except:
pass
class CreateDrawing(bpy.types.Operator): class CreateDrawing(bpy.types.Operator):
@@ -301,12 +306,13 @@ class CreateDrawing(bpy.types.Operator):
elements = set(ifc.by_type("IfcElement")) - set(ifc.by_type("IfcOpeningElement")) elements = set(ifc.by_type("IfcElement")) - set(ifc.by_type("IfcOpeningElement"))
self.setup_serialiser(ifc) self.setup_serialiser(ifc)
cache_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True)
cache = IfcStore.get_cache() cache = IfcStore.get_cache()
[cache.remove(guid) for guid in invalidated_guids] [cache.remove(guid) for guid in invalidated_guids]
if target_view_contexts and elements: if target_view_contexts and elements:
geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, INCLUDE_CURVES=True) geom_settings = ifcopenshell.geom.settings(
DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True, INCLUDE_CURVES=True
)
geom_settings.set_context_ids(target_view_contexts) geom_settings.set_context_ids(target_view_contexts)
it = ifcopenshell.geom.iterator(geom_settings, ifc, multiprocessing.cpu_count(), include=elements) it = ifcopenshell.geom.iterator(geom_settings, ifc, multiprocessing.cpu_count(), include=elements)
it.set_cache(cache) it.set_cache(cache)
@@ -317,18 +323,14 @@ class CreateDrawing(bpy.types.Operator):
elements -= processed elements -= processed
if body_contexts and elements: if body_contexts and elements:
geom_settings = ifcopenshell.geom.settings( geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True)
DISABLE_TRIANGULATION=True,
)
geom_settings.set_context_ids(body_contexts) geom_settings.set_context_ids(body_contexts)
it = ifcopenshell.geom.iterator(geom_settings, ifc, multiprocessing.cpu_count(), include=elements) it = ifcopenshell.geom.iterator(geom_settings, ifc, multiprocessing.cpu_count(), include=elements)
it.set_cache(cache) it.set_cache(cache)
for elem in self.yield_from_iterator(it): for elem in self.yield_from_iterator(it):
self.serialiser.write(elem) self.serialiser.write(elem)
geom_settings = ifcopenshell.geom.settings( geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True)
DISABLE_TRIANGULATION=True,
)
it = ifcopenshell.geom.iterator(geom_settings, ifc, include=[self.camera_element]) it = ifcopenshell.geom.iterator(geom_settings, ifc, include=[self.camera_element])
for elem in self.yield_from_iterator(it): for elem in self.yield_from_iterator(it):
self.serialiser.write(elem) self.serialiser.write(elem)
@@ -345,7 +347,9 @@ class CreateDrawing(bpy.types.Operator):
return svg_path return svg_path
def setup_serialiser(self, ifc): def setup_serialiser(self, ifc):
self.svg_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, INCLUDE_CURVES=True) self.svg_settings = ifcopenshell.geom.settings(
DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True, INCLUDE_CURVES=True
)
self.svg_buffer = ifcopenshell.geom.serializers.buffer() self.svg_buffer = ifcopenshell.geom.serializers.buffer()
self.serialiser = ifcopenshell.geom.serializers.svg(self.svg_buffer, self.svg_settings) self.serialiser = ifcopenshell.geom.serializers.svg(self.svg_buffer, self.svg_settings)
self.serialiser.setFile(ifc) self.serialiser.setFile(ifc)
@@ -453,6 +457,10 @@ class CreateDrawing(bpy.types.Operator):
svg_writer.annotations.setdefault("equal_objs", []).append(obj) svg_writer.annotations.setdefault("equal_objs", []).append(obj)
elif element.ObjectType == "DIMENSION": elif element.ObjectType == "DIMENSION":
svg_writer.annotations.setdefault("dimension_objs", []).append(obj) svg_writer.annotations.setdefault("dimension_objs", []).append(obj)
elif element.ObjectType == "ELEVATION":
svg_writer.annotations.setdefault("elevation_objs", []).append(obj)
elif element.ObjectType == "SECTION":
svg_writer.annotations.setdefault("section_objs", []).append(obj)
elif element.ObjectType == "BREAKLINE": elif element.ObjectType == "BREAKLINE":
svg_writer.annotations["break_obj"] = obj svg_writer.annotations["break_obj"] = obj
elif element.ObjectType == "HIDDEN_LINE": elif element.ObjectType == "HIDDEN_LINE":
@@ -563,7 +571,6 @@ class AddDrawingToSheet(bpy.types.Operator):
bl_idname = "bim.add_drawing_to_sheet" bl_idname = "bim.add_drawing_to_sheet"
bl_label = "Add Drawing To Sheet" bl_label = "Add Drawing To Sheet"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
# TODO: check undo redo
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
@@ -572,9 +579,78 @@ class AddDrawingToSheet(bpy.types.Operator):
def execute(self, context): def execute(self, context):
props = context.scene.DocProperties props = context.scene.DocProperties
active_drawing = props.drawings[props.active_drawing_index]
active_sheet = props.sheets[props.active_sheet_index]
drawing = tool.Ifc.get().by_id(active_drawing.ifc_definition_id)
sheet = tool.Ifc.get().by_id(active_sheet.ifc_definition_id)
if not sheet.is_a("IfcDocumentInformation"):
return {"FINISHED"}
if tool.Ifc.get_schema() == "IFC2X3":
references = sheet.DocumentReferences or []
else:
references = sheet.HasDocumentReferences or []
has_drawing = False
for reference in references:
new = props.sheets.add()
new.ifc_definition_id = reference.id()
new.is_sheet = False
if tool.Ifc.get_schema() == "IFC2X3":
new.identification = reference.ItemReference or "X"
element = [r for r in tool.Ifc.by_type("IfcRelAssociatesDocument") if r.RelatingDocument == reference][
0
].RelatedObjects[0]
else:
new.identification = reference.Identification or "X"
element = reference.DocumentRefForObjects[0].RelatedObjects[0]
if element == drawing:
has_drawing = True
break
if has_drawing:
return {"FINISHED"}
reference = tool.Ifc.run("document.add_reference", information=sheet)
if tool.Ifc.get_schema() == "IFC2X3":
attributes = {"ItemReference": str(len(sheet.DocumentReferences or []))}
else:
attributes = {"Identification": str(len(sheet.HasDocumentReferences or []))}
tool.Ifc.run("document.edit_reference", reference=reference, attributes=attributes)
tool.Ifc.run("document.assign_document", product=drawing, document=reference)
sheet_builder = sheeter.SheetBuilder() sheet_builder = sheeter.SheetBuilder()
sheet_builder.data_dir = context.scene.BIMProperties.data_dir sheet_builder.data_dir = context.scene.BIMProperties.data_dir
sheet_builder.add_drawing(props.active_drawing.name, props.active_sheet.name) sheet_builder.add_drawing(drawing, sheet)
tool.Drawing.import_sheets()
return {"FINISHED"}
class RemoveDrawingFromSheet(bpy.types.Operator):
bl_idname = "bim.remove_drawing_from_sheet"
bl_label = "Remove Drawing From Sheet"
bl_options = {"REGISTER", "UNDO"}
reference: bpy.props.IntProperty()
def execute(self, context):
reference = tool.Ifc.get().by_id(self.reference)
if tool.Ifc.get_schema() == "IFC2X3":
sheet = reference.ReferenceToDocument[0]
drawing = [r for r in tool.Ifc.by_type("IfcRelAssociatesDocument") if r.RelatingDocument == reference][
0
].RelatedObjects[0]
else:
sheet = reference.ReferencedDocument
drawing = reference.DocumentRefForObjects[0].RelatedObjects[0]
tool.Ifc.run("document.unassign_document", product=drawing, document=reference)
tool.Ifc.run("document.remove_reference", reference=reference)
sheet_builder = sheeter.SheetBuilder()
sheet_builder.data_dir = context.scene.BIMProperties.data_dir
sheet_builder.remove_drawing(drawing, sheet)
tool.Drawing.import_sheets()
return {"FINISHED"} return {"FINISHED"}
@@ -590,7 +666,8 @@ class CreateSheets(bpy.types.Operator):
def execute(self, context): def execute(self, context):
scene = context.scene scene = context.scene
props = scene.DocProperties props = scene.DocProperties
name = props.active_sheet.name active_sheet = props.sheets[props.active_sheet_index]
name = active_sheet.name
sheet_builder = sheeter.SheetBuilder() sheet_builder = sheeter.SheetBuilder()
sheet_builder.data_dir = scene.BIMProperties.data_dir sheet_builder.data_dir = scene.BIMProperties.data_dir
sheet_builder.build(name) sheet_builder.build(name)
@@ -677,7 +754,9 @@ class ActivateView(bpy.types.Operator):
project_collection.children["Views"].children[camera.users_collection[0].name].hide_viewport = False project_collection.children["Views"].children[camera.users_collection[0].name].hide_viewport = False
bpy.data.collections.get(camera.users_collection[0].name).hide_render = False bpy.data.collections.get(camera.users_collection[0].name).hide_render = False
bpy.context.scene.DocProperties.active_drawing_id = self.drawing
bpy.ops.bim.activate_drawing_style() bpy.ops.bim.activate_drawing_style()
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=tool.Ifc.get().by_id(self.drawing))
return {"FINISHED"} return {"FINISHED"}
@@ -777,12 +856,10 @@ class RemoveDrawing(bpy.types.Operator, Operator):
bl_idname = "bim.remove_drawing" bl_idname = "bim.remove_drawing"
bl_label = "Remove Drawing" bl_label = "Remove Drawing"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
index: bpy.props.IntProperty() drawing: bpy.props.IntProperty()
def _execute(self, context): def _execute(self, context):
props = context.scene.DocProperties core.remove_drawing(tool.Ifc, tool.Drawing, drawing=tool.Ifc.get().by_id(self.drawing))
drawing = tool.Ifc.get().by_id(props.drawings[self.index].ifc_definition_id)
core.remove_drawing(tool.Ifc, tool.Drawing, drawing=drawing)
class AddDrawingStyle(bpy.types.Operator): class AddDrawingStyle(bpy.types.Operator):
@@ -935,12 +1012,10 @@ class RemoveSheet(bpy.types.Operator, Operator):
bl_idname = "bim.remove_sheet" bl_idname = "bim.remove_sheet"
bl_label = "Remove Sheet" bl_label = "Remove Sheet"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
index: bpy.props.IntProperty() sheet: bpy.props.IntProperty()
def _execute(self, context): def _execute(self, context):
self.props = context.scene.DocProperties core.remove_sheet(tool.Ifc, tool.Drawing, sheet=tool.Ifc.get().by_id(self.sheet))
sheet = tool.Ifc.get().by_id(self.props.sheets[self.props.active_sheet_index].ifc_definition_id)
core.remove_sheet(tool.Ifc, tool.Drawing, sheet=sheet)
class AddSchedule(bpy.types.Operator): class AddSchedule(bpy.types.Operator):
@@ -1070,8 +1145,10 @@ class CopyGrid(bpy.types.Operator):
return helper.get_active_drawing(context.scene)[0] is not None return helper.get_active_drawing(context.scene)[0] is not None
def execute(self, context): def execute(self, context):
drawing = tool.Ifc.get_entity(context.scene.camera)
target_view = tool.Drawing.get_drawing_target_view(drawing)
subcontext = ifcopenshell.util.representation.get_context( subcontext = ifcopenshell.util.representation.get_context(
IfcStore.get_file(), "Plan", "Annotation", context.scene.camera.data.BIMCameraProperties.target_view IfcStore.get_file(), "Plan", "Annotation", target_view
) )
if not subcontext: if not subcontext:
return {"FINISHED"} return {"FINISHED"}
@@ -1080,8 +1157,8 @@ class CopyGrid(bpy.types.Operator):
view_coll, camera = helper.get_active_drawing(context.scene) view_coll, camera = helper.get_active_drawing(context.scene)
is_ortho = camera.data.type == "ORTHO" is_ortho = camera.data.type == "ORTHO"
bounds = helper.ortho_view_frame(camera.data) if is_ortho else None bounds = helper.ortho_view_frame(camera.data) if is_ortho else None
clipping = is_ortho and camera.data.BIMCameraProperties.target_view in ("PLAN_VIEW", "REFLECTED_PLAN_VIEW") clipping = is_ortho and target_view in ("PLAN_VIEW", "REFLECTED_PLAN_VIEW")
elevating = is_ortho and camera.data.BIMCameraProperties.target_view in ("ELEVATION_VIEW", "SECTION_VIEW") elevating = is_ortho and target_view in ("ELEVATION_VIEW", "SECTION_VIEW")
def grep(coll): def grep(coll):
results = [] results = []
@@ -1313,3 +1390,31 @@ class DisableEditingDrawings(bpy.types.Operator, Operator):
def _execute(self, context): def _execute(self, context):
core.disable_editing_drawings(tool.Drawing) core.disable_editing_drawings(tool.Drawing)
class ExpandSheet(bpy.types.Operator):
bl_idname = "bim.expand_sheet"
bl_label = "Expand Sheet"
bl_options = {"REGISTER", "UNDO"}
sheet: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.DocProperties
for sheet in [s for s in props.sheets if s.ifc_definition_id == self.sheet]:
sheet.is_expanded = True
core.load_sheets(tool.Drawing)
return {"FINISHED"}
class ContractSheet(bpy.types.Operator):
bl_idname = "bim.contract_sheet"
bl_label = "Contract Sheet"
bl_options = {"REGISTER", "UNDO"}
sheet: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.DocProperties
for sheet in [s for s in props.sheets if s.ifc_definition_id == self.sheet]:
sheet.is_expanded = False
core.load_sheets(tool.Drawing)
return {"FINISHED"}
@@ -199,6 +199,7 @@ class Variable(PropertyGroup):
class Drawing(PropertyGroup): class Drawing(PropertyGroup):
ifc_definition_id: IntProperty(name="IFC Definition ID") ifc_definition_id: IntProperty(name="IFC Definition ID")
name: StringProperty(name="Name", update=update_drawing_name) name: StringProperty(name="Name", update=update_drawing_name)
target_view: StringProperty(name="Target View")
class Schedule(PropertyGroup): class Schedule(PropertyGroup):
@@ -220,8 +221,9 @@ class Sheet(PropertyGroup):
ifc_definition_id: IntProperty(name="IFC Definition ID") ifc_definition_id: IntProperty(name="IFC Definition ID")
identification: StringProperty(name="Identification") identification: StringProperty(name="Identification")
name: StringProperty(name="Name", get=get_name, set=set_name) name: StringProperty(name="Name", get=get_name, set=set_name)
drawings: CollectionProperty(name="Drawings", type=Drawing) is_sheet: BoolProperty(name="Is Sheet", default=False)
active_drawing_index: IntProperty(name="Active Drawing Index") reference_type: StringProperty(name="Reference Type")
is_expanded: BoolProperty(name="Is Expanded", default=False)
class DrawingStyle(PropertyGroup): class DrawingStyle(PropertyGroup):
@@ -292,6 +294,7 @@ class DocProperties(PropertyGroup):
) )
location_hint: EnumProperty(items=get_location_hint, name="Location Hint") location_hint: EnumProperty(items=get_location_hint, name="Location Hint")
drawings: CollectionProperty(name="Drawings", type=Drawing) drawings: CollectionProperty(name="Drawings", type=Drawing)
active_drawing_id: IntProperty(name="Active Drawing Id")
active_drawing_index: IntProperty(name="Active Drawing Index") active_drawing_index: IntProperty(name="Active Drawing Index")
current_drawing_index: IntProperty(name="Current Drawing Index") current_drawing_index: IntProperty(name="Current Drawing Index")
schedules: CollectionProperty(name="Schedules", type=Schedule) schedules: CollectionProperty(name="Schedules", type=Schedule)
@@ -311,14 +314,6 @@ class DocProperties(PropertyGroup):
def active_schedule(self): def active_schedule(self):
return self.schedules[self.active_schedule_index] return self.schedules[self.active_schedule_index]
@property
def active_drawing(self):
return self.drawings[self.active_drawing_index]
@property
def active_sheet(self):
return self.sheets[self.active_sheet_index]
class BIMCameraProperties(PropertyGroup): class BIMCameraProperties(PropertyGroup):
representation: StringProperty(name="Representation") representation: StringProperty(name="Representation")
@@ -16,11 +16,12 @@
# You should have received a copy of the GNU General Public License # 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/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import xml.etree.ElementTree as ET
import urllib.parse
import pystache
import ntpath
import os import os
import ntpath
import pystache
import urllib.parse
import xml.etree.ElementTree as ET
import blenderbim.tool as tool
from shutil import copy from shutil import copy
from xml.dom import minidom from xml.dom import minidom
@@ -59,11 +60,16 @@ class SheetBuilder:
with open(sheet_path, "w") as f: with open(sheet_path, "w") as f:
f.write(minidom.parseString(ET.tostring(root)).toprettyxml(indent=" ")) f.write(minidom.parseString(ET.tostring(root)).toprettyxml(indent=" "))
def add_drawing(self, view_name, sheet_name): def add_drawing(self, drawing, sheet):
sheet_path = os.path.join(self.data_dir, "sheets", sheet_name + ".svg") filename = drawing.Name
view_path = os.path.join(self.data_dir, "diagrams", view_name + ".svg") sheet_name = tool.Drawing.get_sheet_filename(sheet)
sheet_dir = os.path.join(self.data_dir, "sheets")
drawing_dir = os.path.join(self.data_dir, "diagrams")
sheet_path = os.path.join(sheet_dir, sheet_name + ".svg")
drawing_path = os.path.join(drawing_dir, filename + ".svg")
underlay_path = os.path.join(drawing_dir, filename + ".png")
if not os.path.isfile(view_path): if not os.path.isfile(sheet_path):
raise FileNotFoundError raise FileNotFoundError
ET.register_namespace("", "http://www.w3.org/2000/svg") ET.register_namespace("", "http://www.w3.org/2000/svg")
@@ -72,7 +78,7 @@ class SheetBuilder:
sheet_tree = ET.parse(sheet_path) sheet_tree = ET.parse(sheet_path)
sheet_root = sheet_tree.getroot() sheet_root = sheet_tree.getroot()
view_tree = ET.parse(view_path) view_tree = ET.parse(drawing_path)
view_root = view_tree.getroot() view_root = view_tree.getroot()
# The view is placed into a group with a background image element. # The view is placed into a group with a background image element.
@@ -80,26 +86,48 @@ class SheetBuilder:
# here to accommodate browsers which do not nest images. # here to accommodate browsers which do not nest images.
view = ET.SubElement(sheet_root, "g") view = ET.SubElement(sheet_root, "g")
view.attrib["data-type"] = "drawing" view.attrib["data-type"] = "drawing"
view.attrib["data-guid"] = drawing.GlobalId
view_width = self.convert_to_mm(view_root.attrib.get("width")) view_width = self.convert_to_mm(view_root.attrib.get("width"))
view_height = self.convert_to_mm(view_root.attrib.get("height")) view_height = self.convert_to_mm(view_root.attrib.get("height"))
background = ET.SubElement(view, "image") if os.path.isfile(underlay_path):
background.attrib["xlink:href"] = "../diagrams/{}.png".format(view_name) background = ET.SubElement(view, "image")
background.attrib["x"] = "30" background.attrib["xlink:href"] = os.path.relpath(underlay_path, sheet_dir)
background.attrib["y"] = "30" background.attrib["x"] = "30"
background.attrib["width"] = str(view_width) background.attrib["y"] = "30"
background.attrib["height"] = str(view_height) background.attrib["width"] = str(view_width)
background.attrib["height"] = str(view_height)
foreground = ET.SubElement(view, "image") if os.path.isfile(drawing_path):
foreground.attrib["xlink:href"] = "../diagrams/{}.svg".format(view_name) foreground = ET.SubElement(view, "image")
foreground.attrib["x"] = "30" foreground.attrib["xlink:href"] = os.path.relpath(drawing_path, sheet_dir)
foreground.attrib["y"] = "30" foreground.attrib["x"] = "30"
foreground.attrib["width"] = str(view_width) foreground.attrib["y"] = "30"
foreground.attrib["height"] = str(view_height) foreground.attrib["width"] = str(view_width)
foreground.attrib["height"] = str(view_height)
self.add_view_title(30, view_height + 35, view) self.add_view_title(30, view_height + 35, view)
sheet_tree.write(sheet_path) sheet_tree.write(sheet_path)
def remove_drawing(self, drawing, sheet):
sheet_name = tool.Drawing.get_sheet_filename(sheet)
sheet_dir = os.path.join(self.data_dir, "sheets")
sheet_path = os.path.join(sheet_dir, sheet_name + ".svg")
ET.register_namespace("", "http://www.w3.org/2000/svg")
sheet_tree = ET.parse(sheet_path)
sheet_root = sheet_tree.getroot()
print('removing drawing', sheet_root.findall("{http://www.w3.org/2000/svg}g"))
for g in sheet_root.findall("{http://www.w3.org/2000/svg}g"):
print('checking g', g)
if g.attrib["data-type"] == "drawing" and g.attrib["data-guid"] == drawing.GlobalId:
sheet_root.remove(g)
break
sheet_tree.write(sheet_path)
def add_schedule(self, schedule_name, sheet_name): def add_schedule(self, schedule_name, sheet_name):
sheet_path = os.path.join(self.data_dir, "sheets", sheet_name + ".svg") sheet_path = os.path.join(self.data_dir, "sheets", sheet_name + ".svg")
view_path = os.path.join(self.data_dir, "schedules", schedule_name + ".svg") view_path = os.path.join(self.data_dir, "schedules", schedule_name + ".svg")
@@ -286,7 +286,15 @@ class SvgWriter:
}, },
) )
) )
self.draw_text_annotations()
for obj in self.annotations.get("elevation_objs", []):
self.draw_elevation_annotation(obj)
for obj in self.annotations.get("section_objs", []):
self.draw_section_annotation(obj)
for text_obj in self.annotations.get("text_objs", []):
self.draw_text_annotation(text_obj, text_obj.location)
def draw_ifc_annotation(self): def draw_ifc_annotation(self):
x_offset = self.raw_width / 2 x_offset = self.raw_width / 2
@@ -425,9 +433,48 @@ class SvgWriter:
position = Vector((0, 0, 0)) position = Vector((0, 0, 0))
self.draw_text_annotation(obj, position) self.draw_text_annotation(obj, position)
def draw_text_annotations(self): def draw_section_annotation(self, obj):
for text_obj in self.annotations.get("text_objs", []): x_offset = self.raw_width / 2
self.draw_text_annotation(text_obj, text_obj.location) y_offset = self.raw_height / 2
self.draw_line_annotation((obj, obj.data), ["solid"])
v1 = self.project_point_onto_camera(obj.matrix_world @ Vector((0, 0, 0)))
v2 = self.project_point_onto_camera(obj.matrix_world @ Vector((0, 0, -1)))
angle = -math.degrees((v2 - v1).xy.angle_signed(Vector((0, 1))))
for vert in obj.data.vertices:
point = obj.matrix_world @ vert.co
symbol_position = self.project_point_onto_camera(point)
symbol_position = Vector(((x_offset + symbol_position.x), (y_offset - symbol_position.y)))
transform = "rotate({}, {}, {})".format(
angle,
(symbol_position * self.scale)[0],
(symbol_position * self.scale)[1],
)
self.svg.add(self.svg.use("#section-arrow", insert=tuple(symbol_position * self.scale), transform=transform))
self.svg.add(self.svg.use("#section-tag", insert=tuple(symbol_position * self.scale)))
def draw_elevation_annotation(self, obj):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
symbol_position = self.project_point_onto_camera(obj.location)
symbol_position = Vector(((x_offset + symbol_position.x), (y_offset - symbol_position.y)))
v1 = self.project_point_onto_camera(obj.matrix_world @ Vector((0, 0, 0)))
v2 = self.project_point_onto_camera(obj.matrix_world @ Vector((0, 0, -1)))
angle = -math.degrees((v2 - v1).xy.angle_signed(Vector((0, 1))))
transform = "rotate({}, {}, {})".format(
angle,
(symbol_position * self.scale)[0],
(symbol_position * self.scale)[1],
)
self.svg.add(self.svg.use("#elevation-arrow", insert=tuple(symbol_position * self.scale), transform=transform))
self.svg.add(self.svg.use("#elevation-tag", insert=tuple(symbol_position * self.scale)))
def draw_text_annotation(self, text_obj, position): def draw_text_annotation(self, text_obj, position):
x_offset = self.raw_width / 2 x_offset = self.raw_width / 2
@@ -646,6 +693,7 @@ class SvgWriter:
) )
def project_point_onto_camera(self, point): def project_point_onto_camera(self, point):
# TODO is this needlessly complex?
return self.camera.matrix_world.inverted() @ geometry.intersect_line_plane( return self.camera.matrix_world.inverted() @ geometry.intersect_line_plane(
point.xyz, point.xyz,
point.xyz - Vector(self.camera_projection), point.xyz - Vector(self.camera_projection),
@@ -172,14 +172,15 @@ class BIM_PT_drawings(Panel):
if self.props.drawings: if self.props.drawings:
if self.props.active_drawing_index < len(self.props.drawings): if self.props.active_drawing_index < len(self.props.drawings):
active_drawing = self.props.drawings[self.props.active_drawing_index]
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.alignment = "RIGHT" row.alignment = "RIGHT"
op = row.operator("bim.open_view", icon="URL", text="") op = row.operator("bim.open_view", icon="URL", text="")
op.view = self.props.active_drawing.name op.view = active_drawing.name
op = row.operator("bim.activate_view", icon="OUTLINER_OB_CAMERA", text="") op = row.operator("bim.activate_view", icon="OUTLINER_OB_CAMERA", text="")
op.drawing = self.props.active_drawing.ifc_definition_id op.drawing = active_drawing.ifc_definition_id
row.operator("bim.create_drawing", text="", icon="OUTPUT") row.operator("bim.create_drawing", text="", icon="OUTPUT")
row.operator("bim.remove_drawing", icon="X", text="").index = self.props.active_drawing_index row.operator("bim.remove_drawing", icon="X", text="").drawing = active_drawing.ifc_definition_id
self.layout.template_list( self.layout.template_list(
"BIM_UL_drawinglist", "", self.props, "drawings", self.props, "active_drawing_index" "BIM_UL_drawinglist", "", self.props, "drawings", self.props, "active_drawing_index"
) )
@@ -245,14 +246,19 @@ class BIM_PT_sheets(Panel):
row.operator("bim.add_sheet", text="", icon="ADD") row.operator("bim.add_sheet", text="", icon="ADD")
row.operator("bim.disable_editing_sheets", text="", icon="CANCEL") row.operator("bim.disable_editing_sheets", text="", icon="CANCEL")
if self.props.sheets: if self.props.sheets and self.props.active_sheet_index < len(self.props.sheets):
active_sheet = self.props.sheets[self.props.active_sheet_index]
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.alignment = "RIGHT" row.alignment = "RIGHT"
row.operator("bim.open_sheet", icon="URL", text="") row.operator("bim.open_sheet", icon="URL", text="")
row.operator("bim.add_drawing_to_sheet", icon="IMAGE_PLANE", text="") row.operator("bim.add_drawing_to_sheet", icon="IMAGE_PLANE", text="")
row.operator("bim.add_schedule_to_sheet", icon="PRESET_NEW", text="") row.operator("bim.add_schedule_to_sheet", icon="PRESET_NEW", text="")
row.operator("bim.create_sheets", icon="FILE_REFRESH", text="") row.operator("bim.create_sheets", icon="FILE_REFRESH", text="")
row.operator("bim.remove_sheet", icon="X", text="").index = self.props.active_sheet_index if active_sheet.is_sheet:
row.operator("bim.remove_sheet", icon="X", text="").sheet = active_sheet.ifc_definition_id
else:
op = row.operator("bim.remove_drawing_from_sheet", icon="X", text="")
op.reference = active_sheet.ifc_definition_id
self.layout.template_list("BIM_UL_sheets", "", self.props, "sheets", self.props, "active_sheet_index") self.layout.template_list("BIM_UL_sheets", "", self.props, "sheets", self.props, "active_sheet_index")
@@ -369,7 +375,16 @@ class BIM_UL_drawinglist(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname): def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item: if item:
row = layout.row(align=True) row = layout.row(align=True)
row.prop(item, "name", text="", emboss=False) icon = "UV_FACESEL"
if item.target_view == "ELEVATION_VIEW":
icon = "UV_VERTEXSEL"
elif item.target_view == "SECTION_VIEW":
icon = "UV_EDGESEL"
elif item.target_view == "REFLECTED_PLAN_VIEW":
icon = "XRAY"
elif item.target_view == "MODEL_VIEW":
icon = "SNAP_VOLUME"
row.prop(item, "name", text="", icon=icon, emboss=False)
else: else:
layout.label(text="", translate=False) layout.label(text="", translate=False)
@@ -378,9 +393,22 @@ class BIM_UL_sheets(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname): def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item: if item:
row = layout.row(align=True) row = layout.row(align=True)
split1 = row.split(factor=0.2)
split1.label(text=item.identification or "X") if item.is_sheet:
split2 = split1.split(factor=0.8) if item.is_expanded:
split2.label(text=item.name or "Unnamed") row.operator(
"bim.contract_sheet", text="", emboss=False, icon="DISCLOSURE_TRI_DOWN"
).sheet = item.ifc_definition_id
else:
row.operator(
"bim.expand_sheet", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT"
).sheet = item.ifc_definition_id
else:
row.label(text="", icon="BLANK1")
if item.reference_type == "DRAWING":
row.label(text="", icon="IMAGE_DATA")
name = "{} - {}".format(item.identification or "X", item.name or "Unnamed")
row.label(text=name)
else: else:
layout.label(text="", translate=False) layout.label(text="", translate=False)
@@ -157,6 +157,9 @@ class UpdateRepresentation(bpy.types.Operator):
def update_obj_mesh_representation(self, context, obj): def update_obj_mesh_representation(self, context, obj):
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
if not product.is_a("IfcGridAxis"):
tool.Geometry.clear_cache(product)
if product.is_a("IfcGridAxis"): if product.is_a("IfcGridAxis"):
ifcopenshell.api.run("grid.create_axis_curve", self.file, **{"axis_curve": obj, "grid_axis": product}) ifcopenshell.api.run("grid.create_axis_curve", self.file, **{"axis_curve": obj, "grid_axis": product})
return return
@@ -355,6 +358,7 @@ class OverrideDeleteTrait:
class OverrideDelete(bpy.types.Operator, OverrideDeleteTrait): class OverrideDelete(bpy.types.Operator, OverrideDeleteTrait):
bl_idname = "object.delete" bl_idname = "object.delete"
bl_label = "Delete" bl_label = "Delete"
bl_options = {"REGISTER", "UNDO"}
use_global: bpy.props.BoolProperty(default=False) use_global: bpy.props.BoolProperty(default=False)
confirm: bpy.props.BoolProperty(default=True) confirm: bpy.props.BoolProperty(default=True)
@@ -368,21 +372,29 @@ class OverrideDelete(bpy.types.Operator, OverrideDeleteTrait):
return IfcStore.execute_ifc_operator(self, context) return IfcStore.execute_ifc_operator(self, context)
for obj in context.selected_objects: for obj in context.selected_objects:
bpy.data.objects.remove(obj) bpy.data.objects.remove(obj)
# Required otherwise gizmos are still visible
context.view_layer.objects.active = None
return {"FINISHED"} return {"FINISHED"}
def invoke(self, context, event): def invoke(self, context, event):
return context.window_manager.invoke_confirm(self, event) if self.confirm:
return context.window_manager.invoke_confirm(self, event)
self.confirm = True
return self.execute(context)
def _execute(self, context): def _execute(self, context):
for obj in context.selected_objects: for obj in context.selected_objects:
self.delete_ifc_object(obj) self.delete_ifc_object(obj)
bpy.data.objects.remove(obj) bpy.data.objects.remove(obj)
# Required otherwise gizmos are still visible
context.view_layer.objects.active = None
return {"FINISHED"} return {"FINISHED"}
class OverrideOutlinerDelete(bpy.types.Operator, OverrideDeleteTrait): class OverrideOutlinerDelete(bpy.types.Operator, OverrideDeleteTrait):
bl_idname = "outliner.delete" bl_idname = "outliner.delete"
bl_label = "Delete" bl_label = "Delete"
bl_options = {"REGISTER", "UNDO"}
hierarchy: bpy.props.BoolProperty(default=False) hierarchy: bpy.props.BoolProperty(default=False)
@classmethod @classmethod
@@ -405,7 +417,6 @@ class OverrideOutlinerDelete(bpy.types.Operator, OverrideDeleteTrait):
if item.bl_rna.identifier == "Collection": if item.bl_rna.identifier == "Collection":
collection = bpy.data.collections.get(item.name) collection = bpy.data.collections.get(item.name)
collection_data = self.get_collection_objects_and_children(collection) collection_data = self.get_collection_objects_and_children(collection)
print(collection_data)
objects_to_delete |= collection_data["objects"] objects_to_delete |= collection_data["objects"]
collections_to_delete |= collection_data["children"] collections_to_delete |= collection_data["children"]
collections_to_delete.add(collection) collections_to_delete.add(collection)
@@ -453,6 +464,7 @@ class OverrideOutlinerDelete(bpy.types.Operator, OverrideDeleteTrait):
class OverrideDuplicateMove(bpy.types.Operator): class OverrideDuplicateMove(bpy.types.Operator):
bl_idname = "object.duplicate_move" bl_idname = "object.duplicate_move"
bl_label = "Duplicate Objects" bl_label = "Duplicate Objects"
bl_options = {"REGISTER", "UNDO"}
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
@@ -21,37 +21,47 @@ from . import ui, prop, operator
classes = ( classes = (
operator.AddConstituent, operator.AddConstituent,
operator.AddDefaultMaterial,
operator.AddLayer, operator.AddLayer,
operator.AddListItem, operator.AddListItem,
operator.AddMaterial, operator.AddMaterial,
operator.AddMaterialSet,
operator.AddProfile, operator.AddProfile,
operator.AssignMaterial, operator.AssignMaterial,
operator.AssignParameterizedProfile, operator.AssignParameterizedProfile,
operator.CopyMaterial, operator.CopyMaterial,
operator.DisableEditingAssignedMaterial, operator.DisableEditingAssignedMaterial,
operator.DisableEditingMaterialSetItem, operator.DisableEditingMaterialSetItem,
operator.DisableEditingMaterials,
operator.EditAssignedMaterial, operator.EditAssignedMaterial,
operator.EditMaterialSetItem, operator.EditMaterialSetItem,
operator.EnableEditingAssignedMaterial, operator.EnableEditingAssignedMaterial,
operator.EnableEditingMaterialSetItem, operator.EnableEditingMaterialSetItem,
operator.LoadMaterials,
operator.RemoveConstituent, operator.RemoveConstituent,
operator.RemoveLayer, operator.RemoveLayer,
operator.RemoveListItem, operator.RemoveListItem,
operator.RemoveMaterial, operator.RemoveMaterial,
operator.RemoveMaterialSet,
operator.RemoveProfile, operator.RemoveProfile,
operator.ReorderMaterialSetItem, operator.ReorderMaterialSetItem,
operator.SelectByMaterial,
operator.UnassignMaterial, operator.UnassignMaterial,
operator.UnlinkMaterial, operator.UnlinkMaterial,
prop.Material,
prop.BIMMaterialProperties,
prop.BIMObjectMaterialProperties, prop.BIMObjectMaterialProperties,
ui.BIM_PT_materials,
ui.BIM_PT_material, ui.BIM_PT_material,
ui.BIM_PT_object_material, ui.BIM_PT_object_material,
ui.BIM_UL_materials,
) )
def register(): def register():
bpy.types.Scene.BIMMaterialProperties = bpy.props.PointerProperty(type=prop.BIMMaterialProperties)
bpy.types.Object.BIMObjectMaterialProperties = bpy.props.PointerProperty(type=prop.BIMObjectMaterialProperties) bpy.types.Object.BIMObjectMaterialProperties = bpy.props.PointerProperty(type=prop.BIMObjectMaterialProperties)
def unregister(): def unregister():
del bpy.types.Scene.BIMMaterialProperties
del bpy.types.Object.BIMObjectMaterialProperties del bpy.types.Object.BIMObjectMaterialProperties
@@ -0,0 +1,86 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2022 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/>.
import os
import bpy
import ifcopenshell
import ifcopenshell.util.schema
import blenderbim.tool as tool
def refresh():
MaterialsData.is_loaded = False
ObjectMaterialData.is_loaded = False
class MaterialsData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {
"total_materials": cls.total_materials(),
"material_types": cls.material_types(),
}
cls.is_loaded = True
@classmethod
def total_materials(cls):
if tool.Ifc.get_schema() == "IFC2X3":
return (
len(tool.Ifc.get().by_type("IfcMaterial"))
+ len(tool.Ifc.get().by_type("IfcMaterialLayerSet"))
+ len(tool.Ifc.get().by_type("IfcMaterialList"))
)
return (
len(tool.Ifc.get().by_type("IfcMaterial"))
+ len(tool.Ifc.get().by_type("IfcMaterialConstituentSet"))
+ len(tool.Ifc.get().by_type("IfcMaterialLayerSet"))
+ len(tool.Ifc.get().by_type("IfcMaterialProfileSet"))
+ len(tool.Ifc.get().by_type("IfcMaterialList"))
)
@classmethod
def material_types(cls):
material_types = [
"IfcMaterial",
"IfcMaterialConstituentSet",
"IfcMaterialLayerSet",
"IfcMaterialProfileSet",
"IfcMaterialList",
]
if tool.Ifc.get_schema() == "IFC2X3":
material_types = ["IfcMaterial", "IfcMaterialLayerSet", "IfcMaterialList"]
return [(m, m, "") for m in material_types]
class ObjectMaterialData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {
"materials": cls.materials(),
}
cls.is_loaded = True
@classmethod
def materials(cls):
return sorted([(str(m.id()), m.Name or "Unnamed", "") for m in tool.Ifc.get().by_type("IfcMaterial")], key=lambda x: x[1])
@@ -31,11 +31,32 @@ from ifcopenshell.api.material.data import Data
from ifcopenshell.api.profile.data import Data as ProfileData from ifcopenshell.api.profile.data import Data as ProfileData
class Operator: class LoadMaterials(bpy.types.Operator, tool.Ifc.Operator):
def execute(self, context): bl_idname = "bim.load_materials"
IfcStore.execute_ifc_operator(self, context) bl_label = "Load Materials"
blenderbim.bim.handler.refresh_ui_data() bl_options = {"REGISTER", "UNDO"}
return {"FINISHED"}
def _execute(self, context):
core.load_materials(tool.Material, context.scene.BIMMaterialProperties.material_type)
class DisableEditingMaterials(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.disable_editing_materials"
bl_label = "Disable Editing Materials"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.disable_editing_materials(tool.Material)
class SelectByMaterial(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.select_by_material"
bl_label = "Select By Material"
bl_options = {"REGISTER", "UNDO"}
material: bpy.props.IntProperty()
def _execute(self, context):
core.select_by_material(tool.Material, material=tool.Ifc.get().by_id(self.material))
class AssignParameterizedProfile(bpy.types.Operator): class AssignParameterizedProfile(bpy.types.Operator):
@@ -68,70 +89,54 @@ class AssignParameterizedProfile(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
class AddDefaultMaterial(bpy.types.Operator, Operator): class AddMaterial(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_default_material"
bl_label = "Add Default Material"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.add_default_material(tool.Ifc, tool.Material)
Data.load(IfcStore.get_file())
class AddMaterial(bpy.types.Operator):
bl_idname = "bim.add_material" bl_idname = "bim.add_material"
bl_label = "Add Material" bl_label = "Add Material"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty() obj: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context): def _execute(self, context):
obj = bpy.data.materials.get(self.obj) if self.obj else context.active_object.active_material obj = bpy.data.materials.get(self.obj) if self.obj else None
self.file = IfcStore.get_file() core.add_material(tool.Ifc, tool.Material, tool.Style, obj=obj)
result = ifcopenshell.api.run("material.add_material", self.file, **{"name": obj.name})
IfcStore.link_element(result, obj)
if obj.BIMMaterialProperties.ifc_style_id:
context = ifcopenshell.util.representation.get_context(self.file, "Model", "Body", "MODEL_VIEW")
if context:
ifcopenshell.api.run(
"style.assign_material_style",
self.file,
**{
"material": result,
"style": self.file.by_id(obj.BIMMaterialProperties.ifc_style_id),
"context": context,
},
)
Data.load(IfcStore.get_file()) Data.load(IfcStore.get_file())
material_prop_purge() material_prop_purge()
return {"FINISHED"}
class RemoveMaterial(bpy.types.Operator): class AddMaterialSet(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_material_set"
bl_label = "Add Material Set"
bl_options = {"REGISTER", "UNDO"}
set_type: bpy.props.StringProperty()
def _execute(self, context):
core.add_material_set(tool.Ifc, tool.Material, set_type=self.set_type)
Data.load(IfcStore.get_file())
material_prop_purge()
class RemoveMaterial(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_material" bl_idname = "bim.remove_material"
bl_label = "Remove Material" bl_label = "Remove Material"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty() material: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context): def _execute(self, context):
obj = bpy.data.materials.get(self.obj) if self.obj else context.active_object.active_material core.remove_material(tool.Ifc, tool.Material, tool.Style, material=tool.Ifc.get().by_id(self.material))
self.file = IfcStore.get_file()
result = ifcopenshell.api.run(
"material.remove_material",
self.file,
**{"material": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)},
)
obj.BIMObjectProperties.ifc_definition_id = 0
Data.load(IfcStore.get_file()) Data.load(IfcStore.get_file())
return {"FINISHED"}
class UnlinkMaterial(bpy.types.Operator, Operator): class RemoveMaterialSet(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_material_set"
bl_label = "Remove Material Set"
bl_options = {"REGISTER", "UNDO"}
material: bpy.props.IntProperty()
def _execute(self, context):
core.remove_material_set(tool.Ifc, tool.Material, material=tool.Ifc.get().by_id(self.material))
Data.load(IfcStore.get_file())
class UnlinkMaterial(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.unlink_material" bl_idname = "bim.unlink_material"
bl_label = "Unlink Material" bl_label = "Unlink Material"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
@@ -18,6 +18,7 @@
import bpy import bpy
from ifcopenshell.api.material.data import Data from ifcopenshell.api.material.data import Data
from blenderbim.bim.module.material.data import MaterialsData, ObjectMaterialData
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.prop import StrProperty, Attribute from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup from bpy.types import PropertyGroup
@@ -32,18 +33,15 @@ from bpy.props import (
CollectionProperty, CollectionProperty,
) )
materials_enum = []
materialtypes_enum = [] materialtypes_enum = []
profileclasses_enum = [] profileclasses_enum = []
parameterizedprofileclasses_enum = [] parameterizedprofileclasses_enum = []
def purge(): def purge():
global materials_enum
global materialtypes_enum global materialtypes_enum
global profileclasses_enum global profileclasses_enum
global parameterizedprofileclasses_enum global parameterizedprofileclasses_enum
materials_enum = []
materialtypes_enum = [] materialtypes_enum = []
profileclasses_enum = [] profileclasses_enum = []
parameterizedprofileclasses_enum = [] parameterizedprofileclasses_enum = []
@@ -77,12 +75,10 @@ def getParameterizedProfileClasses(self, context):
return parameterizedprofileclasses_enum return parameterizedprofileclasses_enum
def getMaterials(self, context): def get_materials(self, context):
global materials_enum if not ObjectMaterialData.is_loaded:
if len(materials_enum) == 0 and IfcStore.get_file(): ObjectMaterialData.load()
materials_enum.clear() return ObjectMaterialData.data["materials"]
materials_enum = [(str(m_id), m["Name"], "") for m_id, m in Data.materials.items()]
return materials_enum
def getMaterialTypes(self, context): def getMaterialTypes(self, context):
@@ -104,9 +100,28 @@ def getMaterialTypes(self, context):
return materialtypes_enum return materialtypes_enum
def get_material_types(self, context):
if not MaterialsData.is_loaded:
MaterialsData.load()
return MaterialsData.data["material_types"]
class Material(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
total_elements: IntProperty(name="Total Elements")
class BIMMaterialProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing", default=False)
material_type: EnumProperty(items=get_material_types, name="Material Type")
materials: CollectionProperty(name="Materials", type=Material)
active_material_index: IntProperty(name="Active Material Index")
class BIMObjectMaterialProperties(PropertyGroup): class BIMObjectMaterialProperties(PropertyGroup):
material_type: EnumProperty(items=getMaterialTypes, name="Material Type") material_type: EnumProperty(items=getMaterialTypes, name="Material Type")
material: EnumProperty(items=getMaterials, name="Material") material: EnumProperty(items=get_materials, name="Material")
is_editing: BoolProperty(name="Is Editing", default=False) is_editing: BoolProperty(name="Is Editing", default=False)
material_set_usage_attributes: CollectionProperty(name="Material Set Usage Attributes", type=Attribute) material_set_usage_attributes: CollectionProperty(name="Material Set Usage Attributes", type=Attribute)
material_set_attributes: CollectionProperty(name="Material Set Attributes", type=Attribute) material_set_attributes: CollectionProperty(name="Material Set Attributes", type=Attribute)
@@ -115,7 +130,7 @@ class BIMObjectMaterialProperties(PropertyGroup):
material_set_item_profile_attributes: CollectionProperty( material_set_item_profile_attributes: CollectionProperty(
name="Material Set Item Profile Attributes", type=Attribute name="Material Set Item Profile Attributes", type=Attribute
) )
material_set_item_material: EnumProperty(items=getMaterials, name="Material") material_set_item_material: EnumProperty(items=get_materials, name="Material")
profile_classes: EnumProperty(items=getProfileClasses, name="Profile Classes") profile_classes: EnumProperty(items=getProfileClasses, name="Profile Classes")
parameterized_profile_classes: EnumProperty( parameterized_profile_classes: EnumProperty(
items=getParameterizedProfileClasses, name="Parameterized Profile Classes" items=getParameterizedProfileClasses, name="Parameterized Profile Classes"
@@ -17,11 +17,62 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import blenderbim.bim.helper import blenderbim.bim.helper
from bpy.types import Panel from bpy.types import Panel, UIList
from ifcopenshell.api.material.data import Data from ifcopenshell.api.material.data import Data
from ifcopenshell.api.profile.data import Data as ProfileData from ifcopenshell.api.profile.data import Data as ProfileData
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.helper import draw_attributes from blenderbim.bim.helper import draw_attributes
from blenderbim.bim.module.material.data import MaterialsData, ObjectMaterialData
class BIM_PT_materials(Panel):
bl_label = "IFC Materials"
bl_idname = "BIM_PT_materials"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_geometry"
@classmethod
def poll(cls, context):
return IfcStore.get_file()
def draw(self, context):
if not MaterialsData.is_loaded:
MaterialsData.load()
self.props = context.scene.BIMMaterialProperties
row = self.layout.row(align=True)
row.label(text="{} Materials Found".format(MaterialsData.data["total_materials"]), icon="MATERIAL")
if self.props.is_editing:
row.operator("bim.disable_editing_materials", text="", icon="CANCEL")
else:
row = self.layout.row(align=True)
row.prop(self.props, "material_type", text="")
row.operator("bim.load_materials", text="", icon="IMPORT")
return
row = self.layout.row(align=True)
row.alignment = "RIGHT"
if self.props.material_type == "IfcMaterial":
row.operator("bim.add_material", text="", icon="ADD")
if self.props.materials and self.props.active_material_index < len(self.props.materials):
material = self.props.materials[self.props.active_material_index]
op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF")
op.material = material.ifc_definition_id
row.operator("bim.remove_material", text="", icon="X").material = material.ifc_definition_id
else:
row.operator("bim.add_material_set", text="", icon="ADD").set_type = self.props.material_type
if self.props.materials and self.props.active_material_index < len(self.props.materials):
material = self.props.materials[self.props.active_material_index]
op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF")
op.material = material.ifc_definition_id
row.operator("bim.remove_material_set", text="", icon="X").material = material.ifc_definition_id
self.layout.template_list("BIM_UL_materials", "", self.props, "materials", self.props, "active_material_index")
class BIM_PT_material(Panel): class BIM_PT_material(Panel):
@@ -37,11 +88,13 @@ class BIM_PT_material(Panel):
def draw(self, context): def draw(self, context):
row = self.layout.row(align=True) row = self.layout.row(align=True)
if bool(context.active_object.active_material.BIMObjectProperties.ifc_definition_id): material_id = context.active_object.active_material.BIMObjectProperties.ifc_definition_id
row.operator("bim.remove_material", icon="X", text="Remove IFC Material") if bool(material_id):
row.operator("bim.remove_material", icon="X", text="Remove IFC Material").material = material_id
row.operator("bim.unlink_material", icon="UNLINKED", text="") row.operator("bim.unlink_material", icon="UNLINKED", text="")
else: else:
row.operator("bim.add_material", icon="ADD", text="Create IFC Material") op = row.operator("bim.add_material", icon="ADD", text="Create IFC Material")
op.obj = context.active_object.active_material.name
class BIM_PT_object_material(Panel): class BIM_PT_object_material(Panel):
@@ -66,6 +119,9 @@ class BIM_PT_object_material(Panel):
return True return True
def draw(self, context): def draw(self, context):
if not ObjectMaterialData.is_loaded:
ObjectMaterialData.load()
self.file = IfcStore.get_file() self.file = IfcStore.get_file()
self.oprops = context.active_object.BIMObjectProperties self.oprops = context.active_object.BIMObjectProperties
self.props = context.active_object.BIMObjectMaterialProperties self.props = context.active_object.BIMObjectMaterialProperties
@@ -77,10 +133,10 @@ class BIM_PT_object_material(Panel):
ProfileData.load(self.file) ProfileData.load(self.file)
self.product_data = Data.products[self.oprops.ifc_definition_id] self.product_data = Data.products[self.oprops.ifc_definition_id]
if not Data.materials: if not ObjectMaterialData.data["materials"]:
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.label(text="No Materials Available") row.label(text="No Materials Available")
row.operator("bim.add_default_material", icon="ADD", text="") row.operator("bim.add_material", icon="ADD", text="").obj = ""
return return
if self.product_data: if self.product_data:
@@ -333,3 +389,13 @@ class BIM_PT_object_material(Panel):
if total_thickness: if total_thickness:
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.label(text=f"Total Thickness: {total_thickness:.3f}") row.label(text=f"Total Thickness: {total_thickness:.3f}")
class BIM_UL_materials(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.label(text=item.name)
row2 = row.row()
row2.alignment = "RIGHT"
row2.label(text=str(item.total_elements))
@@ -39,7 +39,12 @@ class AuthoringData:
@classmethod @classmethod
def ifc_classes(cls): def ifc_classes(cls):
results = [] results = []
classes = {e.is_a() for e in tool.Ifc.get().by_type("IfcElementType")} classes = {
e.is_a()
for e in tool.Ifc.get().by_type("IfcElementType")
+ tool.Ifc.get().by_type("IfcDoorStyle")
+ tool.Ifc.get().by_type("IfcWindowStyle")
}
results.extend([(c, c, "") for c in sorted(classes)]) results.extend([(c, c, "") for c in sorted(classes)])
return results return results
@@ -27,6 +27,7 @@ from bpy.app.handlers import persistent
@persistent @persistent
def load_post(*args): def load_post(*args):
ifcopenshell.api.add_pre_listener("attribute.edit_attributes", "BlenderBIM.Root.SyncName", root.sync_name) ifcopenshell.api.add_pre_listener("attribute.edit_attributes", "BlenderBIM.Root.SyncName", root.sync_name)
ifcopenshell.api.add_pre_listener("style.edit_presentation_style", "BlenderBIM.Root.SyncStyleName", root.sync_name)
ifcopenshell.api.add_post_listener( ifcopenshell.api.add_post_listener(
"geometry.add_representation", "BlenderBIM.Product.GenerateBox", product.generate_box "geometry.add_representation", "BlenderBIM.Product.GenerateBox", product.generate_box
@@ -34,7 +34,6 @@ from math import pi, degrees
from mathutils import Vector, Matrix from mathutils import Vector, Matrix
class MepGenerator: class MepGenerator:
def __init__(self, relating_type): def __init__(self, relating_type):
self.relating_type = relating_type self.relating_type = relating_type
@@ -17,24 +17,34 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy import bpy
import blenderbim.bim.handler
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.style.data import Data as StyleData
def sync_name(usecase_path, ifc_file, settings): def sync_name(usecase_path, ifc_file, settings):
if usecase_path == "attribute.edit_attributes":
element = settings["product"]
elif usecase_path == "style.edit_presentation_style":
element = settings["style"]
if "Name" not in settings["attributes"]: if "Name" not in settings["attributes"]:
return return
obj = IfcStore.get_element(settings["product"].id()) obj = tool.Ifc.get_object(element)
if not obj: if not obj:
return return
if isinstance(obj, bpy.types.Object): if isinstance(obj, bpy.types.Object):
new_name = "{}/{}".format(settings["product"].is_a(), settings["attributes"]["Name"] or "Unnamed") new_name = "{}/{}".format(element.is_a(), settings["attributes"]["Name"] or "Unnamed")
elif isinstance(obj, bpy.types.Material): elif isinstance(obj, bpy.types.Material):
new_name = settings["attributes"]["Name"] or "Unnamed" new_name = settings["attributes"]["Name"] or "Unnamed"
if obj.BIMMaterialProperties.ifc_style_id: material = tool.Ifc.get_entity(obj)
IfcStore.get_file().by_id(obj.BIMMaterialProperties.ifc_style_id).Name = new_name if material and material != element:
StyleData.load(IfcStore.get_file(), obj.BIMMaterialProperties.ifc_style_id) material.Name = new_name
style = tool.Style.get_style(obj)
if style and style != element:
style.Name = new_name
collection = bpy.data.collections.get(obj.name) collection = bpy.data.collections.get(obj.name)
if collection: if collection:
collection.name = new_name collection.name = new_name
obj.name = new_name obj.name = new_name
blenderbim.bim.handler.refresh_ui_data()
@@ -611,10 +611,14 @@ class LinkIfc(bpy.types.Operator):
bl_description = "Link a Blender file" bl_description = "Link a Blender file"
filepath: bpy.props.StringProperty(subtype="FILE_PATH") filepath: bpy.props.StringProperty(subtype="FILE_PATH")
filter_glob: bpy.props.StringProperty(default="*.blend;*.blend1", options={"HIDDEN"}) filter_glob: bpy.props.StringProperty(default="*.blend;*.blend1", options={"HIDDEN"})
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
def execute(self, context): def execute(self, context):
new = context.scene.BIMProjectProperties.links.add() new = context.scene.BIMProjectProperties.links.add()
new.name = self.filepath filepath = self.filepath
if self.use_relative_path:
filepath = os.path.relpath(filepath, bpy.path.abspath("//"))
new.name = filepath
bpy.ops.bim.load_link(filepath=self.filepath) bpy.ops.bim.load_link(filepath=self.filepath)
return {"FINISHED"} return {"FINISHED"}
@@ -646,13 +650,16 @@ class UnloadLink(bpy.types.Operator):
filepath: bpy.props.StringProperty() filepath: bpy.props.StringProperty()
def execute(self, context): def execute(self, context):
filepath = self.filepath
if not os.path.isabs(filepath):
filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), filepath))
for collection in context.scene.collection.children: for collection in context.scene.collection.children:
if collection.library and collection.library.filepath == self.filepath: if collection.library and collection.library.filepath == filepath:
context.scene.collection.children.unlink(collection) context.scene.collection.children.unlink(collection)
for scene in bpy.data.scenes: for scene in bpy.data.scenes:
if scene.library and scene.library.filepath == self.filepath: if scene.library and scene.library.filepath == filepath:
bpy.data.scenes.remove(scene) bpy.data.scenes.remove(scene)
link = context.scene.BIMProjectProperties.links.get(self.filepath) link = context.scene.BIMProjectProperties.links.get(filepath)
link.is_loaded = False link.is_loaded = False
return {"FINISHED"} return {"FINISHED"}
@@ -665,16 +672,19 @@ class LoadLink(bpy.types.Operator):
filepath: bpy.props.StringProperty() filepath: bpy.props.StringProperty()
def execute(self, context): def execute(self, context):
filepath = self.filepath
if not os.path.isabs(filepath):
filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), filepath))
with bpy.data.libraries.load(self.filepath, link=True) as (data_from, data_to): with bpy.data.libraries.load(self.filepath, link=True) as (data_from, data_to):
data_to.scenes = data_from.scenes data_to.scenes = data_from.scenes
for scene in bpy.data.scenes: for scene in bpy.data.scenes:
if not scene.library or scene.library.filepath != self.filepath: if not scene.library or scene.library.filepath != filepath:
continue continue
for child in scene.collection.children: for child in scene.collection.children:
if "IfcProject" not in child.name: if "IfcProject" not in child.name:
continue continue
bpy.data.scenes[0].collection.children.link(child) bpy.data.scenes[0].collection.children.link(child)
link = context.scene.BIMProjectProperties.links.get(self.filepath) link = context.scene.BIMProjectProperties.links.get(filepath)
link.is_loaded = True link.is_loaded = True
return {"FINISHED"} return {"FINISHED"}
@@ -284,11 +284,12 @@ class BIM_UL_links(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname): def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item: if item:
row = layout.row(align=True) row = layout.row(align=True)
row.label(text=item.name)
if item.is_loaded: if item.is_loaded:
row.label(text=item.name)
op = row.operator("bim.unload_link", text="", icon="UNLINKED") op = row.operator("bim.unload_link", text="", icon="UNLINKED")
op.filepath = item.name op.filepath = item.name
else: else:
row.prop(item, "name", text="")
op = row.operator("bim.load_link", text="", icon="LINKED") op = row.operator("bim.load_link", text="", icon="LINKED")
op.filepath = item.name op.filepath = item.name
op = row.operator("bim.unlink_ifc", text="", icon="X") op = row.operator("bim.unlink_ifc", text="", icon="X")
@@ -32,12 +32,47 @@ class IfcClassData:
@classmethod @classmethod
def load(cls): def load(cls):
cls.is_loaded = True cls.is_loaded = True
cls.data = { cls.data = {}
"contexts": cls.contexts(), cls.data["ifc_products"] = cls.ifc_products()
"has_entity": cls.has_entity(), cls.data["ifc_classes"] = cls.ifc_classes()
"name": cls.name(), cls.data["contexts"] = cls.contexts()
"ifc_class": cls.ifc_class(), cls.data["has_entity"] = cls.has_entity()
} cls.data["name"] = cls.name()
cls.data["ifc_class"] = cls.ifc_class()
@classmethod
def ifc_products(cls):
products = [
"IfcElement",
"IfcElementType",
"IfcSpatialElement",
"IfcGroup",
"IfcStructuralItem",
"IfcContext",
"IfcAnnotation",
"IfcRelSpaceBoundary",
]
if tool.Ifc.get_schema() == "IFC2X3":
products = [
"IfcElement",
"IfcElementType",
"IfcSpatialStructureElement",
"IfcGroup",
"IfcStructuralItem",
"IfcAnnotation",
"IfcRelSpaceBoundary",
]
return [(e, e, "") for e in products]
@classmethod
def ifc_classes(cls):
ifc_product = bpy.context.scene.BIMRootProperties.ifc_product
declaration = tool.Ifc.schema().declaration_by_name(ifc_product)
declarations = ifcopenshell.util.schema.get_subtypes(declaration)
names = [d.name() for d in declarations]
if ifc_product == "IfcElementType":
names.extend(("IfcDoorStyle", "IfcWindowStyle"))
return [(c, c, "") for c in sorted(names)]
@classmethod @classmethod
def contexts(cls): def contexts(cls):
@@ -33,17 +33,11 @@ from bpy.props import (
CollectionProperty, CollectionProperty,
) )
products_enum = []
classes_enum = []
types_enum = [] types_enum = []
def purge(): def purge():
global products_enum
global classes_enum
global types_enum global types_enum
products_enum = []
classes_enum = []
types_enum = [] types_enum = []
@@ -61,10 +55,9 @@ def getIfcPredefinedTypes(self, context):
return types_enum return types_enum
def refreshClasses(self, context): def refresh_classes(self, context):
global classes_enum IfcClassData.load()
classes_enum.clear() enum = get_ifc_classes(self, context)
enum = getIfcClasses(self, context)
context.scene.BIMRootProperties.ifc_class = enum[0][0] context.scene.BIMRootProperties.ifc_class = enum[0][0]
@@ -76,38 +69,16 @@ def refreshPredefinedTypes(self, context):
context.scene.BIMRootProperties.ifc_predefined_type = enum[0][0] context.scene.BIMRootProperties.ifc_predefined_type = enum[0][0]
def getIfcProducts(self, context): def get_ifc_products(self, context):
global products_enum if not IfcClassData.is_loaded:
file = IfcStore.get_file() IfcClassData.load()
if len(products_enum) < 1: return IfcClassData.data["ifc_products"]
products_enum.extend(
[
(e, e, "")
for e in [
"IfcElement",
"IfcElementType",
"IfcSpatialElement",
"IfcGroup",
"IfcStructuralItem",
"IfcContext",
"IfcAnnotation",
"IfcRelSpaceBoundary",
]
]
)
if file.schema == "IFC2X3":
products_enum[2] = ("IfcSpatialStructureElement", "IfcSpatialStructureElement", "")
return products_enum
def getIfcClasses(self, context): def get_ifc_classes(self, context):
global classes_enum if not IfcClassData.is_loaded:
file = IfcStore.get_file() IfcClassData.load()
if len(classes_enum) < 1 and file: return IfcClassData.data["ifc_classes"]
declaration = IfcStore.get_schema().declaration_by_name(context.scene.BIMRootProperties.ifc_product)
declarations = ifcopenshell.util.schema.get_subtypes(declaration)
classes_enum.extend([(c, c, "") for c in sorted([d.name() for d in declarations])])
return classes_enum
def get_contexts(self, context): def get_contexts(self, context):
@@ -118,7 +89,7 @@ def get_contexts(self, context):
class BIMRootProperties(PropertyGroup): class BIMRootProperties(PropertyGroup):
contexts: EnumProperty(items=get_contexts, name="Contexts") contexts: EnumProperty(items=get_contexts, name="Contexts")
ifc_product: EnumProperty(items=getIfcProducts, name="Products", update=refreshClasses) ifc_product: EnumProperty(items=get_ifc_products, name="Products", update=refresh_classes)
ifc_class: EnumProperty(items=getIfcClasses, name="Class", update=refreshPredefinedTypes) ifc_class: EnumProperty(items=get_ifc_classes, name="Class", update=refreshPredefinedTypes)
ifc_predefined_type: EnumProperty(items=getIfcPredefinedTypes, name="Predefined Type", default=None) ifc_predefined_type: EnumProperty(items=getIfcPredefinedTypes, name="Predefined Type", default=None)
ifc_userdefined_type: StringProperty(name="Userdefined Type") ifc_userdefined_type: StringProperty(name="Userdefined Type")
@@ -989,6 +989,7 @@ class GenerateGanttChart(bpy.types.Operator):
data = { data = {
"pID": task.id(), "pID": task.id(),
"pName": task.Name, "pName": task.Name,
"pCaption": task.Name,
"pStart": task.TaskTime.ScheduleStart if task.TaskTime else "", "pStart": task.TaskTime.ScheduleStart if task.TaskTime else "",
"pEnd": task.TaskTime.ScheduleFinish if task.TaskTime else "", "pEnd": task.TaskTime.ScheduleFinish if task.TaskTime else "",
"pPlanStart": task.TaskTime.ScheduleStart if task.TaskTime else "", "pPlanStart": task.TaskTime.ScheduleStart if task.TaskTime else "",
@@ -999,6 +1000,7 @@ class GenerateGanttChart(bpy.types.Operator):
"pParent": task.Nests[0].RelatingObject.id() if task.Nests else 0, "pParent": task.Nests[0].RelatingObject.id() if task.Nests else 0,
"pOpen": 1, "pOpen": 1,
"pCost": 1, "pCost": 1,
"ifcduration": task.TaskTime.ScheduleDuration if task.TaskTime else "",
} }
if task.TaskTime and task.TaskTime.IsCritical: if task.TaskTime and task.TaskTime.IsCritical:
data["pClass"] = "gtaskred" data["pClass"] = "gtaskred"
@@ -1230,7 +1232,7 @@ class ExportMSP(bpy.types.Operator, ImportHelper):
start = time.time() start = time.time()
ifc2msp = Ifc2Msp() ifc2msp = Ifc2Msp()
ifc2msp.work_schedule = self.file.by_type("IfcWorkSchedule")[0] ifc2msp.work_schedule = self.file.by_type("IfcWorkSchedule")[0]
ifc2msp.xml = self.filepath ifc2msp.xml = bpy.path.ensure_ext(self.filepath, ".xml")
ifc2msp.file = self.file ifc2msp.file = self.file
ifc2msp.holiday_start_date = parser.parse(self.holiday_start_date).date() ifc2msp.holiday_start_date = parser.parse(self.holiday_start_date).date()
ifc2msp.holiday_finish_date = parser.parse(self.holiday_finish_date).date() ifc2msp.holiday_finish_date = parser.parse(self.holiday_finish_date).date()
@@ -1259,7 +1261,7 @@ class ExportP6(bpy.types.Operator, ImportHelper):
self.file = IfcStore.get_file() self.file = IfcStore.get_file()
start = time.time() start = time.time()
ifc2p6 = Ifc2P6() ifc2p6 = Ifc2P6()
ifc2p6.xml = self.filepath ifc2p6.xml = bpy.path.ensure_ext(self.filepath, ".xml")
ifc2p6.file = self.file ifc2p6.file = self.file
ifc2p6.holiday_start_date = parser.parse(self.holiday_start_date).date() ifc2p6.holiday_start_date = parser.parse(self.holiday_start_date).date()
ifc2p6.holiday_finish_date = parser.parse(self.holiday_finish_date).date() ifc2p6.holiday_finish_date = parser.parse(self.holiday_finish_date).date()
@@ -1679,6 +1681,10 @@ class EnableEditingSequenceTimeLag(bpy.types.Operator):
def import_attributes(self, name, prop, data): def import_attributes(self, name, prop, data):
if name == "LagValue": if name == "LagValue":
prop = self.props.time_lag_attributes.add()
prop.name = name
prop.is_null = data[name] is None
prop.is_optional = False
if isinstance(data[name], isodate.Duration): if isinstance(data[name], isodate.Duration):
prop.data_type = "string" prop.data_type = "string"
prop.string_value = ( prop.string_value = (
@@ -17,6 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy import bpy
import isodate
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.util.attribute import ifcopenshell.util.attribute
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
@@ -184,6 +185,16 @@ def updateTaskDuration(self, context):
props = context.scene.BIMWorkScheduleProperties props = context.scene.BIMWorkScheduleProperties
if not props.is_task_update_enabled: if not props.is_task_update_enabled:
return return
if self.duration == "-":
return
try:
isodate.parse_duration(self.duration),
except:
self.duration = "-"
return
self.file = IfcStore.get_file() self.file = IfcStore.get_file()
task = self.file.by_id(self.ifc_definition_id) task = self.file.by_id(self.ifc_definition_id)
if task.TaskTime: if task.TaskTime:
@@ -21,22 +21,30 @@ from . import ui, prop, operator
classes = ( classes = (
operator.AddStyle, operator.AddStyle,
operator.EnableEditingStyle,
operator.DisableEditingStyle, operator.DisableEditingStyle,
operator.DisableEditingStyles,
operator.EditStyle, operator.EditStyle,
operator.EnableEditingStyle,
operator.LoadStyles,
operator.RemoveStyle, operator.RemoveStyle,
operator.UnlinkStyle,
operator.UpdateStyleColours, operator.UpdateStyleColours,
operator.UpdateStyleTextures, operator.UpdateStyleTextures,
operator.UnlinkStyle, prop.Style,
prop.BIMStylesProperties,
prop.BIMStyleProperties, prop.BIMStyleProperties,
ui.BIM_PT_styles,
ui.BIM_PT_style, ui.BIM_PT_style,
ui.BIM_PT_style_attributes, ui.BIM_PT_style_attributes,
ui.BIM_UL_styles,
) )
def register(): def register():
bpy.types.Scene.BIMStylesProperties = bpy.props.PointerProperty(type=prop.BIMStylesProperties)
bpy.types.Material.BIMStyleProperties = bpy.props.PointerProperty(type=prop.BIMStyleProperties) bpy.types.Material.BIMStyleProperties = bpy.props.PointerProperty(type=prop.BIMStyleProperties)
def unregister(): def unregister():
del bpy.types.Scene.BIMStylesProperties
del bpy.types.Material.BIMStyleProperties del bpy.types.Material.BIMStyleProperties
@@ -18,12 +18,33 @@
import bpy import bpy
import blenderbim.tool as tool import blenderbim.tool as tool
import ifcopenshell
def refresh(): def refresh():
StylesData.is_loaded = False
StyleAttributesData.is_loaded = False StyleAttributesData.is_loaded = False
class StylesData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {"style_types": cls.style_types(), "total_styles": cls.total_styles()}
@classmethod
def style_types(cls):
declaration = tool.Ifc.schema().declaration_by_name("IfcPresentationStyle")
declarations = ifcopenshell.util.schema.get_subtypes(declaration)
return [(c, c, "") for c in sorted([d.name() for d in declarations])]
@classmethod
def total_styles(cls):
return len(tool.Ifc.get().by_type("IfcPresentationStyle"))
class StyleAttributesData: class StyleAttributesData:
data = {} data = {}
is_loaded = False is_loaded = False
@@ -59,9 +59,10 @@ class RemoveStyle(bpy.types.Operator, Operator):
bl_idname = "bim.remove_style" bl_idname = "bim.remove_style"
bl_label = "Remove Style" bl_label = "Remove Style"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
style: bpy.props.IntProperty()
def _execute(self, context): def _execute(self, context):
core.remove_style(tool.Ifc, tool.Style, obj=context.active_object.active_material) core.remove_style(tool.Ifc, tool.Material, tool.Style, style=tool.Ifc.get().by_id(self.style))
class AddStyle(bpy.types.Operator, Operator): class AddStyle(bpy.types.Operator, Operator):
@@ -107,3 +108,22 @@ class EditStyle(bpy.types.Operator, Operator):
def _execute(self, context): def _execute(self, context):
core.edit_style(tool.Ifc, tool.Style, obj=context.active_object.active_material) core.edit_style(tool.Ifc, tool.Style, obj=context.active_object.active_material)
class DisableEditingStyles(bpy.types.Operator, Operator):
bl_idname = "bim.disable_editing_styles"
bl_options = {"REGISTER", "UNDO"}
bl_label = "Disable Editing Styles"
def _execute(self, context):
core.disable_editing_styles(tool.Style)
class LoadStyles(bpy.types.Operator, Operator):
bl_idname = "bim.load_styles"
bl_label = "Load Styles"
bl_options = {"REGISTER", "UNDO"}
style_type: bpy.props.StringProperty()
def _execute(self, context):
core.load_styles(tool.Style, style_type=self.style_type)
@@ -19,6 +19,7 @@
import bpy import bpy
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.prop import StrProperty, Attribute from blenderbim.bim.prop import StrProperty, Attribute
from blenderbim.bim.module.style.data import StylesData
from bpy.types import PropertyGroup from bpy.types import PropertyGroup
from bpy.props import ( from bpy.props import (
PointerProperty, PointerProperty,
@@ -32,6 +33,25 @@ from bpy.props import (
) )
def get_style_types(self, context):
if not StylesData.is_loaded:
StylesData.load()
return StylesData.data["style_types"]
class Style(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
total_elements: IntProperty(name="Total Elements")
class BIMStylesProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing")
style_type: EnumProperty(items=get_style_types, name="Style Type")
styles: CollectionProperty(name="Styles", type=Style)
active_style_index: IntProperty(name="Active Style Index")
class BIMStyleProperties(PropertyGroup): class BIMStyleProperties(PropertyGroup):
attributes: CollectionProperty(name="Attributes", type=Attribute) attributes: CollectionProperty(name="Attributes", type=Attribute)
is_editing: BoolProperty(name="Is Editing") is_editing: BoolProperty(name="Is Editing")
@@ -17,9 +17,51 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import blenderbim.bim.helper import blenderbim.bim.helper
from bpy.types import Panel from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.style.data import StyleAttributesData from blenderbim.bim.module.style.data import StylesData, StyleAttributesData
class BIM_PT_styles(Panel):
bl_label = "IFC Styles"
bl_idname = "BIM_PT_styles"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_geometry"
@classmethod
def poll(cls, context):
return IfcStore.get_file()
def draw(self, context):
if not StylesData.is_loaded:
StylesData.load()
self.props = context.scene.BIMStylesProperties
row = self.layout.row(align=True)
row.label(text="{} Styles Found".format(StylesData.data["total_styles"]), icon="MATERIAL")
row = self.layout.row(align=True)
if self.props.is_editing:
row.operator("bim.disable_editing_styles", text="", icon="CANCEL")
else:
row.prop(self.props, "style_type", text="")
row.operator("bim.load_styles", text="", icon="IMPORT").style_type = self.props.style_type
return
row = self.layout.row(align=True)
row.alignment = "RIGHT"
#row.operator("bim.add_presentation_style", text="", icon="ADD")
if self.props.styles and self.props.active_style_index < len(self.props.styles):
style = self.props.styles[self.props.active_style_index]
# op = row.operator("bim.select_by_style", text="", icon="RESTRICT_SELECT_OFF")
# op.style = style.ifc_definition_id
row.operator("bim.remove_style", text="", icon="X").style = style.ifc_definition_id
self.layout.template_list("BIM_UL_styles", "", self.props, "styles", self.props, "active_style_index")
class BIM_PT_style(Panel): class BIM_PT_style(Panel):
@@ -44,7 +86,7 @@ class BIM_PT_style(Panel):
row.operator("bim.update_style_colours", icon="GREASEPENCIL") row.operator("bim.update_style_colours", icon="GREASEPENCIL")
row.operator("bim.update_style_textures", icon="TEXTURE", text="") row.operator("bim.update_style_textures", icon="TEXTURE", text="")
row.operator("bim.unlink_style", icon="UNLINKED", text="") row.operator("bim.unlink_style", icon="UNLINKED", text="")
row.operator("bim.remove_style", icon="X", text="") row.operator("bim.remove_style", icon="X", text="").style = props.ifc_style_id
else: else:
row.operator("bim.add_style", icon="ADD") row.operator("bim.add_style", icon="ADD")
@@ -89,3 +131,13 @@ class BIM_PT_style_attributes(Panel):
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.label(text=attribute["name"]) row.label(text=attribute["name"])
row.label(text=attribute["value"]) row.label(text=attribute["value"])
class BIM_UL_styles(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.label(text=item.name)
#row2 = row.row()
#row2.alignment = "RIGHT"
#row2.label(text=str(item.total_elements))
@@ -20,18 +20,19 @@ import bpy
from . import ui, prop, operator from . import ui, prop, operator
classes = ( classes = (
operator.AssignSceneUnits, operator.AddContextDependentUnit,
operator.AssignUnit, operator.AddConversionBasedUnit,
operator.UnassignUnit,
operator.LoadUnits,
operator.DisableUnitEditingUI,
operator.RemoveUnit,
operator.AddMonetaryUnit, operator.AddMonetaryUnit,
operator.AddSIUnit, operator.AddSIUnit,
operator.AddContextDependentUnit, operator.AssignSceneUnits,
operator.EnableEditingUnit, operator.AssignUnit,
operator.DisableEditingUnit, operator.DisableEditingUnit,
operator.DisableUnitEditingUI,
operator.EditUnit, operator.EditUnit,
operator.EnableEditingUnit,
operator.LoadUnits,
operator.RemoveUnit,
operator.UnassignUnit,
prop.Unit, prop.Unit,
prop.BIMUnitProperties, prop.BIMUnitProperties,
ui.BIM_PT_units, ui.BIM_PT_units,
@@ -16,6 +16,10 @@
# You should have received a copy of the GNU General Public License # 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/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.util.unit
import ifcopenshell.util.schema
import ifcopenshell.util.attribute
import blenderbim.tool as tool import blenderbim.tool as tool
@@ -29,9 +33,32 @@ class UnitsData:
@classmethod @classmethod
def load(cls): def load(cls):
cls.data = {"total_units": cls.get_total_units()} cls.data = {
"unit_classes": cls.unit_classes(),
"named_unit_types": cls.named_unit_types(),
"conversion_unit_types": cls.conversion_unit_types(),
"total_units": cls.get_total_units()
}
cls.is_loaded = True cls.is_loaded = True
@classmethod
def unit_classes(cls):
declarations = ifcopenshell.util.schema.get_subtypes(tool.Ifc.schema().declaration_by_name("IfcNamedUnit"))
results = [(c, c, "") for c in sorted([d.name() for d in declarations])]
results.extend([("IfcDerivedUnit", "IfcDerivedUnit", ""), ("IfcMonetaryUnit", "IfcMonetaryUnit", "")])
return results
@classmethod
def named_unit_types(cls):
values = ifcopenshell.util.attribute.get_enum_items(
tool.Ifc.schema().declaration_by_name("IfcNamedUnit").all_attributes()[1]
)
return [(c, c, "") for c in sorted(values)]
@classmethod
def conversion_unit_types(cls):
return [(u, u, "") for u in ifcopenshell.util.unit.si_conversions.keys()]
@classmethod @classmethod
def get_total_units(cls): def get_total_units(cls):
ifc = tool.Ifc.get() ifc = tool.Ifc.get()
@@ -94,6 +94,16 @@ class RemoveUnit(bpy.types.Operator, Operator):
core.remove_unit(tool.Ifc, tool.Unit, unit=tool.Ifc.get().by_id(self.unit)) core.remove_unit(tool.Ifc, tool.Unit, unit=tool.Ifc.get().by_id(self.unit))
class AddConversionBasedUnit(bpy.types.Operator, Operator):
bl_idname = "bim.add_conversion_based_unit"
bl_label = "Add Conversion Based Unit"
bl_options = {"REGISTER", "UNDO"}
name: bpy.props.StringProperty()
def _execute(self, context):
core.add_conversion_based_unit(tool.Ifc, tool.Unit, name=self.name)
class AddMonetaryUnit(bpy.types.Operator, Operator): class AddMonetaryUnit(bpy.types.Operator, Operator):
bl_idname = "bim.add_monetary_unit" bl_idname = "bim.add_monetary_unit"
bl_label = "Add Monetary Unit" bl_label = "Add Monetary Unit"
@@ -17,11 +17,9 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy import bpy
import ifcopenshell
import ifcopenshell.util.schema
import ifcopenshell.util.attribute
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.prop import StrProperty, Attribute from blenderbim.bim.prop import StrProperty, Attribute
from blenderbim.bim.module.unit.data import UnitsData
from bpy.types import PropertyGroup from bpy.types import PropertyGroup
from bpy.props import ( from bpy.props import (
PointerProperty, PointerProperty,
@@ -35,34 +33,22 @@ from bpy.props import (
) )
unitclasses_enum = [] def get_unit_classes(self, context):
namedunittypes_enum = [] if not UnitsData.is_loaded:
UnitsData.load()
return UnitsData.data["unit_classes"]
def purge(): def get_conversion_unit_types(self, context):
global unitclasses_enum if not UnitsData.is_loaded:
global namedunittypes_enum UnitsData.load()
unitclasses_enum = [] return UnitsData.data["conversion_unit_types"]
namedunittypes_enum = []
def getUnitClasses(self, context): def get_named_unit_types(self, context):
global unitclasses_enum if not UnitsData.is_loaded:
if not len(unitclasses_enum) and IfcStore.get_file(): UnitsData.load()
declarations = ifcopenshell.util.schema.get_subtypes(IfcStore.get_schema().declaration_by_name("IfcNamedUnit")) return UnitsData.data["named_unit_types"]
unitclasses_enum.extend([(c, c, "") for c in sorted([d.name() for d in declarations])])
unitclasses_enum.extend([("IfcDerivedUnit", "IfcDerivedUnit", ""), ("IfcMonetaryUnit", "IfcMonetaryUnit", "")])
return unitclasses_enum
def getNamedUnitTypes(self, context):
global namedunittypes_enum
if not len(namedunittypes_enum) and IfcStore.get_file():
values = ifcopenshell.util.attribute.get_enum_items(
IfcStore.get_schema().declaration_by_name("IfcNamedUnit").all_attributes()[1]
)
namedunittypes_enum.extend([(c, c, "") for c in sorted(values)])
return namedunittypes_enum
class Unit(PropertyGroup): class Unit(PropertyGroup):
@@ -78,6 +64,7 @@ class BIMUnitProperties(PropertyGroup):
units: CollectionProperty(name="Units", type=Unit) units: CollectionProperty(name="Units", type=Unit)
active_unit_index: IntProperty(name="Active Unit Index") active_unit_index: IntProperty(name="Active Unit Index")
active_unit_id: IntProperty(name="Active Unit Id") active_unit_id: IntProperty(name="Active Unit Id")
unit_classes: EnumProperty(items=getUnitClasses, name="Unit Classes") unit_classes: EnumProperty(items=get_unit_classes, name="Unit Classes")
named_unit_types: EnumProperty(items=getNamedUnitTypes, name="Named Unit Types") conversion_unit_types: EnumProperty(items=get_conversion_unit_types, name="Conversion Unit Types")
named_unit_types: EnumProperty(items=get_named_unit_types, name="Named Unit Types")
unit_attributes: CollectionProperty(name="Unit Attributes", type=Attribute) unit_attributes: CollectionProperty(name="Unit Attributes", type=Attribute)
@@ -57,6 +57,10 @@ class BIM_PT_units(Panel):
if self.props.unit_classes == "IfcMonetaryUnit": if self.props.unit_classes == "IfcMonetaryUnit":
row.operator("bim.add_monetary_unit", text="", icon="ADD") row.operator("bim.add_monetary_unit", text="", icon="ADD")
elif self.props.unit_classes in ("IfcConversionBasedUnit", "IfcConversionBasedUnitWithOffset"):
row.prop(self.props, "conversion_unit_types", text="")
op = row.operator("bim.add_conversion_based_unit", text="", icon="ADD")
op.name = self.props.conversion_unit_types
elif self.props.unit_classes == "IfcDerivedUnit": elif self.props.unit_classes == "IfcDerivedUnit":
pass # TODO pass # TODO
elif self.props.unit_classes == "IfcSIUnit": elif self.props.unit_classes == "IfcSIUnit":
@@ -44,6 +44,7 @@ class AddOpening(bpy.types.Operator):
if not element_id: if not element_id:
return {"FINISHED"} return {"FINISHED"}
element = self.file.by_id(element_id) element = self.file.by_id(element_id)
tool.Geometry.clear_cache(element)
if element.is_a("IfcOpeningElement"): if element.is_a("IfcOpeningElement"):
self.report({"WARNING"}, "An IfcOpeningElement can't be voided") self.report({"WARNING"}, "An IfcOpeningElement can't be voided")
return {"FINISHED"} return {"FINISHED"}
@@ -132,6 +133,7 @@ class RemoveOpening(bpy.types.Operator):
should_sync_changes_first=False, should_sync_changes_first=False,
) )
tool.Geometry.clear_cache(tool.Ifc.get_entity(obj))
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id) Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
return {"FINISHED"} return {"FINISHED"}
@@ -49,7 +49,11 @@ class BIM_PT_voids(Panel):
if len(context.selected_objects) == 2: if len(context.selected_objects) == 2:
op = row.operator("bim.add_opening", icon="ADD", text="Add Opening") op = row.operator("bim.add_opening", icon="ADD", text="Add Opening")
for obj in context.selected_objects: for obj in context.selected_objects:
if "IfcOpeningElement" in obj.name or not obj.BIMObjectProperties.ifc_definition_id: if (
"IfcOpeningElement" in obj.name
or "IfcOpeningStandardCase" in obj.name
or not obj.BIMObjectProperties.ifc_definition_id
):
op.opening = obj.name op.opening = obj.name
elif len(obj.children) == 1 and not obj.children[0].BIMObjectProperties.ifc_definition_id: elif len(obj.children) == 1 and not obj.children[0].BIMObjectProperties.ifc_definition_id:
op.opening = obj.children[0].name op.opening = obj.children[0].name
+38
View File
@@ -22,6 +22,7 @@ import json
import webbrowser import webbrowser
import ifcopenshell import ifcopenshell
import blenderbim.bim.handler import blenderbim.bim.handler
import blenderbim.tool as tool
from . import schema from . import schema
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.ui import IFCFileSelector from blenderbim.bim.ui import IFCFileSelector
@@ -39,6 +40,43 @@ class OpenUri(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
class SelectURIAttribute(bpy.types.Operator):
bl_idname = "bim.select_uri_attribute"
bl_label = "Select URI Attribute"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Select a local file"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
data_path: bpy.props.StringProperty(name="Data Path")
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
def execute(self, context):
# data_path contains the latter half of the path to the string_value property
# I have no idea how to find out the former half, so let's just use brute force.
data_path = self.data_path.replace(".string_value", "")
attribute = None
try:
attribute = eval(f"bpy.context.scene.{data_path}")
except:
try:
attribute = eval(f"bpy.context.active_object.{data_path}")
except:
try:
attribute = eval(f"bpy.context.active_object.active_material.{data_path}")
except:
# Do you know a better way?
pass
if attribute:
filepath = self.filepath
if self.use_relative_path:
filepath = os.path.relpath(filepath, os.path.dirname(tool.Ifc.get_path()))
attribute.string_value = filepath
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class SelectIfcFile(bpy.types.Operator, IFCFileSelector): class SelectIfcFile(bpy.types.Operator, IFCFileSelector):
bl_idname = "bim.select_ifc_file" bl_idname = "bim.select_ifc_file"
bl_label = "Select IFC File" bl_label = "Select IFC File"
+3 -2
View File
@@ -190,10 +190,11 @@ class Attribute(PropertyGroup):
bool_value: BoolProperty(name="Value", update=updateAttributeValue) bool_value: BoolProperty(name="Value", update=updateAttributeValue)
int_value: IntProperty(name="Value", update=updateAttributeValue) int_value: IntProperty(name="Value", update=updateAttributeValue)
float_value: FloatProperty(name="Value", update=updateAttributeValue) float_value: FloatProperty(name="Value", update=updateAttributeValue)
is_null: BoolProperty(name="Is Null")
is_optional: BoolProperty(name="Is Optional")
enum_items: StringProperty(name="Value") enum_items: StringProperty(name="Value")
enum_value: EnumProperty(items=getAttributeEnumValues, name="Value", update=updateAttributeValue) enum_value: EnumProperty(items=getAttributeEnumValues, name="Value", update=updateAttributeValue)
is_null: BoolProperty(name="Is Null")
is_optional: BoolProperty(name="Is Optional")
is_uri: BoolProperty(name="Is Uri", default=False)
def get_value(self): def get_value(self):
if self.is_null: if self.is_null:
+1 -1
View File
@@ -233,7 +233,7 @@ class BIM_PT_geometry(Panel):
class BIM_PT_4D5D(Panel): class BIM_PT_4D5D(Panel):
bl_label = "IFC 4D/5D" bl_label = "IFC Costing and Scheduling"
bl_space_type = "PROPERTIES" bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW" bl_region_type = "WINDOW"
bl_context = "scene" bl_context = "scene"
+60 -44
View File
@@ -17,16 +17,31 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
def load_information(document): def load_project_documents(document):
document.import_information() document.clear_document_tree()
document.enable_information_editing_ui() document.import_project_documents()
document.disable_editing_document() document.clear_breadcrumbs()
document.enable_editing_ui()
def load_references(document): def load_document(document_tool, document=None):
document.import_references() document_tool.clear_document_tree()
document.enable_reference_editing_ui() document_tool.import_subdocuments(document)
document.disable_editing_document() document_tool.import_references(document)
document_tool.disable_editing_document()
document_tool.add_breadcrumb(document)
def load_parent_document(document):
document.clear_document_tree()
document.remove_latest_breadcrumb()
parent = document.get_active_breadcrumb()
if parent:
document.import_subdocuments(parent)
document.import_references(parent)
document.disable_editing_document()
else:
document.import_project_documents()
def disable_document_editing_ui(document): def disable_document_editing_ui(document):
@@ -44,52 +59,53 @@ def disable_editing_document(document):
def add_information(ifc, document): def add_information(ifc, document):
result = ifc.run("document.add_information") document.clear_document_tree()
document.import_information() parent = document.get_active_breadcrumb()
document.import_document_attributes(result) information = ifc.run("document.add_information", parent=parent)
document.set_active_document(result) ifc.run("document.add_reference", information=information)
if parent:
document.import_subdocuments(parent)
document.import_references(parent)
else:
document.import_project_documents()
def add_reference(ifc, document): def add_reference(ifc, document):
result = ifc.run("document.add_reference") parent = document.get_active_breadcrumb()
document.import_references() ifc.run("document.add_reference", information=parent)
document.import_document_attributes(result) document.clear_document_tree()
document.set_active_document(result) document.import_subdocuments(parent)
document.import_references(parent)
def edit_information(ifc, document, information=None): def edit_document(ifc, document_tool, document=None):
attributes = document.export_document_attributes() attributes = document_tool.export_document_attributes()
ifc.run("document.edit_information", information=information, attributes=attributes) if document_tool.is_document_information(document):
document.disable_editing_document() ifc.run("document.edit_information", information=document, attributes=attributes)
document.import_information() else:
ifc.run("document.edit_reference", reference=document, attributes=attributes)
document_tool.disable_editing_document()
def edit_reference(ifc, document, reference=None): document_tool.clear_document_tree()
attributes = document.export_document_attributes() parent = document_tool.get_active_breadcrumb()
ifc.run("document.edit_reference", reference=reference, attributes=attributes) if parent:
document.disable_editing_document() document_tool.import_subdocuments(parent)
document.import_references() document_tool.import_references(parent)
else:
document_tool.import_project_documents()
def remove_document(ifc, document_tool, document=None): def remove_document(ifc, document_tool, document=None):
document_tool.clear_document_tree()
if document_tool.is_document_information(document): if document_tool.is_document_information(document):
ifc.run("document.remove_document", document=document) ifc.run("document.remove_information", information=document)
document_tool.import_information()
else: else:
ifc.run("document.remove_document", document=document) ifc.run("document.remove_reference", reference=document)
document_tool.import_references() parent = document_tool.get_active_breadcrumb()
document_tool.disable_editing_document() if parent:
document_tool.import_subdocuments(parent)
document_tool.import_references(parent)
def enable_assigning_document(document, obj=None): else:
document.import_references() document_tool.import_project_documents()
document.enable_reference_editing_ui()
document.disable_editing_document()
document.enable_document_assignment_ui(obj)
def disable_assigning_document(document, obj=None):
document.disable_document_assignment_ui(obj)
def assign_document(ifc, product=None, document=None): def assign_document(ifc, product=None, document=None):
+47 -2
View File
@@ -85,7 +85,7 @@ def open_sheet(drawing, sheet=None):
def remove_sheet(ifc, drawing, sheet=None): def remove_sheet(ifc, drawing, sheet=None):
ifc.run("document.remove_document", document=sheet) ifc.run("document.remove_information", information=sheet)
drawing.import_sheets() drawing.import_sheets()
@@ -99,7 +99,7 @@ def disable_editing_drawings(drawing):
def add_drawing(ifc, collector, drawing, target_view=None, location_hint=None): def add_drawing(ifc, collector, drawing, target_view=None, location_hint=None):
drawing_name = drawing.ensure_unique_drawing_name("UNTITLED") 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) drawing_matrix = drawing.generate_drawing_matrix(target_view, location_hint)
camera = drawing.create_camera(drawing_name, drawing_matrix) camera = drawing.create_camera(drawing_name, drawing_matrix)
element = drawing.run_root_assign_class( element = drawing.run_root_assign_class(
@@ -158,3 +158,48 @@ def add_annotation(ifc, collector, drawing_tool, drawing=None, object_type=None)
ifc.run("group.assign_group", group=drawing_tool.get_drawing_group(drawing), product=element) ifc.run("group.assign_group", group=drawing_tool.get_drawing_group(drawing), product=element)
collector.assign(obj) collector.assign(obj)
drawing_tool.enable_editing(obj) drawing_tool.enable_editing(obj)
def sync_references(ifc, collector, drawing_tool, drawing=None):
context = drawing_tool.get_annotation_context(drawing_tool.get_drawing_target_view(drawing))
if not context:
return
group = drawing_tool.get_drawing_group(drawing)
for reference_element in drawing_tool.get_potential_reference_elements(drawing):
reference_obj = ifc.get_object(reference_element)
annotation = drawing_tool.get_drawing_reference_annotation(drawing, reference_element)
should_delete_existing_annotation = False
should_create_annotation = False
if annotation and (
not reference_obj
or ifc.is_moved(reference_obj)
or ifc.is_edited(reference_obj)
or ifc.is_deleted(reference_element)
or ifc.is_deleted(annotation)
):
should_delete_existing_annotation = True
if reference_obj and (should_delete_existing_annotation or not annotation):
should_create_annotation = True
if should_delete_existing_annotation:
annotation_obj = ifc.get_object(annotation)
if annotation_obj:
drawing_tool.delete_object(annotation_obj)
ifc.run("root.remove_product", product=annotation)
if should_create_annotation:
annotation = drawing_tool.generate_reference_annotation(drawing, reference_element, context)
if annotation:
ifc.run("drawing.assign_product", relating_product=reference_element, related_object=annotation)
ifc.run("group.assign_group", group=group, product=annotation)
collector.assign(ifc.get_object(annotation))
if reference_obj and ifc.is_moved(reference_obj):
drawing_tool.sync_object_placement(reference_obj)
if reference_obj and ifc.is_edited(reference_obj):
drawing_tool.sync_object_representation(reference_obj)
+49 -4
View File
@@ -21,7 +21,52 @@ def unlink_material(ifc, obj=None):
ifc.unlink(obj=obj) ifc.unlink(obj=obj)
def add_default_material(ifc, material): def add_material(ifc, material, style, obj=None):
obj = material.add_default_material_object() if not obj:
ifc.link(ifc.run("material.add_material", name="Default"), obj) obj = material.add_default_material_object()
return obj ifc_material = ifc.run("material.add_material", name=material.get_name(obj))
ifc.link(ifc_material, obj)
ifc_style = style.get_style(obj)
if ifc_style:
context = style.get_context(obj)
if context:
ifc.run("style.assign_material_style", material=ifc_material, style=ifc_style, context=context)
if material.is_editing_materials():
material.import_material_definitions(material.get_active_material_type())
return ifc_material
def add_material_set(ifc, material, set_type=None):
ifc_material = ifc.run("material.add_material_set", name="Unnamed", set_type=set_type)
if material.is_editing_materials():
material.import_material_definitions(material.get_active_material_type())
return ifc_material
def remove_material(ifc, material_tool, style, material=None):
obj = ifc.get_object(material)
ifc.unlink(element=material)
ifc.run("material.remove_material", material=material)
if obj and not style.get_style(obj):
material_tool.delete_object(obj)
if material_tool.is_editing_materials():
material_tool.import_material_definitions(material_tool.get_active_material_type())
def remove_material_set(ifc, material_tool, material=None):
ifc.run("material.remove_material_set", material=material)
if material_tool.is_editing_materials():
material_tool.import_material_definitions(material_tool.get_active_material_type())
def load_materials(material, material_type=None):
material.import_material_definitions(material_type)
material.enable_editing_materials()
def disable_editing_materials(material):
material.disable_editing_materials()
def select_by_material(material_tool, material=None):
material_tool.select_elements(material_tool.get_elements_by_material(material))
+17 -4
View File
@@ -33,10 +33,14 @@ def add_style(ifc, style, obj=None):
return element return element
def remove_style(ifc, style, obj=None): def remove_style(ifc, material, style_tool, style=None):
element = style.get_style(obj) obj = ifc.get_object(style)
ifc.unlink(obj=obj, element=element) ifc.unlink(obj=obj, element=style)
ifc.run("style.remove_style", style=element) ifc.run("style.remove_style", style=style)
if obj and not ifc.get_entity(obj):
material.delete_object(obj)
if style_tool.is_editing_styles():
style_tool.import_presentation_styles(style_tool.get_active_style_type())
def update_style_colours(ifc, style, obj=None): def update_style_colours(ifc, style, obj=None):
@@ -97,3 +101,12 @@ def edit_style(ifc, style, obj=None):
attributes = style.export_surface_attributes(obj) attributes = style.export_surface_attributes(obj)
ifc.run("style.edit_presentation_style", style=style.get_style(obj), attributes=attributes) ifc.run("style.edit_presentation_style", style=style.get_style(obj), attributes=attributes)
style.disable_editing(obj) style.disable_editing(obj)
def load_styles(style, style_type=None):
style.import_presentation_styles(style_type)
style.enable_editing_styles()
def disable_editing_styles(style):
style.disable_editing_styles()
+39
View File
@@ -133,6 +133,25 @@ class Debug:
def purge_hdf5_cache(cls): pass def purge_hdf5_cache(cls): pass
@interface
class Document:
def add_breadcrumb(cls, document): pass
def clear_breadcrumbs(cls): pass
def clear_document_tree(cls): pass
def disable_editing_document(cls): pass
def disable_editing_ui(cls): pass
def enable_editing_ui(cls): pass
def export_document_attributes(cls): pass
def get_active_breadcrumb(cls): pass
def import_document_attributes(cls, document): pass
def import_project_documents(cls): pass
def import_references(cls, document): pass
def import_subdocuments(cls, document): pass
def is_document_information(cls, document): pass
def remove_latest_breadcrumb(cls): pass
def set_active_document(cls, document): pass
@interface @interface
class Drawing: class Drawing:
def create_annotation_object(cls, object_type): pass def create_annotation_object(cls, object_type): pass
@@ -140,6 +159,7 @@ class Drawing:
def create_svg_sheet(cls, document, titleblock): pass def create_svg_sheet(cls, document, titleblock): pass
def delete_collection(cls, collection): pass def delete_collection(cls, collection): pass
def delete_drawing_elements(cls, elements): pass def delete_drawing_elements(cls, elements): pass
def delete_object(cls, obj): pass
def disable_editing_drawings(cls): pass def disable_editing_drawings(cls): pass
def disable_editing_sheets(cls): pass def disable_editing_sheets(cls): pass
def disable_editing_text(cls, obj): pass def disable_editing_text(cls, obj): pass
@@ -153,6 +173,7 @@ class Drawing:
def ensure_unique_identification(cls, identification): pass def ensure_unique_identification(cls, identification): pass
def export_text_literal_attributes(cls, obj): pass def export_text_literal_attributes(cls, obj): pass
def generate_drawing_matrix(cls, target_view, location_hint): pass def generate_drawing_matrix(cls, target_view, location_hint): pass
def generate_drawing_name(cls, target_view, location_hint): pass
def generate_sheet_identification(cls): pass def generate_sheet_identification(cls): pass
def get_annotation_context(cls, target_view): pass def get_annotation_context(cls, target_view): pass
def get_body_context(cls): pass def get_body_context(cls): pass
@@ -173,6 +194,7 @@ class Drawing:
def run_root_assign_class(cls, obj=None, ifc_class=None, predefined_type=None, should_add_representation=True, context=None, ifc_representation_class=None): pass def run_root_assign_class(cls, obj=None, ifc_class=None, predefined_type=None, should_add_representation=True, context=None, ifc_representation_class=None): pass
def set_drawing_collection_name(cls, group, collection): pass def set_drawing_collection_name(cls, group, collection): pass
def show_decorations(cls): pass def show_decorations(cls): pass
def sync_object_placement(cls, obj): pass
def update_text_value(cls, obj): pass def update_text_value(cls, obj): pass
@@ -227,6 +249,9 @@ class Ifc:
def get_entity(cls, obj): pass def get_entity(cls, obj): pass
def get_object(cls, entity): pass def get_object(cls, entity): pass
def get_schema(cls): pass def get_schema(cls): pass
def is_deleted(cls, element): pass
def is_edited(cls, obj): pass
def is_moved(cls, obj): pass
def link(cls, element, obj): pass def link(cls, element, obj): pass
def run(cls, command, **kwargs): pass def run(cls, command, **kwargs): pass
def set(cls, ifc): pass def set(cls, ifc): pass
@@ -251,6 +276,15 @@ class Library:
@interface @interface
class Material: class Material:
def add_default_material_object(cls): pass def add_default_material_object(cls): pass
def delete_object(cls, obj): pass
def disable_editing_materials(cls): pass
def enable_editing_materials(cls): pass
def get_active_material_type(cls): pass
def get_elements_by_material(cls, material): pass
def get_name(cls, obj): pass
def import_material_definitions(cls, material_type): pass
def is_editing_materials(cls): pass
def select_elements(cls, elements): pass
@interface @interface
@@ -398,8 +432,11 @@ class Structural:
class Style: class Style:
def can_support_rendering_style(cls, obj): pass def can_support_rendering_style(cls, obj): pass
def disable_editing(cls, obj): pass def disable_editing(cls, obj): pass
def disable_editing_styles(cls): pass
def enable_editing(cls, obj): pass def enable_editing(cls, obj): pass
def enable_editing_styles(cls): pass
def export_surface_attributes(cls, obj): pass def export_surface_attributes(cls, obj): pass
def get_active_style_type(cls): pass
def get_context(cls, obj): pass def get_context(cls, obj): pass
def get_name(cls, obj): pass def get_name(cls, obj): pass
def get_style(cls, obj): pass def get_style(cls, obj): pass
@@ -409,7 +446,9 @@ class Style:
def get_surface_shading_style(cls, obj): pass def get_surface_shading_style(cls, obj): pass
def get_surface_texture_style(cls, obj): pass def get_surface_texture_style(cls, obj): pass
def get_uv_maps(cls, representation): pass def get_uv_maps(cls, representation): pass
def import_presentation_styles(cls, style_type): pass
def import_surface_attributes(cls, style, obj): pass def import_surface_attributes(cls, style, obj): pass
def is_editing_styles(cls): pass
@interface @interface
+9 -1
View File
@@ -32,7 +32,9 @@ def assign_scene_units(ifc, unit):
areaunit = ifc.run("unit.add_conversion_based_unit", name=area_name) areaunit = ifc.run("unit.add_conversion_based_unit", name=area_name)
volumeunit = ifc.run("unit.add_conversion_based_unit", name=volume_name) volumeunit = ifc.run("unit.add_conversion_based_unit", name=volume_name)
ifc.run("unit.assign_unit", units=[lengthunit, areaunit, volumeunit]) planeangleunit = ifc.run("unit.add_conversion_based_unit", name="degree")
ifc.run("unit.assign_unit", units=[lengthunit, areaunit, volumeunit, planeangleunit])
def assign_unit(ifc, unit_tool, unit=None): def assign_unit(ifc, unit_tool, unit=None):
@@ -77,6 +79,12 @@ def add_context_dependent_unit(ifc, unit, unit_type=None, name=None):
return result return result
def add_conversion_based_unit(ifc, unit, name=None):
result = ifc.run("unit.add_conversion_based_unit", name=name)
unit.import_units()
return result
def enable_editing_unit(unit_tool, unit=None): def enable_editing_unit(unit_tool, unit=None):
unit_tool.set_active_unit(unit) unit_tool.set_active_unit(unit)
unit_tool.import_unit_attributes(unit) unit_tool.import_unit_attributes(unit)
+69 -30
View File
@@ -25,8 +25,20 @@ from blenderbim.bim import import_ifc
class Document(blenderbim.core.tool.Document): class Document(blenderbim.core.tool.Document):
@classmethod @classmethod
def disable_document_assignment_ui(cls, obj): def add_breadcrumb(cls, document):
obj.BIMObjectDocumentProperties.is_adding = "" props = bpy.context.scene.BIMDocumentProperties
new = props.breadcrumbs.add()
new.name = str(document.id())
@classmethod
def clear_breadcrumbs(cls):
props = bpy.context.scene.BIMDocumentProperties
props.breadcrumbs.clear()
@classmethod
def clear_document_tree(cls):
props = bpy.context.scene.BIMDocumentProperties
props.documents.clear()
@classmethod @classmethod
def disable_editing_document(cls): def disable_editing_document(cls):
@@ -34,24 +46,22 @@ class Document(blenderbim.core.tool.Document):
@classmethod @classmethod
def disable_editing_ui(cls): def disable_editing_ui(cls):
bpy.context.scene.BIMDocumentProperties.is_editing = "" bpy.context.scene.BIMDocumentProperties.is_editing = False
@classmethod @classmethod
def enable_document_assignment_ui(cls, obj): def enable_editing_ui(cls):
obj.BIMObjectDocumentProperties.is_adding = "IfcDocumentReference" bpy.context.scene.BIMDocumentProperties.is_editing = True
@classmethod
def enable_information_editing_ui(cls):
bpy.context.scene.BIMDocumentProperties.is_editing = "information"
@classmethod
def enable_reference_editing_ui(cls):
bpy.context.scene.BIMDocumentProperties.is_editing = "reference"
@classmethod @classmethod
def export_document_attributes(cls): def export_document_attributes(cls):
return blenderbim.bim.helper.export_attributes(bpy.context.scene.BIMDocumentProperties.document_attributes) return blenderbim.bim.helper.export_attributes(bpy.context.scene.BIMDocumentProperties.document_attributes)
@classmethod
def get_active_breadcrumb(cls):
props = bpy.context.scene.BIMDocumentProperties
if len(props.breadcrumbs):
return tool.Ifc.get().by_id(int(props.breadcrumbs[-1].name))
@classmethod @classmethod
def import_document_attributes(cls, document): def import_document_attributes(cls, document):
props = bpy.context.scene.BIMDocumentProperties props = bpy.context.scene.BIMDocumentProperties
@@ -59,35 +69,64 @@ class Document(blenderbim.core.tool.Document):
blenderbim.bim.helper.import_attributes2(document, props.document_attributes) blenderbim.bim.helper.import_attributes2(document, props.document_attributes)
@classmethod @classmethod
def import_information(cls): def import_project_documents(cls):
props = bpy.context.scene.BIMDocumentProperties props = bpy.context.scene.BIMDocumentProperties
props.documents.clear() props.documents.clear()
for element in tool.Ifc.get().by_type("IfcDocumentInformation"): project = tool.Ifc.get().by_type("IfcProject")[0]
new = props.documents.add() for rel in project.HasAssociations or []:
new.ifc_definition_id = element.id() if rel.is_a("IfcRelAssociatesDocument") and rel.RelatingDocument.is_a("IfcDocumentInformation"):
new.name = element.Name or "Unnamed" element = rel.RelatingDocument
if tool.Ifc.get_schema() == "IFC2X3": new = props.documents.add()
new.identification = element.DocumentId or "*" new.ifc_definition_id = element.id()
else: new.name = element.Name or "Unnamed"
new.identification = element.Identification or "*" new.is_information = True
if tool.Ifc.get_schema() == "IFC2X3":
new.identification = element.DocumentId or "*"
else:
new.identification = element.Identification or "*"
@classmethod @classmethod
def import_references(cls): def import_references(cls, document):
props = bpy.context.scene.BIMDocumentProperties props = bpy.context.scene.BIMDocumentProperties
props.documents.clear() if tool.Ifc.get_schema() == "IFC2X3":
for element in tool.Ifc.get().by_type("IfcDocumentReference"): for element in document.DocumentReferences or []:
new = props.documents.add() new = props.documents.add()
new.ifc_definition_id = element.id() new.ifc_definition_id = element.id()
new.name = element.Name or "Unnamed" new.name = element.Name or "Unnamed"
if tool.Ifc.get_schema() == "IFC2X3":
new.identification = element.ItemReference or "*" new.identification = element.ItemReference or "*"
else: new.is_information = False
else:
for element in document.HasDocumentReferences:
new = props.documents.add()
new.ifc_definition_id = element.id()
new.name = element.Name or "Unnamed"
new.identification = element.Identification or "*" new.identification = element.Identification or "*"
new.is_information = False
@classmethod
def import_subdocuments(cls, document):
props = bpy.context.scene.BIMDocumentProperties
if document.IsPointer:
for element in document.IsPointer[0].RelatedDocuments or []:
new = props.documents.add()
new.ifc_definition_id = element.id()
new.name = element.Name or "Unnamed"
new.is_information = True
if tool.Ifc.get_schema() == "IFC2X3":
new.identification = element.DocumentId or "*"
else:
new.identification = element.Identification or "*"
@classmethod @classmethod
def is_document_information(cls, document): def is_document_information(cls, document):
return document.is_a("IfcDocumentInformation") return document.is_a("IfcDocumentInformation")
@classmethod
def remove_latest_breadcrumb(cls):
props = bpy.context.scene.BIMDocumentProperties
if len(props.breadcrumbs):
props.breadcrumbs.remove(len(props.breadcrumbs) - 1)
@classmethod @classmethod
def set_active_document(cls, document): def set_active_document(cls, document):
bpy.context.scene.BIMDocumentProperties.active_document_id = document.id() bpy.context.scene.BIMDocumentProperties.active_document_id = document.id()
+373 -2
View File
@@ -19,13 +19,18 @@
import os import os
import re import re
import bpy import bpy
import math
import bmesh
import mathutils import mathutils
import webbrowser import webbrowser
import numpy as np
import blenderbim.core.tool import blenderbim.core.tool
import blenderbim.core.geometry
import blenderbim.tool as tool import blenderbim.tool as tool
import ifcopenshell.util.representation import ifcopenshell.util.representation
import blenderbim.bim.module.drawing.sheeter as sheeter import blenderbim.bim.module.drawing.sheeter as sheeter
import blenderbim.bim.module.drawing.annotation as annotation import blenderbim.bim.module.drawing.annotation as annotation
import blenderbim.bim.module.drawing.helper as helper
class Drawing(blenderbim.core.tool.Drawing): class Drawing(blenderbim.core.tool.Drawing):
@@ -83,6 +88,10 @@ class Drawing(blenderbim.core.tool.Drawing):
if obj: if obj:
bpy.data.objects.remove(obj) bpy.data.objects.remove(obj)
@classmethod
def delete_object(cls, obj):
bpy.data.objects.remove(obj)
@classmethod @classmethod
def disable_editing_drawings(cls): def disable_editing_drawings(cls):
bpy.context.scene.DocProperties.is_editing_drawings = False bpy.context.scene.DocProperties.is_editing_drawings = False
@@ -260,19 +269,49 @@ class Drawing(blenderbim.core.tool.Drawing):
new = bpy.context.scene.DocProperties.drawings.add() new = bpy.context.scene.DocProperties.drawings.add()
new.ifc_definition_id = drawing.id() new.ifc_definition_id = drawing.id()
new.name = drawing.Name or "Unnamed" new.name = drawing.Name or "Unnamed"
new.target_view = cls.get_drawing_target_view(drawing)
@classmethod @classmethod
def import_sheets(cls): def import_sheets(cls):
bpy.context.scene.DocProperties.sheets.clear() props = bpy.context.scene.DocProperties
expanded_sheets = {s.ifc_definition_id for s in props.sheets if s.is_expanded}
props.sheets.clear()
sheets = [d for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "DOCUMENTATION"] sheets = [d for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "DOCUMENTATION"]
for sheet in sheets: for sheet in sheets:
new = bpy.context.scene.DocProperties.sheets.add() new = props.sheets.add()
new.ifc_definition_id = sheet.id() new.ifc_definition_id = sheet.id()
if tool.Ifc.get_schema() == "IFC2X3": if tool.Ifc.get_schema() == "IFC2X3":
new.identification = sheet.DocumentId new.identification = sheet.DocumentId
else: else:
new.identification = sheet.Identification new.identification = sheet.Identification
new.name = sheet.Name new.name = sheet.Name
new.is_sheet = True
new.is_expanded = sheet.id() in expanded_sheets
if not new.is_expanded:
continue
if tool.Ifc.get_schema() == "IFC2X3":
references = sheet.DocumentReferences or []
else:
references = sheet.HasDocumentReferences or []
for reference in references:
new = props.sheets.add()
new.ifc_definition_id = reference.id()
new.is_sheet = False
if tool.Ifc.get_schema() == "IFC2X3":
new.identification = reference.ItemReference or "X"
element = [
r for r in tool.Ifc.by_type("IfcRelAssociatesDocument") if r.RelatingDocument == reference
][0].RelatedObjects[0]
else:
new.identification = reference.Identification or "X"
element = reference.DocumentRefForObjects[0].RelatedObjects[0]
new.name = element.Name
new.reference_type = "DRAWING"
@classmethod @classmethod
def import_text_attributes(cls, obj): def import_text_attributes(cls, obj):
@@ -349,3 +388,335 @@ class Drawing(blenderbim.core.tool.Drawing):
for variable in re.findall("{{.*?}}", value): for variable in re.findall("{{.*?}}", value):
value = value.replace(variable, selector.get_element_value(product, variable[2:-2]) or "") value = value.replace(variable, selector.get_element_value(product, variable[2:-2]) or "")
obj.BIMTextProperties.value = value obj.BIMTextProperties.value = value
# TODO below this point is highly experimental prototype code with no tests
@classmethod
def generate_drawing_name(cls, target_view, location_hint):
if target_view in ("PLAN_VIEW", "REFLECTED_PLAN_VIEW") and location_hint:
location = tool.Ifc.get().by_id(location_hint)
if target_view == "REFLECTED_PLAN_VIEW":
target_view = "RCP_VIEW"
return (location.Name or "UNNAMED").upper() + " " + target_view.split("_")[0]
elif target_view in ("SECTION_VIEW", "ELEVATION_VIEW") and location_hint:
return location_hint + " " + target_view.split("_")[0]
return target_view
@classmethod
def get_potential_reference_elements(cls, drawing):
elements = []
existing_references = cls.get_group_elements(cls.get_drawing_group(drawing))
for element in tool.Ifc.get().by_type("IfcAnnotation"):
if element in existing_references or element == drawing:
continue
if element.ObjectType == "DRAWING":
psets = ifcopenshell.util.element.get_psets(element)
if psets.get("EPset_Drawing", {}).get("TargetView", None) in ("SECTION_VIEW", "ELEVATION_VIEW"):
elements.append(element)
for element in tool.Ifc.get().by_type("IfcGridAxis"):
elements.append(element)
return elements
@classmethod
def get_drawing_reference_annotation(cls, drawing, reference_element):
if drawing == reference_element:
return True
for element in cls.get_group_elements(cls.get_drawing_group(drawing)):
if element.is_a("IfcAnnotation"):
for rel in element.HasAssignments:
if rel.is_a("IfcRelAssignsToProduct"):
if rel.RelatingProduct == reference_element:
return element
# We cannot associate IfcGridAxis directly, so we establish a convention:
# IfcRelAssignsToProduct.RelatingProduct = IfcGrid
# IfcRelAssignsToProduct.Name = IfcGridAxis.AxisTag
elif reference_element.is_a("IfcGridAxis") and rel.Name == reference_element.AxisTag:
return element
@classmethod
def generate_reference_annotation(cls, drawing, reference_element, context):
if reference_element.is_a("IfcGridAxis"):
return cls.generate_grid_axis_reference_annotation(drawing, reference_element, context)
elif reference_element.is_a("IfcAnnotation") and reference_element.ObjectType == "DRAWING":
psets = ifcopenshell.util.element.get_psets(reference_element)
target_view = psets.get("EPset_Drawing", {}).get("TargetView", None)
if target_view == "ELEVATION_VIEW":
return cls.generate_elevation_reference_annotation(drawing, reference_element, context)
elif target_view == "SECTION_VIEW":
return cls.generate_section_reference_annotation(drawing, reference_element, context)
@classmethod
def generate_section_reference_annotation(cls, drawing, reference_element, context):
reference_obj = tool.Ifc.get_object(reference_element)
reference_obj.matrix_world
camera = tool.Ifc.get_object(drawing)
bounds = helper.ortho_view_frame(camera.data) if camera.data.type == "ORTHO" else None
def to_camera_coords(camera, reference_obj):
mat = reference_obj.matrix_world.copy()
xyz = camera.matrix_world.inverted() @ reference_obj.matrix_world.translation
xyz[2] = 0
xyz = camera.matrix_world @ xyz
mat[0][3] = xyz[0]
mat[1][3] = xyz[1]
mat[2][3] = xyz[2]
annotation_offset = mathutils.Vector((0, 0, -camera.data.clip_start - 0.05))
annotation_offset = camera.matrix_world.to_quaternion() @ annotation_offset
mat[0][3] += annotation_offset[0]
mat[1][3] += annotation_offset[1]
mat[2][3] += annotation_offset[2]
return mat
def clip_to_camera_boundary(mesh, bounds):
mesh.verts.ensure_lookup_table()
points = [v.co for v in mesh.verts[0:2]]
points = helper.clip_segment(bounds, points)
if points is None:
return None
mesh.verts[0].co = points[0]
mesh.verts[1].co = points[1]
return mesh
if cls.is_perpendicular(camera, reference_obj) and cls.is_intersecting(camera, reference_obj):
reference_mesh = cls.get_camera_block(reference_obj)
obj_matrix = to_camera_coords(camera, reference_obj)
# The reference mesh vertices represent a view cube. To convert this into a section line we:
# 1. Select the 4 +Z vertices local to the reference element. This is the cutting plane.
verts_local_to_reference = [reference_obj.matrix_world.inverted() @ v for v in reference_mesh["verts"]]
cutting_plane_verts = sorted(verts_local_to_reference, key=lambda x: x.z)[-4:]
global_cutting_plane_verts = [reference_obj.matrix_world @ v for v in cutting_plane_verts]
# 2. Project the cutting plane onto our viewing camera.
verts_local_to_camera = [camera.matrix_world.inverted() @ v for v in global_cutting_plane_verts]
# 3. Collapse verts with the same XY coords, and set Z to be just below the clip_start so it's visible
collapsed_verts = []
for vert in verts_local_to_camera:
if not [True for v in collapsed_verts if (vert.xy - v.xy).length < 1e-2]:
collapsed_verts.append(mathutils.Vector((vert.x, vert.y, -camera.data.clip_start - 0.05)))
# 4. The first two vertices is the section line
section_line = collapsed_verts[0:2]
global_section_line = [camera.matrix_world @ v for v in section_line]
local_section_line = [obj_matrix.inverted() @ v for v in global_section_line]
mesh = bpy.data.meshes.new(name="Annotation")
mesh.from_pydata(local_section_line, [(0, 1)], [])
bm = bmesh.new()
bm.from_mesh(mesh)
bm = clip_to_camera_boundary(bm, bounds)
bm.to_mesh(mesh)
bm.free()
obj = bpy.data.objects.new(reference_obj.name, mesh)
obj.matrix_world = obj_matrix
element = cls.run_root_assign_class(
obj=obj,
ifc_class="IfcAnnotation",
predefined_type="SECTION",
should_add_representation=True,
context=context,
ifc_representation_class=None,
)
return element
@classmethod
def generate_elevation_reference_annotation(cls, drawing, reference_element, context):
reference_obj = tool.Ifc.get_object(reference_element)
reference_obj.matrix_world
camera = tool.Ifc.get_object(drawing)
def to_camera_coords(camera, reference_obj):
mat = reference_obj.matrix_world.copy()
xyz = camera.matrix_world.inverted() @ reference_obj.matrix_world.translation
xyz[2] = 0
xyz = camera.matrix_world @ xyz
mat[0][3] = xyz[0]
mat[1][3] = xyz[1]
mat[2][3] = xyz[2]
annotation_offset = mathutils.Vector((0, 0, -camera.data.clip_start - 0.05))
annotation_offset = camera.matrix_world.to_quaternion() @ annotation_offset
mat[0][3] += annotation_offset[0]
mat[1][3] += annotation_offset[1]
mat[2][3] += annotation_offset[2]
return mat
if cls.is_perpendicular(camera, reference_obj) and cls.is_intersecting(camera, reference_obj):
obj = bpy.data.objects.new(reference_obj.name, None)
obj.empty_display_size = 0.1
obj.matrix_world = to_camera_coords(camera, reference_obj)
element = cls.run_root_assign_class(
obj=obj,
ifc_class="IfcAnnotation",
predefined_type="ELEVATION",
should_add_representation=False,
context=context,
ifc_representation_class=None,
)
return element
@classmethod
def generate_grid_axis_reference_annotation(cls, drawing, reference_element, context):
target_view = tool.Drawing.get_drawing_target_view(drawing)
camera = tool.Ifc.get_object(drawing)
is_ortho = camera.data.type == "ORTHO"
bounds = helper.ortho_view_frame(camera.data) if is_ortho else None
clipping = is_ortho and target_view in ("PLAN_VIEW", "REFLECTED_PLAN_VIEW")
elevating = is_ortho and target_view in ("ELEVATION_VIEW", "SECTION_VIEW")
def clone(src):
dst = src.copy()
dst.data = dst.data.copy()
dst.name = dst.name.replace("IfcGridAxis/", "")
dst.BIMObjectProperties.ifc_definition_id = 0
dst.data.BIMMeshProperties.ifc_definition_id = 0
return dst
def disassemble(obj):
mesh = bmesh.new()
mesh.verts.ensure_lookup_table()
mesh.from_mesh(obj.data)
return obj, mesh
def assemble(obj, mesh):
mesh.to_mesh(obj.data)
return obj
def to_camera_coords(obj, mesh):
mesh.transform(camera.matrix_world.inverted() @ obj.matrix_world)
obj.matrix_world = camera.matrix_world
annotation_offset = mathutils.Vector((0, 0, -camera.data.clip_start - 0.05))
annotation_offset = camera.matrix_world.to_quaternion() @ annotation_offset
obj.matrix_world[0][3] += annotation_offset[0]
obj.matrix_world[1][3] += annotation_offset[1]
obj.matrix_world[2][3] += annotation_offset[2]
return obj, mesh
def clip_to_camera_boundary(mesh):
mesh.verts.ensure_lookup_table()
points = [v.co for v in mesh.verts[0:2]]
points = helper.clip_segment(bounds, points)
if points is None:
return None
mesh.verts[0].co = points[0]
mesh.verts[1].co = points[1]
return mesh
def draw_grids_vertically(mesh):
mesh.verts.ensure_lookup_table()
points = [v.co for v in mesh.verts[0:2]]
points = helper.elevate_segment(bounds, points)
if points is None:
return None
points = helper.clip_segment(bounds, points)
if points is None:
return None
mesh.verts[0].co = points[0]
mesh.verts[1].co = points[1]
return mesh
obj = tool.Ifc.get_object(reference_element)
if not obj:
return
obj, mesh = to_camera_coords(*disassemble(clone(obj)))
if clipping:
mesh = clip_to_camera_boundary(mesh)
elif elevating:
mesh = draw_grids_vertically(mesh)
if mesh is None:
return
assemble(obj, mesh)
element = cls.run_root_assign_class(
obj=obj,
ifc_class="IfcAnnotation",
predefined_type="GRID",
should_add_representation=True,
context=context,
ifc_representation_class=None,
)
return element
@classmethod
def is_perpendicular(cls, a, b):
axes = [mathutils.Vector((1, 0, 0)), mathutils.Vector((0, 1, 0)), mathutils.Vector((0, 0, 1))]
a_quaternion = a.matrix_world.to_quaternion()
b_quaternion = b.matrix_world.to_quaternion()
for axis in axes:
if abs((a_quaternion @ axis).angle((b_quaternion @ axis)) - (math.pi / 2)) < 1e-5:
return True
return False
@classmethod
def get_camera_block(cls, obj):
raster_x = obj.data.BIMCameraProperties.raster_x
raster_y = obj.data.BIMCameraProperties.raster_y
if raster_x > raster_y:
width = obj.data.ortho_scale
height = width / raster_x * raster_y
else:
height = obj.data.ortho_scale
width = height / raster_y * raster_x
depth = obj.data.clip_end
verts = (
obj.matrix_world @ mathutils.Vector((-width / 2, -height / 2, -depth)),
obj.matrix_world @ mathutils.Vector((-width / 2, -height / 2, 0)),
obj.matrix_world @ mathutils.Vector((-width / 2, height / 2, -depth)),
obj.matrix_world @ mathutils.Vector((-width / 2, height / 2, 0)),
obj.matrix_world @ mathutils.Vector((width / 2, -height / 2, -depth)),
obj.matrix_world @ mathutils.Vector((width / 2, -height / 2, 0)),
obj.matrix_world @ mathutils.Vector((width / 2, height / 2, -depth)),
obj.matrix_world @ mathutils.Vector((width / 2, height / 2, 0)),
)
faces = [
[0, 1, 3, 2],
[2, 3, 7, 6],
[6, 7, 5, 4],
[4, 5, 1, 0],
[2, 6, 4, 0],
[7, 3, 1, 5],
]
return {"verts": verts, "faces": faces}
@classmethod
def is_intersecting(cls, a, b):
a_block = cls.get_camera_block(a)
a_tree = mathutils.bvhtree.BVHTree.FromPolygons(a_block["verts"], a_block["faces"])
b_block = cls.get_camera_block(b)
b_tree = mathutils.bvhtree.BVHTree.FromPolygons(b_block["verts"], b_block["faces"])
return bool(a_tree.overlap(b_tree))
@classmethod
def sync_object_representation(cls, obj):
bpy.ops.bim.update_representation(obj=obj.name)
@classmethod
def sync_object_placement(cls, obj):
blender_matrix = np.array(obj.matrix_world)
element = tool.Ifc.get_entity(obj)
if (obj.scale - mathutils.Vector((1.0, 1.0, 1.0))).length > 1e-4:
bpy.ops.bim.update_representation(obj=obj.name)
return element
if element.is_a("IfcGridAxis"):
return cls.sync_grid_axis_object_placement(obj, element)
if not hasattr(element, "ObjectPlacement"):
return
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
return element
@classmethod
def sync_grid_axis_object_placement(cls, obj, element):
grid = (element.PartOfU or element.PartOfV or element.PartOfW)[0]
grid_obj = tool.Ifc.get_object(grid)
if grid_obj:
cls.sync_object_placement(grid_obj)
if grid_obj.matrix_world != obj.matrix_world:
bpy.ops.bim.update_representation(obj=obj.name)
tool.Geometry.record_object_position(obj)
+21
View File
@@ -17,6 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy import bpy
import numpy as np
import ifcopenshell.api import ifcopenshell.api
import blenderbim.core.tool import blenderbim.core.tool
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
@@ -44,6 +45,26 @@ class Ifc(blenderbim.core.tool.Ifc):
if IfcStore.get_file(): if IfcStore.get_file():
return IfcStore.get_file().schema return IfcStore.get_file().schema
@classmethod
def is_deleted(cls, element):
return element.id() in IfcStore.deleted_ids
@classmethod
def is_edited(cls, obj):
return list(obj.scale) != [1.0, 1.0, 1.0] or obj in IfcStore.edited_objs
@classmethod
def is_moved(cls, obj):
if not obj.BIMObjectProperties.location_checksum:
return True # Let's be conservative
loc_check = np.frombuffer(eval(obj.BIMObjectProperties.location_checksum))
rot_check = np.frombuffer(eval(obj.BIMObjectProperties.rotation_checksum))
loc_real = np.array(obj.matrix_world.translation).flatten()
rot_real = np.array(obj.matrix_world.to_3x3()).flatten()
if np.allclose(loc_check, loc_real, atol=1e-4) and np.allclose(rot_check, rot_real, atol=1e-2):
return False
return True
@classmethod @classmethod
def schema(cls): def schema(cls):
return IfcStore.get_schema() return IfcStore.get_schema()
@@ -27,3 +27,53 @@ class Material(blenderbim.core.tool.Material):
@classmethod @classmethod
def add_default_material_object(cls): def add_default_material_object(cls):
return bpy.data.materials.new("Default") return bpy.data.materials.new("Default")
@classmethod
def delete_object(cls, obj):
bpy.data.materials.remove(obj)
@classmethod
def disable_editing_materials(cls):
bpy.context.scene.BIMMaterialProperties.is_editing = False
@classmethod
def enable_editing_materials(cls):
bpy.context.scene.BIMMaterialProperties.is_editing = True
@classmethod
def get_active_material_type(cls):
return bpy.context.scene.BIMMaterialProperties.material_type
@classmethod
def get_elements_by_material(cls, material):
return set(ifcopenshell.util.element.get_elements_by_material(tool.Ifc.get(), material))
@classmethod
def get_name(cls, obj):
return obj.name
@classmethod
def import_material_definitions(cls, material_type):
props = bpy.context.scene.BIMMaterialProperties
props.materials.clear()
for material in tool.Ifc.get().by_type(material_type):
new = props.materials.add()
new.ifc_definition_id = material.id()
if material.is_a("IfcMaterialLayerSet"):
new.name = material.LayerSetName or "Unnamed"
elif material.is_a("IfcMaterialList"):
new.name = "Unnamed"
else:
new.name = material.Name or "Unnamed"
new.total_elements = len(set(ifcopenshell.util.element.get_elements_by_material(tool.Ifc.get(), material)))
@classmethod
def is_editing_materials(cls):
return bpy.context.scene.BIMMaterialProperties.is_editing
@classmethod
def select_elements(cls, elements):
for element in elements:
obj = tool.Ifc.get_object(element)
if obj:
obj.select_set(True)
+26
View File
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License # 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/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import ifcopenshell import ifcopenshell
import blenderbim.core.tool import blenderbim.core.tool
import blenderbim.tool as tool import blenderbim.tool as tool
@@ -31,14 +32,26 @@ class Style(blenderbim.core.tool.Style):
def disable_editing(cls, obj): def disable_editing(cls, obj):
obj.BIMStyleProperties.is_editing = False obj.BIMStyleProperties.is_editing = False
@classmethod
def disable_editing_styles(cls):
bpy.context.scene.BIMStylesProperties.is_editing = False
@classmethod @classmethod
def enable_editing(cls, obj): def enable_editing(cls, obj):
obj.BIMStyleProperties.is_editing = True obj.BIMStyleProperties.is_editing = True
@classmethod
def enable_editing_styles(cls):
bpy.context.scene.BIMStylesProperties.is_editing = True
@classmethod @classmethod
def export_surface_attributes(cls, obj): def export_surface_attributes(cls, obj):
return blenderbim.bim.helper.export_attributes(obj.BIMStyleProperties.attributes) return blenderbim.bim.helper.export_attributes(obj.BIMStyleProperties.attributes)
@classmethod
def get_active_style_type(cls):
return bpy.context.scene.BIMStylesProperties.style_type
@classmethod @classmethod
def get_context(cls, obj): def get_context(cls, obj):
return ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW") return ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
@@ -166,7 +179,20 @@ class Style(blenderbim.core.tool.Style):
results.append(uv_map) results.append(uv_map)
return results return results
@classmethod
def import_presentation_styles(cls, style_type):
props = bpy.context.scene.BIMStylesProperties
props.styles.clear()
for style in tool.Ifc.get().by_type(style_type):
new = props.styles.add()
new.ifc_definition_id = style.id()
new.name = style.Name or "Unnamed"
@classmethod @classmethod
def import_surface_attributes(cls, style, obj): def import_surface_attributes(cls, style, obj):
obj.BIMStyleProperties.attributes.clear() obj.BIMStyleProperties.attributes.clear()
blenderbim.bim.helper.import_attributes2(style, obj.BIMStyleProperties.attributes) blenderbim.bim.helper.import_attributes2(style, obj.BIMStyleProperties.attributes)
@classmethod
def is_editing_styles(cls):
return bpy.context.scene.BIMStylesProperties.is_editing
@@ -63,7 +63,7 @@ architecture flow charts and diagrams. The code and its comments will guide you
through the process. through the process.
Before playing with the demo module, you may want to switch to using a source Before playing with the demo module, you may want to switch to using a source
installation. See :ref:`blenderbim/installation` for details. installation. See `blenderbim/installation <./installation.rst>`_ for details.
To see the demo module in action, you'll need to enable it. In To see the demo module in action, you'll need to enable it. In
``src/blenderbim/blenderbim/bim/__init__.py``, uncomment the line for the demo ``src/blenderbim/blenderbim/bim/__init__.py``, uncomment the line for the demo
@@ -95,8 +95,8 @@ and how to test and structure it so that you can build incredibly complex
features in a maintainable way. features in a maintainable way.
Tests for quality checking also exist. The system is designed so that you can Tests for quality checking also exist. The system is designed so that you can
do "Test Driven Development". For reference on how to run these tests, see do "Test Driven Development". For reference on how to run these tests, see `blenderbim/running_tests <./running_tests.rst>`_
:ref:`blenderbim/running_tests` for details. You can find the tests here: for details. You can find the tests here:
:: ::
+1
View File
@@ -7,6 +7,7 @@ markers =
context context
debug debug
demo demo
document
drawing drawing
geometry geometry
library library
@@ -49,7 +49,7 @@ Scenario: Add aggregate
When I press "bim.add_aggregate" When I press "bim.add_aggregate"
Then the object "IfcWall/Cube" is in the collection "IfcElementAssembly/Assembly" Then the object "IfcWall/Cube" is in the collection "IfcElementAssembly/Assembly"
And the object "IfcElementAssembly/Assembly" is in the collection "IfcElementAssembly/Assembly" And the object "IfcElementAssembly/Assembly" is in the collection "IfcElementAssembly/Assembly"
And the collection "IfcElementAssembly/Assembly" is in the collection "IfcProject/My Project" And the collection "IfcElementAssembly/Assembly" is in the collection "IfcBuildingStorey/My Storey"
Scenario: Add aggregate - with the aggregate inheriting the existing spatial collection Scenario: Add aggregate - with the aggregate inheriting the existing spatial collection
Given an empty IFC project Given an empty IFC project
@@ -0,0 +1,112 @@
@document
Feature: Document
Scenario: Load project documents
Given an empty IFC project
When I press "bim.load_project_documents"
Then nothing happens
Scenario: Load document
Given an empty IFC project
And I press "bim.load_project_documents"
And I press "bim.add_information"
And the variable "information" is "{ifc}.by_type('IfcDocumentInformation')[-1].id()"
When I press "bim.load_document(document={information})"
Then nothing happens
Scenario: Load parent document
Given an empty IFC project
And I press "bim.load_project_documents"
And I press "bim.add_information"
And the variable "information" is "{ifc}.by_type('IfcDocumentInformation')[-1].id()"
And I press "bim.load_document(document={information})"
When I press "bim.load_parent_document"
Then nothing happens
Scenario: Disable document editing UI
Given an empty IFC project
And I press "bim.load_project_documents"
When I press "bim.disable_document_editing_ui"
Then nothing happens
Scenario: Enable editing document
Given an empty IFC project
And I press "bim.load_project_documents"
And I press "bim.add_information"
And the variable "information" is "{ifc}.by_type('IfcDocumentInformation')[-1].id()"
When I press "bim.enable_editing_document(document={information})"
Then nothing happens
Scenario: Disable editing document
Given an empty IFC project
And I press "bim.load_project_documents"
And I press "bim.add_information"
And the variable "information" is "{ifc}.by_type('IfcDocumentInformation')[-1].id()"
And I press "bim.enable_editing_document(document={information})"
When I press "bim.disable_editing_document"
Then nothing happens
Scenario: Add information
Given an empty IFC project
And I press "bim.load_project_documents"
When I press "bim.add_information"
Then nothing happens
Scenario: Add document reference
Given an empty IFC project
And I press "bim.load_project_documents"
And I press "bim.add_information"
And the variable "information" is "{ifc}.by_type('IfcDocumentInformation')[-1].id()"
And I press "bim.load_document(document={information})"
When I press "bim.add_document_reference"
Then nothing happens
Scenario: Edit document
Given an empty IFC project
And I press "bim.load_project_documents"
And I press "bim.add_information"
And the variable "information" is "{ifc}.by_type('IfcDocumentInformation')[-1].id()"
And I press "bim.enable_editing_document(document={information})"
When I press "bim.edit_document"
Then nothing happens
Scenario: Remove document
Given an empty IFC project
And I press "bim.load_project_documents"
And I press "bim.add_information"
And the variable "information" is "{ifc}.by_type('IfcDocumentInformation')[-1].id()"
When I press "bim.remove_document(document={information})"
Then nothing happens
Scenario: Assign document
Given an empty IFC project
And I press "bim.load_project_documents"
And I press "bim.add_information"
And the variable "information" is "{ifc}.by_type('IfcDocumentInformation')[-1].id()"
And I press "bim.load_document(document={information})"
And I press "bim.add_document_reference"
And the variable "reference" is "{ifc}.by_type('IfcDocumentReference')[-1].id()"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
When I press "bim.assign_document(document={reference})"
Then nothing happens
Scenario: Unassign document
Given an empty IFC project
And I press "bim.load_project_documents"
And I press "bim.add_information"
And the variable "information" is "{ifc}.by_type('IfcDocumentInformation')[-1].id()"
And I press "bim.load_document(document={information})"
And I press "bim.add_document_reference"
And the variable "reference" is "{ifc}.by_type('IfcDocumentReference')[-1].id()"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I press "bim.assign_document(document={reference})"
When I press "bim.unassign_document(document={reference})"
Then nothing happens
@@ -19,7 +19,7 @@ Scenario: Add representation
And I press "bim.assign_class" And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected And the object "IfcWall/Cube" is selected
Then the object "IfcWall/Cube" data is a "Tessellation" representation of "Model/Body/MODEL_VIEW" Then the object "IfcWall/Cube" data is a "Tessellation" representation of "Model/Body/MODEL_VIEW"
When the variable "context" is "{ifc}.by_type('IfcGeometricRepresentationSubContext')[-1].id()" When the variable "context" is "[c for c in {ifc}.by_type('IfcGeometricRepresentationSubContext') if c.TargetView == 'PLAN_VIEW'][0].id()"
And I set "scene.BIMRootProperties.contexts" to "{context}" And I set "scene.BIMRootProperties.contexts" to "{context}"
And I press "bim.add_representation" And I press "bim.add_representation"
Then the object "IfcWall/Cube" data is a "Annotation2D" representation of "Plan/Annotation/PLAN_VIEW" Then the object "IfcWall/Cube" data is a "Annotation2D" representation of "Plan/Annotation/PLAN_VIEW"
@@ -36,7 +36,7 @@ Scenario: Add representation - add a new representation to a typed instance
Then the object "IfcWall/Wall" data is a "Tessellation" representation of "Model/Body/MODEL_VIEW" Then the object "IfcWall/Wall" data is a "Tessellation" representation of "Model/Body/MODEL_VIEW"
And the object "IfcWall/Wall.001" data is a "Tessellation" representation of "Model/Body/MODEL_VIEW" And the object "IfcWall/Wall.001" data is a "Tessellation" representation of "Model/Body/MODEL_VIEW"
When the object "IfcWall/Wall" is selected When the object "IfcWall/Wall" is selected
And the variable "context" is "{ifc}.by_type('IfcGeometricRepresentationSubContext')[-1].id()" And the variable "context" is "[c for c in {ifc}.by_type('IfcGeometricRepresentationSubContext') if c.TargetView == 'PLAN_VIEW'][0].id()"
And I set "scene.BIMRootProperties.contexts" to "{context}" And I set "scene.BIMRootProperties.contexts" to "{context}"
And I press "bim.add_representation" And I press "bim.add_representation"
Then the object "IfcWall/Wall" data is a "Annotation2D" representation of "Plan/Annotation/PLAN_VIEW" Then the object "IfcWall/Wall" data is a "Annotation2D" representation of "Plan/Annotation/PLAN_VIEW"
@@ -195,7 +195,7 @@ Scenario: Update representation - updating a layered extrusion
And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType" And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType" And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class" And I press "bim.assign_class"
And I press "bim.add_default_material" And I press "bim.add_material(obj='')"
And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet" And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet"
And I press "bim.assign_material" And I press "bim.assign_material"
And I press "bim.enable_editing_assigned_material" And I press "bim.enable_editing_assigned_material"
@@ -222,7 +222,7 @@ Scenario: Update representation - updating a profiled extrusion
And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType" And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType" And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class" And I press "bim.assign_class"
And I press "bim.add_default_material" And I press "bim.add_material(obj='')"
And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialProfileSet" And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialProfileSet"
And I press "bim.assign_material" And I press "bim.assign_material"
And I press "bim.enable_editing_assigned_material" And I press "bim.enable_editing_assigned_material"
@@ -357,7 +357,7 @@ Scenario: Override duplicate move - copying a layered extrusion
And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType" And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType" And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class" And I press "bim.assign_class"
And I press "bim.add_default_material" And I press "bim.add_material(obj='')"
And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet" And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet"
And I press "bim.assign_material" And I press "bim.assign_material"
And I press "bim.enable_editing_assigned_material" And I press "bim.enable_editing_assigned_material"
@@ -386,7 +386,7 @@ Scenario: Override duplicate move - copying a profiled extrusion
And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType" And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType" And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class" And I press "bim.assign_class"
And I press "bim.add_default_material" And I press "bim.add_material(obj='')"
And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialProfileSet" And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialProfileSet"
And I press "bim.assign_material" And I press "bim.assign_material"
And I press "bim.enable_editing_assigned_material" And I press "bim.enable_editing_assigned_material"
@@ -1,9 +1,35 @@
@material @material
Feature: Material Feature: Material
Scenario: Load materials
Given an empty IFC project
And I press "bim.add_material(obj='')"
When I press "bim.load_materials"
Then nothing happens
Scenario: Disable editing materials
Given an empty IFC project
And I press "bim.add_material(obj='')"
And I press "bim.load_materials"
When I press "bim.disable_editing_materials"
Then nothing happens
Scenario: Load materials - then add material
Given an empty IFC project
And I press "bim.load_materials"
When I press "bim.add_material(obj='')"
Then the material "Default" exists
Scenario: Load materials - then add material set
Given an empty IFC project
And I set "scene.BIMMaterialProperties.material_type" to "IfcMaterialLayerSet"
And I press "bim.load_materials"
When I press "bim.add_material_set(set_type='IfcMaterialLayerSet')"
Then nothing happens
Scenario: Add default material Scenario: Add default material
Given an empty IFC project Given an empty IFC project
When I press "bim.add_default_material" When I press "bim.add_material(obj='')"
Then the material "Default" exists Then the material "Default" exists
Scenario: Add material Scenario: Add material
@@ -11,7 +37,7 @@ Scenario: Add material
And I add a cube And I add a cube
And the object "Cube" is selected And the object "Cube" is selected
And I add a material And I add a material
When I press "bim.add_material" When I press "bim.add_material(obj='Material')"
Then the material "Material" is an IFC material Then the material "Material" is an IFC material
Scenario: Remove material Scenario: Remove material
@@ -19,16 +45,26 @@ Scenario: Remove material
And I add a cube And I add a cube
And the object "Cube" is selected And the object "Cube" is selected
And I add a material And I add a material
And I press "bim.add_material" And I press "bim.add_material(obj='Material')"
When I press "bim.remove_material" And the variable "material" is "{ifc}.by_type('IfcMaterial')[0].id()"
Then the material "Material" is not an IFC material When I press "bim.remove_material(material={material})"
Then the material "Material" does not exist
Scenario: Remove material set
Given an empty IFC project
And I set "scene.BIMMaterialProperties.material_type" to "IfcMaterialLayerSet"
And I press "bim.load_materials"
And I press "bim.add_material_set(set_type='IfcMaterialLayerSet')"
And the variable "material" is "{ifc}.by_type('IfcMaterialLayerSet')[0].id()"
When I press "bim.remove_material_set(material={material})"
Then nothing happens
Scenario: Unlink material Scenario: Unlink material
Given an empty IFC project Given an empty IFC project
And I add a cube And I add a cube
And the object "Cube" is selected And the object "Cube" is selected
And I add a material And I add a material
And I press "bim.add_material" And I press "bim.add_material(obj='Material')"
When I press "bim.unlink_material" When I press "bim.unlink_material"
Then the material "Material" is not an IFC material Then the material "Material" is not an IFC material
@@ -38,7 +74,7 @@ Scenario: Assign material - Assign a material
And the object "Cube" is selected And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class" And I press "bim.assign_class"
And I press "bim.add_default_material" And I press "bim.add_material(obj='')"
When I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterial" When I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterial"
And I press "bim.assign_material" And I press "bim.assign_material"
Then the object "IfcWall/Cube" has the material "Default" Then the object "IfcWall/Cube" has the material "Default"
@@ -54,7 +90,7 @@ Scenario: Assign material - Assign a material layer set
And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType" And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType" And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class" And I press "bim.assign_class"
And I press "bim.add_default_material" And I press "bim.add_material(obj='')"
When I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet" When I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet"
And I press "bim.assign_material" And I press "bim.assign_material"
Then nothing happens Then nothing happens
@@ -70,7 +106,15 @@ Scenario: Assign material - Assign a material profile set
And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType" And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType" And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class" And I press "bim.assign_class"
And I press "bim.add_default_material" And I press "bim.add_material(obj='')"
When I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialProfileSet" When I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialProfileSet"
And I press "bim.assign_material" And I press "bim.assign_material"
Then nothing happens Then nothing happens
Scenario: Select by material
Given an empty IFC project
And I press "bim.load_materials"
And I press "bim.add_material(obj='')"
And the variable "material" is "{ifc}.by_type('IfcMaterial')[0].id()"
When I press "bim.select_by_material(material={material})"
Then nothing happens
@@ -40,7 +40,7 @@ Scenario: Add type instance - add a mesh where existing instances have changed c
And I press "bim.add_type_instance" And I press "bim.add_type_instance"
And the object "IfcWall/Wall" data is a "Tessellation" representation of "Model/Body/MODEL_VIEW" And the object "IfcWall/Wall" data is a "Tessellation" representation of "Model/Body/MODEL_VIEW"
And the object "IfcWall/Wall" is selected And the object "IfcWall/Wall" is selected
And the variable "context" is "{ifc}.by_type('IfcGeometricRepresentationSubContext')[-1].id()" And the variable "context" is "[c for c in {ifc}.by_type('IfcGeometricRepresentationSubContext') if c.TargetView == 'PLAN_VIEW'][0].id()"
And I set "scene.BIMRootProperties.contexts" to "{context}" And I set "scene.BIMRootProperties.contexts" to "{context}"
And I press "bim.add_representation" And I press "bim.add_representation"
And the object "IfcWall/Wall" data is a "Annotation2D" representation of "Plan/Annotation/PLAN_VIEW" And the object "IfcWall/Wall" data is a "Annotation2D" representation of "Plan/Annotation/PLAN_VIEW"
+1 -1
View File
@@ -43,7 +43,7 @@ Scenario: Assign a class to a cube
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class" And I press "bim.assign_class"
Then the object "IfcWall/Cube" is an "IfcWall" Then the object "IfcWall/Cube" is an "IfcWall"
And the object "IfcWall/Cube" is in the collection "IfcProject/My Project" And the object "IfcWall/Cube" is in the collection "IfcBuildingStorey/My Storey"
And the object "IfcWall/Cube" has a "Tessellation" representation of "Model/Body/MODEL_VIEW" And the object "IfcWall/Cube" has a "Tessellation" representation of "Model/Body/MODEL_VIEW"
Scenario: Assign a type class to a cube Scenario: Assign a type class to a cube
@@ -79,7 +79,7 @@ Scenario: Animate the construction of a wall
Then "scene.objects.get('IfcWall/Cube').hide_viewport" is "False" Then "scene.objects.get('IfcWall/Cube').hide_viewport" is "False"
And "scene.objects.get('IfcWall/Cube').hide_render" is "False" And "scene.objects.get('IfcWall/Cube').hide_render" is "False"
And "scene.objects.get('IfcWall/Cube').color" is "[0.0, 1.0, 0.0, 1]" And "scene.objects.get('IfcWall/Cube').color" is "[0.0, 1.0, 0.0, 1]"
When I am on frame "6" When I am on frame "7"
Then "scene.objects.get('IfcWall/Cube').color" is "[1.0, 1.0, 1.0, 1]" Then "scene.objects.get('IfcWall/Cube').color" is "[1.0, 1.0, 1.0, 1]"
Scenario: Animate the demolition of a wall Scenario: Animate the demolition of a wall
@@ -116,7 +116,7 @@ Scenario: Animate the demolition of a wall
Then "scene.objects.get('IfcWall/Cube').hide_viewport" is "False" Then "scene.objects.get('IfcWall/Cube').hide_viewport" is "False"
And "scene.objects.get('IfcWall/Cube').hide_render" is "False" And "scene.objects.get('IfcWall/Cube').hide_render" is "False"
And "scene.objects.get('IfcWall/Cube').color" is "[1.0, 0.0, 0.0, 1]" And "scene.objects.get('IfcWall/Cube').color" is "[1.0, 0.0, 0.0, 1]"
When I am on frame "6" When I am on frame "7"
Then "scene.objects.get('IfcWall/Cube').hide_viewport" is "True" Then "scene.objects.get('IfcWall/Cube').hide_viewport" is "True"
And "scene.objects.get('IfcWall/Cube').hide_render" is "True" And "scene.objects.get('IfcWall/Cube').hide_render" is "True"
And "scene.objects.get('IfcWall/Cube').color" is "[0.0, 0.0, 0.0, 1]" And "scene.objects.get('IfcWall/Cube').color" is "[0.0, 0.0, 0.0, 1]"
@@ -151,7 +151,7 @@ Scenario: Animate the operation of a wall
Then "scene.objects.get('IfcWall/Cube').color" is "[1.0, 1.0, 1.0, 1]" Then "scene.objects.get('IfcWall/Cube').color" is "[1.0, 1.0, 1.0, 1]"
When I am on frame "2" When I am on frame "2"
Then "scene.objects.get('IfcWall/Cube').color" is "[0.0, 0.0, 1.0, 1]" Then "scene.objects.get('IfcWall/Cube').color" is "[0.0, 0.0, 1.0, 1]"
When I am on frame "6" When I am on frame "7"
Then "scene.objects.get('IfcWall/Cube').color" is "[1.0, 1.0, 1.0, 1]" Then "scene.objects.get('IfcWall/Cube').color" is "[1.0, 1.0, 1.0, 1]"
Scenario: Animate the movement of a wall Scenario: Animate the movement of a wall
@@ -200,7 +200,7 @@ Scenario: Animate the movement of a wall
And "scene.objects.get('IfcWall/ToObject').color" is "[1.0, 1.0, 0.0, 1]" And "scene.objects.get('IfcWall/ToObject').color" is "[1.0, 1.0, 0.0, 1]"
And "scene.objects.get('IfcWall/ToObject').hide_viewport" is "False" And "scene.objects.get('IfcWall/ToObject').hide_viewport" is "False"
And "scene.objects.get('IfcWall/ToObject').hide_render" is "False" And "scene.objects.get('IfcWall/ToObject').hide_render" is "False"
When I am on frame "6" When I am on frame "7"
Then "scene.objects.get('IfcWall/FromObject').color" is "[0.0, 0.0, 0.0, 1]" Then "scene.objects.get('IfcWall/FromObject').color" is "[0.0, 0.0, 0.0, 1]"
Then "scene.objects.get('IfcWall/FromObject').hide_viewport" is "True" Then "scene.objects.get('IfcWall/FromObject').hide_viewport" is "True"
Then "scene.objects.get('IfcWall/FromObject').hide_render" is "True" Then "scene.objects.get('IfcWall/FromObject').hide_render" is "True"
@@ -239,7 +239,7 @@ Scenario: Animate the consumption of a wall
Then "scene.objects.get('IfcWall/Cube').color" is "[0.0, 1.0, 1.0, 1]" Then "scene.objects.get('IfcWall/Cube').color" is "[0.0, 1.0, 1.0, 1]"
And "scene.objects.get('IfcWall/Cube').hide_viewport" is "False" And "scene.objects.get('IfcWall/Cube').hide_viewport" is "False"
And "scene.objects.get('IfcWall/Cube').hide_render" is "False" And "scene.objects.get('IfcWall/Cube').hide_render" is "False"
When I am on frame "6" When I am on frame "7"
Then "scene.objects.get('IfcWall/Cube').color" is "[0.0, 0.0, 0.0, 1]" Then "scene.objects.get('IfcWall/Cube').color" is "[0.0, 0.0, 0.0, 1]"
Then "scene.objects.get('IfcWall/Cube').hide_viewport" is "True" Then "scene.objects.get('IfcWall/Cube').hide_viewport" is "True"
Then "scene.objects.get('IfcWall/Cube').hide_render" is "True" Then "scene.objects.get('IfcWall/Cube').hide_render" is "True"
+19 -1
View File
@@ -14,7 +14,8 @@ Scenario: Remove style
And I add a cube And I add a cube
And I add a material And I add a material
And I press "bim.add_style" And I press "bim.add_style"
When I press "bim.remove_style" And the variable "style" is "{ifc}.by_type('IfcSurfaceStyle')[0].id()"
When I press "bim.remove_style(style={style})"
Then nothing happens Then nothing happens
Scenario: Add style Scenario: Add style
@@ -57,3 +58,20 @@ Scenario: Edit style
And I press "bim.enable_editing_style" And I press "bim.enable_editing_style"
When I press "bim.edit_style" When I press "bim.edit_style"
Then nothing happens Then nothing happens
Scenario: Load styles
Given an empty IFC project
And I add a cube
And I add a material
And I press "bim.add_style"
When I press "bim.load_styles(style_type='IfcSurfaceStyle')"
Then nothing happens
Scenario: Disable editing styles
Given an empty IFC project
And I add a cube
And I add a material
And I press "bim.add_style"
And I press "bim.load_styles(style_type='IfcSurfaceStyle')"
When I press "bim.disable_editing_style"
Then nothing happens
+2 -2
View File
@@ -42,7 +42,7 @@ Scenario: Assign type - assign to a type with a material layer set
And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType" And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType" And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class" And I press "bim.assign_class"
And I press "bim.add_default_material" And I press "bim.add_material(obj='')"
And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet" And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet"
And I press "bim.assign_material" And I press "bim.assign_material"
And I press "bim.enable_editing_assigned_material" And I press "bim.enable_editing_assigned_material"
@@ -68,7 +68,7 @@ Scenario: Assign type - assign to a type with a material profile set
And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType" And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType" And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class" And I press "bim.assign_class"
And I press "bim.add_default_material" And I press "bim.add_material(obj='')"
And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialProfileSet" And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialProfileSet"
And I press "bim.assign_material" And I press "bim.assign_material"
And I press "bim.enable_editing_assigned_material" And I press "bim.enable_editing_assigned_material"
@@ -50,6 +50,16 @@ Scenario: Add context dependent unit
When I press "bim.add_context_dependent_unit(name='THINGAMAJIGS', unit_type='LENGTHUNIT')" When I press "bim.add_context_dependent_unit(name='THINGAMAJIGS', unit_type='LENGTHUNIT')"
Then nothing happens Then nothing happens
Scenario: Add conversion based unit
Given an empty IFC project
When I press "bim.add_conversion_based_unit(name='inch')"
Then nothing happens
Scenario: Add conversion based unit with offset
Given an empty IFC project
When I press "bim.add_conversion_based_unit(name='fahrenheit')"
Then nothing happens
Scenario: Enable editing unit Scenario: Enable editing unit
Given an empty IFC project Given an empty IFC project
And the variable "unit" is "{ifc}.by_type('IfcSIUnit')[0].id()" And the variable "unit" is "{ifc}.by_type('IfcSIUnit')[0].id()"
+5
View File
@@ -297,6 +297,11 @@ def the_material_name_exists(name) -> bpy.types.Material:
return obj return obj
@then(parsers.parse('the material "{name}" does not exist'))
def the_material_name_does_not_exist(name):
assert bpy.data.materials.get(name) is None, "Material exists"
@then("an IFC file does not exist") @then("an IFC file does not exist")
def an_ifc_file_does_not_exist(): def an_ifc_file_does_not_exist():
ifc = IfcStore.get_file() ifc = IfcStore.get_file()
+57 -50
View File
@@ -21,20 +21,23 @@ import blenderbim.core.document as subject
from test.core.bootstrap import ifc, document from test.core.bootstrap import ifc, document
class TestLoadInformation: class TestLoadProjectDocuments:
def test_run(self, document): def test_run(self, document):
document.import_information().should_be_called() document.clear_document_tree().should_be_called()
document.enable_information_editing_ui().should_be_called() document.import_project_documents().should_be_called()
document.disable_editing_document().should_be_called() document.clear_breadcrumbs().should_be_called()
subject.load_information(document) document.enable_editing_ui().should_be_called()
subject.load_project_documents(document)
class TestLoadReferences: class TestLoadDocument:
def test_run(self, document): def test_run(self, document):
document.import_references().should_be_called() document.clear_document_tree().should_be_called()
document.enable_reference_editing_ui().should_be_called() document.import_subdocuments("document").should_be_called()
document.import_references("document").should_be_called()
document.disable_editing_document().should_be_called() document.disable_editing_document().should_be_called()
subject.load_references(document) document.add_breadcrumb("document").should_be_called()
subject.load_document(document, document="document")
class TestDisableDocumentEditingUi: class TestDisableDocumentEditingUi:
@@ -58,72 +61,76 @@ class TestDisableEditingDocument:
class TestAddInformation: class TestAddInformation:
def test_run(self, ifc, document): def test_add_and_reload_tree_at_project_root(self, ifc, document):
ifc.run("document.add_information").should_be_called().will_return("information") document.clear_document_tree().should_be_called()
document.import_information().should_be_called() document.get_active_breadcrumb().should_be_called().will_return(None)
document.import_document_attributes("information").should_be_called() ifc.run("document.add_information", parent=None).should_be_called().will_return("information")
document.set_active_document("information").should_be_called() ifc.run("document.add_reference", information="information").should_be_called()
document.import_project_documents().should_be_called()
subject.add_information(ifc, document)
def test_add_and_reload_tree_at_current_parent(self, ifc, document):
document.clear_document_tree().should_be_called()
document.get_active_breadcrumb().should_be_called().will_return("parent")
ifc.run("document.add_information", parent="parent").should_be_called().will_return("information")
ifc.run("document.add_reference", information="information").should_be_called()
document.import_subdocuments("parent").should_be_called()
document.import_references("parent").should_be_called()
subject.add_information(ifc, document) subject.add_information(ifc, document)
class TestAddReference: class TestAddReference:
def test_run(self, ifc, document): def test_run(self, ifc, document):
ifc.run("document.add_reference").should_be_called().will_return("reference") document.get_active_breadcrumb().should_be_called().will_return("parent")
document.import_references().should_be_called() ifc.run("document.add_reference", information="parent").should_be_called()
document.import_document_attributes("reference").should_be_called() document.clear_document_tree().should_be_called()
document.set_active_document("reference").should_be_called() document.import_subdocuments("parent").should_be_called()
document.import_references("parent").should_be_called()
subject.add_reference(ifc, document) subject.add_reference(ifc, document)
class TestEditInformation: class TestEditDocument:
def test_run(self, ifc, document): def test_edit_information(self, ifc, document):
document.export_document_attributes().should_be_called().will_return("attributes") document.export_document_attributes().should_be_called().will_return("attributes")
ifc.run("document.edit_information", information="information", attributes="attributes").should_be_called() document.is_document_information("document").should_be_called().will_return(True)
ifc.run("document.edit_information", information="document", attributes="attributes").should_be_called()
document.disable_editing_document().should_be_called() document.disable_editing_document().should_be_called()
document.import_information().should_be_called() document.clear_document_tree().should_be_called()
subject.edit_information(ifc, document, information="information") document.get_active_breadcrumb().should_be_called().will_return(None)
document.import_project_documents().should_be_called()
subject.edit_document(ifc, document, document="document")
def test_edit_reference(self, ifc, document):
class TestEditInformation:
def test_run(self, ifc, document):
document.export_document_attributes().should_be_called().will_return("attributes") document.export_document_attributes().should_be_called().will_return("attributes")
ifc.run("document.edit_reference", reference="reference", attributes="attributes").should_be_called() document.is_document_information("document").should_be_called().will_return(False)
ifc.run("document.edit_reference", reference="document", attributes="attributes").should_be_called()
document.disable_editing_document().should_be_called() document.disable_editing_document().should_be_called()
document.import_references().should_be_called() document.clear_document_tree().should_be_called()
subject.edit_reference(ifc, document, reference="reference") document.get_active_breadcrumb().should_be_called().will_return("parent")
document.import_subdocuments("parent").should_be_called()
document.import_references("parent").should_be_called()
subject.edit_document(ifc, document, document="document")
class TestRemoveDocument: class TestRemoveDocument:
def test_remove_information(self, ifc, document): def test_remove_information(self, ifc, document):
document.clear_document_tree().should_be_called()
document.is_document_information("document").should_be_called().will_return(True) document.is_document_information("document").should_be_called().will_return(True)
ifc.run("document.remove_document", document="document").should_be_called() ifc.run("document.remove_information", information="document").should_be_called()
document.import_information().should_be_called() document.get_active_breadcrumb().should_be_called().will_return(None)
document.disable_editing_document().should_be_called() document.import_project_documents().should_be_called()
subject.remove_document(ifc, document, document="document") subject.remove_document(ifc, document, document="document")
def test_remove_reference(self, ifc, document): def test_remove_reference(self, ifc, document):
document.clear_document_tree().should_be_called()
document.is_document_information("document").should_be_called().will_return(False) document.is_document_information("document").should_be_called().will_return(False)
ifc.run("document.remove_document", document="document").should_be_called() ifc.run("document.remove_reference", reference="document").should_be_called()
document.import_references().should_be_called() document.get_active_breadcrumb().should_be_called().will_return("parent")
document.disable_editing_document().should_be_called() document.import_subdocuments("parent").should_be_called()
document.import_references("parent").should_be_called()
subject.remove_document(ifc, document, document="document") subject.remove_document(ifc, document, document="document")
class TestEnableAssigningDocument:
def test_run(self, document):
document.import_references().should_be_called()
document.enable_reference_editing_ui().should_be_called()
document.disable_editing_document().should_be_called()
document.enable_document_assignment_ui("obj").should_be_called()
subject.enable_assigning_document(document, obj="obj")
class TestDisableAssigningDocument:
def test_run(self, document):
document.disable_document_assignment_ui("obj").should_be_called()
subject.disable_assigning_document(document, obj="obj")
class TestAssignDocument: class TestAssignDocument:
def test_run(self, ifc): def test_run(self, ifc):
ifc.run("document.assign_document", product="product", document="document").should_be_called() ifc.run("document.assign_document", product="product", document="document").should_be_called()
+2 -1
View File
@@ -141,7 +141,8 @@ class TestDisableEditingDrawings:
class TestAddDrawing: class TestAddDrawing:
def test_run(self, ifc, collector, drawing): def test_run(self, ifc, collector, drawing):
drawing.ensure_unique_drawing_name("UNTITLED").should_be_called().will_return("name") drawing.generate_drawing_name("target_view", "location_hint").should_be_called().will_return("drawing_name")
drawing.ensure_unique_drawing_name("drawing_name").should_be_called().will_return("name")
drawing.generate_drawing_matrix("target_view", "location_hint").should_be_called().will_return("matrix") drawing.generate_drawing_matrix("target_view", "location_hint").should_be_called().will_return("matrix")
drawing.create_camera("name", "matrix").should_be_called().will_return("obj") drawing.create_camera("name", "matrix").should_be_called().will_return("obj")
drawing.get_body_context().should_be_called().will_return("context") drawing.get_body_context().should_be_called().will_return("context")
+127 -5
View File
@@ -17,7 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import blenderbim.core.material as subject import blenderbim.core.material as subject
from test.core.bootstrap import ifc, material from test.core.bootstrap import ifc, material, style
class TestUnlinkMaterial: class TestUnlinkMaterial:
@@ -26,9 +26,131 @@ class TestUnlinkMaterial:
subject.unlink_material(ifc, obj="obj") subject.unlink_material(ifc, obj="obj")
class TestAddDefaultMaterial: class TestAddMaterial:
def test_run(self, ifc, material): def test_add_a_default_material(self, ifc, material, style):
material.add_default_material_object().should_be_called().will_return("obj") material.add_default_material_object().should_be_called().will_return("obj")
ifc.run("material.add_material", name="Default").should_be_called().will_return("material") material.get_name("obj").should_be_called().will_return("name")
ifc.run("material.add_material", name="name").should_be_called().will_return("material")
ifc.link("material", "obj").should_be_called() ifc.link("material", "obj").should_be_called()
assert subject.add_default_material(ifc, material) == "obj" style.get_style("obj").should_be_called().will_return(None)
material.is_editing_materials().should_be_called().will_return(False)
assert subject.add_material(ifc, material, style) == "material"
def test_add_a_material_to_a_blender_material_object(self, ifc, material, style):
material.get_name("obj").should_be_called().will_return("name")
ifc.run("material.add_material", name="name").should_be_called().will_return("material")
ifc.link("material", "obj").should_be_called()
style.get_style("obj").should_be_called().will_return(None)
material.is_editing_materials().should_be_called().will_return(False)
assert subject.add_material(ifc, material, style, obj="obj") == "material"
def test_reloading_imported_materials_if_you_are_editing_scene_materials(self, ifc, material, style):
material.get_name("obj").should_be_called().will_return("name")
ifc.run("material.add_material", name="name").should_be_called().will_return("material")
ifc.link("material", "obj").should_be_called()
style.get_style("obj").should_be_called().will_return(None)
material.is_editing_materials().should_be_called().will_return(True)
material.get_active_material_type().should_be_called().will_return("material_type")
material.import_material_definitions("material_type").should_be_called()
assert subject.add_material(ifc, material, style, obj="obj") == "material"
def test_add_a_style_to_the_material_if_the_object_also_has_an_attached_style(self, ifc, material, style):
material.get_name("obj").should_be_called().will_return("name")
ifc.run("material.add_material", name="name").should_be_called().will_return("material")
ifc.link("material", "obj").should_be_called()
style.get_style("obj").should_be_called().will_return("style")
style.get_context("obj").should_be_called().will_return("context")
ifc.run("style.assign_material_style", material="material", style="style", context="context").should_be_called()
material.is_editing_materials().should_be_called().will_return(False)
assert subject.add_material(ifc, material, style, obj="obj") == "material"
def test_not_adding_a_style_if_there_is_no_style_context_available(self, ifc, material, style):
material.get_name("obj").should_be_called().will_return("name")
ifc.run("material.add_material", name="name").should_be_called().will_return("material")
ifc.link("material", "obj").should_be_called()
style.get_style("obj").should_be_called().will_return("style")
style.get_context("obj").should_be_called().will_return(None)
material.is_editing_materials().should_be_called().will_return(False)
assert subject.add_material(ifc, material, style, obj="obj") == "material"
class TestAddMaterialSet:
def test_adding_a_material_set(self, ifc, material):
ifc.run("material.add_material_set", name="Unnamed", set_type="set_type").should_be_called().will_return(
"material"
)
material.is_editing_materials().should_be_called().will_return(False)
assert subject.add_material_set(ifc, material, set_type="set_type") == "material"
def test_adding_a_material_set_and_reloading_imported_materials(self, ifc, material):
ifc.run("material.add_material_set", name="Unnamed", set_type="set_type").should_be_called().will_return(
"material"
)
material.is_editing_materials().should_be_called().will_return(True)
material.get_active_material_type().should_be_called().will_return("material_type")
material.import_material_definitions("material_type").should_be_called()
assert subject.add_material_set(ifc, material, set_type="set_type") == "material"
class TestRemoveMaterial:
def test_removing_a_material(self, ifc, material, style):
ifc.get_object("material").should_be_called().will_return(None)
ifc.unlink(element="material").should_be_called()
ifc.run("material.remove_material", material="material").should_be_called()
material.is_editing_materials().should_be_called().will_return(False)
subject.remove_material(ifc, material, style, material="material")
def test_removing_a_material_and_reloading_imported_materials(self, ifc, material, style):
ifc.get_object("material").should_be_called().will_return(None)
ifc.unlink(element="material").should_be_called()
ifc.run("material.remove_material", material="material").should_be_called()
material.is_editing_materials().should_be_called().will_return(True)
material.get_active_material_type().should_be_called().will_return("material_type")
material.import_material_definitions("material_type").should_be_called()
subject.remove_material(ifc, material, style, material="material")
def test_removing_a_material_object_if_it_has_no_style(self, ifc, material, style):
ifc.get_object("material").should_be_called().will_return("obj")
ifc.unlink(element="material").should_be_called()
ifc.run("material.remove_material", material="material").should_be_called()
style.get_style("obj").should_be_called().will_return(None)
material.delete_object("obj").should_be_called()
material.is_editing_materials().should_be_called().will_return(False)
subject.remove_material(ifc, material, style, material="material")
def test_preserving_a_material_object_if_it_is_still_used_as_a_style(self, ifc, material, style):
ifc.get_object("material").should_be_called().will_return("obj")
ifc.unlink(element="material").should_be_called()
ifc.run("material.remove_material", material="material").should_be_called()
style.get_style("obj").should_be_called().will_return("style")
material.is_editing_materials().should_be_called().will_return(False)
subject.remove_material(ifc, material, style, material="material")
class TestRemoveMaterialSet:
def test_run(self, ifc, material):
ifc.run("material.remove_material_set", material="material").should_be_called()
material.is_editing_materials().should_be_called().will_return(True)
material.get_active_material_type().should_be_called().will_return("material_type")
material.import_material_definitions("material_type").should_be_called()
subject.remove_material_set(ifc, material, material="material")
class TestLoadMaterials:
def test_run(self, material):
material.import_material_definitions("material_type").should_be_called()
material.enable_editing_materials().should_be_called()
subject.load_materials(material, "material_type")
class TestDisableEditingMaterials:
def test_run(self, material):
material.disable_editing_materials().should_be_called()
subject.disable_editing_materials(material)
class TestSelectByMaterial:
def test_run(self, material):
material.get_elements_by_material("material").should_be_called().will_return("elements")
material.select_elements("elements").should_be_called()
subject.select_by_material(material, material="material")
+38 -4
View File
@@ -17,7 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import blenderbim.core.style as subject import blenderbim.core.style as subject
from test.core.bootstrap import ifc, style from test.core.bootstrap import ifc, material, style
class TestAddStyle: class TestAddStyle:
@@ -53,11 +53,32 @@ class TestAddStyle:
class TestRemoveStyle: class TestRemoveStyle:
def test_run(self, ifc, style): def test_removing_a_style(self, ifc, material, style):
style.get_style("obj").should_be_called().will_return("style") ifc.get_object("style").should_be_called().will_return("obj")
ifc.unlink(obj="obj", element="style").should_be_called() ifc.unlink(obj="obj", element="style").should_be_called()
ifc.run("style.remove_style", style="style").should_be_called() ifc.run("style.remove_style", style="style").should_be_called()
subject.remove_style(ifc, style, obj="obj") ifc.get_entity("obj").should_be_called().will_return("material")
style.is_editing_styles().should_be_called().will_return(False)
subject.remove_style(ifc, material, style, style="style")
def test_removing_a_style_and_reloading_imported_styles(self, ifc, material, style):
ifc.get_object("style").should_be_called().will_return("obj")
ifc.unlink(obj="obj", element="style").should_be_called()
ifc.run("style.remove_style", style="style").should_be_called()
ifc.get_entity("obj").should_be_called().will_return("material")
style.is_editing_styles().should_be_called().will_return(True)
style.get_active_style_type().should_be_called().will_return("style_type")
style.import_presentation_styles("style_type").should_be_called()
subject.remove_style(ifc, material, style, style="style")
def test_removing_an_object_if_it_is_not_still_used_for_a_material(self, ifc, material, style):
ifc.get_object("style").should_be_called().will_return("obj")
ifc.unlink(obj="obj", element="style").should_be_called()
ifc.run("style.remove_style", style="style").should_be_called()
ifc.get_entity("obj").should_be_called().will_return(None)
material.delete_object("obj").should_be_called()
style.is_editing_styles().should_be_called().will_return(False)
subject.remove_style(ifc, material, style, style="style")
class TestUpdateStyleColours: class TestUpdateStyleColours:
@@ -174,3 +195,16 @@ class TestEditStyle:
ifc.run("style.edit_presentation_style", style="style", attributes="attributes").should_be_called() ifc.run("style.edit_presentation_style", style="style", attributes="attributes").should_be_called()
style.disable_editing("obj").should_be_called() style.disable_editing("obj").should_be_called()
subject.edit_style(ifc, style, obj="obj") subject.edit_style(ifc, style, obj="obj")
class TestLoadStyles:
def test_run(self, style):
style.import_presentation_styles("style_type").should_be_called()
style.enable_editing_styles().should_be_called()
subject.load_styles(style, style_type="style_type")
class TestDisableEditingStyles:
def test_run(self, style):
style.disable_editing_styles().should_be_called()
subject.disable_editing_styles(style)
+13 -2
View File
@@ -39,7 +39,9 @@ class TestAssignSceneUnits:
"unit.add_si_unit", unit_type="VOLUMEUNIT", name="name", prefix="prefix" "unit.add_si_unit", unit_type="VOLUMEUNIT", name="name", prefix="prefix"
).should_be_called().will_return("volumeunit") ).should_be_called().will_return("volumeunit")
ifc.run("unit.assign_unit", units=["lengthunit", "areaunit", "volumeunit"]).should_be_called() ifc.run("unit.add_conversion_based_unit", name="degree").should_be_called().will_return("planeangleunit")
ifc.run("unit.assign_unit", units=["lengthunit", "areaunit", "volumeunit", "planeangleunit"]).should_be_called()
subject.assign_scene_units(ifc, unit) subject.assign_scene_units(ifc, unit)
def test_creating_and_assigning_imperial_units(self, ifc, unit): def test_creating_and_assigning_imperial_units(self, ifc, unit):
@@ -53,7 +55,9 @@ class TestAssignSceneUnits:
unit.get_scene_unit_name("volume").should_be_called().will_return("volumename") unit.get_scene_unit_name("volume").should_be_called().will_return("volumename")
ifc.run("unit.add_conversion_based_unit", name="volumename").should_be_called().will_return("volumeunit") ifc.run("unit.add_conversion_based_unit", name="volumename").should_be_called().will_return("volumeunit")
ifc.run("unit.assign_unit", units=["lengthunit", "areaunit", "volumeunit"]).should_be_called() ifc.run("unit.add_conversion_based_unit", name="degree").should_be_called().will_return("planeangleunit")
ifc.run("unit.assign_unit", units=["lengthunit", "areaunit", "volumeunit", "planeangleunit"]).should_be_called()
subject.assign_scene_units(ifc, unit) subject.assign_scene_units(ifc, unit)
@@ -115,6 +119,13 @@ class TestAddContextDependentUnit:
assert subject.add_context_dependent_unit(ifc, unit, unit_type="unit_type", name="name") == "unit" assert subject.add_context_dependent_unit(ifc, unit, unit_type="unit_type", name="name") == "unit"
class TestAddConversionBasedUnit:
def test_run(self, ifc, unit):
ifc.run("unit.add_conversion_based_unit", name="name").should_be_called().will_return("unit")
unit.import_units().should_be_called()
assert subject.add_conversion_based_unit(ifc, unit, name="name") == "unit"
class TestEnableEditingUnit: class TestEnableEditingUnit:
def test_run(self, unit): def test_run(self, unit):
unit.set_active_unit("unit").should_be_called() unit.set_active_unit("unit").should_be_called()
+76 -34
View File
@@ -29,12 +29,30 @@ class TestImplementsTool(NewFile):
assert isinstance(subject(), blenderbim.core.tool.Document) assert isinstance(subject(), blenderbim.core.tool.Document)
class TestDisableDocumentAssignmentUI(NewFile): class TestAddBreadcrumb(NewFile):
def test_run(self): def test_run(self):
obj = bpy.data.objects.new("Object", None) ifc = ifcopenshell.file()
obj.BIMObjectDocumentProperties.is_adding = "foo" tool.Ifc().set(ifc)
subject.disable_document_assignment_ui(obj) document = ifc.createIfcDocumentInformation()
assert obj.BIMObjectDocumentProperties.is_adding == "" subject.add_breadcrumb(document)
props = bpy.context.scene.BIMDocumentProperties
assert props.breadcrumbs[0].name == str(document.id())
class TestClearBreadcrumbs(NewFile):
def test_run(self):
props = bpy.context.scene.BIMDocumentProperties
props.breadcrumbs.add()
subject.clear_breadcrumbs()
assert len(props.breadcrumbs) == 0
class TestClearDocumentTree(NewFile):
def test_run(self):
props = bpy.context.scene.BIMDocumentProperties
new = props.documents.add()
subject.clear_document_tree()
assert len(props.documents) == 0
class TestDisableEditingDocument(NewFile): class TestDisableEditingDocument(NewFile):
@@ -46,31 +64,16 @@ class TestDisableEditingDocument(NewFile):
class TestDisableEditingUI(NewFile): class TestDisableEditingUI(NewFile):
def test_run(self): def test_run(self):
bpy.context.scene.BIMDocumentProperties.is_editing = "is_editing" bpy.context.scene.BIMDocumentProperties.is_editing = True
subject.disable_editing_ui() subject.disable_editing_ui()
assert bpy.context.scene.BIMDocumentProperties.is_editing == "" assert bpy.context.scene.BIMDocumentProperties.is_editing == False
class TestEnableDocumentAssignmentUI(NewFile): class TestEnableEditingUI(NewFile):
def test_run(self): def test_run(self):
obj = bpy.data.objects.new("Object", None) bpy.context.scene.BIMDocumentProperties.is_editing = False
obj.BIMObjectDocumentProperties.is_adding = "" subject.enable_editing_ui()
subject.enable_document_assignment_ui(obj) assert bpy.context.scene.BIMDocumentProperties.is_editing == True
assert obj.BIMObjectDocumentProperties.is_adding == "IfcDocumentReference"
class TestEnableInformationEditingUI(NewFile):
def test_run(self):
bpy.context.scene.BIMDocumentProperties.is_editing = ""
subject.enable_information_editing_ui()
assert bpy.context.scene.BIMDocumentProperties.is_editing == "information"
class TestEnableReferenceEditingUI(NewFile):
def test_run(self):
bpy.context.scene.BIMDocumentProperties.is_editing = ""
subject.enable_reference_editing_ui()
assert bpy.context.scene.BIMDocumentProperties.is_editing == "reference"
class TestExportDocumentAttributes(NewFile): class TestExportDocumentAttributes(NewFile):
@@ -95,6 +98,15 @@ class TestExportDocumentAttributes(NewFile):
} }
class TestGetActiveBreadcrumb(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc().set(ifc)
document = ifc.createIfcDocumentInformation()
subject.add_breadcrumb(document)
assert subject.get_active_breadcrumb() == document
class TestImportDocumentAttributes(NewFile): class TestImportDocumentAttributes(NewFile):
def test_importing_information(self): def test_importing_information(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
@@ -149,30 +161,51 @@ class TestImportDocumentAttributes(NewFile):
assert props.document_attributes.get("Description").string_value == "Description" assert props.document_attributes.get("Description").string_value == "Description"
class TestImportInformation(NewFile): class TestImportProjectDocuments(NewFile):
def test_run(self): def test_run(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
tool.Ifc().set(ifc) tool.Ifc().set(ifc)
document = ifc.createIfcDocumentInformation() ifc.createIfcProject()
subject.import_information() document = ifcopenshell.api.run("document.add_information", ifc)
subject.import_project_documents()
props = bpy.context.scene.BIMDocumentProperties props = bpy.context.scene.BIMDocumentProperties
assert len(props.documents) == 1 assert len(props.documents) == 1
assert props.documents[0].ifc_definition_id == document.id() assert props.documents[0].ifc_definition_id == document.id()
assert props.documents[0].name == "Unnamed" assert props.documents[0].name == "Unnamed"
assert props.documents[0].identification == "*" assert props.documents[0].identification == "X"
assert props.documents[0].is_information is True
class TestImportReference(NewFile): class TestImportReferences(NewFile):
def test_run(self): def test_run(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
tool.Ifc().set(ifc) tool.Ifc().set(ifc)
document = ifc.createIfcDocumentReference() ifc.createIfcProject()
subject.import_references() document = ifcopenshell.api.run("document.add_information", ifc)
reference = ifcopenshell.api.run("document.add_reference", ifc, information=document)
subject.import_references(document)
props = bpy.context.scene.BIMDocumentProperties props = bpy.context.scene.BIMDocumentProperties
assert len(props.documents) == 1 assert len(props.documents) == 1
assert props.documents[0].ifc_definition_id == document.id() assert props.documents[0].ifc_definition_id == reference.id()
assert props.documents[0].name == "Unnamed" assert props.documents[0].name == "Unnamed"
assert props.documents[0].identification == "*" assert props.documents[0].identification == "*"
assert props.documents[0].is_information is False
class TestImportSubdocuments(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc().set(ifc)
ifc.createIfcProject()
document = ifcopenshell.api.run("document.add_information", ifc)
subdocument = ifcopenshell.api.run("document.add_information", ifc, parent=document)
subject.import_subdocuments(document)
props = bpy.context.scene.BIMDocumentProperties
assert len(props.documents) == 1
assert props.documents[0].ifc_definition_id == subdocument.id()
assert props.documents[0].name == "Unnamed"
assert props.documents[0].identification == "X"
assert props.documents[0].is_information is True
class TestIsDocumentInformation(NewFile): class TestIsDocumentInformation(NewFile):
@@ -184,6 +217,15 @@ class TestIsDocumentInformation(NewFile):
assert subject.is_document_information(reference) is False assert subject.is_document_information(reference) is False
class TestRemoveLatestBreadcrumb(NewFile):
def test_run(self):
props = bpy.context.scene.BIMDocumentProperties
props.breadcrumbs.add()
props.breadcrumbs.add()
subject.remove_latest_breadcrumb()
assert len(props.breadcrumbs) == 1
class TestSetActiveDocument(NewFile): class TestSetActiveDocument(NewFile):
def test_run(self): def test_run(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
+3
View File
@@ -399,10 +399,13 @@ class TestImportDrawings(NewFile):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
tool.Ifc.set(ifc) tool.Ifc.set(ifc)
drawing = ifc.createIfcAnnotation(Name="FOOBAR", ObjectType="DRAWING") drawing = ifc.createIfcAnnotation(Name="FOOBAR", ObjectType="DRAWING")
pset = ifcopenshell.api.run("pset.add_pset", ifc, product=drawing, name="EPset_Drawing")
ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"TargetView": "PLAN_VIEW"})
subject.import_drawings() subject.import_drawings()
props = bpy.context.scene.DocProperties props = bpy.context.scene.DocProperties
assert props.drawings[0].ifc_definition_id == drawing.id() assert props.drawings[0].ifc_definition_id == drawing.id()
assert props.drawings[0].name == "FOOBAR" assert props.drawings[0].name == "FOOBAR"
assert props.drawings[0].target_view == "PLAN_VIEW"
class TestImportSheets(NewFile): class TestImportSheets(NewFile):
+119
View File
@@ -34,3 +34,122 @@ class TestAddDefaultMaterialObject(NewFile):
material = subject.add_default_material_object() material = subject.add_default_material_object()
assert isinstance(material, bpy.types.Material) assert isinstance(material, bpy.types.Material)
assert material.name == "Default" assert material.name == "Default"
class TestDeleteObject(NewFile):
def test_run(self):
material = subject.add_default_material_object()
assert bpy.data.materials.get("Default")
subject.delete_object(material)
assert not bpy.data.materials.get("Default")
class TestDisableEditingMaterials(NewFile):
def test_run(self):
bpy.context.scene.BIMMaterialProperties.is_editing = True
subject.disable_editing_materials()
assert bpy.context.scene.BIMMaterialProperties.is_editing is False
class TestEnableEditingMaterials(NewFile):
def test_run(self):
bpy.context.scene.BIMMaterialProperties.is_editing = False
subject.enable_editing_materials()
assert bpy.context.scene.BIMMaterialProperties.is_editing is True
class TestGetActiveMaterialType(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
bpy.context.scene.BIMMaterialProperties.material_type = "IfcMaterial"
assert subject.get_active_material_type() == "IfcMaterial"
bpy.context.scene.BIMMaterialProperties.material_type = "IfcMaterialLayerSet"
assert subject.get_active_material_type() == "IfcMaterialLayerSet"
class TestGetElementsByMaterial(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall")
material = ifcopenshell.api.run("material.add_material", ifc)
ifcopenshell.api.run("material.assign_material", ifc, product=element, material=material)
assert subject.get_elements_by_material(material) == {element}
class TestGetName(NewFile):
def test_run(self):
assert subject.get_name(bpy.data.materials.new("Material")) == "Material"
class TestImportMaterialDefinitions(NewFile):
def test_import_materials(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
material = ifc.createIfcMaterial(Name="Name")
subject.import_material_definitions("IfcMaterial")
props = bpy.context.scene.BIMMaterialProperties
assert props.materials[0].ifc_definition_id == material.id()
assert props.materials[0].name == "Name"
assert props.materials[0].total_elements == 0
def test_import_material_layer_sets(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
material = ifc.createIfcMaterialLayerSet(LayerSetName="Name")
subject.import_material_definitions("IfcMaterialLayerSet")
props = bpy.context.scene.BIMMaterialProperties
assert props.materials[0].ifc_definition_id == material.id()
assert props.materials[0].name == "Name"
assert props.materials[0].total_elements == 0
def test_import_material_profile_sets(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
material = ifc.createIfcMaterialProfileSet(Name="Name")
subject.import_material_definitions("IfcMaterialProfileSet")
props = bpy.context.scene.BIMMaterialProperties
assert props.materials[0].ifc_definition_id == material.id()
assert props.materials[0].name == "Name"
assert props.materials[0].total_elements == 0
def test_import_material_constituent_sets(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
material = ifc.createIfcMaterialConstituentSet(Name="Name")
subject.import_material_definitions("IfcMaterialConstituentSet")
props = bpy.context.scene.BIMMaterialProperties
assert props.materials[0].ifc_definition_id == material.id()
assert props.materials[0].name == "Name"
assert props.materials[0].total_elements == 0
def test_import_material_lists(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
material = ifc.createIfcMaterialList()
subject.import_material_definitions("IfcMaterialList")
props = bpy.context.scene.BIMMaterialProperties
assert props.materials[0].ifc_definition_id == material.id()
assert props.materials[0].name == "Unnamed"
assert props.materials[0].total_elements == 0
class TestIsEditingMaterials(NewFile):
def test_run(self):
bpy.context.scene.BIMMaterialProperties.is_editing = False
subject.is_editing_materials() is False
bpy.context.scene.BIMMaterialProperties.is_editing = True
subject.is_editing_materials() is True
class TestSelectElements(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc().set(ifc)
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcPump")
obj = bpy.data.objects.new("Object", None)
bpy.context.scene.collection.objects.link(obj)
tool.Ifc.link(element, obj)
subject.select_elements([element])
assert obj in bpy.context.selected_objects
+75
View File
@@ -50,6 +50,13 @@ class TestDisableEditing(NewFile):
assert obj.BIMStyleProperties.is_editing is False assert obj.BIMStyleProperties.is_editing is False
class TestDisableEditingStyles(NewFile):
def test_run(self):
bpy.context.scene.BIMStylesProperties.is_editing = True
subject.disable_editing_styles()
assert bpy.context.scene.BIMStylesProperties.is_editing is False
class TestEnableEditing(NewFile): class TestEnableEditing(NewFile):
def test_run(self): def test_run(self):
obj = bpy.data.materials.new("Material") obj = bpy.data.materials.new("Material")
@@ -57,6 +64,13 @@ class TestEnableEditing(NewFile):
assert obj.BIMStyleProperties.is_editing is True assert obj.BIMStyleProperties.is_editing is True
class TestEnableEditingStyles(NewFile):
def test_run(self):
bpy.context.scene.BIMStylesProperties.is_editing = False
subject.enable_editing_styles()
assert bpy.context.scene.BIMStylesProperties.is_editing is True
class TestExportSurfaceAttributes(NewFile): class TestExportSurfaceAttributes(NewFile):
def test_run(self): def test_run(self):
TestImportSurfaceAttributes().test_run() TestImportSurfaceAttributes().test_run()
@@ -64,6 +78,16 @@ class TestExportSurfaceAttributes(NewFile):
assert subject.export_surface_attributes(obj) == {"Name": "Name", "Side": "BOTH"} assert subject.export_surface_attributes(obj) == {"Name": "Name", "Side": "BOTH"}
class TestGetActiveStyleType(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
bpy.context.scene.BIMStylesProperties.style_type = "IfcSurfaceStyle"
assert subject.get_active_style_type() == "IfcSurfaceStyle"
bpy.context.scene.BIMStylesProperties.style_type = "IfcCurveStyle"
assert subject.get_active_style_type() == "IfcCurveStyle"
class TestGetContext(NewFile): class TestGetContext(NewFile):
def test_run(self): def test_run(self):
bpy.ops.bim.create_project() bpy.ops.bim.create_project()
@@ -116,6 +140,7 @@ class TestGetSurfaceRenderingAttributes(NewFile):
"Green": 0.5, "Green": 0.5,
"Blue": 0.5, "Blue": 0.5,
}, },
"SpecularColour": 0.0,
"SpecularHighlight": {"IfcSpecularRoughness": 0.2}, "SpecularHighlight": {"IfcSpecularRoughness": 0.2},
"ReflectanceMethod": "NOTDEFINED", "ReflectanceMethod": "NOTDEFINED",
} }
@@ -351,3 +376,53 @@ class TestImportSurfaceAttributes(NewFile):
assert len(obj.BIMStyleProperties.attributes) == 2 assert len(obj.BIMStyleProperties.attributes) == 2
assert obj.BIMStyleProperties.attributes.get("Name").string_value == "Name" assert obj.BIMStyleProperties.attributes.get("Name").string_value == "Name"
assert obj.BIMStyleProperties.attributes.get("Side").enum_value == "BOTH" assert obj.BIMStyleProperties.attributes.get("Side").enum_value == "BOTH"
class TestImportPresentationStyles(NewFile):
def test_import_curve_styles(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
style = ifc.createIfcCurveStyle(Name="Name")
subject.import_presentation_styles("IfcCurveStyle")
props = bpy.context.scene.BIMStylesProperties
assert props.styles[0].ifc_definition_id == style.id()
assert props.styles[0].name == "Name"
assert props.styles[0].total_elements == 0
def test_import_fillarea_styles(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
style = ifc.createIfcFillAreaStyle(Name="Name")
subject.import_presentation_styles("IfcFillAreaStyle")
props = bpy.context.scene.BIMStylesProperties
assert props.styles[0].ifc_definition_id == style.id()
assert props.styles[0].name == "Name"
assert props.styles[0].total_elements == 0
def test_import_surface_styles(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
style = ifc.createIfcSurfaceStyle(Name="Name")
subject.import_presentation_styles("IfcSurfaceStyle")
props = bpy.context.scene.BIMStylesProperties
assert props.styles[0].ifc_definition_id == style.id()
assert props.styles[0].name == "Name"
assert props.styles[0].total_elements == 0
def test_import_text_styles(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
style = ifc.createIfcTextStyle(Name="Name")
subject.import_presentation_styles("IfcTextStyle")
props = bpy.context.scene.BIMStylesProperties
assert props.styles[0].ifc_definition_id == style.id()
assert props.styles[0].name == "Name"
assert props.styles[0].total_elements == 0
class TestIsEditingStyles(NewFile):
def test_run(self):
bpy.context.scene.BIMStylesProperties.is_editing = False
subject.is_editing_styles() is False
bpy.context.scene.BIMStylesProperties.is_editing = True
subject.is_editing_styles() is True
+2 -2
View File
@@ -299,7 +299,7 @@ private:
public: public:
MAKE_TYPE_NAME(Kernel)() MAKE_TYPE_NAME(Kernel)()
: IfcGeom::Kernel(0) : IfcGeom::Kernel()
, deflection_tolerance(0.001) , deflection_tolerance(0.001)
, max_faces_to_orient(-1.0) , max_faces_to_orient(-1.0)
, ifc_length_unit(1.0) , ifc_length_unit(1.0)
@@ -321,7 +321,7 @@ public:
{} {}
MAKE_TYPE_NAME(Kernel)(const MAKE_TYPE_NAME(Kernel)& other) MAKE_TYPE_NAME(Kernel)(const MAKE_TYPE_NAME(Kernel)& other)
: IfcGeom::Kernel(0) : IfcGeom::Kernel()
, deflection_tolerance(other.deflection_tolerance) , deflection_tolerance(other.deflection_tolerance)
, max_faces_to_orient(other.max_faces_to_orient) , max_faces_to_orient(other.max_faces_to_orient)
, ifc_length_unit(other.ifc_length_unit) , ifc_length_unit(other.ifc_length_unit)
+2 -2
View File
@@ -563,8 +563,8 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcRectangleHollowProfileDef* l,
const double y = l->YDim() / 2.0f * getValue(GV_LENGTH_UNIT); const double y = l->YDim() / 2.0f * getValue(GV_LENGTH_UNIT);
const double d = l->WallThickness() * getValue(GV_LENGTH_UNIT); const double d = l->WallThickness() * getValue(GV_LENGTH_UNIT);
const bool fr1 = !l->OuterFilletRadius(); const bool fr1 = !!l->OuterFilletRadius();
const bool fr2 = !l->InnerFilletRadius(); const bool fr2 = !!l->InnerFilletRadius();
const double r1 = fr1 ? (*l->OuterFilletRadius()) * getValue(GV_LENGTH_UNIT) : 0.; const double r1 = fr1 ? (*l->OuterFilletRadius()) * getValue(GV_LENGTH_UNIT) : 0.;
const double r2 = fr2 ? (*l->InnerFilletRadius()) * getValue(GV_LENGTH_UNIT) : 0.; const double r2 = fr2 ? (*l->InnerFilletRadius()) * getValue(GV_LENGTH_UNIT) : 0.;
+6 -2
View File
@@ -4240,6 +4240,7 @@ bool IfcGeom::Kernel::wire_intersections(const TopoDS_Wire& wire, TopTools_ListO
: (std::min)(min_edge_length(wire) / 2., getValue(GV_PRECISION) * 10.); : (std::min)(min_edge_length(wire) / 2., getValue(GV_PRECISION) * 10.);
} }
// @todo: should this start from 0 in case of n > 64?
for (int i = 2; i < n; ++i) { for (int i = 2; i < n; ++i) {
std::vector<int> js; std::vector<int> js;
@@ -5224,8 +5225,11 @@ bool IfcGeom::Kernel::boolean_operation(const TopoDS_Shape& a_input, const TopTo
PERF("boolean operation: result min face-face dist check"); PERF("boolean operation: result min face-face dist check");
if ((v = min_face_face_distance(r, 1.e-4)) < 1.e-4) { if ((v = min_face_face_distance(r, 1.e-4)) < 1.e-4) {
reason = 2; // #2095 Check if this distance wasn't already realized in the input first operand.
success = false; if (v < min_face_face_distance(a, 1.e-4)) {
reason = 2;
success = false;
}
} }
} }
+14 -1
View File
@@ -92,7 +92,12 @@
#include <TopTools_ListIteratorOfListOfShape.hxx> #include <TopTools_ListIteratorOfListOfShape.hxx>
#include <BRepAdaptor_CompCurve.hxx> #include <BRepAdaptor_CompCurve.hxx>
#include <Standard_Version.hxx>
#if OCC_VERSION_HEX < 0x70600
#include <BRepAdaptor_HCompCurve.hxx> #include <BRepAdaptor_HCompCurve.hxx>
#endif
#include <Approx_Curve3d.hxx> #include <Approx_Curve3d.hxx>
#include "../ifcgeom/IfcGeom.h" #include "../ifcgeom/IfcGeom.h"
@@ -483,8 +488,12 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcTrimmedCurve* l, TopoDS_Wire&
Logger::Warning("Approximating BasisCurve due to possible discontinuities", l); Logger::Warning("Approximating BasisCurve due to possible discontinuities", l);
TopoDS_Wire w; TopoDS_Wire w;
if (!convert_wire(basis_curve, w)) return false; if (!convert_wire(basis_curve, w)) return false;
#if OCC_VERSION_HEX < 0x70600
BRepAdaptor_CompCurve cc(w, true); BRepAdaptor_CompCurve cc(w, true);
Handle(Adaptor3d_HCurve) hcc = Handle(Adaptor3d_HCurve)(new BRepAdaptor_HCompCurve(cc)); Handle(Adaptor3d_HCurve) hcc = Handle(Adaptor3d_HCurve)(new BRepAdaptor_HCompCurve(cc));
#else
auto hcc = new BRepAdaptor_CompCurve(w, true);
#endif
// @todo, arbitrary numbers here, note they cannot be too high as contiguous memory is allocated based on them. // @todo, arbitrary numbers here, note they cannot be too high as contiguous memory is allocated based on them.
Approx_Curve3d approx(hcc, getValue(GV_PRECISION), GeomAbs_C0, 10, 10); Approx_Curve3d approx(hcc, getValue(GV_PRECISION), GeomAbs_C0, 10, 10);
curve = approx.Curve(); curve = approx.Curve();
@@ -606,7 +615,10 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcTrimmedCurve* l, TopoDS_Wire&
} }
} }
if (isConic && ALMOST_THE_SAME(fmod(flts[1]-flts[0],M_PI*2.), 0., 100 * getValue(GV_PRECISION) / (2 * M_PI * radius))) { // @todo is 100. not too much? Check with the original issue.
const double precision_markup = getValue(IfcGeom::Kernel::GV_PRECISION_FACTOR) == 1. ? 1. : 100.;
if (isConic && ALMOST_THE_SAME(fmod(flts[1]-flts[0],M_PI*2.), 0., precision_markup * getValue(GV_PRECISION) / (2 * M_PI * radius))) {
e = BRepBuilderAPI_MakeEdge(curve).Edge(); e = BRepBuilderAPI_MakeEdge(curve).Edge();
} else { } else {
BRepBuilderAPI_MakeEdge me (curve,flts[0],flts[1]); BRepBuilderAPI_MakeEdge me (curve,flts[0],flts[1]);
@@ -681,6 +693,7 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcPolyline* l, TopoDS_Wire& resu
polygon.Append(pnt); polygon.Append(pnt);
} }
// @todo the strict tolerance should also govern these arbitrary precision increases
const double eps = getValue(GV_PRECISION) * 10; const double eps = getValue(GV_PRECISION) * 10;
const bool closed_by_proximity = polygon.Length() >= 3 && polygon.First().Distance(polygon.Last()) < eps; const bool closed_by_proximity = polygon.Length() >= 3 && polygon.First().Distance(polygon.Last()) < eps;
if (closed_by_proximity) { if (closed_by_proximity) {
+4 -1
View File
@@ -42,6 +42,9 @@ namespace IfcGeom {
private: private:
Kernel* implementation_; Kernel* implementation_;
protected:
Kernel() {};
public: public:
// Tolerances and settings for various geometrical operations: // Tolerances and settings for various geometrical operations:
enum GeomValue { enum GeomValue {
@@ -80,7 +83,7 @@ namespace IfcGeom {
GV_BOOLEAN_ATTEMPT_2D GV_BOOLEAN_ATTEMPT_2D
}; };
Kernel(IfcParse::IfcFile* file_ = 0); Kernel(IfcParse::IfcFile* file_);
virtual ~Kernel() {} virtual ~Kernel() {}
@@ -22,12 +22,36 @@ import ifcopenshell
class Usecase: class Usecase:
def __init__(self, file, **settings): def __init__(self, file, **settings):
self.file = file self.file = file
self.settings = {} self.settings = {"parent": None}
for key, value in settings.items(): for key, value in settings.items():
self.settings[key] = value self.settings[key] = value
def execute(self): def execute(self):
id_attribute = "DocumentId" if self.file.schema == "IFC2X3" else "Identification" id_attribute = "DocumentId" if self.file.schema == "IFC2X3" else "Identification"
return self.file.create_entity( information = self.file.create_entity(
"IfcDocumentInformation", **{id_attribute: ifcopenshell.guid.new(), "Name": "Unnamed"} "IfcDocumentInformation", **{id_attribute: "X", "Name": "Unnamed"}
) )
parent = self.settings["parent"]
if not parent and self.file.by_type("IfcProject"):
parent = self.file.by_type("IfcProject")[0]
if parent.is_a("IfcProject") or parent.is_a("IfcContext"):
self.file.create_entity(
"IfcRelAssociatesDocument",
GlobalId=ifcopenshell.guid.new(),
OwnerHistory=ifcopenshell.api.run("owner.create_owner_history", self.file),
RelatingDocument=information,
RelatedObjects=[parent],
)
elif parent.is_a("IfcDocumentInformation"):
if parent.IsPointer:
rel = parent.IsPointer[0]
documents = set(rel.RelatedDocuments)
documents.add(information)
rel.RelatedDocuments = list(documents)
else:
self.file.create_entity(
"IfcDocumentInformationRelationship",
RelatingDocument=parent,
RelatedDocuments=[information]
)
return information
@@ -16,16 +16,15 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
class Usecase: class Usecase:
def __init__(self, file, **settings): def __init__(self, file, **settings):
self.file = file self.file = file
self.settings = {} self.settings = {"information": None}
for key, value in settings.items(): for key, value in settings.items():
self.settings[key] = value self.settings[key] = value
def execute(self): def execute(self):
id_attribute = "ItemReference" if self.file.schema == "IFC2X3" else "Identification" if self.file.schema == "IFC2X3":
return self.file.create_entity("IfcDocumentReference", **{id_attribute: ifcopenshell.guid.new()}) return self.file.create_entity("IfcDocumentReference")
return self.file.create_entity("IfcDocumentReference", ReferencedDocument=self.settings["information"])
@@ -56,7 +56,7 @@ class Usecase:
"IfcRelAssociatesDocument", "IfcRelAssociatesDocument",
**{ **{
"GlobalId": ifcopenshell.guid.new(), "GlobalId": ifcopenshell.guid.new(),
# TODO: owner history "OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"RelatingDocument": self.settings["document"], "RelatingDocument": self.settings["document"],
} }
) )
@@ -0,0 +1,39 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell 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 Lesser General Public License for more details.
#
# 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.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"information": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for reference in self.settings["information"].HasDocumentReferences or []:
ifcopenshell.api.run("document.remove_reference", self.file, reference=reference)
for rel in self.settings["information"].IsPointer or []:
for information in rel.RelatedDocuments:
ifcopenshell.api.run("document.remove_information", self.file, information=information)
self.file.remove(rel)
for rel in self.settings["information"].DocumentInfoForObjects or []:
self.file.remove(rel)
self.file.remove(self.settings["information"])
@@ -1,5 +1,5 @@
# IfcOpenShell - IFC toolkit and geometry engine # IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com> # Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
# #
# This file is part of IfcOpenShell. # This file is part of IfcOpenShell.
# #
@@ -20,12 +20,11 @@
class Usecase: class Usecase:
def __init__(self, file, **settings): def __init__(self, file, **settings):
self.file = file self.file = file
self.settings = {"document": None} self.settings = {"reference": None}
for key, value in settings.items(): for key, value in settings.items():
self.settings[key] = value self.settings[key] = value
def execute(self): def execute(self):
self.file.remove(self.settings["document"]) for rel in self.settings["reference"].DocumentRefForObjects or []:
for rel in self.file.by_type("IfcRelAssociatesDocument"): self.file.remove(rel)
if not rel.RelatingDocument: self.file.remove(self.settings["reference"])
self.file.remove(rel)

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