Merge branch 'v0.7.0' into pset_select_same_value

This commit is contained in:
Dion Moult
2023-07-08 20:05:13 +10:00
committed by GitHub
67 changed files with 5504 additions and 5007 deletions
@@ -106,6 +106,7 @@ classes = [
prop.ObjProperty,
prop.Attribute,
prop.ModuleVisibility,
prop.BIMAreaProperties,
prop.BIMProperties,
prop.IfcParameter,
prop.PsetQto,
@@ -119,6 +120,7 @@ classes = [
ui.BIM_UL_topics,
ui.BIM_ADDON_preferences,
# Scene panel groups
ui.BIM_PT_root,
ui.BIM_PT_project_info,
ui.BIM_PT_project_setup,
ui.BIM_PT_collaboration,
@@ -159,6 +161,7 @@ def register():
bpy.app.handlers.load_post.append(handler.loadIfcStore)
bpy.app.handlers.save_post.append(handler.ensureIfcExported)
bpy.types.Scene.BIMProperties = bpy.props.PointerProperty(type=prop.BIMProperties)
bpy.types.Screen.BIMAreaProperties = bpy.props.CollectionProperty(type=prop.BIMAreaProperties)
bpy.types.Collection.BIMCollectionProperties = bpy.props.PointerProperty(type=prop.BIMCollectionProperties)
bpy.types.Object.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
bpy.types.Material.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
@@ -389,7 +389,7 @@
"scene.display.shading.studiolight_background_blur": 0.0,
"scene.display.shading.studiolight_intensity": 0.0,
"scene.display.shading.studiolight_rotate_z": 0.0,
"scene.display.shading.type": "SOLID",
"scene.display.shading.type": "RENDERED",
"scene.display.shading.use_compositor": "DISABLED",
"scene.display.shading.use_dof": false,
"scene.display.shading.use_scene_lights": false,
@@ -640,7 +640,7 @@
"scene.display.shading.studiolight_background_blur": 0.0,
"scene.display.shading.studiolight_intensity": 0.0,
"scene.display.shading.studiolight_rotate_z": 0.0,
"scene.display.shading.type": "SOLID",
"scene.display.shading.type": "RENDERED",
"scene.display.shading.use_compositor": "DISABLED",
"scene.display.shading.use_dof": false,
"scene.display.shading.use_scene_lights": false,
@@ -752,4 +752,4 @@
"space.overlay.xray_alpha_bone": 0.0
}
}
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
@@ -1,2 +0,0 @@
Name,foo
TransomThickness,500
1 Name foo
2 TransomThickness 500
Binary file not shown.
+35
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 os
import bpy
import json
import addon_utils
@@ -30,6 +31,7 @@ from mathutils import Vector
from math import cos, degrees
cwd = os.path.dirname(os.path.realpath(__file__))
global_subscription_owner = object()
@@ -72,6 +74,9 @@ def name_callback(obj, data):
if not obj.BIMObjectProperties.ifc_definition_id or "/" not in obj.name:
return
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
if element.is_a("IfcGridAxis"):
element.AxisTag = obj.name.split("/")[1]
refresh_ui_data()
if not element.is_a("IfcRoot"):
return
if obj.BIMObjectProperties.collection:
@@ -293,3 +298,33 @@ def setDefaultProperties(scene):
ifcopenshell.api.owner.settings.get_user = lambda ifc: core_owner.get_user(tool.Owner)
ifcopenshell.api.owner.settings.get_application = get_application
AuthoringData.type_thumbnails = {}
if bpy.context.preferences.addons["blenderbim"].preferences.should_setup_workspace:
if "BIM" in bpy.data.workspaces:
bpy.context.window.workspace = bpy.data.workspaces["BIM"]
else:
bpy.ops.workspace.append_activate(idname="BIM", filepath=os.path.join(cwd, "data", "workspace.blend"))
for obj in [bpy.data.objects.get("Camera"), bpy.data.objects.get("Light")]:
if obj:
bpy.data.objects.remove(obj)
for panel in [
"SCENE_PT_scene",
"SCENE_PT_unit",
"SCENE_PT_physics",
"SCENE_PT_rigid_body_world",
"SCENE_PT_audio",
"SCENE_PT_keying_sets",
"SCENE_PT_custom_props",
]:
try:
bpy.utils.unregister_class(getattr(bpy.types, panel))
except:
pass
# https://blender.stackexchange.com/questions/140644/how-can-make-the-state-of-a-boolean-property-relative-to-the-3d-view-area
for screen in bpy.data.screens:
screen.BIMAreaProperties.clear()
for i in range(20): # 20 is an arbitrary value of split areas
screen.BIMAreaProperties.add()
@@ -36,6 +36,7 @@ classes = (
operator.UndoBrick,
operator.RedoBrick,
operator.SerializeBrick,
operator.AddBrickNamespace,
prop.Brick,
prop.BIMBrickProperties,
ui.BIM_PT_brickschema,
@@ -109,9 +109,10 @@ class BrickschemaData:
if BrickStore.graph is None:
return []
results = []
filter = ["brick", "owl", "w3", "xml"]
for alias, uri in BrickStore.graph.namespaces():
# results.append((uri, f"{alias}: {uri}", ""))
results.append((uri, f"{alias}", ""))
if alias not in filter:
results.append((uri, f"{alias}: {uri}", ""))
return results
@classmethod
@@ -22,7 +22,7 @@ import blenderbim.tool as tool
import blenderbim.core.brick as core
import blenderbim.bim.handler
from blenderbim.bim.ifc import IfcStore
from blenderbim.tool.brick import BrickStore
class Operator:
def execute(self, context):
@@ -126,6 +126,7 @@ class AddBrick(bpy.types.Operator, Operator):
namespace=props.namespace,
brick_class=props.brick_equipment_class,
library=library,
label=props.new_brick_label
)
@@ -207,6 +208,36 @@ class RedoBrick(bpy.types.Operator, Operator):
class SerializeBrick(bpy.types.Operator, Operator):
bl_idname = "bim.serialize_brick"
bl_label = "Serialize Brick"
filter_glob: bpy.props.StringProperty(default="*.ttl", options={"HIDDEN"})
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
should_save_as: bpy.props.BoolProperty(name="Should Save As", default=False, options={"HIDDEN"})
def invoke(self, context, event):
if self.should_save_as or not BrickStore.path:
WindowManager = context.window_manager
WindowManager.fileselect_add(self)
return {"RUNNING_MODAL"}
else:
return self.execute(context)
def _execute(self, context):
core.serialize_brick(tool.Brick)
if self.should_save_as or not BrickStore.path:
BrickStore.path = self.filepath
core.serialize_brick(tool.Brick)
return {"FINISHED"}
@classmethod
def description(cls, context, properties):
if properties.should_save_as:
return "Save Brick project to a selected file"
return "Save the Brick project"
class AddBrickNamespace(bpy.types.Operator, Operator):
bl_idname = "bim.add_brick_namespace"
bl_label = "Add Brick Namespace"
def _execute(self, context):
props = context.scene.BIMBrickProperties
alias = props.new_brick_namespace_alias
uri = props.new_brick_namespace_uri
core.add_namespace(tool.Brick, alias=alias, uri=uri)
@@ -69,3 +69,7 @@ class BIMBrickProperties(PropertyGroup):
libraries: EnumProperty(name="Libraries", items=get_libraries)
namespace: EnumProperty(name="Namespace", items=get_namespaces)
brick_equipment_class: EnumProperty(name="Brick Equipment Class", items=get_brick_equipment_classes)
brick_settings_toggled: BoolProperty(name="Brick Settings Toggled", default=False)
new_brick_label: StringProperty(name="New Brick Label")
new_brick_namespace_alias: StringProperty(name="New Brick Namespace Alias")
new_brick_namespace_uri: StringProperty(name="New Brick Namespace URI")
@@ -20,7 +20,7 @@ import blenderbim.tool as tool
from bpy.types import Panel, UIList
from blenderbim.bim.helper import prop_with_search
from blenderbim.bim.module.brick.data import BrickschemaData, BrickschemaReferencesData
from blenderbim.tool.brick import BrickStore
class BIM_PT_brickschema(Panel):
bl_label = "Brickschema Project"
@@ -30,6 +30,11 @@ class BIM_PT_brickschema(Panel):
bl_region_type = "WINDOW"
bl_context = "scene"
@classmethod
def poll(cls, context):
aprops = context.screen.BIMAreaProperties[context.screen.areas[:].index(context.area)]
return aprops.tab == "OTHER"
def draw(self, context):
if not BrickschemaData.is_loaded:
BrickschemaData.load()
@@ -41,15 +46,39 @@ class BIM_PT_brickschema(Panel):
row.operator("bim.load_brick_project", text="Load Project")
return
if BrickStore.path:
row = self.layout.row(align=True)
row.label(text=BrickStore.path, icon='FILEBROWSER')
row = self.layout.row(align=True)
if len(self.props.brick_breadcrumbs):
row.operator("bim.rewind_brick_class", text="", icon="FRAME_PREV")
row.label(text=self.props.active_brick_class)
row.prop(data=self.props, property="brick_settings_toggled", text="", icon="PREFERENCES")
row.operator("bim.refresh_brick_viewer", text="", icon="FILE_REFRESH")
row.operator("bim.close_brick_project", text="", icon="CANCEL")
if self.props.brick_settings_toggled:
box = self.layout.box()
row = box.row(align=True)
row.label(text="Active Namespace:")
row = box.row(align=True)
prop_with_search(row, self.props, "namespace", text="")
row = box.row(align=True)
row.label(text="Bind New Namespace:")
row = box.row(align=True)
row.prop(data=self.props, property="new_brick_namespace_alias", text="")
col = row.column()
col.alignment = "CENTER"
col.scale_x = 1.1
col.label(text=":")
row.prop(data=self.props, property="new_brick_namespace_uri", text="")
row.operator("bim.add_brick_namespace", text="", icon="ADD")
row = self.layout.row(align=True)
prop_with_search(row, self.props, "namespace", text="")
row.label(text="Create Entity:")
row = self.layout.row(align=True)
row.prop(data=self.props, property="new_brick_label", text="")
prop_with_search(row, self.props, "brick_equipment_class", text="")
row.operator("bim.add_brick", text="", icon="ADD")
@@ -63,7 +92,10 @@ class BIM_PT_brickschema(Panel):
row.operator("bim.redo_brick", icon="LOOP_FORWARDS")
row = self.layout.row(align=True)
row.operator("bim.serialize_brick")
op = row.operator("bim.serialize_brick", icon="EXPORT", text="Save")
op.should_save_as = False
op = row.operator("bim.serialize_brick", icon="FILE_TICK", text="Save As")
op.should_save_as = True
self.layout.template_list("BIM_UL_bricks", "", self.props, "bricks", self.props, "active_brick_index")
@@ -190,6 +190,8 @@ class CreateAllShapes(bpy.types.Operator):
total = len(elements)
settings = ifcopenshell.geom.settings()
settings_2d = ifcopenshell.geom.settings()
settings_2d.set(settings_2d.INCLUDE_CURVES, True)
failures = []
excludes = () # For the developer to debug with
for i, element in enumerate(elements):
@@ -197,8 +199,16 @@ class CreateAllShapes(bpy.types.Operator):
continue
print(f"{i}/{total}:", element)
start = time.time()
shape = None
try:
shape = ifcopenshell.geom.create_shape(settings, element)
except:
try:
shape = ifcopenshell.geom.create_shape(settings_2d, element)
except:
failures.append(element)
print("***** FAILURE *****")
if shape:
print(
"Success",
time.time() - start,
@@ -206,9 +216,6 @@ class CreateAllShapes(bpy.types.Operator):
len(shape.geometry.edges),
len(shape.geometry.faces),
)
except:
failures.append(element)
print("***** FAILURE *****")
print(f"Failures: {len(failures)}")
for failure in failures:
print(failure)
@@ -89,7 +89,6 @@ classes = (
prop.BIMAnnotationProperties,
ui.BIM_PT_camera,
ui.BIM_PT_drawing_underlay,
ui.BIM_PT_annotation_utilities,
ui.BIM_PT_sheets,
ui.BIM_PT_drawings,
ui.BIM_PT_schedules,
@@ -300,9 +300,14 @@ class DecoratorData:
return None
dimension_style = "arrow"
fill_bg = False
classes = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Classes")
if classes and "oblique" in classes.lower().split():
dimension_style = "oblique"
if classes:
classes_split = classes.lower().split()
if "oblique" in classes_split:
dimension_style = "oblique"
elif "fill-bg" in classes_split:
fill_bg = True
pset_data = ifcopenshell.util.element.get_pset(element, "BBIM_Dimension") or {}
show_description_only = pset_data.get("ShowDescriptionOnly", False)
@@ -316,6 +321,7 @@ class DecoratorData:
"suppress_zero_inches": suppress_zero_inches,
"text_prefix": text_prefix,
"text_suffix": text_suffix,
"fill_bg": fill_bg,
}
cls.data[obj.name] = dimension_data
return dimension_data
@@ -1633,10 +1633,17 @@ class ActivateDrawingStyle(bpy.types.Operator, Operator):
space = self.get_view_3d(context) # Do not remove. It is used in exec later
style = json.loads(self.drawing_style.raster_style)
for path, value in style.items():
if isinstance(value, str):
exec(f"{path} = '{value}'")
else:
exec(f"{path} = {value}")
try:
if isinstance(value, str):
exec(f"{path} = '{value}'")
else:
exec(f"{path} = {value}")
except:
# Differences in Blender versions mean result in failures here
print("Failed to set shading style {path} to {value}")
shading_type = style.get("scene.display.shading.type", None)
if shading_type:
space.shading.type = shading_type
def set_query(self, context):
self.include_global_ids = []
@@ -828,45 +828,20 @@ class SvgWriter:
line_number = 0
for text_literal in text_literals:
# after pretty indentation some redundant spaces can occur in svg tags
# this is why we apply "font-size: 0;" to the text tag to remove those spaces
# and add clases to the tspan tags
# ref: https://github.com/IfcOpenShell/IfcOpenShell/issues/2833#issuecomment-1471584960
text = tool.Drawing.replace_text_literal_variables(text_literal.Literal, product)
attribs = {
"transform": text_transform,
"style": "font-size: 0;",
}
def add_text_tag(add_fill_bg):
nonlocal line_number
text_tag = self.svg.text(
"",
**(attribs | {"filter": "url(#fill-background)"}) if add_fill_bg else attribs,
**SvgWriter.get_box_alignment_parameters(text_literal.BoxAlignment),
)
self.svg.add(text_tag)
text_lines = text.replace("\\n", "\n").split("\n")
for text_line in text_lines:
# position has to be inserted at tspan to avoid x offset between tspans
# note that tspan doesn't support using `transform` attribute
# so we use (0,0) position because tspan is already offseted by text transform
tspan = self.svg.tspan(text_line, class_=classes_str, insert=(0, 0))
# doing it here and not in tspan constructor because constructor adds unnecessary spaces
tspan.update({"dy": f"{line_number}em"})
text_tag.add(tspan)
line_number += 1
if add_fill_bg:
# return line_number back to the original value
line_number -= len(text_lines)
if "fill-bg" in classes:
add_text_tag(True)
add_text_tag(False)
text_tags = self.create_text_tag(
text,
text_position_svg,
angle,
text_literal.BoxAlignment,
classes_str,
multiline=True,
fill_bg="fill-bg" in classes,
line_number_start=line_number,
)
for tag in text_tags:
self.svg.add(tag)
line_number += len(tag.elements)
def draw_break_annotations(self, obj):
x_offset = self.raw_width / 2
@@ -1231,6 +1206,7 @@ class SvgWriter:
suppress_zero_inches=dimension_data["suppress_zero_inches"],
text_prefix=dimension_data["text_prefix"],
text_suffix=dimension_data["text_suffix"],
fill_bg=dimension_data["fill_bg"],
)
def draw_measureit_arch_dimension_annotations(self):
@@ -1256,6 +1232,7 @@ class SvgWriter:
suppress_zero_inches=False,
text_prefix="",
text_suffix="",
fill_bg=False,
):
offset = Vector([self.raw_width, self.raw_height]) / 2
v0 = self.project_point_onto_camera(v0_global)
@@ -1281,44 +1258,44 @@ class SvgWriter:
line = self.svg.line(start=start, end=end, class_=" ".join(classes))
self.svg.add(line)
text_tags = []
text_tag_kwargs = {
"angle": angle,
"class_str": "DIMENSION",
"text_format": text_format,
"multiline": True,
"fill_bg": fill_bg,
}
if not show_description_only:
text = f"{text_prefix}{str(dimension)}{text_suffix}"
text_tag = self.create_text_tag(
text_tags += self.create_text_tag(
text,
text_position + perpendicular,
angle,
"bottom-middle",
"DIMENSION",
text_format=text_format,
multiline=True,
box_alignment="bottom-middle",
multiline_to_bottom=False,
**text_tag_kwargs,
)
self.svg.add(text_tag)
if dimension_text:
text_tag = self.create_text_tag(
text_tags += self.create_text_tag(
dimension_text,
text_position - perpendicular,
angle,
"top-middle",
"DIMENSION",
text_format=text_format,
multiline=True,
box_alignment="top-middle",
multiline_to_bottom=True,
**text_tag_kwargs,
)
self.svg.add(text_tag)
elif show_description_only and dimension_text:
text_tag = self.create_text_tag(
text_tags.extend += self.create_text_tag(
dimension_text,
text_position + perpendicular,
angle,
"bottom-middle",
"DIMENSION",
text_format=text_format,
multiline=True,
box_alignment="bottom-middle",
multiline_to_bottom=False,
**text_tag_kwargs,
)
self.svg.add(text_tag)
for tag in text_tags:
self.svg.add(tag)
def create_text_tag(
self,
@@ -1329,33 +1306,58 @@ class SvgWriter:
class_str,
text_format=lambda x: x,
multiline=False,
multiline_to_bottom=False,
multiline_to_bottom=True,
fill_bg=False,
line_number_start=0,
_draw_fill_bg=False,
):
"""returns list of created text tags"""
text_tags = []
if fill_bg:
method_kwargs = locals() | {"_draw_fill_bg": True, "fill_bg": False}
del method_kwargs["self"]
del method_kwargs["text_tags"]
text_tags += self.create_text_tag(**method_kwargs)
base_text_attrs = SvgWriter.get_box_alignment_parameters(box_alignment)
base_text_attrs = base_text_attrs | ({"filter": "url(#fill-background)"} if _draw_fill_bg else {})
if not multiline:
text_kwargs = {"transform": "rotate({} {} {})".format(angle, text_position.x, text_position.y)}
return self.svg.text(
transform_kwargs = {"transform": "rotate({} {} {})".format(angle, text_position.x, text_position.y)}
text_tag = self.svg.text(
text_format(text),
insert=text_position,
class_=class_str,
**(text_kwargs | SvgWriter.get_box_alignment_parameters(box_alignment)),
**(transform_kwargs | base_text_attrs),
)
text_tags.append(text_tag)
return text_tags
text_position_svg_str = ", ".join(map(str, text_position))
text_transform = f"translate({text_position_svg_str}) rotate({angle})"
# after pretty indentation some redundant spaces can occur in svg tags
# this is why we apply "font-size: 0;" to the text tag to remove those spaces
# and add clases to the tspan tags
# ref: https://github.com/IfcOpenShell/IfcOpenShell/issues/2833#issuecomment-1471584960
text_kwargs = {
"transform": text_transform,
"style": "font-size: 0;",
}
text_tag = self.svg.text("", **text_kwargs, **SvgWriter.get_box_alignment_parameters(box_alignment))
text_tag = self.svg.text("", **text_kwargs, **base_text_attrs)
text_tags.append(text_tag)
text_lines = text.replace("\\n", "\n").split("\n")
text_lines = text_lines if multiline_to_bottom else text_lines[::-1]
for line_number, text_line in enumerate(text_lines):
for line_number, text_line in enumerate(text_lines, line_number_start):
# position has to be inserted at tspan to avoid x offset between tspans
# note that tspan doesn't support using `transform` attribute
# so we use (0,0) position because tspan is already offseted by text transform
tspan = self.svg.tspan(text_format(text_line), class_=class_str, insert=(0, 0))
# doing it here and not in tspan constructor because constructor adds unnecessary spaces
tspan.update({"dy": f"{line_number if multiline_to_bottom else -line_number}em"})
text_tag.add(tspan)
return text_tag
return text_tags
def project_point_onto_camera(self, point):
# TODO is this needlessly complex?
@@ -489,87 +489,6 @@ class BIM_PT_text(Panel):
row.label(text=literal_data[attribute])
class BIM_PT_annotation_utilities(Panel):
bl_idname = "BIM_PT_annotation_utilities"
bl_label = "Annotation"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "BIM Documentation"
def draw(self, context):
layout = self.layout
self.props = context.scene.DocProperties
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Dimension", icon="FIXED_SIZE")
op.object_type = "DIMENSION"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op = row.operator("bim.add_annotation", text="Angle", icon="DRIVER_ROTATIONAL_DIFFERENCE")
op.object_type = "ANGLE"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Radius", icon="FORWARD")
op.object_type = "RADIUS"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op = row.operator("bim.add_annotation", text="Diameter", icon="ARROW_LEFTRIGHT")
op.object_type = "DIAMETER"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Text", icon="SMALL_CAPS")
op.object_type = "TEXT"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op = row.operator("bim.add_annotation", text="Leader", icon="TRACKING_BACKWARDS")
op.object_type = "TEXT_LEADER"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Stair Arrow", icon="SCREEN_BACK")
op.object_type = "STAIR_ARROW"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op = row.operator("bim.add_annotation", text="Hidden", icon="CON_TRACKTO")
op.object_type = "HIDDEN_LINE"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Level (Plan)", icon="SORTBYEXT")
op.object_type = "PLAN_LEVEL"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op = row.operator("bim.add_annotation", text="Level (Section)", icon="TRIA_DOWN")
op.object_type = "SECTION_LEVEL"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Breakline", icon="FCURVE")
op.object_type = "BREAKLINE"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op = row.operator("bim.add_annotation", text="Line", icon="MESH_MONKEY")
op.object_type = "LINEWORK"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Batting", icon="FORCE_FORCE")
op.object_type = "BATTING"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op.description = "Add batting annotation.\nThickness could be changed through Thickness property of BBIM_Batting property set"
op = row.operator("bim.add_annotation", text="Fill Area", icon="NODE_TEXTURE")
op.object_type = "FILL_AREA"
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Fall", icon="SORT_ASC")
op.object_type = "FALL"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op = row.operator("bim.add_annotation", text="Revision", icon="VOLUME_DATA")
op.object_type = "REVISION_CLOUD"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
row = layout.row(align=True)
row.prop(self.props, "should_draw_decorations", text="Viewport Annotations")
row.enabled = context.scene.camera is not None
class BIM_UL_drawinglist(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
@@ -187,6 +187,9 @@ class AnnotationToolUI:
selected_icon = "CHECKBOX_HLT" if cls.props.create_representation_for_type else "CHECKBOX_DEHLT"
row.prop(cls.props, "create_representation_for_type", text="", icon=selected_icon)
row = cls.layout.row(align=True)
row.prop(bpy.context.scene.DocProperties, "should_draw_decorations", text="Viewport Annotations")
@classmethod
def draw_edit_object_interface(cls, context):
if DecoratorData.get_ifc_text_data(bpy.context.object):
@@ -415,7 +415,11 @@ class OverrideDelete(bpy.types.Operator):
if self.is_batch:
ifcopenshell.util.element.batch_remove_deep2(tool.Ifc.get())
for obj in context.selected_objects:
if tool.Ifc.get_entity(obj):
element = tool.Ifc.get_entity(obj)
if element:
if ifcopenshell.util.element.get_pset(element, "BBIM_Array"):
self.report({"INFO"}, "Elements that are part of an array cannot be deleted.")
return {"FINISHED"}
tool.Geometry.delete_ifc_object(obj)
else:
bpy.data.objects.remove(obj)
@@ -33,6 +33,7 @@ classes = (
operator.ConvertGlobalToLocal,
operator.GetCursorLocation,
operator.SetCursorLocation,
operator.ConvertAngleToCoordinates,
prop.BIMGeoreferenceProperties,
ui.BIM_PT_gis,
ui.BIM_PT_gis_utilities,
@@ -171,3 +171,19 @@ class ConvertGlobalToLocal(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
core.convert_global_to_local(tool.Georeference)
class ConvertAngleToCoordinates(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.convert_angle_to_coord"
bl_label = "Convert Angle To Y Axis"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Convert angle to Y axis"
type: bpy.props.StringProperty()
@classmethod
def poll(cls, context):
file = tool.Ifc.get()
props = context.scene.BIMGeoreferenceProperties
return file and (props.angle_degree_input_x or props.angle_degree_input_y)
def _execute(self, context):
core.convert_angle_to_coord(tool.Georeference, type=self.type)
@@ -37,6 +37,12 @@ class BIMGeoreferenceProperties(PropertyGroup):
projected_crs: CollectionProperty(name="Projected CRS", type=Attribute)
coordinate_input: StringProperty(name="Coordinate Input", description='Formatted "x,y,z" (without quotes)')
coordinate_output: StringProperty(name="Coordinate Output", description='Formatted "x,y,z" (without quotes)')
angle_degree_input_x: FloatProperty(name="Angle Degree Input", description="Angle (in degrees) rel to Easting")
angle_degree_input_y: FloatProperty(name="Angle Degree Input", description="Angle (in degrees) rel to +Y")
x_axis_abscissa_output: StringProperty(name="X Axis Abscissa Ordinate Output", description="X axis abscissa and ordinate", )
x_axis_ordinate_output: StringProperty(name="X Axis Abscissa Ordinate Output", description="X axis abscissa and ordinate", )
y_axis_abscissa_output: StringProperty(name="Y Axis Abscissa Ordinate Output", description="Y axis abscissa and ordinate", )
y_axis_ordinate_output: StringProperty(name="Y Axis Abscissa Ordinate Output", description="Y axis abscissa and ordinate", )
has_blender_offset: BoolProperty(name="Has Blender Offset")
blender_eastings: StringProperty(name="Blender Eastings", default="0")
blender_northings: StringProperty(name="Blender Northings", default="0")
@@ -167,3 +167,21 @@ class BIM_PT_gis_utilities(Panel):
row = self.layout.row(align=True)
row.operator("bim.convert_local_to_global", text="Local to Global")
row.operator("bim.convert_global_to_local", text="Global to Local")
row = self.layout.row(align=True)
row.label(text="Orientation Calculator", icon="TRACKING_REFINE_FORWARDS")
row = self.layout.row(align=True)
row.prop(props, "angle_degree_input_x", text="Angle (°) rel to Easting")
row.operator("bim.convert_angle_to_coord", text="", icon="FILE_REFRESH").type = "rel_x"
row = self.layout.row(align=True)
row.prop(props, "x_axis_abscissa_output", text="XAxis Abscissa")
row = self.layout.row(align=True)
row.prop(props, "x_axis_ordinate_output", text="XAxis Ordinate")
row = self.layout.row(align=True)
row.prop(props, "angle_degree_input_y", text="Angle (°) rel to to +Y")
row.operator("bim.convert_angle_to_coord", text="", icon="FILE_REFRESH").type = "rel_y"
row = self.layout.row(align=True)
row.prop(props, "y_axis_abscissa_output", text="North Abscissa")
row = self.layout.row(align=True)
row.prop(props, "y_axis_ordinate_output", text="North Ordinate")
@@ -54,6 +54,8 @@ classes = (
product.AddEmptyType,
product.AlignProduct,
product.ChangeTypePage,
product.DisableAddType,
product.EnableAddType,
product.LoadTypeThumbnails,
product.MirrorElements,
workspace.Hotkey,
@@ -37,6 +37,24 @@ from . import prop
import json
class EnableAddType(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.enable_add_type"
bl_label = "Enable Add Type"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
bpy.context.scene.BIMModelProperties.is_adding_type = True
class DisableAddType(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.disable_add_type"
bl_label = "Disable Add Type"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
bpy.context.scene.BIMModelProperties.is_adding_type = False
class AddEmptyType(bpy.types.Operator, AddObjectHelper):
bl_idname = "bim.add_empty_type"
bl_label = "Add Empty Type"
@@ -154,10 +172,11 @@ class AddConstrTypeInstance(bpy.types.Operator):
)
else:
parent = ifcopenshell.util.element.get_container(building_element)
parent_obj = tool.Ifc.get_object(parent)
blenderbim.core.spatial.assign_container(
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=parent_obj, element_obj=obj
)
if parent:
parent_obj = tool.Ifc.get_object(parent)
blenderbim.core.spatial.assign_container(
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=parent_obj, element_obj=obj
)
# set occurences properties for the types defined with modifiers
if instance_class in ["IfcWindow", "IfcDoor"]:
@@ -92,6 +92,7 @@ class BIMModelProperties(PropertyGroup):
)
icon_id: bpy.props.IntProperty()
updating: bpy.props.BoolProperty(default=False)
is_adding_type: bpy.props.BoolProperty(default=False)
occurrence_name_style: bpy.props.EnumProperty(
items=[("CLASS", "By Class", ""), ("TYPE", "By Type", ""), ("CUSTOM", "Custom", "")],
name="Occurrence Name Style",
@@ -132,7 +133,7 @@ class BIMModelProperties(PropertyGroup):
z: bpy.props.FloatProperty(name="Z", default=0.5)
rl1: bpy.props.FloatProperty(name="RL", default=1) # Used for things like walls, doors, flooring, skirting, etc
rl2: bpy.props.FloatProperty(name="RL", default=1) # Used for things like windows, other hosted furniture
x_angle: bpy.props.FloatProperty(name="X Angle", default=0, subtype="ANGLE")
x_angle: bpy.props.FloatProperty(name="X Angle", default=0, subtype="ANGLE", min=0, max=pi / 180 * 89)
type_page: bpy.props.IntProperty(name="Type Page", default=1, update=update_type_page)
type_template: bpy.props.EnumProperty(
items=(
@@ -70,10 +70,7 @@ class LaunchTypeManager(bpy.types.Operator):
row = columns.row(align=True)
row.alignment = "CENTER"
row.prop(props, "type_predefined_type", text="")
row.prop(props, "type_template", text="")
row.prop(props, "type_name", text="")
row.operator("bim.add_type", icon="ADD", text="")
# In case you want something here in the future
row = columns.row(align=True)
row.alignment = "RIGHT"
@@ -86,6 +83,22 @@ class LaunchTypeManager(bpy.types.Operator):
op = row.operator("bim.change_type_page", icon="TRIA_RIGHT", text="")
op.page = AuthoringData.data["next_page"]
if props.is_adding_type:
row = self.layout.row()
box = row.box()
row = box.row()
row.prop(props, "type_predefined_type")
row = box.row()
row.prop(props, "type_template")
row = box.row()
row.prop(props, "type_name")
row = box.row(align=True)
row.operator("bim.add_type", icon="CHECKMARK", text="Save New Type")
row.operator("bim.disable_add_type", icon="CANCEL", text="")
else:
row = self.layout.row()
row.operator("bim.enable_add_type", icon="ADD", text="Create New Type")
flow = self.layout.grid_flow(row_major=True, columns=3, even_columns=True, even_rows=True, align=True)
for relating_type in AuthoringData.data["paginated_relating_types"]:
@@ -77,14 +77,15 @@ class JoinWall(bpy.types.Operator, tool.Ifc.Operator):
return {"FINISHED"}
if self.join_type in ("L", "V"):
if len(selected_objs) == 2:
another_selected_object = next(o for o in selected_objs if o != context.active_object)
if self.join_type == "L":
joiner.join_L(another_selected_object, context.active_object)
elif self.join_type == "V":
joiner.join_V(another_selected_object, context.active_object)
self.report({"ERROR"}, f"It requires 2 selected objects to do join of type {self.join_type}")
return {"CANCELLED"}
if len(selected_objs) != 2:
self.report({"ERROR"}, f"It requires 2 selected objects to do join of type {self.join_type}")
return {"CANCELLED"}
another_selected_object = next(o for o in selected_objs if o != context.active_object)
if self.join_type == "L":
joiner.join_L(another_selected_object, context.active_object)
elif self.join_type == "V":
joiner.join_V(another_selected_object, context.active_object)
return {"FINISHED"}
if self.join_type == "T":
elements = [tool.Ifc.get_entity(o) for o in context.selected_objects]
@@ -22,6 +22,7 @@ from . import ui, prop, operator
classes = (
operator.AppendLibraryElement,
operator.AssignLibraryDeclaration,
operator.AppendEntireLibrary,
operator.ChangeLibraryElement,
operator.CreateProject,
operator.DisableEditingHeader,
@@ -33,10 +34,10 @@ classes = (
operator.LoadLink,
operator.LoadProject,
operator.LoadProjectElements,
operator.NewProject,
operator.RefreshLibrary,
operator.RewindLibrary,
operator.SaveLibraryFile,
operator.AppendEntireLibrary,
operator.SelectLibraryFile,
operator.ToggleFilterCategories,
operator.ToggleLinkVisibility,
@@ -48,6 +49,7 @@ classes = (
prop.FilterCategory,
prop.Link,
prop.BIMProjectProperties,
ui.BIM_MT_project,
ui.BIM_PT_project,
ui.BIM_PT_project_library,
ui.BIM_PT_links,
@@ -57,22 +59,11 @@ classes = (
)
def menu_func_export(self, context):
op = self.layout.operator(operator.ExportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcjson)")
op.should_save_as = True
def menu_func_import(self, context):
self.layout.operator(operator.ImportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcxml)")
def register():
bpy.types.Scene.BIMProjectProperties = bpy.props.PointerProperty(type=prop.BIMProjectProperties)
bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
bpy.types.TOPBAR_MT_file.prepend(ui.file_menu)
def unregister():
bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
bpy.types.TOPBAR_MT_file.remove(ui.file_menu)
del bpy.types.Scene.BIMProjectProperties
@@ -38,6 +38,53 @@ from blenderbim.bim import export_ifc
from pathlib import Path
class NewProject(bpy.types.Operator):
bl_idname = "bim.new_project"
bl_label = "New Project"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Start a new IFC project in a fresh session"
preset: bpy.props.StringProperty()
def execute(self, context):
bpy.ops.wm.read_homefile()
for obj in bpy.data.objects:
bpy.data.objects.remove(obj)
if self.preset == "metric_m":
bpy.context.scene.BIMProjectProperties.export_schema = "IFC4"
bpy.context.scene.unit_settings.system = "METRIC"
bpy.context.scene.unit_settings.length_unit = "METERS"
bpy.context.scene.BIMProperties.area_unit = "SQUARE_METRE"
bpy.context.scene.BIMProperties.volume_unit = "CUBIC_METRE"
bpy.context.scene.BIMProjectProperties.template_file = "0"
elif self.preset == "metric_mm":
bpy.context.scene.BIMProjectProperties.export_schema = "IFC4"
bpy.context.scene.unit_settings.system = "METRIC"
bpy.context.scene.unit_settings.length_unit = "MILLIMETERS"
bpy.context.scene.BIMProperties.area_unit = "SQUARE_METRE"
bpy.context.scene.BIMProperties.volume_unit = "CUBIC_METRE"
bpy.context.scene.BIMProjectProperties.template_file = "0"
elif self.preset == "imperial_ft":
bpy.context.scene.BIMProjectProperties.export_schema = "IFC4"
bpy.context.scene.unit_settings.system = "IMPERIAL"
bpy.context.scene.unit_settings.length_unit = "FEET"
bpy.context.scene.BIMProperties.area_unit = "square foot"
bpy.context.scene.BIMProperties.volume_unit = "cubic foot"
bpy.context.scene.BIMProjectProperties.template_file = "0"
elif self.preset == "demo":
bpy.context.scene.BIMProjectProperties.export_schema = "IFC4"
bpy.context.scene.unit_settings.system = "METRIC"
bpy.context.scene.unit_settings.length_unit = "MILLIMETERS"
bpy.context.scene.BIMProperties.area_unit = "SQUARE_METRE"
bpy.context.scene.BIMProperties.volume_unit = "CUBIC_METRE"
bpy.context.scene.BIMProjectProperties.template_file = "IFC4 Demo Library.ifc"
if self.preset != "wizard":
bpy.ops.bim.create_project()
return {"FINISHED"}
class CreateProject(bpy.types.Operator):
bl_idname = "bim.create_project"
bl_label = "Create Project"
@@ -528,6 +575,7 @@ class LoadProject(bpy.types.Operator, IFCFileSelector):
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml;*.ifcsqlite", options={"HIDDEN"})
is_advanced: bpy.props.BoolProperty(name="Enable Advanced Mode", default=False)
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
should_start_fresh_session: bpy.props.BoolProperty(name="Should Start Fresh Session", default=True)
def execute(self, context):
if not self.is_existing_ifc_file():
@@ -540,6 +588,12 @@ class LoadProject(bpy.types.Operator, IFCFileSelector):
return {"FINISHED"}
def invoke(self, context, event):
if self.should_start_fresh_session:
bpy.ops.wm.read_homefile()
for obj in bpy.data.objects:
bpy.data.objects.remove(obj)
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
@@ -828,6 +882,10 @@ class ExportIFC(bpy.types.Operator):
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
save_as_invoked: bpy.props.BoolProperty(name="Save As Dialog Was Invoked", default=False, options={"HIDDEN"})
@classmethod
def poll(cls, context):
return tool.Ifc.get()
def draw(self, context):
layout = self.layout
layout.prop(self, "json_version")
@@ -18,11 +18,36 @@
import os
from blenderbim.bim.helper import prop_with_search
from bpy.types import Panel, UIList
from bpy.types import Panel, Menu, UIList
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.project.data import ProjectData
class BIM_MT_project(Menu):
bl_idname = "BIM_MT_project"
bl_label = "New IFC Project"
def draw(self, context):
layout = self.layout
layout.operator("bim.new_project", text="New Metric (m) Project").preset = "metric_m"
layout.operator("bim.new_project", text="New Metric (mm) Project").preset = "metric_mm"
layout.operator("bim.new_project", text="New Imperial (ft) Project").preset = "imperial_ft"
layout.operator("bim.new_project", text="New Demo Project").preset = "demo"
layout.operator("bim.new_project", text="New Project Wizard").preset = "wizard"
def file_menu(self, context):
self.layout.menu("BIM_MT_project", icon="COLLECTION_NEW")
op = self.layout.operator("bim.load_project", text="Open IFC Project", icon="FILEBROWSER")
op.should_start_fresh_session = True
self.layout.separator()
op = self.layout.operator("export_ifc.bim", icon="FILE_TICK", text="Save IFC Project")
op.should_save_as = False
op = self.layout.operator("export_ifc.bim", text="Save IFC Project As...")
op.should_save_as = True
self.layout.separator()
class BIM_PT_project(Panel):
bl_label = "IFC Project"
bl_idname = "BIM_PT_project"
@@ -160,10 +185,8 @@ class BIM_PT_project(Panel):
row.operator("bim.select_ifc_file", icon="FILE_FOLDER", text="")
row = self.layout.row(align=True)
op = row.operator("export_ifc.bim", icon="EXPORT", text="Save Project")
op = row.operator("export_ifc.bim", icon="FILE_TICK", text="Save Project")
op.should_save_as = False
op = row.operator("export_ifc.bim", icon="FILE_TICK", text="Save As")
op.should_save_as = True
row.operator("bim.unload_project", text="", icon="CANCEL")
def draw_create_project_ui(self, context):
@@ -182,7 +205,7 @@ class BIM_PT_project(Panel):
row = self.layout.row(align=True)
row.operator("bim.create_project")
row.operator("bim.load_project")
row.operator("bim.load_project").should_start_fresh_session = False
class BIM_PT_project_library(Panel):
@@ -320,7 +320,6 @@ class EditPset(bpy.types.Operator, Operator):
tool.Blender.update_viewport()
class SelectSimilarPsetValue(bpy.types.Operator):
"""
Selects objects with the same property value.
@@ -364,9 +363,9 @@ class SelectSimilarPsetValue(bpy.types.Operator):
obj.select_set(True)
except:
continue
return {"FINISHED"}
class RemovePset(bpy.types.Operator, Operator):
bl_idname = "bim.remove_pset"
bl_label = "Remove Pset"
@@ -159,7 +159,7 @@ class IfcClassData:
@classmethod
def name(cls):
element = tool.Ifc.get_entity(bpy.context.active_object)
element = tool.Ifc.get_entity(bpy.context.view_layer.objects.active)
if not element:
return
name = element.is_a()
@@ -170,13 +170,13 @@ class IfcClassData:
@classmethod
def ifc_class(cls):
element = tool.Ifc.get_entity(bpy.context.active_object)
element = tool.Ifc.get_entity(bpy.context.view_layer.objects.active)
if element:
return element.is_a()
@classmethod
def can_reassign_class(cls):
element = tool.Ifc.get_entity(bpy.context.active_object)
element = tool.Ifc.get_entity(bpy.context.view_layer.objects.active)
if element:
if element.is_a("IfcOpeningElement") or element.is_a("IfcOpeningStandardCase"):
return False
@@ -383,7 +383,7 @@ class AddType(bpy.types.Operator, tool.Ifc.Operator):
elif template == "RAILING":
mesh = bpy.data.meshes.new("IfcRailing")
obj = bpy.data.objects.new("TYPEX", mesh)
obj = bpy.data.objects.new(name, mesh)
element = blenderbim.core.root.assign_class(
tool.Ifc,
tool.Collector,
@@ -402,7 +402,7 @@ class AddType(bpy.types.Operator, tool.Ifc.Operator):
elif template == "ROOF":
mesh = bpy.data.meshes.new("IfcRoof")
obj = bpy.data.objects.new("TYPEX", mesh)
obj = bpy.data.objects.new(name, mesh)
element = blenderbim.core.root.assign_class(
tool.Ifc,
tool.Collector,
+18
View File
@@ -335,6 +335,24 @@ class ModuleVisibility(PropertyGroup):
is_visible: BoolProperty(name="Value", default=True, update=update_is_visible)
class BIMAreaProperties(PropertyGroup):
tab: EnumProperty(
default="PROJECT",
items=[
("PROJECT", "Project Overview", "", "VIEW3D", 1),
("OBJECT", "Object Information", "", "FILE_3D", 2),
("MATERIALS", "Materials and Styles", "", "MATERIAL", 3),
("DRAWINGS", "Drawings and Documents", "", "DOCUMENTS", 4),
("SERVICES", "Services and Systems", "", "NETWORK_DRIVE", 5),
("STRUCTURE", "Structural Analysis", "", "EDITMODE_HLT", 6),
("SCHEDULING", "Construction Scheduling", "", "NLA", 7),
("FM", "Facility Management", "", "PACKAGE", 8),
("OTHER", "Other Properties", "", "COLLAPSEMENU", 9),
],
name="Tab",
)
class BIMProperties(PropertyGroup):
ui_preset: EnumProperty(
name="UI Preset",
+52 -6
View File
@@ -133,6 +133,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
spreadsheet_command: StringProperty(name="Spreadsheet Command", description="E.g. [['libreoffice', path]]")
openlca_port: IntProperty(name="OpenLCA IPC Port", default=8080)
should_hide_empty_props: BoolProperty(name="Should Hide Empty Properties", default=True)
should_setup_workspace: BoolProperty(name="Should Setup Workspace Layout for BIM", default=True)
should_play_chaching_sound: BoolProperty(
name="Should Make A Cha-Ching Sound When Project Costs Updates", default=False
)
@@ -209,6 +210,8 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
row = layout.row()
row.prop(self, "should_hide_empty_props")
row = layout.row()
row.prop(self, "should_setup_workspace")
row = layout.row()
row.prop(self, "should_play_chaching_sound")
row = layout.row()
row.prop(self, "lock_grids_on_import")
@@ -278,12 +281,30 @@ def ifc_units(self, context):
# Scene panel groups
class BIM_PT_root(Panel):
bl_label = "BlenderBIM Add-on"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_order = 0
bl_options = {"HIDE_HEADER"}
def draw(self, context):
aprops = context.screen.BIMAreaProperties[context.screen.areas[:].index(context.area)]
self.layout.prop(aprops, "tab", text="")
class BIM_PT_project_info(Panel):
bl_label = "IFC Project Info"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@classmethod
def poll(cls, context):
aprops = context.screen.BIMAreaProperties[context.screen.areas[:].index(context.area)]
return aprops.tab == "PROJECT"
def draw(self, context):
pass
@@ -295,6 +316,11 @@ class BIM_PT_project_setup(Panel):
bl_context = "scene"
bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
aprops = context.screen.BIMAreaProperties[context.screen.areas[:].index(context.area)]
return aprops.tab == "PROJECT"
def draw(self, context):
pass
@@ -306,6 +332,11 @@ class BIM_PT_collaboration(Panel):
bl_context = "scene"
bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
aprops = context.screen.BIMAreaProperties[context.screen.areas[:].index(context.area)]
return aprops.tab == "OTHER"
def draw(self, context):
pass
@@ -319,7 +350,8 @@ class BIM_PT_selection(Panel):
@classmethod
def poll(cls, context):
return tool.Ifc.get()
aprops = context.screen.BIMAreaProperties[context.screen.areas[:].index(context.area)]
return aprops.tab == "PROJECT" and tool.Ifc.get()
def draw(self, context):
pass
@@ -334,7 +366,8 @@ class BIM_PT_geometry(Panel):
@classmethod
def poll(cls, context):
return tool.Ifc.get()
aprops = context.screen.BIMAreaProperties[context.screen.areas[:].index(context.area)]
return aprops.tab == "PROJECT" and tool.Ifc.get()
def draw(self, context):
pass
@@ -349,7 +382,8 @@ class BIM_PT_4D5D(Panel):
@classmethod
def poll(cls, context):
return tool.Ifc.get()
aprops = context.screen.BIMAreaProperties[context.screen.areas[:].index(context.area)]
return aprops.tab == "SCHEDULING" and tool.Ifc.get()
def draw(self, context):
pass
@@ -364,7 +398,8 @@ class BIM_PT_structural(Panel):
@classmethod
def poll(cls, context):
return tool.Ifc.get()
aprops = context.screen.BIMAreaProperties[context.screen.areas[:].index(context.area)]
return aprops.tab == "STRUCTURE" and tool.Ifc.get()
def draw(self, context):
pass
@@ -379,7 +414,8 @@ class BIM_PT_services(Panel):
@classmethod
def poll(cls, context):
return tool.Ifc.get()
aprops = context.screen.BIMAreaProperties[context.screen.areas[:].index(context.area)]
return aprops.tab == "SERVICES" and tool.Ifc.get()
def draw(self, context):
pass
@@ -392,6 +428,11 @@ class BIM_PT_quality_control(Panel):
bl_context = "scene"
bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
aprops = context.screen.BIMAreaProperties[context.screen.areas[:].index(context.area)]
return aprops.tab == "OTHER"
def draw(self, context):
pass
@@ -403,6 +444,11 @@ class BIM_PT_integrations(Panel):
bl_context = "scene"
bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
aprops = context.screen.BIMAreaProperties[context.screen.areas[:].index(context.area)]
return aprops.tab == "OTHER"
def draw(self, context):
pass
@@ -493,7 +539,7 @@ class UIData:
@classmethod
def load(cls):
cls.data = { "version": cls.version() }
cls.data = {"version": cls.version()}
cls.is_loaded = True
@classmethod
+11 -4
View File
@@ -47,6 +47,7 @@ def rewind_brick_class(brick):
def close_brick_project(brick):
brick.clear_project()
brick.clear_brick_browser()
def convert_brick_project(ifc, brick):
@@ -67,13 +68,13 @@ def assign_brick_reference(ifc, brick, element=None, library=None, brick_uri=Non
brick.add_brickifc_reference(brick_uri, element, project)
def add_brick(ifc, brick, element=None, namespace=None, brick_class=None, library=None):
def add_brick(ifc, brick, element=None, namespace=None, brick_class=None, library=None, label="Unnamed"):
if element:
brick_uri = brick.add_brick_from_element(element, namespace, brick_class)
if library:
brick.run_assign_brick_reference(element=element, library=library, brick_uri=brick_uri)
else:
brick_uri = brick.add_brick(namespace, brick_class)
brick_uri = brick.add_brick(namespace, brick_class, label)
brick.run_refresh_brick_viewer()
@@ -110,13 +111,19 @@ def remove_brick(ifc, brick, library=None, brick_uri=None):
brick.remove_brick(brick_uri)
brick.run_refresh_brick_viewer()
def undo_brick(brick):
brick.undo_brick()
brick.run_refresh_brick_viewer()
def redo_brick(brick):
brick.redo_brick()
brick.run_refresh_brick_viewer()
def serialize_brick(brick, file_name="BlenderBIMSerializeTest.ttl"):
brick.serialize_brick(file_name)
def serialize_brick(brick):
brick.serialize_brick()
def add_namespace(brick, alias=None, uri=None):
brick.add_namespace(alias, uri)
@@ -80,3 +80,7 @@ def convert_global_to_local(georeference):
coordinates = georeference.enh2xyz(georeference.get_coordinates("input"), georeference.get_map_conversion())
georeference.set_coordinates("output", coordinates)
georeference.set_cursor_location(coordinates)
def convert_angle_to_coord(georeference, type):
vector_coordinates = georeference.angle2coords(georeference.get_angle(type), type)
georeference.set_vector_coordinates(vector_coordinates,type)
+4 -1
View File
@@ -370,13 +370,15 @@ class Geometry:
@interface
class Georeference:
def angle2coords(cls, angle, type): pass
def disable_editing(cls): pass
def enable_editing(cls): pass
def enh2xyz(cls, map_conversion, coordinates): pass
def get_angle(cls, type): pass
def get_coordinates(cls, io): pass
def get_cursor_location(cls): pass
def get_map_conversion(cls): pass
def get_map_conversion_attributes(cls): pass
def get_map_conversion(cls): pass
def get_projected_crs_attributes(cls): pass
def get_true_north_attributes(cls): pass
def import_map_conversion(cls): pass
@@ -388,6 +390,7 @@ class Georeference:
def set_cursor_location(cls, coordinates): pass
def set_ifc_grid_north(cls): pass
def set_ifc_true_north(cls): pass
def set_vector_coordinates(cls, vector_coordinates, type): pass
def xyz2enh(cls, map_conversion, coordinates): pass
+10 -10
View File
@@ -41,12 +41,12 @@ logger.setLevel(logging.ERROR)
class Brick(blenderbim.core.tool.Brick):
@classmethod
def add_brick(cls, namespace, brick_class):
def add_brick(cls, namespace, brick_class, label):
ns = Namespace(namespace)
brick = ns[ifcopenshell.guid.expand(ifcopenshell.guid.new())]
with BrickStore.graph.new_changeset("PROJECT") as cs:
cs.add((brick, RDF.type, URIRef(brick_class)))
cs.add((brick, URIRef("http://www.w3.org/2000/01/rdf-schema#label"), Literal("Unnamed")))
cs.add((brick, URIRef("http://www.w3.org/2000/01/rdf-schema#label"), Literal(label)))
return str(brick)
@classmethod
@@ -275,8 +275,6 @@ class Brick(blenderbim.core.tool.Brick):
with BrickStore.graph.new_changeset("SCHEMA") as cs:
cs.load_file(BrickStore.schema)
BrickStore.graph.bind("digitaltwin", Namespace("https://example.org/digitaltwin#"))
BrickStore.graph.bind("brick", Namespace("https://brickschema.org/schema/Brick#"))
BrickStore.graph.bind("rdfs", Namespace("http://www.w3.org/2000/01/rdf-schema#"))
@classmethod
def pop_brick_breadcrumb(cls):
@@ -332,12 +330,14 @@ class Brick(blenderbim.core.tool.Brick):
BrickStore.graph.redo()
@classmethod
def serialize_brick(cls, file_name):
#temporary file path, could either be user selected for "save as" or use the BrickStore.path for simply "save"
cwd = os.path.dirname(os.path.realpath(__file__))
dest = os.path.join(cwd, "..", "bim", "schema", file_name)
BrickStore.get_project().serialize(destination=dest, format="turtle")
def serialize_brick(cls):
BrickStore.get_project().serialize(destination=BrickStore.path, format="turtle")
@classmethod
def add_namespace(cls, alias, uri):
BrickStore.graph.bind(alias, Namespace(uri))
# need some way to reload namespace enum view
class BrickStore:
schema = None # this is now a os path
path = None # file path if the project was loaded in
+4 -1
View File
@@ -1499,7 +1499,10 @@ class Drawing(blenderbim.core.tool.Drawing):
filtered_elements = cls.get_drawing_elements(drawing) | cls.get_drawing_spaces(drawing)
for visible_obj in bpy.context.visible_objects:
hide = tool.Ifc.get_entity(visible_obj) not in filtered_elements
element = tool.Ifc.get_entity(visible_obj)
if not element:
continue
hide = element not in filtered_elements
if bpy.context.view_layer.objects.get(visible_obj.name):
visible_obj.hide_set(hide)
visible_obj.hide_render = hide
@@ -272,3 +272,27 @@ class Georeference(blenderbim.core.tool.Georeference):
float(props.map_conversion.get("XAxisOrdinate").string_value),
)
bpy.context.scene.sun_pos_properties.north_offset = -radians(angle)
@classmethod
def angle2coords(cls, angle, type):
if type == "rel_x":
return ifcopenshell.util.geolocation.angle2xaxis(angle)
elif type == "rel_y":
return ifcopenshell.util.geolocation.angle2yaxis(angle)
@classmethod
def get_angle(cls, type):
if type == "rel_x":
return bpy.context.scene.BIMGeoreferenceProperties.angle_degree_input_x
elif type == "rel_y":
return bpy.context.scene.BIMGeoreferenceProperties.angle_degree_input_y
@classmethod
def set_vector_coordinates(cls, vector_coordinates, type):
x, y = vector_coordinates
if type == "rel_x":
bpy.context.scene.BIMGeoreferenceProperties.x_axis_abscissa_output = str(x)
bpy.context.scene.BIMGeoreferenceProperties.x_axis_ordinate_output = str(y)
elif type == "rel_y":
bpy.context.scene.BIMGeoreferenceProperties.y_axis_abscissa_output = str(x)
bpy.context.scene.BIMGeoreferenceProperties.y_axis_ordinate_output = str(y)
+2 -2
View File
@@ -103,7 +103,7 @@ class Ifc(blenderbim.core.tool.Ifc):
ifc_path = cls.get_path()
if os.path.isfile(ifc_path):
ifc_path = os.path.dirname(ifc_path)
return uri if not uri or os.path.isabs(uri) else os.path.join(ifc_path, uri)
return (uri if not uri or os.path.isabs(uri) else os.path.join(ifc_path, uri)).replace("\\", "/")
@classmethod
def get_relative_uri(cls, uri):
@@ -112,7 +112,7 @@ class Ifc(blenderbim.core.tool.Ifc):
ifc_path = cls.get_path()
if os.path.isfile(ifc_path):
ifc_path = os.path.dirname(ifc_path)
return os.path.relpath(uri, ifc_path)
return os.path.relpath(uri, ifc_path).replace("\\", "/")
@classmethod
def unlink(cls, element=None, obj=None):
@@ -31,6 +31,8 @@ class Project(blenderbim.core.tool.Project):
# TODO refactor
filepath = os.path.join(bpy.context.scene.BIMProperties.data_dir, "libraries", template)
bpy.ops.bim.select_library_file(filepath=filepath)
if IfcStore.library_file.schema != tool.Ifc.get().schema:
return
for element in IfcStore.library_file.by_type("IfcTypeProduct"):
bpy.ops.bim.append_library_element(definition=element.id())
@@ -135,6 +135,7 @@ def create_z_profile_lips_curve(ifc_file, FirstFlangeWidth, SecondFlangeWidth, D
return ifc_curve
class LibraryGenerator:
def generate(self, parse_profiles_type="EU", output_filename="IFC4 EU Steel.ifc"):
ifcopenshell.api.pre_listeners = {}
@@ -171,10 +172,13 @@ class LibraryGenerator:
"profile_z_lips": ("IfcArbitraryClosedProfileDef", {"t": "WallThickness", "c1": "FirstFlangeWidth", "c2": "SecondFlangeWidth", "h": "Depth", "r": "FilletRadius", "ll": "Girth"}),
}
US_profiles = ("ibeam_w_imp", )
if parse_profiles_type == "AU":
bolt_class_filter = lambda x: "bluescope" in x.id
elif parse_profiles_type == "EU":
bolt_class_filter = lambda x: "bluescope" not in x.id
bolt_class_filter = lambda x: "bluescope" not in x.id and x.id not in US_profiles
elif parse_profiles_type == "US":
bolt_class_filter = lambda x: x.id in US_profiles
else:
bolt_class_filter = lambda x: True
@@ -195,12 +199,27 @@ class LibraryGenerator:
# like hollow_generic_square
continue
bolts_cols = bolt_class.parameters.tables[0].columns
bolts_cols = [ifc_params_translation.get(c, "unused") for c in bolts_cols]
bolts_cols_original = bolt_class.parameters.tables[0].columns
bolts_cols = [ifc_params_translation.get(c, "unused") for c in bolts_cols_original]
inch_to_mm = lambda x: x * 0.0254 * 1000
def assure_data_units_is_mm(data, units):
data = data.copy()
for i in range(len(units)):
unit = units[i]
assert unit in ("Length (in)", "Length (mm)")
if unit != "Length (in)":
continue
for profile in data:
data[profile][i] = inch_to_mm(data[profile][i])
return data
bolts_data = bolt_class.parameters.tables[0].data
data_units = [bolt_class.parameters.types[col] for col in bolts_cols_original]
bolts_data = assure_data_units_is_mm(bolts_data, data_units)
for prof_name in bolts_data.keys():
ifc_params = dict(zip(bolts_cols, bolts_data[prof_name]))
ifc_params = dict(zip(bolts_cols, bolts_data[prof_name], strict=True))
if "unused" in ifc_params:
del ifc_params["unused"]
@@ -240,3 +259,4 @@ if __name__ == "__main__":
path = Path(__file__).parents[1] / "blenderbim/bim/data/libraries"
LibraryGenerator().generate(parse_profiles_type="EU", output_filename=str(path / "IFC4 EU Steel.ifc"))
LibraryGenerator().generate(parse_profiles_type="AU", output_filename=str(path / "IFC4 AU Steel.ifc"))
LibraryGenerator().generate(parse_profiles_type="US", output_filename=str(path / "IFC4 US Steel.ifc"))
+1 -1
View File
@@ -175,7 +175,7 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcBooleanResult* l, TopoDS_Shape
TopoDS_Shape temp;
double d;
if (util::fit_halfspace(s1, s2, temp, d, getValue(GV_PRECISION) * 1000.)) {
// #2665 we also set a precision-independent treshold, because in the boolean op routine
// #2665 we also set a precision-independent threshold, because in the boolean op routine
// the working fuzziness might still be increased.
if (d < getValue(GV_PRECISION) * 20. || d < 0.00002) {
Logger::Message(Logger::LOG_WARNING, "Halfspace subtraction yields unchanged volume:", l);
+1 -1
View File
@@ -169,7 +169,7 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcCompositeCurve* l, TopoDS_Wire
}
if (converted_segments.Extent() == 0) {
Logger::Message(Logger::LOG_ERROR, "No segment succesfully converted:", l);
Logger::Message(Logger::LOG_ERROR, "No segment successfully converted:", l);
return false;
}
+1 -1
View File
@@ -62,7 +62,7 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcCurveBoundedPlane* l, TopoDS_S
ShapeFix_Shape sfs(mf.Face());
sfs.Perform();
// `trsf` consitutes the placement of the plane and therefore has unit scale factor
// `trsf` constitutes the placement of the plane and therefore has unit scale factor
face = TopoDS::Face(sfs.Shape()).Moved(trsf);
return true;
+1 -1
View File
@@ -56,7 +56,7 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcFace* l, TopoDS_Shape& result)
TopoDS_Shape surface_shape;
if (!convert_shape(fs->FaceSurface(), surface_shape)) return false;
// FIXME: Assert this obtaines the only face
// FIXME: Assert this obtains the only face
TopExp_Explorer exp(surface_shape, TopAbs_FACE);
if (!exp.More()) return false;
+3 -3
View File
@@ -1239,7 +1239,7 @@ bool IfcGeom::Kernel::fold_layers(const IfcSchema::IfcWall* wall, const IfcRepre
typedef std::vector< std::vector<Handle_Geom_Surface> > result_t;
endpoint_connections_t endpoint_connections;
// Find the semantic connections ot other wall elements when they are not connected 'AT_PATH' because
// Find the semantic connections to other wall elements when they are not connected 'AT_PATH' because
// in that latter case no folds need to be made.
for (IfcSchema::IfcRelConnectsPathElements::list::it it = connections->begin(); it != connections->end(); ++it) {
IfcSchema::IfcRelConnectsPathElements* connection = *it;
@@ -1360,7 +1360,7 @@ bool IfcGeom::Kernel::fold_layers(const IfcSchema::IfcWall* wall, const IfcRepre
// range. It's only a safeguard though, so can probably be approximated.
const double axis_length = own_axis_start.Distance(own_axis_end);
if (length_required > axis_length) {
Logger::Warning("The wall axis is not long enough to accomodate the fold points");
Logger::Warning("The wall axis is not long enough to accommodate the fold points");
return false;
}
@@ -1455,7 +1455,7 @@ bool IfcGeom::Kernel::fold_layers(const IfcSchema::IfcWall* wall, const IfcRepre
result_t::iterator result_vector = result.begin() + 1;
// nb The first layer is never folded, because it corresponds
// to one of the longitudonal faces of the wall. Hence the +1
// to one of the longitudinal faces of the wall. Hence the +1
for (surfaces_t::const_iterator jt = surfaces.begin() + 1; jt != surfaces.end() - 1; ++jt, ++result_vector) {
layer_offset += *thickness++;
+1 -1
View File
@@ -700,7 +700,7 @@ namespace IfcGeom {
}
}
// Check if this represenation has (or will be) processed as part its mapped representation
// Check if this representation has (or will be) processed as part its mapped representation
bool representation_processed_as_mapped_item = false;
IfcSchema::IfcRepresentation* representation_mapped_to = kernel.representation_mapped_to(representation);
if (representation_mapped_to) {
+2 -2
View File
@@ -172,7 +172,7 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcTrimmedCurve* l, TopoDS_Wire&
// Fix from @sanderboer to compare using model tolerance, see #744
// Made dependent on radius, see #928
// A good critereon for determining whether to take full curve
// A good criterion for determining whether to take full curve
// or trimmed segment would be whether there are other curve segments or this
// is the only one.
boost::optional<size_t> num_segments;
@@ -227,7 +227,7 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcTrimmedCurve* l, TopoDS_Wire&
TopoDS_Vertex v0, v1;
TopExp::Vertices(e, v0, v1);
e = TopoDS::Edge(BRepBuilderAPI_MakeEdge(v0, v1).Edge().Oriented(e.Orientation()));
Logger::Warning("Subsituted edge with linear approximation", l);
Logger::Warning("Substituted edge with linear approximation", l);
}
}
+1 -1
View File
@@ -72,7 +72,7 @@ IfcGeom::Kernel::faceset_helper<CP, LP>::faceset_helper(
// double bdiff = std::sqrt(box.SquareExtent());
// @todo the bounding box diagonal is not used (see above)
// because we're explicitly interested in the miminal
// because we're explicitly interested in the minimal
// dimension of the element to limit the tolerance (for sheet-
// like elements for example). But the way below is very
// dependent on orientation due to the usage of the
+1 -1
View File
@@ -589,7 +589,7 @@ bool IfcGeom::util::create_solid_from_faces(const TopTools_ListOfShape& face_lis
bool has_shared_edges = false;
TopTools_MapOfShape edge_set;
// In case there are wire interesections or failures in non-planar wire triangulations
// In case there are wire intersections or failures in non-planar wire triangulations
// the idea is to let occt do an exhaustive search of edge partners. But we have not
// found a case where this actually improves boolean ops later on.
// if (!faceset_helper_ || !faceset_helper_->non_manifold()) {
@@ -1183,9 +1183,9 @@ bool IfcGeom::util::boolean_operation(const boolean_settings& settings, const To
}
if (!success) {
PERF("boolean operation: manifoldness check excemption");
PERF("boolean operation: manifoldness check exemption");
// An excemption for the requirement to be manifold: When the cut operands have overlapping edge belonging to faces that do not overlap.
// An exemption for the requirement to be manifold: When the cut operands have overlapping edge belonging to faces that do not overlap.
bool operands_nonmanifold = false;
if (op == BOPAlgo_CUT) {
TopTools_IndexedMapOfShape edges;
@@ -1248,7 +1248,7 @@ bool IfcGeom::util::boolean_operation(const boolean_settings& settings, const To
bool has_open_shells = false;
if (op == BOPAlgo_CUT) {
PERF("boolean operation: open shell face adition check");
PERF("boolean operation: open shell face addition check");
for (TopExp_Explorer exp(a, TopAbs_SHELL); exp.More(); exp.Next()) {
if (!exp.Current().Closed()) {
@@ -43,12 +43,12 @@ changes in the IfcOpenShell C++ core.
.. _py39-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-fc50bdd-linux64.zip
.. _py310-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-fc50bdd-linux64.zip
.. _py311-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.7.0-fc50bdd-linux64.zip
.. _py36-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-fc50bdd-win64.zip
.. _py37-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-fc50bdd-win64.zip
.. _py38-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-fc50bdd-win64.zip
.. _py39-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-fc50bdd-win64.zip
.. _py310-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-fc50bdd-win64.zip
.. _py311-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.7.0-fc50bdd-win64.zip
.. _py36-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-fc50bdd-win32.zip
.. _py37-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-fc50bdd-win32.zip
.. _py38-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-fc50bdd-win32.zip
.. _py39-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-fc50bdd-win32.zip
.. _py310-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-fc50bdd-win32.zip
.. _py311-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.7.0-fc50bdd-win32.zip
.. _py36-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-fc50bdd-win64.zip
.. _py37-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-fc50bdd-win64.zip
.. _py38-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-fc50bdd-win64.zip
@@ -188,4 +188,28 @@ def register_schema(schema):
register_schema_attributes(schema.schema)
def schema_by_name(schema=None, schema_version=None):
"""Returns an object allowing you to query the IFC schema itself
:param schema: Which IFC schema to use, chosen from "IFC2X3", "IFC4",
or "IFC4X3". These refer to the ISO approved versions of IFC.
:type schema: string
:param schema_version: If you want to specify an exact version of IFC
that may not be an ISO approved version, use this argument instead
of ``schema``. IFC versions on technical.buildingsmart.org are
described using 4 integers representing the major, minor, addendum,
and corrigendum number. For example, (4, 0, 2, 1) refers to IFC4
ADD2 TC1, which is the official version approved by ISO when people
refer to "IFC4". Generally you should not use this argument unless
you are testing non-ISO IFC releases.
:type schema_version: tuple[int]
"""
if schema_version:
prefixes = ("IFC", "X", "_ADD", "_TC")
schema = "".join("".join(map(str, t)) if t[1] else "" for t in zip(prefixes, schema_version))
else:
schema = {"IFC4X3": "IFC4X3_ADD1"}.get(schema, schema)
return ifcopenshell_wrapper.schema_by_name(schema)
from .main import *
@@ -103,7 +103,7 @@ def run(f, logger):
cd = compile(a, f"{f.schema}.py", "exec")
scope = {}
exec(cd, scope)
S = ifcopenshell.ifcopenshell_wrapper.schema_by_name(f.schema)
S = ifcopenshell.ifcopenshell_wrapper.schema_by_name(f.schema_identifier)
rules = list(filter(lambda x: hasattr(x, "SCOPE"), scope.values()))
+60 -4
View File
@@ -22,10 +22,11 @@ from __future__ import division
from __future__ import print_function
import os
from pathlib import Path
import re
import numbers
import functools
import zipfile
import functools
from pathlib import Path
import ifcopenshell.util.element
import ifcopenshell.util.file
@@ -191,8 +192,52 @@ class file(object):
print(products[0] == ifc_file[122] == ifc_file["2XQ$n5SLP5MBLyL442paFx"]) # True
"""
def __init__(self, f=None, schema=None):
"""Create a new file object"""
def __init__(self, f=None, schema=None, schema_version=None):
"""Create a new blank IFC model
This IFC model does not have any entities in it yet. See the
``create_entity`` function for how to create new entities. All data is
stored in memory. If you wish to write the IFC model to disk, see the
``write`` function.
:param f: The underlying IfcOpenShell file object to be wrapped. This
is an internal implementation detail and should generally be left
as None by users.
:param schema: Which IFC schema to use, chosen from "IFC2X3", "IFC4",
or "IFC4X3". These refer to the ISO approved versions of IFC.
Defaults to "IFC4" if not specified, which is currently recommended
for all new projects.
:type schema: string
:param schema_version: If you want to specify an exact version of IFC
that may not be an ISO approved version, use this argument instead
of ``schema``. IFC versions on technical.buildingsmart.org are
described using 4 integers representing the major, minor, addendum,
and corrigendum number. For example, (4, 0, 2, 1) refers to IFC4
ADD2 TC1, which is the official version approved by ISO when people
refer to "IFC4". Generally you should not use this argument unless
you are testing non-ISO IFC releases.
:type schema_version: tuple[int]
Example:
.. code:: python
# Create a new IFC4 model, create a wall, then save it to an IFC-SPF file.
model = ifcopenshell.file()
model.create_entity("IfcWall")
model.write("/path/to/model.ifc")
# Create a new IFC4X3 model
model = ifcopenshell.file(schema="IFC4X3")
# A poweruser testing out a particular version of IFC4X3
model = ifcopenshell.file(schema_version=(4, 3, 0, 1))
"""
if schema_version:
prefixes = ("IFC", "X", "_ADD", "_TC")
schema = "".join("".join(map(str, t)) if t[1] else "" for t in zip(prefixes, schema_version))
else:
schema = {"IFC4X3": "IFC4X3_ADD1"}.get(schema, schema)
if f is not None:
self.wrapped_data = f
else:
@@ -306,6 +351,17 @@ class file(object):
def __getattr__(self, attr):
if attr[0:6] == "create":
return functools.partial(self.create_entity, attr[6:])
elif attr == "schema":
return {"IFC4X3_ADD1": "IFC4X3"}.get(self.wrapped_data.schema, self.wrapped_data.schema)
elif attr == "schema_identifier":
return self.wrapped_data.schema
elif attr == "schema_version":
schema = self.wrapped_data.schema
version = []
for prefix in ("IFC", "X", "_ADD", "_TC"):
number = re.search(prefix + r"(\d)", schema)
version.append(int(number.group(1)) if number else 0)
return tuple(version)
else:
return getattr(self.wrapped_data, attr)
+1 -3
View File
@@ -62,9 +62,7 @@ class sqlite(file):
self.ifc_class_references = {}
self.ifc_class_inverses = {}
for declaration in self.ifc_schema.declarations():
if not str(declaration).startswith("<entity"):
continue
for declaration in self.ifc_schema.entities():
# print('Dealing with declaration', declaration.name())
self.ifc_class_subtypes[declaration.name()] = ifcopenshell.util.schema.get_subtypes(declaration)
@@ -167,10 +167,7 @@ try:
self.ifc_class_references = {}
self.ifc_class_inverses = {}
for declaration in self.ifc_schema.declarations():
if not str(declaration).startswith("<entity"):
continue
for declaration in self.ifc_schema.entities():
self.ifc_class_names[declaration.name().upper()] = declaration.name()
self.ifc_class_subtypes[declaration.name()] = ifcopenshell.util.schema.get_subtypes(declaration)
@@ -187,3 +187,18 @@ def get_true_north(ifc_file):
except:
return 0
return 0
# Used for converting an angle in degrees to return the X and Y vectors of the X Axis in IFC grid north geolocation:
def angle2xaxis(angle):
angle_rad = math.radians(angle)
x = math.cos(angle_rad)
y = - math.sin(angle_rad)
return x, y
# Used for converting True North angle as seen in CAD (relative to +Y)
def angle2yaxis(angle):
angle_rad = math.radians(angle)
x = - math.sin(angle_rad)
y = math.cos(angle_rad)
return x, y
+14
View File
@@ -156,6 +156,20 @@ class TestFile(test.bootstrap.IFC4):
def test_creating_a_new_file(self):
f = ifcopenshell.file(schema="IFC4")
assert f.schema == "IFC4"
assert f.schema_identifier == "IFC4"
assert f.schema_version == (4, 0, 0, 0)
def test_creating_an_ifc4x3_file(self):
f = ifcopenshell.file(schema="IFC4X3")
assert f.schema == "IFC4X3"
assert f.schema_identifier == "IFC4X3_ADD1"
assert f.schema_version == (4, 3, 1, 0)
def test_creating_a_specific_version(self):
f = ifcopenshell.file(schema_version=(4, 3, 1, 0))
assert f.schema == "IFC4X3"
assert f.schema_identifier == "IFC4X3_ADD1"
assert f.schema_version == (4, 3, 1, 0)
def test_creating_an_entity(self):
element = self.file.create_entity("IfcPerson")
@@ -46,10 +46,46 @@ class Patcher:
def patch(self):
source = ifcopenshell.open(self.filepath)
self.existing_contexts = self.file.by_type("IfcGeometricRepresentationContext")
self.added_contexts = set()
original_project = self.file.by_type("IfcProject")[0]
merged_project = self.file.add(source.by_type("IfcProject")[0])
for element in source.by_type("IfcGeometricRepresentationContext"):
new = self.file.add(element)
self.added_contexts.add(new)
for element in source:
self.file.add(element)
for inverse in self.file.get_inverse(merged_project):
ifcopenshell.util.element.replace_attribute(inverse, merged_project, original_project)
self.file.remove(merged_project)
self.reuse_existing_contexts()
def reuse_existing_contexts(self):
to_delete = set()
for added_context in self.added_contexts:
equivalent_existing_context = self.get_equivalent_existing_context(added_context)
if equivalent_existing_context:
for inverse in self.file.get_inverse(added_context):
ifcopenshell.util.element.replace_attribute(inverse, added_context, equivalent_existing_context)
to_delete.add(added_context)
for added_context in to_delete:
ifcopenshell.util.element.remove_deep2(self.file, added_context)
def get_equivalent_existing_context(self, added_context):
for context in self.existing_contexts:
if context.is_a() != added_context.is_a():
continue
if context.is_a("IfcGeometricRepresentationSubContext"):
if (
context.ContextType == added_context.ContextType
and context.ContextIdentifier == added_context.ContextIdentifier
and context.TargetView == added_context.TargetView
):
return context
elif (
context.ContextType == added_context.ContextType
and context.ContextIdentifier == added_context.ContextIdentifier
):
return context
@@ -23,7 +23,7 @@ import ifcopenshell.util.placement
class Patcher:
def __init__(self, src, file, logger, x=None, y=None, z=None, ax=None, ay=None, az=None):
def __init__(self, src, file, logger, x=None, y=None, z=None, should_rotate_first=True, ax=None, ay=None, az=None):
"""Offset and rotate all object placements in a model
Every physical object in an IFC model has an object placement, a
@@ -46,17 +46,21 @@ class Patcher:
:type y: float
:param z: The Z coordinate to offset by in project length units.
:type z: float
:param should_rotate_first: Whether or not to rotate first and then
translate, or to first translate and rotate afterwards. Defaults to
rotate first then translate.
:type should_rotate_first: bool
:param ax: An optional angle to rotate by. If only this angle is
specified, it is treated as the angle to rotate in plan view (i.e.
around the Z axis). If all angle parameters are specified, then it
is treated as the angle to rotate around the X axis. Angles are in
decimal degrees.
decimal degrees and positive is anticlockwise.
:type ax: float,optional
:param ay: An optional angle to rotate by for 3D rotations along the Y
axis. Angles are in decimal degrees.
axis. Angles are in decimal degrees and positive is anticlockwise.
:type ay: float,optional
:param az: An optional angle to rotate by for 3D rotations along the Z
axis. Angles are in decimal degrees.
axis. Angles are in decimal degrees and positive is anticlockwise.
:type az: float,optional
Example:
@@ -67,10 +71,10 @@ class Patcher:
ifcpatch.execute({"input": model, "recipe": "OffsetObjectPlacements", "arguments": [100,100,0]})
# Rotate by 90 degrees, but don't do any offset
ifcpatch.execute({"input": model, "recipe": "OffsetObjectPlacements", "arguments": [0,0,0,90]})
ifcpatch.execute({"input": model, "recipe": "OffsetObjectPlacements", "arguments": [0,0,0,True,90]})
# Some crazy 3D rotation and offset
ifcpatch.execute({"input": model, "recipe": "OffsetObjectPlacements", "arguments": [12.5,5,2,90,90,45]})
ifcpatch.execute({"input": model, "recipe": "OffsetObjectPlacements", "arguments": [12.5,5,2,False,90,90,45]})
"""
self.src = src
self.file = file
@@ -78,6 +82,7 @@ class Patcher:
self.x = x
self.y = y
self.z = z
self.should_rotate_first = should_rotate_first
self.ax = ax
self.ay = ay
self.az = az
@@ -99,18 +104,26 @@ class Patcher:
absolute_placements.append(absolute_placement)
absolute_placements = set(absolute_placements)
transformation = self.identity_matrix()
translate = self.identity_matrix()
rotate = self.identity_matrix()
if self.angle_type == "2D":
angle = float(self.ax)
if angle:
transformation = self.z_rotation_matrix(math.radians(angle), transformation)
rotate = self.z_rotation_matrix(math.radians(angle), rotate)
elif self.angle_type == "3D":
for arg in (("x", float(self.ax)), ("y", float(self.ay)), ("z", float(self.az))):
if arg[1]:
transformation = getattr(self, f"{arg[0]}_rotation_matrix")(math.radians(arg[1]), transformation)
transformation[0][3] += float(self.x)
transformation[1][3] += float(self.y)
transformation[2][3] += float(self.z)
rotate = getattr(self, f"{arg[0]}_rotation_matrix")(math.radians(arg[1]), rotate)
translate[0][3] += float(self.x)
translate[1][3] += float(self.y)
translate[2][3] += float(self.z)
if self.should_rotate_first:
transformation = translate @ rotate
else:
transformation = rotate @ translate
for placement in absolute_placements:
placement.RelativePlacement = self.get_relative_placement(
+1
View File
@@ -391,6 +391,7 @@ namespace {
void add(const TopoDS_Shape& s) {
if (!use_prefiltering_) {
items_.insert(items_.end(), s);
return;
}
TopoDS_Compound C;