mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-12 10:33:20 +00:00
Merge branch 'v0.7.0' into pset_select_same_value
This commit is contained in:
@@ -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
|
||||
|
Binary file not shown.
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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++;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()))
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -391,6 +391,7 @@ namespace {
|
||||
void add(const TopoDS_Shape& s) {
|
||||
if (!use_prefiltering_) {
|
||||
items_.insert(items_.end(), s);
|
||||
return;
|
||||
}
|
||||
|
||||
TopoDS_Compound C;
|
||||
|
||||
Reference in New Issue
Block a user