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 pytest
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
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(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
# OPTION(BUILD_QTVIEWER "Build IfcOpenShell Qt GUI Viewer (requires Qt 4 framework)." OFF)
include(GNUInstallDirs)
@@ -478,10 +482,6 @@ if(HDF5_SUPPORT)
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_HDF5)
endif()
IF(NOT CMAKE_BUILD_TYPE)
SET(CMAKE_BUILD_TYPE "Release")
ENDIF()
if(ENABLE_BUILD_OPTIMIZATIONS)
if(MSVC)
# 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
endif
ifeq ($(PYVERSION), py310)
# https://github.com/humanoid-path-planner/hpp-fcl/issues/272
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/linux-64/hpp-fcl-1.7.5-py39hbcdfc36_0.tar.bz2
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/linux-64/hpp-fcl-1.8.0-py310hdaf7e41_1.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
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
endif
ifeq ($(PYVERSION), py310)
# https://github.com/humanoid-path-planner/hpp-fcl/issues/272
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-64/hpp-fcl-1.7.5-py39h1e32b98_0.tar.bz2
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/osx-64/hpp-fcl-1.8.0-py310h651ac30_1.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
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
endif
ifeq ($(PYVERSION), py310)
# https://github.com/humanoid-path-planner/hpp-fcl/issues/272
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/win-64/hpp-fcl-1.7.5-py39h2e7c763_0.tar.bz2
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/win-64/hpp-fcl-1.8.0-py310hc5a3c62_1.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
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/
# 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*
cp -r dist/working/ifcopenshell dist/blenderbim/libs/site/packages/
@@ -292,8 +289,6 @@ endif
rm -rf dist/working
# Required by IFCClash
# https://github.com/humanoid-path-planner/hpp-fcl/issues/272
ifneq ($(PYVERSION), py310)
mkdir dist/working
cd dist/working && wget $(HPPFCL_URL)
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/
endif
rm -rf dist/working
# https://github.com/humanoid-path-planner/hpp-fcl/issues/272
endif
# Required by BIMTester
mkdir dist/working
+9 -8
View File
@@ -79,17 +79,18 @@ for name in modules.keys():
classes = [
operator.OpenUri,
operator.SelectDataDir,
operator.SelectSchemaDir,
operator.SelectIfcFile,
operator.OpenUpstream,
operator.AddIfcFile,
operator.BIM_OT_add_section_plane,
operator.BIM_OT_remove_section_plane,
operator.ReloadIfcFile,
operator.AddIfcFile,
operator.RemoveIfcFile,
operator.ConfigureVisibility,
operator.OpenUpstream,
operator.OpenUri,
operator.ReloadIfcFile,
operator.RemoveIfcFile,
operator.SelectDataDir,
operator.SelectIfcFile,
operator.SelectSchemaDir,
operator.SelectURIAttribute,
prop.StrProperty,
prop.ObjProperty,
prop.Attribute,
@@ -38,7 +38,7 @@
<script type="text/javascript">
var g = new JSGantt.GanttChart(document.getElementById('GanttChartDIV'), 'day');
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,
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
@@ -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,
vShowRes: false, // Disable the resource 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.
vTotalHeight: 900,
});
@@ -5,6 +5,20 @@
</g>
<g id="door-tag">
<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>
</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.util.placement
import blenderbim.tool as tool
import blenderbim.core.geometry
import blenderbim.core.aggregate
import blenderbim.core.spatial
import blenderbim.core.style
@@ -47,7 +48,7 @@ class IfcExporter:
self.sync_deletions()
self.sync_all_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":
with tempfile.TemporaryDirectory() as unzipped_path:
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)
if grid_obj.matrix_world != obj.matrix_world:
bpy.ops.bim.update_representation(obj=obj.name)
tool.Geometry.record_object_position(obj)
def get_application_name(self):
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 blenderbim.bim.ifc import IfcStore
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.style.data import Data as StyleData
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 obj.BIMObjectProperties.ifc_definition_id:
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()
if obj.BIMMaterialProperties.ifc_style_id:
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
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"):
TypeData.purge()
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):
+5
View File
@@ -48,6 +48,9 @@ def draw_attribute(attribute, layout, copy_operator=None):
if copy_operator:
op = layout.operator(f"{copy_operator}", text="", icon="COPYDOWN")
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):
@@ -78,6 +81,8 @@ def import_attribute(attribute, props, data, callback=None):
props.remove(len(props) - 1)
elif data_type == "string":
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":
new.bool_value = False if new.is_null else data[attribute.name()]
elif data_type == "integer":
+1
View File
@@ -86,6 +86,7 @@ class IfcStore:
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")
cache_settings = ifcopenshell.geom.settings()
cache_settings.set(cache_settings.STRICT_TOLERANCE, True)
try:
IfcStore.cache = ifcopenshell.geom.serializers.hdf5(IfcStore.cache_path, cache_settings)
except:
+15 -15
View File
@@ -163,6 +163,7 @@ class IfcImporter:
self.settings = ifcopenshell.geom.settings()
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(self.settings.STRICT_TOLERANCE, True)
self.settings_native = ifcopenshell.geom.settings()
self.settings_native.set(self.settings_native.INCLUDE_CURVES, True)
self.settings_2d = ifcopenshell.geom.settings()
@@ -283,11 +284,13 @@ class IfcImporter:
if c.ContextIdentifier in ["Body", "Facetation"]
]
# Ideally, all representations should be in a subcontext, but some BIM programs don't do this correctly
self.body_contexts.extend([
c.id()
for c in self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False)
if c.ContextType == "Model"
])
self.body_contexts.extend(
[
c.id()
for c in self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False)
if c.ContextType == "Model"
]
)
if self.body_contexts:
self.settings.set_context_ids(self.body_contexts)
# Annotation is to accommodate broken Revit files
@@ -363,7 +366,7 @@ class IfcImporter:
def is_native_swept_disk_solid(self, 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"):
return True
return False
@@ -1295,10 +1298,7 @@ class IfcImporter:
name = style.Name or str(style.id())
blender_material = bpy.data.materials.new(name)
old_definition_id = blender_material.BIMObjectProperties.ifc_definition_id
if not old_definition_id:
self.link_element(style, blender_material)
blender_material.BIMObjectProperties.ifc_definition_id = old_definition_id
self.link_element(style, blender_material)
blender_material.BIMMaterialProperties.ifc_style_id = style.id()
self.material_creator.styles[style.id()] = blender_material
@@ -1352,12 +1352,12 @@ class IfcImporter:
elif surface_style.ReflectanceMethod == "FLAT":
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"]
mix = blender_material.node_tree.nodes.new(type="ShaderNodeMixShader")
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)
@@ -1722,10 +1722,10 @@ class IfcImporter:
):
verts = [None] * len(geometry.verts)
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+1],
geometry.verts[i+2],
geometry.verts[i + 1],
geometry.verts[i + 2],
float(props.blender_eastings) * self.unit_scale,
float(props.blender_northings) * self.unit_scale,
float(props.blender_orthogonal_height) * self.unit_scale,
@@ -19,7 +19,7 @@
import os
from pathlib import Path
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.props import (
PointerProperty,
@@ -33,14 +33,11 @@ from bpy.props import (
)
scenarios_enum = []
classes_enum = []
def purge():
global scenarios_enum
global classes_enum
scenarios_enum = []
classes_enum = []
def getScenarios(self, context):
@@ -68,7 +65,7 @@ class BimTesterProperties(PropertyGroup):
feature: StringProperty(default="", name="Feature / IDS", update=refreshScenarios)
steps: StringProperty(default="", name="Custom Steps")
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")
scenario: EnumProperty(items=getScenarios, name="Scenario")
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
@@ -44,6 +44,19 @@ class Loader:
self.load_settings()
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):
self.settings = ifcopenshell.geom.settings()
self.settings.set(self.settings.EXCLUDE_SOLIDS_AND_SURFACES, False)
@@ -61,13 +74,7 @@ class Loader:
obj = tool.Ifc.get_object(boundary)
if obj:
return obj
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)
mesh = self.create_mesh(boundary)
obj = bpy.data.objects.new(f"{boundary.is_a()}/{boundary.Name}", mesh)
obj.matrix_world = blender_space.matrix_world
boundaries_collection = get_boundaries_collection(blender_space)
@@ -20,35 +20,29 @@ import bpy
from . import ui, prop, operator
classes = (
operator.LoadInformation,
operator.LoadDocumentReferences,
operator.DisableDocumentEditingUI,
operator.EnableEditingDocument,
operator.DisableEditingDocument,
operator.AddInformation,
operator.AddDocumentReference,
operator.EditInformation,
operator.EditDocumentReference,
operator.RemoveDocument,
operator.EnableAssigningDocument,
operator.DisableAssigningDocument,
operator.AddInformation,
operator.AssignDocument,
operator.DisableDocumentEditingUI,
operator.DisableEditingDocument,
operator.EditDocument,
operator.EnableEditingDocument,
operator.LoadDocument,
operator.LoadParentDocument,
operator.LoadProjectDocuments,
operator.RemoveDocument,
operator.UnassignDocument,
prop.Document,
prop.BIMDocumentProperties,
prop.BIMObjectDocumentProperties,
ui.BIM_PT_documents,
ui.BIM_PT_object_documents,
ui.BIM_UL_documents,
ui.BIM_UL_object_documents,
)
def register():
bpy.types.Scene.BIMDocumentProperties = bpy.props.PointerProperty(type=prop.BIMDocumentProperties)
bpy.types.Object.BIMObjectDocumentProperties = bpy.props.PointerProperty(type=prop.BIMObjectDocumentProperties)
def unregister():
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
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import os
import bpy
import ifcopenshell
import ifcopenshell.util.schema
@@ -35,17 +36,29 @@ class DocumentData:
def load(cls):
cls.data = {
"total_information": cls.total_information(),
"total_references": cls.total_references(),
"parent_document": cls.parent_document(),
}
cls.is_loaded = True
@classmethod
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
def total_references(cls):
return len(tool.Ifc.get().by_type("IfcDocumentReference"))
def parent_document(cls):
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:
@@ -69,16 +82,35 @@ class ObjectDocumentData:
if rel.is_a("IfcRelAssociatesDocument"):
if not rel.RelatingDocument.is_a("IfcDocumentReference"):
continue
name = rel.RelatingDocument.Name
if not name and rel.RelatingDocument.ReferencedDocument:
name = rel.RelatingDocument.ReferencedDocument.Name
if tool.Ifc.get_schema() == "IFC2X3":
identification = rel.RelatingDocument.ItemReference
if not identification and rel.RelatingDocument.ReferencedDocument:
identification = rel.RelatingDocument.ReferencedDocument.DocumentId
else:
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(
{
"type": rel.RelatingDocument.is_a(),
"id": rel.RelatingDocument.id(),
"identification": identification,
"name": rel.RelatingDocument.Name,
"name": name,
"location": location,
}
)
return results
@@ -26,22 +26,32 @@ from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.document.data import Data
class LoadInformation(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.load_information"
bl_label = "Load Information"
class LoadProjectDocuments(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.load_project_documents"
bl_label = "Load Project Documents"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.load_information(tool.Document)
core.load_project_documents(tool.Document)
class LoadDocumentReferences(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.load_document_references"
bl_label = "Load Document References"
class LoadDocument(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.load_document"
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"}
def _execute(self, context):
core.load_references(tool.Document)
core.load_parent_document(tool.Document)
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)
class EditInformation(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_information"
class EditDocument(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_document"
bl_label = "Edit Information"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMDocumentProperties
core.edit_information(tool.Ifc, tool.Document, information=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))
core.edit_document(tool.Ifc, tool.Document, document=tool.Ifc.get().by_id(props.active_document_id))
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))
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):
bl_idname = "bim.assign_document"
bl_label = "Assign Document"
@@ -34,6 +34,7 @@ from bpy.props import (
class Document(PropertyGroup):
name: StringProperty(name="Name")
identification: StringProperty(name="Identification")
is_information: BoolProperty(name="Is Information")
ifc_definition_id: IntProperty(name="IFC Definition ID")
@@ -41,12 +42,6 @@ class BIMDocumentProperties(PropertyGroup):
document_attributes: CollectionProperty(name="Document Attributes", type=Attribute)
active_document_id: IntProperty(name="Active Document Id")
documents: CollectionProperty(name="Documents", type=Document)
breadcrumbs: CollectionProperty(name="Breadcrumbs", type=StrProperty)
active_document_index: IntProperty(name="Active Document Index")
is_editing: StringProperty(name="Is Editing")
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"
)
is_editing: BoolProperty(name="Is Editing", default=False)
@@ -29,7 +29,7 @@ class BIM_PT_documents(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_collaboration"
bl_parent_id = "BIM_PT_project_setup"
@classmethod
def poll(cls, context):
@@ -41,34 +41,38 @@ class BIM_PT_documents(Panel):
self.props = context.scene.BIMDocumentProperties
if not self.props.is_editing or self.props.is_editing == "information":
row = self.layout.row(align=True)
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")
row = self.layout.row(align=True)
row.label(text="{} Documents Found".format(DocumentData.data["total_information"]), icon="FILE")
if self.props.is_editing:
self.layout.template_list(
"BIM_UL_documents", "", self.props, "documents", self.props, "active_document_index"
)
row.operator("bim.disable_document_editing_ui", text="", icon="CANCEL")
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:
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):
draw_attributes(self.props.document_attributes, self.layout)
self.layout.template_list("BIM_UL_documents", "", self.props, "documents", self.props, "active_document_index")
if self.props.active_document_id:
draw_attributes(self.props.document_attributes, self.layout)
class BIM_PT_object_documents(Panel):
@@ -94,8 +98,7 @@ class BIM_PT_object_documents(Panel):
obj = context.active_object
self.oprops = obj.BIMObjectProperties
self.sprops = context.scene.BIMDocumentProperties
self.props = obj.BIMObjectDocumentProperties
self.props = context.scene.BIMDocumentProperties
self.file = IfcStore.get_file()
self.draw_add_ui()
@@ -108,52 +111,46 @@ class BIM_PT_object_documents(Panel):
row = self.layout.row(align=True)
row.label(text=document["identification"] or "*", icon="FILE")
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"]
def draw_add_ui(self):
if self.props.is_adding:
if not self.props.is_editing:
row = self.layout.row(align=True)
icon = "FILE" if self.props.is_adding == "IfcDocumentInformation" else "FILE_HIDDEN"
row.label(text="Adding {}".format(self.props.is_adding), icon=icon)
row.operator("bim.disable_assigning_document", text="", icon="CANCEL")
self.layout.template_list(
"BIM_UL_object_documents",
"",
self.sprops,
"documents",
self.sprops,
"active_document_index",
)
row.operator("bim.load_project_documents", text="Assign Document References", icon="ADD")
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 = self.layout.row(align=True)
row.prop(self.props, "available_document_types", text="")
row.operator("bim.enable_assigning_document", text="", icon="ADD")
row.alignment = "RIGHT"
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):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.label(text=item.identification)
row.label(text=item.name)
if context.scene.BIMDocumentProperties.active_document_id == item.ifc_definition_id:
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")
if item.is_information:
op = row.operator("bim.load_document", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT")
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")
class BIM_UL_object_documents(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.identification)
row.label(text=item.name)
row.operator("bim.assign_document", text="", icon="ADD").document = item.ifc_definition_id
split1 = row.split(factor=0.1)
split1.label(text=item.identification)
split2 = split1.split(factor=0.9)
split2.label(text=item.name)
@@ -33,6 +33,7 @@ classes = (
operator.AddSheet,
operator.BuildSchedule,
operator.CleanWireframes,
operator.ContractSheet,
operator.CopyGrid,
operator.CreateDrawing,
operator.CreateSheets,
@@ -45,12 +46,14 @@ classes = (
operator.EditVectorStyle,
operator.EnableEditingText,
operator.EnableEditingTextProduct,
operator.ExpandSheet,
operator.GenerateReferences,
operator.LoadDrawings,
operator.LoadSheets,
operator.OpenSheet,
operator.OpenView,
operator.RemoveDrawing,
operator.RemoveDrawingFromSheet,
operator.RemoveDrawingStyle,
operator.RemoveDrawingStyleAttribute,
operator.RemoveSchedule,
@@ -40,7 +40,7 @@ class BaseDecorator:
DEF_GLSL = """
#define PI 3.141592653589793
#define MAX_POINTS 32
#define MAX_POINTS 64
#define CIRCLE_SEGS 12
"""
@@ -1125,15 +1125,15 @@ class GridDecorator(BaseDecorator):
self.draw_label(context, text, p1, dir, vcenter=True, gap=0)
class SectionViewDecorator(LevelDecorator):
objecttype = "SECTION"
class ElevationDecorator(LevelDecorator):
objecttype = "ELEVATION"
DEF_GLSL = (
BaseDecorator.DEF_GLSL
+ """
#define CIRCLE_SIZE 8.0
#define TRIANGLE_L 32.0
#define TRIANGLE_W 16.0
#define TRIANGLE_L 22.63
#define TRIANGLE_W 11.31
"""
)
@@ -1144,12 +1144,14 @@ class SectionViewDecorator(LevelDecorator):
layout(lines) in;
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]) {
vec4 nose = dir * length;
vec4 ear = side * width;
head[0] = vec4(0);
head[1] = nose * .5 + ear;
head[2] = nose;
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() {
@@ -1160,75 +1162,206 @@ class SectionViewDecorator(LevelDecorator):
vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1);
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 p;
vec4 head[CIRCLE_SEGS];
circle_head(viewportDrawingScale * CIRCLE_SIZE, head);
vec4 head3[3];
vec4 head5[5];
// start edge circle
for(int i=0; i<CIRCLE_SEGS; i++) {
p = p0w + gap + head[i];
p = p0w + head[i];
gl_Position = WIN2CLIP(p);
EmitVertex();
}
p = p0w + gap + head[0];
p = p0w + head[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// start edge triangle
triangle_head(dir, -side, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, head3);
p = p0w + head3[0];
// 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 + head3[1];
p = p0w + head5[1];
gl_Position = WIN2CLIP(p);
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);
EmitVertex();
EndPrimitive();
// end edge circle
for(int i=0; i<CIRCLE_SEGS; i++) {
p = p1w - gap + head[i];
gl_Position = WIN2CLIP(p);
EmitVertex();
}
p = p1w - gap + head[0];
// reference divider
p = p0w + normalize(vec4(-1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE;
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// end edge triangle
triangle_head(-dir, -side, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, head3);
p = p1w + head3[0];
p = p0w + normalize(vec4(1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE;
gl_Position = WIN2CLIP(p);
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();
}
"""
def decorate(self, context, obj):
verts, _, _ = self.get_path_geom(obj, topo=False)
self.draw_lines(context, obj, verts, [(0, 1)])
center = obj.matrix_world.translation
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):
@@ -1327,7 +1460,8 @@ class DecorationsHandler:
SectionLevelDecorator,
StairDecorator,
BreakDecorator,
SectionViewDecorator,
SectionDecorator,
ElevationDecorator,
TextDecorator,
]
@@ -260,14 +260,11 @@ def format_distance(value, isArea=False, hide_units=True):
def get_active_drawing(scene):
"""Get active drawing collection and camera"""
props = scene.DocProperties
if props.active_drawing_index is None or len(props.drawings) == 0:
return None, None
try:
drawing = props.active_drawing
camera = tool.Ifc.get_object(tool.Ifc.get().by_id(drawing.ifc_definition_id))
camera = tool.Ifc.get_object(tool.Ifc.get().by_id(props.active_drawing_id))
return camera.users_collection[0], camera
except (KeyError, IndexError):
raise RuntimeError("missing drawing collection")
except:
return None, None
def get_project_collection(scene):
@@ -82,6 +82,11 @@ class AddDrawing(bpy.types.Operator, Operator):
target_view=self.props.target_view,
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):
@@ -301,12 +306,13 @@ class CreateDrawing(bpy.types.Operator):
elements = set(ifc.by_type("IfcElement")) - set(ifc.by_type("IfcOpeningElement"))
self.setup_serialiser(ifc)
cache_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True)
cache = IfcStore.get_cache()
[cache.remove(guid) for guid in invalidated_guids]
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)
it = ifcopenshell.geom.iterator(geom_settings, ifc, multiprocessing.cpu_count(), include=elements)
it.set_cache(cache)
@@ -317,18 +323,14 @@ class CreateDrawing(bpy.types.Operator):
elements -= processed
if body_contexts and elements:
geom_settings = ifcopenshell.geom.settings(
DISABLE_TRIANGULATION=True,
)
geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True)
geom_settings.set_context_ids(body_contexts)
it = ifcopenshell.geom.iterator(geom_settings, ifc, multiprocessing.cpu_count(), include=elements)
it.set_cache(cache)
for elem in self.yield_from_iterator(it):
self.serialiser.write(elem)
geom_settings = ifcopenshell.geom.settings(
DISABLE_TRIANGULATION=True,
)
geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True)
it = ifcopenshell.geom.iterator(geom_settings, ifc, include=[self.camera_element])
for elem in self.yield_from_iterator(it):
self.serialiser.write(elem)
@@ -345,7 +347,9 @@ class CreateDrawing(bpy.types.Operator):
return svg_path
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.serialiser = ifcopenshell.geom.serializers.svg(self.svg_buffer, self.svg_settings)
self.serialiser.setFile(ifc)
@@ -453,6 +457,10 @@ class CreateDrawing(bpy.types.Operator):
svg_writer.annotations.setdefault("equal_objs", []).append(obj)
elif element.ObjectType == "DIMENSION":
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":
svg_writer.annotations["break_obj"] = obj
elif element.ObjectType == "HIDDEN_LINE":
@@ -563,7 +571,6 @@ class AddDrawingToSheet(bpy.types.Operator):
bl_idname = "bim.add_drawing_to_sheet"
bl_label = "Add Drawing To Sheet"
bl_options = {"REGISTER", "UNDO"}
# TODO: check undo redo
@classmethod
def poll(cls, context):
@@ -572,9 +579,78 @@ class AddDrawingToSheet(bpy.types.Operator):
def execute(self, context):
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.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"}
@@ -590,7 +666,8 @@ class CreateSheets(bpy.types.Operator):
def execute(self, context):
scene = context.scene
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.data_dir = scene.BIMProperties.data_dir
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
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()
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=tool.Ifc.get().by_id(self.drawing))
return {"FINISHED"}
@@ -777,12 +856,10 @@ class RemoveDrawing(bpy.types.Operator, Operator):
bl_idname = "bim.remove_drawing"
bl_label = "Remove Drawing"
bl_options = {"REGISTER", "UNDO"}
index: bpy.props.IntProperty()
drawing: bpy.props.IntProperty()
def _execute(self, context):
props = context.scene.DocProperties
drawing = tool.Ifc.get().by_id(props.drawings[self.index].ifc_definition_id)
core.remove_drawing(tool.Ifc, tool.Drawing, drawing=drawing)
core.remove_drawing(tool.Ifc, tool.Drawing, drawing=tool.Ifc.get().by_id(self.drawing))
class AddDrawingStyle(bpy.types.Operator):
@@ -935,12 +1012,10 @@ class RemoveSheet(bpy.types.Operator, Operator):
bl_idname = "bim.remove_sheet"
bl_label = "Remove Sheet"
bl_options = {"REGISTER", "UNDO"}
index: bpy.props.IntProperty()
sheet: bpy.props.IntProperty()
def _execute(self, context):
self.props = context.scene.DocProperties
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)
core.remove_sheet(tool.Ifc, tool.Drawing, sheet=tool.Ifc.get().by_id(self.sheet))
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
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(
IfcStore.get_file(), "Plan", "Annotation", context.scene.camera.data.BIMCameraProperties.target_view
IfcStore.get_file(), "Plan", "Annotation", target_view
)
if not subcontext:
return {"FINISHED"}
@@ -1080,8 +1157,8 @@ class CopyGrid(bpy.types.Operator):
view_coll, camera = helper.get_active_drawing(context.scene)
is_ortho = camera.data.type == "ORTHO"
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")
elevating = is_ortho and camera.data.BIMCameraProperties.target_view in ("ELEVATION_VIEW", "SECTION_VIEW")
clipping = is_ortho and target_view in ("PLAN_VIEW", "REFLECTED_PLAN_VIEW")
elevating = is_ortho and target_view in ("ELEVATION_VIEW", "SECTION_VIEW")
def grep(coll):
results = []
@@ -1313,3 +1390,31 @@ class DisableEditingDrawings(bpy.types.Operator, Operator):
def _execute(self, context):
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):
ifc_definition_id: IntProperty(name="IFC Definition ID")
name: StringProperty(name="Name", update=update_drawing_name)
target_view: StringProperty(name="Target View")
class Schedule(PropertyGroup):
@@ -220,8 +221,9 @@ class Sheet(PropertyGroup):
ifc_definition_id: IntProperty(name="IFC Definition ID")
identification: StringProperty(name="Identification")
name: StringProperty(name="Name", get=get_name, set=set_name)
drawings: CollectionProperty(name="Drawings", type=Drawing)
active_drawing_index: IntProperty(name="Active Drawing Index")
is_sheet: BoolProperty(name="Is Sheet", default=False)
reference_type: StringProperty(name="Reference Type")
is_expanded: BoolProperty(name="Is Expanded", default=False)
class DrawingStyle(PropertyGroup):
@@ -292,6 +294,7 @@ class DocProperties(PropertyGroup):
)
location_hint: EnumProperty(items=get_location_hint, name="Location Hint")
drawings: CollectionProperty(name="Drawings", type=Drawing)
active_drawing_id: IntProperty(name="Active Drawing Id")
active_drawing_index: IntProperty(name="Active Drawing Index")
current_drawing_index: IntProperty(name="Current Drawing Index")
schedules: CollectionProperty(name="Schedules", type=Schedule)
@@ -311,14 +314,6 @@ class DocProperties(PropertyGroup):
def active_schedule(self):
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):
representation: StringProperty(name="Representation")
@@ -16,11 +16,12 @@
# 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 xml.etree.ElementTree as ET
import urllib.parse
import pystache
import ntpath
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 xml.dom import minidom
@@ -59,11 +60,16 @@ class SheetBuilder:
with open(sheet_path, "w") as f:
f.write(minidom.parseString(ET.tostring(root)).toprettyxml(indent=" "))
def add_drawing(self, view_name, sheet_name):
sheet_path = os.path.join(self.data_dir, "sheets", sheet_name + ".svg")
view_path = os.path.join(self.data_dir, "diagrams", view_name + ".svg")
def add_drawing(self, drawing, sheet):
filename = drawing.Name
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
ET.register_namespace("", "http://www.w3.org/2000/svg")
@@ -72,7 +78,7 @@ class SheetBuilder:
sheet_tree = ET.parse(sheet_path)
sheet_root = sheet_tree.getroot()
view_tree = ET.parse(view_path)
view_tree = ET.parse(drawing_path)
view_root = view_tree.getroot()
# 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.
view = ET.SubElement(sheet_root, "g")
view.attrib["data-type"] = "drawing"
view.attrib["data-guid"] = drawing.GlobalId
view_width = self.convert_to_mm(view_root.attrib.get("width"))
view_height = self.convert_to_mm(view_root.attrib.get("height"))
background = ET.SubElement(view, "image")
background.attrib["xlink:href"] = "../diagrams/{}.png".format(view_name)
background.attrib["x"] = "30"
background.attrib["y"] = "30"
background.attrib["width"] = str(view_width)
background.attrib["height"] = str(view_height)
if os.path.isfile(underlay_path):
background = ET.SubElement(view, "image")
background.attrib["xlink:href"] = os.path.relpath(underlay_path, sheet_dir)
background.attrib["x"] = "30"
background.attrib["y"] = "30"
background.attrib["width"] = str(view_width)
background.attrib["height"] = str(view_height)
foreground = ET.SubElement(view, "image")
foreground.attrib["xlink:href"] = "../diagrams/{}.svg".format(view_name)
foreground.attrib["x"] = "30"
foreground.attrib["y"] = "30"
foreground.attrib["width"] = str(view_width)
foreground.attrib["height"] = str(view_height)
if os.path.isfile(drawing_path):
foreground = ET.SubElement(view, "image")
foreground.attrib["xlink:href"] = os.path.relpath(drawing_path, sheet_dir)
foreground.attrib["x"] = "30"
foreground.attrib["y"] = "30"
foreground.attrib["width"] = str(view_width)
foreground.attrib["height"] = str(view_height)
self.add_view_title(30, view_height + 35, view)
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):
sheet_path = os.path.join(self.data_dir, "sheets", sheet_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):
x_offset = self.raw_width / 2
@@ -425,9 +433,48 @@ class SvgWriter:
position = Vector((0, 0, 0))
self.draw_text_annotation(obj, position)
def draw_text_annotations(self):
for text_obj in self.annotations.get("text_objs", []):
self.draw_text_annotation(text_obj, text_obj.location)
def draw_section_annotation(self, obj):
x_offset = self.raw_width / 2
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):
x_offset = self.raw_width / 2
@@ -646,6 +693,7 @@ class SvgWriter:
)
def project_point_onto_camera(self, point):
# TODO is this needlessly complex?
return self.camera.matrix_world.inverted() @ geometry.intersect_line_plane(
point.xyz,
point.xyz - Vector(self.camera_projection),
@@ -172,14 +172,15 @@ class BIM_PT_drawings(Panel):
if 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.alignment = "RIGHT"
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.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.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(
"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.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.alignment = "RIGHT"
row.operator("bim.open_sheet", icon="URL", 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.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")
@@ -369,7 +375,16 @@ class BIM_UL_drawinglist(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
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:
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):
if item:
row = layout.row(align=True)
split1 = row.split(factor=0.2)
split1.label(text=item.identification or "X")
split2 = split1.split(factor=0.8)
split2.label(text=item.name or "Unnamed")
if item.is_sheet:
if item.is_expanded:
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:
layout.label(text="", translate=False)
@@ -157,6 +157,9 @@ class UpdateRepresentation(bpy.types.Operator):
def update_obj_mesh_representation(self, context, obj):
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"):
ifcopenshell.api.run("grid.create_axis_curve", self.file, **{"axis_curve": obj, "grid_axis": product})
return
@@ -355,6 +358,7 @@ class OverrideDeleteTrait:
class OverrideDelete(bpy.types.Operator, OverrideDeleteTrait):
bl_idname = "object.delete"
bl_label = "Delete"
bl_options = {"REGISTER", "UNDO"}
use_global: bpy.props.BoolProperty(default=False)
confirm: bpy.props.BoolProperty(default=True)
@@ -368,21 +372,29 @@ class OverrideDelete(bpy.types.Operator, OverrideDeleteTrait):
return IfcStore.execute_ifc_operator(self, context)
for obj in context.selected_objects:
bpy.data.objects.remove(obj)
# Required otherwise gizmos are still visible
context.view_layer.objects.active = None
return {"FINISHED"}
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):
for obj in context.selected_objects:
self.delete_ifc_object(obj)
bpy.data.objects.remove(obj)
# Required otherwise gizmos are still visible
context.view_layer.objects.active = None
return {"FINISHED"}
class OverrideOutlinerDelete(bpy.types.Operator, OverrideDeleteTrait):
bl_idname = "outliner.delete"
bl_label = "Delete"
bl_options = {"REGISTER", "UNDO"}
hierarchy: bpy.props.BoolProperty(default=False)
@classmethod
@@ -405,7 +417,6 @@ class OverrideOutlinerDelete(bpy.types.Operator, OverrideDeleteTrait):
if item.bl_rna.identifier == "Collection":
collection = bpy.data.collections.get(item.name)
collection_data = self.get_collection_objects_and_children(collection)
print(collection_data)
objects_to_delete |= collection_data["objects"]
collections_to_delete |= collection_data["children"]
collections_to_delete.add(collection)
@@ -453,6 +464,7 @@ class OverrideOutlinerDelete(bpy.types.Operator, OverrideDeleteTrait):
class OverrideDuplicateMove(bpy.types.Operator):
bl_idname = "object.duplicate_move"
bl_label = "Duplicate Objects"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
@@ -21,37 +21,47 @@ from . import ui, prop, operator
classes = (
operator.AddConstituent,
operator.AddDefaultMaterial,
operator.AddLayer,
operator.AddListItem,
operator.AddMaterial,
operator.AddMaterialSet,
operator.AddProfile,
operator.AssignMaterial,
operator.AssignParameterizedProfile,
operator.CopyMaterial,
operator.DisableEditingAssignedMaterial,
operator.DisableEditingMaterialSetItem,
operator.DisableEditingMaterials,
operator.EditAssignedMaterial,
operator.EditMaterialSetItem,
operator.EnableEditingAssignedMaterial,
operator.EnableEditingMaterialSetItem,
operator.LoadMaterials,
operator.RemoveConstituent,
operator.RemoveLayer,
operator.RemoveListItem,
operator.RemoveMaterial,
operator.RemoveMaterialSet,
operator.RemoveProfile,
operator.ReorderMaterialSetItem,
operator.SelectByMaterial,
operator.UnassignMaterial,
operator.UnlinkMaterial,
prop.Material,
prop.BIMMaterialProperties,
prop.BIMObjectMaterialProperties,
ui.BIM_PT_materials,
ui.BIM_PT_material,
ui.BIM_PT_object_material,
ui.BIM_UL_materials,
)
def register():
bpy.types.Scene.BIMMaterialProperties = bpy.props.PointerProperty(type=prop.BIMMaterialProperties)
bpy.types.Object.BIMObjectMaterialProperties = bpy.props.PointerProperty(type=prop.BIMObjectMaterialProperties)
def unregister():
del bpy.types.Scene.BIMMaterialProperties
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
class Operator:
def execute(self, context):
IfcStore.execute_ifc_operator(self, context)
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
class LoadMaterials(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.load_materials"
bl_label = "Load Materials"
bl_options = {"REGISTER", "UNDO"}
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):
@@ -68,70 +89,54 @@ class AssignParameterizedProfile(bpy.types.Operator):
return {"FINISHED"}
class AddDefaultMaterial(bpy.types.Operator, 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):
class AddMaterial(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_material"
bl_label = "Add Material"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.materials.get(self.obj) if self.obj else context.active_object.active_material
self.file = IfcStore.get_file()
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,
},
)
obj = bpy.data.materials.get(self.obj) if self.obj else None
core.add_material(tool.Ifc, tool.Material, tool.Style, obj=obj)
Data.load(IfcStore.get_file())
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_label = "Remove Material"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
material: bpy.props.IntProperty()
def _execute(self, context):
obj = bpy.data.materials.get(self.obj) if self.obj else context.active_object.active_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
core.remove_material(tool.Ifc, tool.Material, tool.Style, material=tool.Ifc.get().by_id(self.material))
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_label = "Unlink Material"
bl_options = {"REGISTER", "UNDO"}
@@ -18,6 +18,7 @@
import bpy
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.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
@@ -32,18 +33,15 @@ from bpy.props import (
CollectionProperty,
)
materials_enum = []
materialtypes_enum = []
profileclasses_enum = []
parameterizedprofileclasses_enum = []
def purge():
global materials_enum
global materialtypes_enum
global profileclasses_enum
global parameterizedprofileclasses_enum
materials_enum = []
materialtypes_enum = []
profileclasses_enum = []
parameterizedprofileclasses_enum = []
@@ -77,12 +75,10 @@ def getParameterizedProfileClasses(self, context):
return parameterizedprofileclasses_enum
def getMaterials(self, context):
global materials_enum
if len(materials_enum) == 0 and IfcStore.get_file():
materials_enum.clear()
materials_enum = [(str(m_id), m["Name"], "") for m_id, m in Data.materials.items()]
return materials_enum
def get_materials(self, context):
if not ObjectMaterialData.is_loaded:
ObjectMaterialData.load()
return ObjectMaterialData.data["materials"]
def getMaterialTypes(self, context):
@@ -104,9 +100,28 @@ def getMaterialTypes(self, context):
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):
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)
material_set_usage_attributes: CollectionProperty(name="Material Set Usage 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(
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")
parameterized_profile_classes: EnumProperty(
items=getParameterizedProfileClasses, name="Parameterized Profile Classes"
@@ -17,11 +17,62 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
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.profile.data import Data as ProfileData
from blenderbim.bim.ifc import IfcStore
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):
@@ -37,11 +88,13 @@ class BIM_PT_material(Panel):
def draw(self, context):
row = self.layout.row(align=True)
if bool(context.active_object.active_material.BIMObjectProperties.ifc_definition_id):
row.operator("bim.remove_material", icon="X", text="Remove IFC Material")
material_id = context.active_object.active_material.BIMObjectProperties.ifc_definition_id
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="")
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):
@@ -66,6 +119,9 @@ class BIM_PT_object_material(Panel):
return True
def draw(self, context):
if not ObjectMaterialData.is_loaded:
ObjectMaterialData.load()
self.file = IfcStore.get_file()
self.oprops = context.active_object.BIMObjectProperties
self.props = context.active_object.BIMObjectMaterialProperties
@@ -77,10 +133,10 @@ class BIM_PT_object_material(Panel):
ProfileData.load(self.file)
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.label(text="No Materials Available")
row.operator("bim.add_default_material", icon="ADD", text="")
row.operator("bim.add_material", icon="ADD", text="").obj = ""
return
if self.product_data:
@@ -333,3 +389,13 @@ class BIM_PT_object_material(Panel):
if total_thickness:
row = self.layout.row(align=True)
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
def ifc_classes(cls):
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)])
return results
@@ -27,6 +27,7 @@ from bpy.app.handlers import persistent
@persistent
def load_post(*args):
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(
"geometry.add_representation", "BlenderBIM.Product.GenerateBox", product.generate_box
@@ -34,7 +34,6 @@ from math import pi, degrees
from mathutils import Vector, Matrix
class MepGenerator:
def __init__(self, relating_type):
self.relating_type = relating_type
@@ -17,24 +17,34 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import blenderbim.bim.handler
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.style.data import Data as StyleData
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"]:
return
obj = IfcStore.get_element(settings["product"].id())
obj = tool.Ifc.get_object(element)
if not obj:
return
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):
new_name = settings["attributes"]["Name"] or "Unnamed"
if obj.BIMMaterialProperties.ifc_style_id:
IfcStore.get_file().by_id(obj.BIMMaterialProperties.ifc_style_id).Name = new_name
StyleData.load(IfcStore.get_file(), obj.BIMMaterialProperties.ifc_style_id)
material = tool.Ifc.get_entity(obj)
if material and material != element:
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)
if collection:
collection.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"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
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):
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)
return {"FINISHED"}
@@ -646,13 +650,16 @@ class UnloadLink(bpy.types.Operator):
filepath: bpy.props.StringProperty()
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:
if collection.library and collection.library.filepath == self.filepath:
if collection.library and collection.library.filepath == filepath:
context.scene.collection.children.unlink(collection)
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)
link = context.scene.BIMProjectProperties.links.get(self.filepath)
link = context.scene.BIMProjectProperties.links.get(filepath)
link.is_loaded = False
return {"FINISHED"}
@@ -665,16 +672,19 @@ class LoadLink(bpy.types.Operator):
filepath: bpy.props.StringProperty()
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):
data_to.scenes = data_from.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
for child in scene.collection.children:
if "IfcProject" not in child.name:
continue
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
return {"FINISHED"}
@@ -284,11 +284,12 @@ class BIM_UL_links(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)
if item.is_loaded:
row.label(text=item.name)
op = row.operator("bim.unload_link", text="", icon="UNLINKED")
op.filepath = item.name
else:
row.prop(item, "name", text="")
op = row.operator("bim.load_link", text="", icon="LINKED")
op.filepath = item.name
op = row.operator("bim.unlink_ifc", text="", icon="X")
@@ -32,12 +32,47 @@ class IfcClassData:
@classmethod
def load(cls):
cls.is_loaded = True
cls.data = {
"contexts": cls.contexts(),
"has_entity": cls.has_entity(),
"name": cls.name(),
"ifc_class": cls.ifc_class(),
}
cls.data = {}
cls.data["ifc_products"] = cls.ifc_products()
cls.data["ifc_classes"] = cls.ifc_classes()
cls.data["contexts"] = cls.contexts()
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
def contexts(cls):
@@ -33,17 +33,11 @@ from bpy.props import (
CollectionProperty,
)
products_enum = []
classes_enum = []
types_enum = []
def purge():
global products_enum
global classes_enum
global types_enum
products_enum = []
classes_enum = []
types_enum = []
@@ -61,10 +55,9 @@ def getIfcPredefinedTypes(self, context):
return types_enum
def refreshClasses(self, context):
global classes_enum
classes_enum.clear()
enum = getIfcClasses(self, context)
def refresh_classes(self, context):
IfcClassData.load()
enum = get_ifc_classes(self, context)
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]
def getIfcProducts(self, context):
global products_enum
file = IfcStore.get_file()
if len(products_enum) < 1:
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 get_ifc_products(self, context):
if not IfcClassData.is_loaded:
IfcClassData.load()
return IfcClassData.data["ifc_products"]
def getIfcClasses(self, context):
global classes_enum
file = IfcStore.get_file()
if len(classes_enum) < 1 and file:
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_ifc_classes(self, context):
if not IfcClassData.is_loaded:
IfcClassData.load()
return IfcClassData.data["ifc_classes"]
def get_contexts(self, context):
@@ -118,7 +89,7 @@ def get_contexts(self, context):
class BIMRootProperties(PropertyGroup):
contexts: EnumProperty(items=get_contexts, name="Contexts")
ifc_product: EnumProperty(items=getIfcProducts, name="Products", update=refreshClasses)
ifc_class: EnumProperty(items=getIfcClasses, name="Class", update=refreshPredefinedTypes)
ifc_product: EnumProperty(items=get_ifc_products, name="Products", update=refresh_classes)
ifc_class: EnumProperty(items=get_ifc_classes, name="Class", update=refreshPredefinedTypes)
ifc_predefined_type: EnumProperty(items=getIfcPredefinedTypes, name="Predefined Type", default=None)
ifc_userdefined_type: StringProperty(name="Userdefined Type")
@@ -989,6 +989,7 @@ class GenerateGanttChart(bpy.types.Operator):
data = {
"pID": task.id(),
"pName": task.Name,
"pCaption": task.Name,
"pStart": task.TaskTime.ScheduleStart if task.TaskTime else "",
"pEnd": task.TaskTime.ScheduleFinish 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,
"pOpen": 1,
"pCost": 1,
"ifcduration": task.TaskTime.ScheduleDuration if task.TaskTime else "",
}
if task.TaskTime and task.TaskTime.IsCritical:
data["pClass"] = "gtaskred"
@@ -1230,7 +1232,7 @@ class ExportMSP(bpy.types.Operator, ImportHelper):
start = time.time()
ifc2msp = Ifc2Msp()
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.holiday_start_date = parser.parse(self.holiday_start_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()
start = time.time()
ifc2p6 = Ifc2P6()
ifc2p6.xml = self.filepath
ifc2p6.xml = bpy.path.ensure_ext(self.filepath, ".xml")
ifc2p6.file = self.file
ifc2p6.holiday_start_date = parser.parse(self.holiday_start_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):
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):
prop.data_type = "string"
prop.string_value = (
@@ -17,6 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import isodate
import ifcopenshell.api
import ifcopenshell.util.attribute
from blenderbim.bim.ifc import IfcStore
@@ -184,6 +185,16 @@ def updateTaskDuration(self, context):
props = context.scene.BIMWorkScheduleProperties
if not props.is_task_update_enabled:
return
if self.duration == "-":
return
try:
isodate.parse_duration(self.duration),
except:
self.duration = "-"
return
self.file = IfcStore.get_file()
task = self.file.by_id(self.ifc_definition_id)
if task.TaskTime:
@@ -21,22 +21,30 @@ from . import ui, prop, operator
classes = (
operator.AddStyle,
operator.EnableEditingStyle,
operator.DisableEditingStyle,
operator.DisableEditingStyles,
operator.EditStyle,
operator.EnableEditingStyle,
operator.LoadStyles,
operator.RemoveStyle,
operator.UnlinkStyle,
operator.UpdateStyleColours,
operator.UpdateStyleTextures,
operator.UnlinkStyle,
prop.Style,
prop.BIMStylesProperties,
prop.BIMStyleProperties,
ui.BIM_PT_styles,
ui.BIM_PT_style,
ui.BIM_PT_style_attributes,
ui.BIM_UL_styles,
)
def register():
bpy.types.Scene.BIMStylesProperties = bpy.props.PointerProperty(type=prop.BIMStylesProperties)
bpy.types.Material.BIMStyleProperties = bpy.props.PointerProperty(type=prop.BIMStyleProperties)
def unregister():
del bpy.types.Scene.BIMStylesProperties
del bpy.types.Material.BIMStyleProperties
@@ -18,12 +18,33 @@
import bpy
import blenderbim.tool as tool
import ifcopenshell
def refresh():
StylesData.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:
data = {}
is_loaded = False
@@ -59,9 +59,10 @@ class RemoveStyle(bpy.types.Operator, Operator):
bl_idname = "bim.remove_style"
bl_label = "Remove Style"
bl_options = {"REGISTER", "UNDO"}
style: bpy.props.IntProperty()
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):
@@ -107,3 +108,22 @@ class EditStyle(bpy.types.Operator, Operator):
def _execute(self, context):
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
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.prop import StrProperty, Attribute
from blenderbim.bim.module.style.data import StylesData
from bpy.types import PropertyGroup
from bpy.props import (
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):
attributes: CollectionProperty(name="Attributes", type=Attribute)
is_editing: BoolProperty(name="Is Editing")
@@ -17,9 +17,51 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import blenderbim.bim.helper
from bpy.types import Panel
from bpy.types import Panel, UIList
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):
@@ -44,7 +86,7 @@ class BIM_PT_style(Panel):
row.operator("bim.update_style_colours", icon="GREASEPENCIL")
row.operator("bim.update_style_textures", icon="TEXTURE", 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:
row.operator("bim.add_style", icon="ADD")
@@ -89,3 +131,13 @@ class BIM_PT_style_attributes(Panel):
row = self.layout.row(align=True)
row.label(text=attribute["name"])
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
classes = (
operator.AssignSceneUnits,
operator.AssignUnit,
operator.UnassignUnit,
operator.LoadUnits,
operator.DisableUnitEditingUI,
operator.RemoveUnit,
operator.AddContextDependentUnit,
operator.AddConversionBasedUnit,
operator.AddMonetaryUnit,
operator.AddSIUnit,
operator.AddContextDependentUnit,
operator.EnableEditingUnit,
operator.AssignSceneUnits,
operator.AssignUnit,
operator.DisableEditingUnit,
operator.DisableUnitEditingUI,
operator.EditUnit,
operator.EnableEditingUnit,
operator.LoadUnits,
operator.RemoveUnit,
operator.UnassignUnit,
prop.Unit,
prop.BIMUnitProperties,
ui.BIM_PT_units,
@@ -16,6 +16,10 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.util.unit
import ifcopenshell.util.schema
import ifcopenshell.util.attribute
import blenderbim.tool as tool
@@ -29,9 +33,32 @@ class UnitsData:
@classmethod
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
@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
def get_total_units(cls):
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))
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):
bl_idname = "bim.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/>.
import bpy
import ifcopenshell
import ifcopenshell.util.schema
import ifcopenshell.util.attribute
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.prop import StrProperty, Attribute
from blenderbim.bim.module.unit.data import UnitsData
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
@@ -35,34 +33,22 @@ from bpy.props import (
)
unitclasses_enum = []
namedunittypes_enum = []
def get_unit_classes(self, context):
if not UnitsData.is_loaded:
UnitsData.load()
return UnitsData.data["unit_classes"]
def purge():
global unitclasses_enum
global namedunittypes_enum
unitclasses_enum = []
namedunittypes_enum = []
def get_conversion_unit_types(self, context):
if not UnitsData.is_loaded:
UnitsData.load()
return UnitsData.data["conversion_unit_types"]
def getUnitClasses(self, context):
global unitclasses_enum
if not len(unitclasses_enum) and IfcStore.get_file():
declarations = ifcopenshell.util.schema.get_subtypes(IfcStore.get_schema().declaration_by_name("IfcNamedUnit"))
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
def get_named_unit_types(self, context):
if not UnitsData.is_loaded:
UnitsData.load()
return UnitsData.data["named_unit_types"]
class Unit(PropertyGroup):
@@ -78,6 +64,7 @@ class BIMUnitProperties(PropertyGroup):
units: CollectionProperty(name="Units", type=Unit)
active_unit_index: IntProperty(name="Active Unit Index")
active_unit_id: IntProperty(name="Active Unit Id")
unit_classes: EnumProperty(items=getUnitClasses, name="Unit Classes")
named_unit_types: EnumProperty(items=getNamedUnitTypes, name="Named Unit Types")
unit_classes: EnumProperty(items=get_unit_classes, name="Unit Classes")
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)
@@ -57,6 +57,10 @@ class BIM_PT_units(Panel):
if self.props.unit_classes == "IfcMonetaryUnit":
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":
pass # TODO
elif self.props.unit_classes == "IfcSIUnit":
@@ -44,6 +44,7 @@ class AddOpening(bpy.types.Operator):
if not element_id:
return {"FINISHED"}
element = self.file.by_id(element_id)
tool.Geometry.clear_cache(element)
if element.is_a("IfcOpeningElement"):
self.report({"WARNING"}, "An IfcOpeningElement can't be voided")
return {"FINISHED"}
@@ -132,6 +133,7 @@ class RemoveOpening(bpy.types.Operator):
should_sync_changes_first=False,
)
tool.Geometry.clear_cache(tool.Ifc.get_entity(obj))
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
return {"FINISHED"}
@@ -49,7 +49,11 @@ class BIM_PT_voids(Panel):
if len(context.selected_objects) == 2:
op = row.operator("bim.add_opening", icon="ADD", text="Add Opening")
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
elif len(obj.children) == 1 and not obj.children[0].BIMObjectProperties.ifc_definition_id:
op.opening = obj.children[0].name
+38
View File
@@ -22,6 +22,7 @@ import json
import webbrowser
import ifcopenshell
import blenderbim.bim.handler
import blenderbim.tool as tool
from . import schema
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.ui import IFCFileSelector
@@ -39,6 +40,43 @@ class OpenUri(bpy.types.Operator):
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):
bl_idname = "bim.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)
int_value: IntProperty(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_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):
if self.is_null:
+1 -1
View File
@@ -233,7 +233,7 @@ class BIM_PT_geometry(Panel):
class BIM_PT_4D5D(Panel):
bl_label = "IFC 4D/5D"
bl_label = "IFC Costing and Scheduling"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
+60 -44
View File
@@ -17,16 +17,31 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
def load_information(document):
document.import_information()
document.enable_information_editing_ui()
document.disable_editing_document()
def load_project_documents(document):
document.clear_document_tree()
document.import_project_documents()
document.clear_breadcrumbs()
document.enable_editing_ui()
def load_references(document):
document.import_references()
document.enable_reference_editing_ui()
document.disable_editing_document()
def load_document(document_tool, document=None):
document_tool.clear_document_tree()
document_tool.import_subdocuments(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):
@@ -44,52 +59,53 @@ def disable_editing_document(document):
def add_information(ifc, document):
result = ifc.run("document.add_information")
document.import_information()
document.import_document_attributes(result)
document.set_active_document(result)
document.clear_document_tree()
parent = document.get_active_breadcrumb()
information = ifc.run("document.add_information", parent=parent)
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):
result = ifc.run("document.add_reference")
document.import_references()
document.import_document_attributes(result)
document.set_active_document(result)
parent = document.get_active_breadcrumb()
ifc.run("document.add_reference", information=parent)
document.clear_document_tree()
document.import_subdocuments(parent)
document.import_references(parent)
def edit_information(ifc, document, information=None):
attributes = document.export_document_attributes()
ifc.run("document.edit_information", information=information, attributes=attributes)
document.disable_editing_document()
document.import_information()
def edit_reference(ifc, document, reference=None):
attributes = document.export_document_attributes()
ifc.run("document.edit_reference", reference=reference, attributes=attributes)
document.disable_editing_document()
document.import_references()
def edit_document(ifc, document_tool, document=None):
attributes = document_tool.export_document_attributes()
if document_tool.is_document_information(document):
ifc.run("document.edit_information", information=document, attributes=attributes)
else:
ifc.run("document.edit_reference", reference=document, attributes=attributes)
document_tool.disable_editing_document()
document_tool.clear_document_tree()
parent = document_tool.get_active_breadcrumb()
if parent:
document_tool.import_subdocuments(parent)
document_tool.import_references(parent)
else:
document_tool.import_project_documents()
def remove_document(ifc, document_tool, document=None):
document_tool.clear_document_tree()
if document_tool.is_document_information(document):
ifc.run("document.remove_document", document=document)
document_tool.import_information()
ifc.run("document.remove_information", information=document)
else:
ifc.run("document.remove_document", document=document)
document_tool.import_references()
document_tool.disable_editing_document()
def enable_assigning_document(document, obj=None):
document.import_references()
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)
ifc.run("document.remove_reference", reference=document)
parent = document_tool.get_active_breadcrumb()
if parent:
document_tool.import_subdocuments(parent)
document_tool.import_references(parent)
else:
document_tool.import_project_documents()
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):
ifc.run("document.remove_document", document=sheet)
ifc.run("document.remove_information", information=sheet)
drawing.import_sheets()
@@ -99,7 +99,7 @@ def disable_editing_drawings(drawing):
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)
camera = drawing.create_camera(drawing_name, drawing_matrix)
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)
collector.assign(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)
def add_default_material(ifc, material):
obj = material.add_default_material_object()
ifc.link(ifc.run("material.add_material", name="Default"), obj)
return obj
def add_material(ifc, material, style, obj=None):
if not obj:
obj = material.add_default_material_object()
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
def remove_style(ifc, style, obj=None):
element = style.get_style(obj)
ifc.unlink(obj=obj, element=element)
ifc.run("style.remove_style", style=element)
def remove_style(ifc, material, style_tool, style=None):
obj = ifc.get_object(style)
ifc.unlink(obj=obj, element=style)
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):
@@ -97,3 +101,12 @@ def edit_style(ifc, style, obj=None):
attributes = style.export_surface_attributes(obj)
ifc.run("style.edit_presentation_style", style=style.get_style(obj), attributes=attributes)
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
@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
class Drawing:
def create_annotation_object(cls, object_type): pass
@@ -140,6 +159,7 @@ class Drawing:
def create_svg_sheet(cls, document, titleblock): pass
def delete_collection(cls, collection): pass
def delete_drawing_elements(cls, elements): pass
def delete_object(cls, obj): pass
def disable_editing_drawings(cls): pass
def disable_editing_sheets(cls): pass
def disable_editing_text(cls, obj): pass
@@ -153,6 +173,7 @@ class Drawing:
def ensure_unique_identification(cls, identification): pass
def export_text_literal_attributes(cls, obj): 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 get_annotation_context(cls, target_view): 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 set_drawing_collection_name(cls, group, collection): pass
def show_decorations(cls): pass
def sync_object_placement(cls, obj): pass
def update_text_value(cls, obj): pass
@@ -227,6 +249,9 @@ class Ifc:
def get_entity(cls, obj): pass
def get_object(cls, entity): 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 run(cls, command, **kwargs): pass
def set(cls, ifc): pass
@@ -251,6 +276,15 @@ class Library:
@interface
class Material:
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
@@ -398,8 +432,11 @@ class Structural:
class Style:
def can_support_rendering_style(cls, obj): pass
def disable_editing(cls, obj): pass
def disable_editing_styles(cls): pass
def enable_editing(cls, obj): pass
def enable_editing_styles(cls): pass
def export_surface_attributes(cls, obj): pass
def get_active_style_type(cls): pass
def get_context(cls, obj): pass
def get_name(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_texture_style(cls, obj): 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 is_editing_styles(cls): pass
@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)
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):
@@ -77,6 +79,12 @@ def add_context_dependent_unit(ifc, unit, unit_type=None, name=None):
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):
unit_tool.set_active_unit(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):
@classmethod
def disable_document_assignment_ui(cls, obj):
obj.BIMObjectDocumentProperties.is_adding = ""
def add_breadcrumb(cls, document):
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
def disable_editing_document(cls):
@@ -34,24 +46,22 @@ class Document(blenderbim.core.tool.Document):
@classmethod
def disable_editing_ui(cls):
bpy.context.scene.BIMDocumentProperties.is_editing = ""
bpy.context.scene.BIMDocumentProperties.is_editing = False
@classmethod
def enable_document_assignment_ui(cls, obj):
obj.BIMObjectDocumentProperties.is_adding = "IfcDocumentReference"
@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"
def enable_editing_ui(cls):
bpy.context.scene.BIMDocumentProperties.is_editing = True
@classmethod
def export_document_attributes(cls):
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
def import_document_attributes(cls, document):
props = bpy.context.scene.BIMDocumentProperties
@@ -59,35 +69,64 @@ class Document(blenderbim.core.tool.Document):
blenderbim.bim.helper.import_attributes2(document, props.document_attributes)
@classmethod
def import_information(cls):
def import_project_documents(cls):
props = bpy.context.scene.BIMDocumentProperties
props.documents.clear()
for element in tool.Ifc.get().by_type("IfcDocumentInformation"):
new = props.documents.add()
new.ifc_definition_id = element.id()
new.name = element.Name or "Unnamed"
if tool.Ifc.get_schema() == "IFC2X3":
new.identification = element.DocumentId or "*"
else:
new.identification = element.Identification or "*"
project = tool.Ifc.get().by_type("IfcProject")[0]
for rel in project.HasAssociations or []:
if rel.is_a("IfcRelAssociatesDocument") and rel.RelatingDocument.is_a("IfcDocumentInformation"):
element = rel.RelatingDocument
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
def import_references(cls):
def import_references(cls, document):
props = bpy.context.scene.BIMDocumentProperties
props.documents.clear()
for element in tool.Ifc.get().by_type("IfcDocumentReference"):
new = props.documents.add()
new.ifc_definition_id = element.id()
new.name = element.Name or "Unnamed"
if tool.Ifc.get_schema() == "IFC2X3":
if tool.Ifc.get_schema() == "IFC2X3":
for element in document.DocumentReferences or []:
new = props.documents.add()
new.ifc_definition_id = element.id()
new.name = element.Name or "Unnamed"
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.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
def is_document_information(cls, document):
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
def set_active_document(cls, document):
bpy.context.scene.BIMDocumentProperties.active_document_id = document.id()
+373 -2
View File
@@ -19,13 +19,18 @@
import os
import re
import bpy
import math
import bmesh
import mathutils
import webbrowser
import numpy as np
import blenderbim.core.tool
import blenderbim.core.geometry
import blenderbim.tool as tool
import ifcopenshell.util.representation
import blenderbim.bim.module.drawing.sheeter as sheeter
import blenderbim.bim.module.drawing.annotation as annotation
import blenderbim.bim.module.drawing.helper as helper
class Drawing(blenderbim.core.tool.Drawing):
@@ -83,6 +88,10 @@ class Drawing(blenderbim.core.tool.Drawing):
if obj:
bpy.data.objects.remove(obj)
@classmethod
def delete_object(cls, obj):
bpy.data.objects.remove(obj)
@classmethod
def disable_editing_drawings(cls):
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.ifc_definition_id = drawing.id()
new.name = drawing.Name or "Unnamed"
new.target_view = cls.get_drawing_target_view(drawing)
@classmethod
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"]
for sheet in sheets:
new = bpy.context.scene.DocProperties.sheets.add()
new = props.sheets.add()
new.ifc_definition_id = sheet.id()
if tool.Ifc.get_schema() == "IFC2X3":
new.identification = sheet.DocumentId
else:
new.identification = sheet.Identification
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
def import_text_attributes(cls, obj):
@@ -349,3 +388,335 @@ class Drawing(blenderbim.core.tool.Drawing):
for variable in re.findall("{{.*?}}", value):
value = value.replace(variable, selector.get_element_value(product, variable[2:-2]) or "")
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/>.
import bpy
import numpy as np
import ifcopenshell.api
import blenderbim.core.tool
from blenderbim.bim.ifc import IfcStore
@@ -44,6 +45,26 @@ class Ifc(blenderbim.core.tool.Ifc):
if IfcStore.get_file():
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
def schema(cls):
return IfcStore.get_schema()
@@ -27,3 +27,53 @@ class Material(blenderbim.core.tool.Material):
@classmethod
def add_default_material_object(cls):
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
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import ifcopenshell
import blenderbim.core.tool
import blenderbim.tool as tool
@@ -31,14 +32,26 @@ class Style(blenderbim.core.tool.Style):
def disable_editing(cls, obj):
obj.BIMStyleProperties.is_editing = False
@classmethod
def disable_editing_styles(cls):
bpy.context.scene.BIMStylesProperties.is_editing = False
@classmethod
def enable_editing(cls, obj):
obj.BIMStyleProperties.is_editing = True
@classmethod
def enable_editing_styles(cls):
bpy.context.scene.BIMStylesProperties.is_editing = True
@classmethod
def export_surface_attributes(cls, obj):
return blenderbim.bim.helper.export_attributes(obj.BIMStyleProperties.attributes)
@classmethod
def get_active_style_type(cls):
return bpy.context.scene.BIMStylesProperties.style_type
@classmethod
def get_context(cls, obj):
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)
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
def import_surface_attributes(cls, style, obj):
obj.BIMStyleProperties.attributes.clear()
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.
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
``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.
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
:ref:`blenderbim/running_tests` for details. You can find the tests here:
do "Test Driven Development". For reference on how to run these tests, see `blenderbim/running_tests <./running_tests.rst>`_
for details. You can find the tests here:
::
+1
View File
@@ -7,6 +7,7 @@ markers =
context
debug
demo
document
drawing
geometry
library
@@ -49,7 +49,7 @@ Scenario: Add aggregate
When I press "bim.add_aggregate"
Then the object "IfcWall/Cube" 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
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 the object "IfcWall/Cube" is selected
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 press "bim.add_representation"
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"
And the object "IfcWall/Wall.001" data is a "Tessellation" representation of "Model/Body/MODEL_VIEW"
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 press "bim.add_representation"
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_class" to "IfcWallType"
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 press "bim.assign_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_class" to "IfcWallType"
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 press "bim.assign_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_class" to "IfcWallType"
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 press "bim.assign_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_class" to "IfcWallType"
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 press "bim.assign_material"
And I press "bim.enable_editing_assigned_material"
@@ -1,9 +1,35 @@
@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
Given an empty IFC project
When I press "bim.add_default_material"
When I press "bim.add_material(obj='')"
Then the material "Default" exists
Scenario: Add material
@@ -11,7 +37,7 @@ Scenario: Add material
And I add a cube
And the object "Cube" is selected
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
Scenario: Remove material
@@ -19,16 +45,26 @@ Scenario: Remove material
And I add a cube
And the object "Cube" is selected
And I add a material
And I press "bim.add_material"
When I press "bim.remove_material"
Then the material "Material" is not an IFC material
And I press "bim.add_material(obj='Material')"
And the variable "material" is "{ifc}.by_type('IfcMaterial')[0].id()"
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
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
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"
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 I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
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"
And I press "bim.assign_material"
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_class" to "IfcWallType"
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"
And I press "bim.assign_material"
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_class" to "IfcWallType"
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"
And I press "bim.assign_material"
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 the object "IfcWall/Wall" data is a "Tessellation" representation of "Model/Body/MODEL_VIEW"
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 press "bim.add_representation"
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 press "bim.assign_class"
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"
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"
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]"
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]"
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"
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]"
When I am on frame "6"
When I am on frame "7"
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').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]"
When I am on frame "2"
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]"
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').hide_viewport" 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').hide_viewport" 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]"
And "scene.objects.get('IfcWall/Cube').hide_viewport" 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').hide_viewport" 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 material
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
Scenario: Add style
@@ -57,3 +58,20 @@ Scenario: Edit style
And I press "bim.enable_editing_style"
When I press "bim.edit_style"
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_class" to "IfcWallType"
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 press "bim.assign_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_class" to "IfcWallType"
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 press "bim.assign_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')"
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
Given an empty IFC project
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
@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")
def an_ifc_file_does_not_exist():
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
class TestLoadInformation:
class TestLoadProjectDocuments:
def test_run(self, document):
document.import_information().should_be_called()
document.enable_information_editing_ui().should_be_called()
document.disable_editing_document().should_be_called()
subject.load_information(document)
document.clear_document_tree().should_be_called()
document.import_project_documents().should_be_called()
document.clear_breadcrumbs().should_be_called()
document.enable_editing_ui().should_be_called()
subject.load_project_documents(document)
class TestLoadReferences:
class TestLoadDocument:
def test_run(self, document):
document.import_references().should_be_called()
document.enable_reference_editing_ui().should_be_called()
document.clear_document_tree().should_be_called()
document.import_subdocuments("document").should_be_called()
document.import_references("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:
@@ -58,72 +61,76 @@ class TestDisableEditingDocument:
class TestAddInformation:
def test_run(self, ifc, document):
ifc.run("document.add_information").should_be_called().will_return("information")
document.import_information().should_be_called()
document.import_document_attributes("information").should_be_called()
document.set_active_document("information").should_be_called()
def test_add_and_reload_tree_at_project_root(self, ifc, document):
document.clear_document_tree().should_be_called()
document.get_active_breadcrumb().should_be_called().will_return(None)
ifc.run("document.add_information", parent=None).should_be_called().will_return("information")
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)
class TestAddReference:
def test_run(self, ifc, document):
ifc.run("document.add_reference").should_be_called().will_return("reference")
document.import_references().should_be_called()
document.import_document_attributes("reference").should_be_called()
document.set_active_document("reference").should_be_called()
document.get_active_breadcrumb().should_be_called().will_return("parent")
ifc.run("document.add_reference", information="parent").should_be_called()
document.clear_document_tree().should_be_called()
document.import_subdocuments("parent").should_be_called()
document.import_references("parent").should_be_called()
subject.add_reference(ifc, document)
class TestEditInformation:
def test_run(self, ifc, document):
class TestEditDocument:
def test_edit_information(self, ifc, document):
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.import_information().should_be_called()
subject.edit_information(ifc, document, information="information")
document.clear_document_tree().should_be_called()
document.get_active_breadcrumb().should_be_called().will_return(None)
document.import_project_documents().should_be_called()
subject.edit_document(ifc, document, document="document")
class TestEditInformation:
def test_run(self, ifc, document):
def test_edit_reference(self, ifc, document):
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.import_references().should_be_called()
subject.edit_reference(ifc, document, reference="reference")
document.clear_document_tree().should_be_called()
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:
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)
ifc.run("document.remove_document", document="document").should_be_called()
document.import_information().should_be_called()
document.disable_editing_document().should_be_called()
ifc.run("document.remove_information", information="document").should_be_called()
document.get_active_breadcrumb().should_be_called().will_return(None)
document.import_project_documents().should_be_called()
subject.remove_document(ifc, document, document="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)
ifc.run("document.remove_document", document="document").should_be_called()
document.import_references().should_be_called()
document.disable_editing_document().should_be_called()
ifc.run("document.remove_reference", reference="document").should_be_called()
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.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:
def test_run(self, ifc):
ifc.run("document.assign_document", product="product", document="document").should_be_called()
+2 -1
View File
@@ -141,7 +141,8 @@ class TestDisableEditingDrawings:
class TestAddDrawing:
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.create_camera("name", "matrix").should_be_called().will_return("obj")
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/>.
import blenderbim.core.material as subject
from test.core.bootstrap import ifc, material
from test.core.bootstrap import ifc, material, style
class TestUnlinkMaterial:
@@ -26,9 +26,131 @@ class TestUnlinkMaterial:
subject.unlink_material(ifc, obj="obj")
class TestAddDefaultMaterial:
def test_run(self, ifc, material):
class TestAddMaterial:
def test_add_a_default_material(self, ifc, material, style):
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()
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/>.
import blenderbim.core.style as subject
from test.core.bootstrap import ifc, style
from test.core.bootstrap import ifc, material, style
class TestAddStyle:
@@ -53,11 +53,32 @@ class TestAddStyle:
class TestRemoveStyle:
def test_run(self, ifc, style):
style.get_style("obj").should_be_called().will_return("style")
def test_removing_a_style(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()
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:
@@ -174,3 +195,16 @@ class TestEditStyle:
ifc.run("style.edit_presentation_style", style="style", attributes="attributes").should_be_called()
style.disable_editing("obj").should_be_called()
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"
).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)
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")
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)
@@ -115,6 +119,13 @@ class TestAddContextDependentUnit:
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:
def test_run(self, unit):
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)
class TestDisableDocumentAssignmentUI(NewFile):
class TestAddBreadcrumb(NewFile):
def test_run(self):
obj = bpy.data.objects.new("Object", None)
obj.BIMObjectDocumentProperties.is_adding = "foo"
subject.disable_document_assignment_ui(obj)
assert obj.BIMObjectDocumentProperties.is_adding == ""
ifc = ifcopenshell.file()
tool.Ifc().set(ifc)
document = ifc.createIfcDocumentInformation()
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):
@@ -46,31 +64,16 @@ class TestDisableEditingDocument(NewFile):
class TestDisableEditingUI(NewFile):
def test_run(self):
bpy.context.scene.BIMDocumentProperties.is_editing = "is_editing"
bpy.context.scene.BIMDocumentProperties.is_editing = True
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):
obj = bpy.data.objects.new("Object", None)
obj.BIMObjectDocumentProperties.is_adding = ""
subject.enable_document_assignment_ui(obj)
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"
bpy.context.scene.BIMDocumentProperties.is_editing = False
subject.enable_editing_ui()
assert bpy.context.scene.BIMDocumentProperties.is_editing == True
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):
def test_importing_information(self):
ifc = ifcopenshell.file()
@@ -149,30 +161,51 @@ class TestImportDocumentAttributes(NewFile):
assert props.document_attributes.get("Description").string_value == "Description"
class TestImportInformation(NewFile):
class TestImportProjectDocuments(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc().set(ifc)
document = ifc.createIfcDocumentInformation()
subject.import_information()
ifc.createIfcProject()
document = ifcopenshell.api.run("document.add_information", ifc)
subject.import_project_documents()
props = bpy.context.scene.BIMDocumentProperties
assert len(props.documents) == 1
assert props.documents[0].ifc_definition_id == document.id()
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):
ifc = ifcopenshell.file()
tool.Ifc().set(ifc)
document = ifc.createIfcDocumentReference()
subject.import_references()
ifc.createIfcProject()
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
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].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):
@@ -184,6 +217,15 @@ class TestIsDocumentInformation(NewFile):
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):
def test_run(self):
ifc = ifcopenshell.file()
+3
View File
@@ -399,10 +399,13 @@ class TestImportDrawings(NewFile):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
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()
props = bpy.context.scene.DocProperties
assert props.drawings[0].ifc_definition_id == drawing.id()
assert props.drawings[0].name == "FOOBAR"
assert props.drawings[0].target_view == "PLAN_VIEW"
class TestImportSheets(NewFile):
+119
View File
@@ -34,3 +34,122 @@ class TestAddDefaultMaterialObject(NewFile):
material = subject.add_default_material_object()
assert isinstance(material, bpy.types.Material)
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
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):
def test_run(self):
obj = bpy.data.materials.new("Material")
@@ -57,6 +64,13 @@ class TestEnableEditing(NewFile):
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):
def test_run(self):
TestImportSurfaceAttributes().test_run()
@@ -64,6 +78,16 @@ class TestExportSurfaceAttributes(NewFile):
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):
def test_run(self):
bpy.ops.bim.create_project()
@@ -116,6 +140,7 @@ class TestGetSurfaceRenderingAttributes(NewFile):
"Green": 0.5,
"Blue": 0.5,
},
"SpecularColour": 0.0,
"SpecularHighlight": {"IfcSpecularRoughness": 0.2},
"ReflectanceMethod": "NOTDEFINED",
}
@@ -351,3 +376,53 @@ class TestImportSurfaceAttributes(NewFile):
assert len(obj.BIMStyleProperties.attributes) == 2
assert obj.BIMStyleProperties.attributes.get("Name").string_value == "Name"
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:
MAKE_TYPE_NAME(Kernel)()
: IfcGeom::Kernel(0)
: IfcGeom::Kernel()
, deflection_tolerance(0.001)
, max_faces_to_orient(-1.0)
, ifc_length_unit(1.0)
@@ -321,7 +321,7 @@ public:
{}
MAKE_TYPE_NAME(Kernel)(const MAKE_TYPE_NAME(Kernel)& other)
: IfcGeom::Kernel(0)
: IfcGeom::Kernel()
, deflection_tolerance(other.deflection_tolerance)
, max_faces_to_orient(other.max_faces_to_orient)
, 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 d = l->WallThickness() * getValue(GV_LENGTH_UNIT);
const bool fr1 = !l->OuterFilletRadius();
const bool fr2 = !l->InnerFilletRadius();
const bool fr1 = !!l->OuterFilletRadius();
const bool fr2 = !!l->InnerFilletRadius();
const double r1 = fr1 ? (*l->OuterFilletRadius()) * 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.);
}
// @todo: should this start from 0 in case of n > 64?
for (int i = 2; i < n; ++i) {
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");
if ((v = min_face_face_distance(r, 1.e-4)) < 1.e-4) {
reason = 2;
success = false;
// #2095 Check if this distance wasn't already realized in the input first operand.
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 <BRepAdaptor_CompCurve.hxx>
#include <Standard_Version.hxx>
#if OCC_VERSION_HEX < 0x70600
#include <BRepAdaptor_HCompCurve.hxx>
#endif
#include <Approx_Curve3d.hxx>
#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);
TopoDS_Wire w;
if (!convert_wire(basis_curve, w)) return false;
#if OCC_VERSION_HEX < 0x70600
BRepAdaptor_CompCurve cc(w, true);
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.
Approx_Curve3d approx(hcc, getValue(GV_PRECISION), GeomAbs_C0, 10, 10);
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();
} else {
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);
}
// @todo the strict tolerance should also govern these arbitrary precision increases
const double eps = getValue(GV_PRECISION) * 10;
const bool closed_by_proximity = polygon.Length() >= 3 && polygon.First().Distance(polygon.Last()) < eps;
if (closed_by_proximity) {
+4 -1
View File
@@ -42,6 +42,9 @@ namespace IfcGeom {
private:
Kernel* implementation_;
protected:
Kernel() {};
public:
// Tolerances and settings for various geometrical operations:
enum GeomValue {
@@ -80,7 +83,7 @@ namespace IfcGeom {
GV_BOOLEAN_ATTEMPT_2D
};
Kernel(IfcParse::IfcFile* file_ = 0);
Kernel(IfcParse::IfcFile* file_);
virtual ~Kernel() {}
@@ -22,12 +22,36 @@ import ifcopenshell
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {}
self.settings = {"parent": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
id_attribute = "DocumentId" if self.file.schema == "IFC2X3" else "Identification"
return self.file.create_entity(
"IfcDocumentInformation", **{id_attribute: ifcopenshell.guid.new(), "Name": "Unnamed"}
information = self.file.create_entity(
"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
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {}
self.settings = {"information": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
id_attribute = "ItemReference" if self.file.schema == "IFC2X3" else "Identification"
return self.file.create_entity("IfcDocumentReference", **{id_attribute: ifcopenshell.guid.new()})
if self.file.schema == "IFC2X3":
return self.file.create_entity("IfcDocumentReference")
return self.file.create_entity("IfcDocumentReference", ReferencedDocument=self.settings["information"])
@@ -56,7 +56,7 @@ class Usecase:
"IfcRelAssociatesDocument",
**{
"GlobalId": ifcopenshell.guid.new(),
# TODO: owner history
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"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
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
@@ -20,12 +20,11 @@
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"document": None}
self.settings = {"reference": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
self.file.remove(self.settings["document"])
for rel in self.file.by_type("IfcRelAssociatesDocument"):
if not rel.RelatingDocument:
self.file.remove(rel)
for rel in self.settings["reference"].DocumentRefForObjects or []:
self.file.remove(rel)
self.file.remove(self.settings["reference"])

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