black ifcblenderexport

This commit is contained in:
htlcnn
2020-11-01 20:08:48 +07:00
committed by Dion Moult
parent 286c77e3b0
commit 163075a821
36 changed files with 7032 additions and 6232 deletions
+27 -23
View File
@@ -1,29 +1,30 @@
import bpy
from bpy.types import Operator
class Object_OT_RenameObjects(Operator):
bl_idname = "object.renameobjects"
bl_label = "Rename Object(s)"
# Multi Object rename UI
BNameCB : bpy.props.BoolProperty(name = "Base Name:")
BaseName : bpy.props.StringProperty(name = "")
PreFixCB : bpy.props.BoolProperty(name = "Prefix:")
PreFix : bpy.props.StringProperty(name = "")
RemFrst : bpy.props.BoolProperty(name = "Remove First")
DgtFrst : bpy.props.IntProperty(name = "Digits")
SuffixCB : bpy.props.BoolProperty(name = "Suffix")
Suffix : bpy.props.StringProperty(name = "")
RemLast : bpy.props.BoolProperty(name = "Remove Last")
DgtLast : bpy.props.IntProperty(name = "Digits")
NumbredCB : bpy.props.BoolProperty(name = "Numbred")
BaseNum : bpy.props.IntProperty(name = "Base Number")
Step : bpy.props.IntProperty(name = "Step", default = 1)
findCB : bpy.props.BoolProperty(name = "Replace")
find : bpy.props.StringProperty(name = "")
replace : bpy.props.StringProperty(name = "")
BNameCB: bpy.props.BoolProperty(name="Base Name:")
BaseName: bpy.props.StringProperty(name="")
PreFixCB: bpy.props.BoolProperty(name="Prefix:")
PreFix: bpy.props.StringProperty(name="")
RemFrst: bpy.props.BoolProperty(name="Remove First")
DgtFrst: bpy.props.IntProperty(name="Digits")
SuffixCB: bpy.props.BoolProperty(name="Suffix")
Suffix: bpy.props.StringProperty(name="")
RemLast: bpy.props.BoolProperty(name="Remove Last")
DgtLast: bpy.props.IntProperty(name="Digits")
NumbredCB: bpy.props.BoolProperty(name="Numbred")
BaseNum: bpy.props.IntProperty(name="Base Number")
Step: bpy.props.IntProperty(name="Step", default=1)
findCB: bpy.props.BoolProperty(name="Replace")
find: bpy.props.StringProperty(name="")
replace: bpy.props.StringProperty(name="")
# Single rename UI
Name : bpy.props.StringProperty(name="Name")
Name: bpy.props.StringProperty(name="Name")
def draw(self, ctx):
SelCount = len(bpy.context.selected_objects)
@@ -70,7 +71,7 @@ class Object_OT_RenameObjects(Operator):
if SelCount > 1:
SelObj = bpy.context.selected_objects
Index = self.BaseNum
for i in range(0,SelCount):
for i in range(0, SelCount):
# Get Object Original Name #
NewName = SelObj[i].name
# Set the Base name #
@@ -90,8 +91,8 @@ class Object_OT_RenameObjects(Operator):
NewName = NewName + self.Suffix
# Add Digits to end of new name #
if self.NumbredCB:
NewName += str(Index)
Index += self.Step
NewName += str(Index)
Index += self.Step
# Find and Replace #
if self.findCB:
NewName = NewName.replace(self.find, self.replace)
@@ -99,17 +100,20 @@ class Object_OT_RenameObjects(Operator):
SelObj[i].name = NewName
elif SelCount == 1:
bpy.context.selected_objects[0].name = self.Name
return {'FINISHED'}
return {"FINISHED"}
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
def register():
bpy.utils.register_class(Object_OT_RenameObjects)
def unregister():
bpy.utils.unregister_class(Object_OT_RenameObjects)
if __name__ == "__main__":
register()
register()
+3 -4
View File
@@ -1,14 +1,13 @@
bl_info = {
"name": "BlenderBIM",
"description": "Author, import, and export files in the "
"Industry Foundation Classes (.ifc) file format",
"description": "Author, import, and export files in the " "Industry Foundation Classes (.ifc) file format",
"author": "Dion Moult, IfcOpenShell",
"blender": (2, 80, 0),
"version": (0, 0, 999999),
"location": "File > Export, File > Import, Scene / Object / Material / Mesh Properties",
"tracker_url": "https://github.com/IfcOpenShell/IfcOpenShell/issues",
"category": "Import-Export"
}
"category": "Import-Export",
}
import os
import site
+17 -18
View File
@@ -1,7 +1,8 @@
# Check if we are running in Blender before loading, to allow for multiprocessing
import sys
import os
bpy = sys.modules.get('bpy')
bpy = sys.modules.get("bpy")
if bpy is not None:
import bpy
@@ -312,15 +313,13 @@ if bpy is not None:
model_window.BIM_OT_add_object,
model_slab.BIM_OT_add_object,
model_opening.BIM_OT_add_object,
)
)
def menu_func_export(self, context):
self.layout.operator(operator.ExportIFC.bl_idname,
text="Industry Foundation Classes (.ifc/.ifczip/.ifcjson)")
self.layout.operator(operator.ExportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcjson)")
def menu_func_import(self, context):
self.layout.operator(operator.ImportIFC.bl_idname,
text="Industry Foundation Classes (.ifc/.ifczip/.ifcxml)")
self.layout.operator(operator.ImportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcxml)")
def on_register(scene):
prop.setDefaultProperties(scene)
@@ -363,18 +362,18 @@ if bpy is not None:
bpy.app.handlers.load_post.remove(prop.setDefaultProperties)
bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
del(bpy.types.Scene.BIMProperties)
del(bpy.types.Scene.BIMDebugProperties)
del(bpy.types.Scene.BCFProperties)
del(bpy.types.Scene.DocProperties)
del(bpy.types.Scene.MapConversion)
del(bpy.types.Scene.TargetCRS)
del(bpy.types.Object.BIMObjectProperties)
del(bpy.types.Collection.BIMObjectProperties)
del(bpy.types.Material.BIMMaterialProperties)
del(bpy.types.Mesh.BIMMeshProperties)
del(bpy.types.Camera.BIMCameraProperties)
del(bpy.types.TextCurve.BIMTextProperties)
del bpy.types.Scene.BIMProperties
del bpy.types.Scene.BIMDebugProperties
del bpy.types.Scene.BCFProperties
del bpy.types.Scene.DocProperties
del bpy.types.Scene.MapConversion
del bpy.types.Scene.TargetCRS
del bpy.types.Object.BIMObjectProperties
del bpy.types.Collection.BIMObjectProperties
del bpy.types.Material.BIMMaterialProperties
del bpy.types.Mesh.BIMMeshProperties
del bpy.types.Camera.BIMCameraProperties
del bpy.types.TextCurve.BIMTextProperties
bpy.types.SCENE_PT_unit.remove(ui.ifc_units)
bpy.types.VIEW3D_MT_mesh_add.remove(model_grid.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.remove(model_wall.add_object_button)
@@ -2,24 +2,24 @@ import bpy
import os
from mathutils import Vector
class Annotator:
class Annotator:
@staticmethod
def get_svg_text_size(size):
sizes = {
'1.8': '2.97',
'2.5': '4.13',
'3.5': '5.78',
'5.0': '8.25',
'7.0': '11.55',
"1.8": "2.97",
"2.5": "4.13",
"3.5": "5.78",
"5.0": "8.25",
"7.0": "11.55",
}
return float(sizes[str(size)])
@staticmethod
def add_text(related_element=None):
curve = bpy.data.curves.new(type='FONT', name='Plan/Annotation/PLAN_VIEW/Text')
curve.body = 'TEXT'
obj = bpy.data.objects.new('IfcAnnotation/Text', curve)
curve = bpy.data.curves.new(type="FONT", name="Plan/Annotation/PLAN_VIEW/Text")
curve.body = "TEXT"
obj = bpy.data.objects.new("IfcAnnotation/Text", curve)
obj.matrix_world = bpy.context.scene.camera.matrix_world
if related_element is None:
location, co2 = Annotator.get_placeholder_coords()
@@ -28,13 +28,14 @@ class Annotator:
location = related_element.location
obj.location = location
obj.hide_render = True
font = bpy.data.fonts.get('OpenGost TypeB TT')
font = bpy.data.fonts.get("OpenGost TypeB TT")
if not font:
font = bpy.data.fonts.load(os.path.join(
bpy.context.scene.BIMProperties.data_dir, 'fonts', 'OpenGost Type B TT.ttf'))
font.name = 'OpenGost Type B TT'
font = bpy.data.fonts.load(
os.path.join(bpy.context.scene.BIMProperties.data_dir, "fonts", "OpenGost Type B TT.ttf")
)
font.name = "OpenGost Type B TT"
obj.data.font = font
obj.data.BIMTextProperties.font_size = '2.5'
obj.data.BIMTextProperties.font_size = "2.5"
collection = bpy.context.scene.camera.users_collection[0]
collection.objects.link(obj)
Annotator.resize_text(obj)
@@ -53,11 +54,11 @@ class Annotator:
font_size = 1.6 / 1000
font_size *= float(text_obj.data.BIMTextProperties.font_size)
if camera.data.BIMCameraProperties.diagram_scale == 'CUSTOM':
human_scale, fraction = camera.data.BIMCameraProperties.custom_diagram_scale.split('|')
if camera.data.BIMCameraProperties.diagram_scale == "CUSTOM":
human_scale, fraction = camera.data.BIMCameraProperties.custom_diagram_scale.split("|")
else:
human_scale, fraction = camera.data.BIMCameraProperties.diagram_scale.split('|')
numerator, denominator = fraction.split('/')
human_scale, fraction = camera.data.BIMCameraProperties.diagram_scale.split("|")
numerator, denominator = fraction.split("/")
font_size /= float(numerator) / float(denominator)
text_obj.data.size = font_size
@@ -75,7 +76,7 @@ class Annotator:
obj.data.edges.add(1)
obj.data.edges[-1].vertices = (obj.data.vertices[-2].index, obj.data.vertices[-1].index)
if isinstance(obj.data, bpy.types.Curve):
polyline = obj.data.splines.new('POLY')
polyline = obj.data.splines.new("POLY")
polyline.points.add(1)
polyline.points[-2].co = list(co1) + [1]
polyline.points[-1].co = list(co2) + [1]
@@ -87,13 +88,13 @@ class Annotator:
for obj in collection.objects:
if name in obj.name:
return obj
if data_type == 'mesh':
data = bpy.data.meshes.new('Plan/Annotation/PLAN_VIEW/' + name)
elif data_type == 'curve':
data = bpy.data.curves.new('Plan/Annotation/PLAN_VIEW/' + name, type='CURVE')
data.dimensions = '3D'
if data_type == "mesh":
data = bpy.data.meshes.new("Plan/Annotation/PLAN_VIEW/" + name)
elif data_type == "curve":
data = bpy.data.curves.new("Plan/Annotation/PLAN_VIEW/" + name, type="CURVE")
data.dimensions = "3D"
data.resolution_u = 2
obj = bpy.data.objects.new('IfcAnnotation/' + name, data)
obj = bpy.data.objects.new("IfcAnnotation/" + name, data)
collection.objects.link(obj)
return obj
@@ -102,7 +103,11 @@ class Annotator:
camera = bpy.context.scene.camera
z_offset = camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))
if bpy.context.scene.render.resolution_x > bpy.context.scene.render.resolution_y:
y = camera.data.ortho_scale * (bpy.context.scene.render.resolution_y / bpy.context.scene.render.resolution_x) / 4
y = (
camera.data.ortho_scale
* (bpy.context.scene.render.resolution_y / bpy.context.scene.render.resolution_x)
/ 4
)
else:
y = camera.data.ortho_scale / 4
y_offset = camera.matrix_world.to_quaternion() @ Vector((0, y, 0))
+1 -1
View File
@@ -1,4 +1,4 @@
class BcfStore():
class BcfStore:
topics = []
viewpoints = []
comments = []
+200 -224
View File
@@ -76,7 +76,8 @@ import ifcopenshell.util.selector
import ifcopenshell.util.element
cwd = os.path.dirname(os.path.realpath(__file__))
this_file = os.path.join(cwd, 'cut_ifc.py')
this_file = os.path.join(cwd, "cut_ifc.py")
def get_booleaned_edges(shape):
edges = []
@@ -86,6 +87,7 @@ def get_booleaned_edges(shape):
exp.Next()
return edges
def connect_edges_into_wires(unconnected_edges):
edges = TopTools.TopTools_HSequenceOfShape()
edges_handle = TopTools.Handle_TopTools_HSequenceOfShape(edges)
@@ -98,28 +100,17 @@ def connect_edges_into_wires(unconnected_edges):
ShapeAnalysis.ShapeAnalysis_FreeBounds.ConnectEdgesToWires(edges_handle, 1e-5, True, wires_handle)
return wires_handle.GetObject()
def do_cut(process_data):
global_id, shape, section, trsf_data = process_data
axis = gp.gp_Ax2(
gp.gp_Pnt(
trsf_data['top_left_corner'][0],
trsf_data['top_left_corner'][1],
trsf_data['top_left_corner'][2]),
gp.gp_Dir(
trsf_data['projection'][0],
trsf_data['projection'][1],
trsf_data['projection'][2]),
gp.gp_Dir(
trsf_data['x_axis'][0],
trsf_data['x_axis'][1],
trsf_data['x_axis'][2])
)
gp.gp_Pnt(trsf_data["top_left_corner"][0], trsf_data["top_left_corner"][1], trsf_data["top_left_corner"][2]),
gp.gp_Dir(trsf_data["projection"][0], trsf_data["projection"][1], trsf_data["projection"][2]),
gp.gp_Dir(trsf_data["x_axis"][0], trsf_data["x_axis"][1], trsf_data["x_axis"][2]),
)
source = gp.gp_Ax3(axis)
destination = gp.gp_Ax3(
gp.gp_Pnt(0, 0, 0),
gp.gp_Dir(0, 0, -1),
gp.gp_Dir(1, 0, 0))
destination = gp.gp_Ax3(gp.gp_Pnt(0, 0, 0), gp.gp_Dir(0, 0, -1), gp.gp_Dir(1, 0, 0))
transformation = gp.gp_Trsf()
transformation.SetDisplacement(source, destination)
@@ -130,10 +121,9 @@ def do_cut(process_data):
return cut_polygons
wires = connect_edges_into_wires(section_edges)
for i in range(wires.Length()):
wire_shape = wires.Value(i+1)
wire_shape = wires.Value(i + 1)
transformed_wire = BRepBuilderAPI.BRepBuilderAPI_Transform(
wire_shape, transformation)
transformed_wire = BRepBuilderAPI.BRepBuilderAPI_Transform(wire_shape, transformation)
wire_shape = transformed_wire.Shape()
wire = topods.Wire(wire_shape)
@@ -145,7 +135,7 @@ def do_cut(process_data):
point = BRep.BRep_Tool.Pnt(exp.CurrentVertex())
points.append((point.X(), -point.Y()))
exp.Next()
cut_polygons.append({ 'global_id': global_id, 'metadata': {}, 'points': points })
cut_polygons.append({"global_id": global_id, "metadata": {}, "points": points})
return cut_polygons
@@ -158,50 +148,50 @@ class IfcCutter:
self.cut_polygons = []
self.template_variables = {}
self.metadata = {}
self.data_dir = ''
self.vector_style = ''
self.data_dir = ""
self.vector_style = ""
self.ifc_filenames = []
self.ifc_files = {}
self.resolved_pixels = set()
self.should_get_background = False
self.text_pickle_file = 'text.pickle'
self.metadata_pickle_file = 'metadata.pickle'
self.cut_pickle_file = 'cut.pickle'
self.text_pickle_file = "text.pickle"
self.metadata_pickle_file = "metadata.pickle"
self.cut_pickle_file = "cut.pickle"
self.should_recut = True
self.should_recut_selected = True
self.cut_objects = ''
self.cut_objects = ""
self.selected_global_ids = []
self.should_extract = True
self.diagram_name = None
self.background_image = None
self.section_box = {
'projection': (0, 1, 0),
'x_axis': (1, 0, 0),
'y_axis': (0, 0, -1),
'top_left_corner': (-2, 2, 8),
'x': 14,
'y': 9,
'z': 2,
'shape': None,
'face': None
"projection": (0, 1, 0),
"x_axis": (1, 0, 0),
"y_axis": (0, 0, -1),
"top_left_corner": (-2, 2, 8),
"x": 14,
"y": 9,
"z": 2,
"shape": None,
"face": None,
}
def cut(self):
self.profile_code('Starting cut process')
self.profile_code("Starting cut process")
self.load_ifc_files()
self.profile_code('Load IFC files')
self.profile_code("Load IFC files")
self.get_template_variables()
self.profile_code('Get template variables')
self.profile_code("Get template variables")
self.get_product_shapes()
self.profile_code('Get product shapes')
self.profile_code("Get product shapes")
self.create_section_box()
self.profile_code('Create section box')
self.profile_code("Create section box")
self.get_cut_polygons()
self.profile_code('Get cut polygons')
self.profile_code("Get cut polygons")
self.get_annotation()
self.profile_code('Get annotation')
self.profile_code("Get annotation")
self.get_cut_polygon_metadata()
self.profile_code('Get cut polygon metadata')
self.profile_code("Get cut polygon metadata")
# should_get_background is False in production as this is experimental
if not self.should_get_background:
@@ -215,7 +205,7 @@ class IfcCutter:
def profile_code(self, message):
if not self.time:
self.time = time.time()
print('{} :: {:.2f}'.format(message, time.time() - self.time))
print("{} :: {:.2f}".format(message, time.time() - self.time))
self.time = time.time()
def load_ifc_files(self):
@@ -224,14 +214,14 @@ class IfcCutter:
loaded_files = []
for filename in self.ifc_filenames:
print('Loading file {} ...'.format(filename))
print("Loading file {} ...".format(filename))
if filename:
self.ifc_files[filename] = ifcopenshell.open(filename)
def get_template_variables(self):
if not self.should_extract:
if os.path.isfile(self.text_pickle_file):
with open(self.text_pickle_file, 'rb') as text_file:
with open(self.text_pickle_file, "rb") as text_file:
self.template_variables = pickle.load(text_file)
return
@@ -241,7 +231,7 @@ class IfcCutter:
if text_obj_data:
data[text_obj.name] = text_obj_data
with open(self.text_pickle_file, 'wb') as text_file:
with open(self.text_pickle_file, "wb") as text_file:
pickle.dump(data, text_file, protocol=pickle.HIGHEST_PROTOCOL)
self.template_variables = data
@@ -252,16 +242,16 @@ class IfcCutter:
related_element = text_obj.data.BIMTextProperties.related_element
if not related_element:
return
global_id = related_element.BIMObjectProperties.attributes.get('GlobalId')
global_id = related_element.BIMObjectProperties.attributes.get("GlobalId")
if not global_id:
return
element = self.get_ifc_element(global_id.string_value)
for variable in text_obj.data.BIMTextProperties.variables:
if element:
if '{{' in variable.prop_key:
prop_key = variable.prop_key.split('{{')[1].split('}}')[0]
if "{{" in variable.prop_key:
prop_key = variable.prop_key.split("{{")[1].split("}}")[0]
prop_value = self.selector.get_element_value(element, prop_key)
variable_value = eval(variable.prop_key.replace('{{' + prop_key + '}}', str(prop_value)))
variable_value = eval(variable.prop_key.replace("{{" + prop_key + "}}", str(prop_value)))
else:
variable_value = self.selector.get_element_value(element, variable.prop_key)
text_obj_data[variable.name] = variable_value
@@ -277,24 +267,26 @@ class IfcCutter:
for filename, ifc_file in self.ifc_files.items():
shape_pickle = os.path.join(
self.data_dir, 'cache', 'shapes', '{}.pickle'.format(os.path.basename(filename)))
self.data_dir, "cache", "shapes", "{}.pickle".format(os.path.basename(filename))
)
shape_map = {}
if self.should_recut_selected and os.path.isfile(shape_pickle):
with open(shape_pickle, 'rb') as shape_file:
with open(shape_pickle, "rb") as shape_file:
shape_map = pickle.load(shape_file)
products.extend(self.selector.parse(ifc_file, self.cut_objects))
selected_elements = []
for i, product in enumerate(products):
if product.is_a('IfcOpeningElement') \
or product.is_a('IfcSite') \
or product.Representation is None \
or self.has_annotation(product):
if (
product.is_a("IfcOpeningElement")
or product.is_a("IfcSite")
or product.Representation is None
or self.has_annotation(product)
):
continue
try:
if self.should_recut_selected \
and product.GlobalId in self.selected_global_ids:
if self.should_recut_selected and product.GlobalId in self.selected_global_ids:
selected_elements.append(product)
elif product.GlobalId in shape_map:
shape = shape_map[product.GlobalId]
@@ -302,19 +294,20 @@ class IfcCutter:
else:
selected_elements.append(product)
except:
print('Failed to create shape for {}'.format(product))
print("Failed to create shape for {}".format(product))
if selected_elements:
total = 0
checkpoint = time.time()
iterator = ifcopenshell.geom.iterator(
settings, ifc_file, multiprocessing.cpu_count(), include=selected_elements)
settings, ifc_file, multiprocessing.cpu_count(), include=selected_elements
)
valid_file = iterator.initialize()
if valid_file:
while True:
total += 1
if total % 250 == 0:
print('{} elements processed in {:.2f}s ...'.format(total, time.time() - checkpoint))
print("{} elements processed in {:.2f}s ...".format(total, time.time() - checkpoint))
checkpoint = time.time()
shape = iterator.get()
shape_map[shape.data.guid] = shape.geometry
@@ -322,7 +315,7 @@ class IfcCutter:
if not iterator.next():
break
with open(shape_pickle, 'wb') as shape_file:
with open(shape_pickle, "wb") as shape_file:
pickle.dump(shape_map, shape_file, protocol=pickle.HIGHEST_PROTOCOL)
def add_product_shape(self, product, shape):
@@ -330,16 +323,18 @@ class IfcCutter:
def has_annotation(self, element):
for representation in element.Representation.Representations:
if representation.ContextOfItems.ContextType == 'Plan' \
and representation.ContextOfItems.ContextIdentifier == 'Annotation':
if (
representation.ContextOfItems.ContextType == "Plan"
and representation.ContextOfItems.ContextIdentifier == "Annotation"
):
return True
return False
def sort_background_elements(self, reverse=None):
if reverse:
new_list = sorted(self.background_elements, key=lambda k: -k['z'])
new_list = sorted(self.background_elements, key=lambda k: -k["z"])
else:
new_list = sorted(self.background_elements, key=lambda k: k['z'])
new_list = sorted(self.background_elements, key=lambda k: k["z"])
self.background_elements = new_list
def process_grid(self, face, resolution):
@@ -351,13 +346,10 @@ class IfcCutter:
current_x = 0
current_y = 0
is_visible = False
while current_x < self.section_box['x']:
while current_x < self.section_box["x"]:
current_y = 0
while current_y > -self.section_box['y']:
if current_x < xmin \
or current_x > xmax \
or current_y < ymin \
or current_y > ymax:
while current_y > -self.section_box["y"]:
if current_x < xmin or current_x > xmax or current_y < ymin or current_y > ymax:
current_y -= resolution
continue
if (current_x, current_y) in self.resolved_pixels:
@@ -375,70 +367,65 @@ class IfcCutter:
def merge_background_elements(self):
background_elements = []
resolution = 0.1 # 10cm
resolution = 0.1 # 10cm
# DO CUT
total_product_shapes = len(self.cut_polygons)
n = 0
for element in self.cut_polygons:
#print('{}/{} background elements processed ...'.format(n, total_product_shapes), end='\r', flush=True)
print('{}/{} cut polygons processed ...'.format(n, total_product_shapes))
print('{} resolved pixels'.format(len(self.resolved_pixels)))
# print('{}/{} background elements processed ...'.format(n, total_product_shapes), end='\r', flush=True)
print("{}/{} cut polygons processed ...".format(n, total_product_shapes))
print("{} resolved pixels".format(len(self.resolved_pixels)))
n += 1
self.process_grid(element['geometry_face'], resolution)
self.process_grid(element["geometry_face"], resolution)
# DO BACKGROUND
total_product_shapes = len(self.background_elements)
n = 0
for element in self.background_elements:
#print('{}/{} background elements processed ...'.format(n, total_product_shapes), end='\r', flush=True)
print('{}/{} background elements processed ...'.format(n, total_product_shapes))
print('{} resolved pixels'.format(len(self.resolved_pixels)))
# print('{}/{} background elements processed ...'.format(n, total_product_shapes), end='\r', flush=True)
print("{}/{} background elements processed ...".format(n, total_product_shapes))
print("{} resolved pixels".format(len(self.resolved_pixels)))
n += 1
if element['type'] != 'polygon':
if element["type"] != "polygon":
background_elements.append(element)
continue
is_visible = self.process_grid(element['geometry_face'], resolution)
is_visible = self.process_grid(element["geometry_face"], resolution)
if is_visible:
background_elements.append(element)
print('##### BEFORE it had {} and after it had {}'.format(
len(self.background_elements), len(background_elements)))
print(
"##### BEFORE it had {} and after it had {}".format(len(self.background_elements), len(background_elements))
)
self.background_elements = background_elements
return
def create_section_box(self):
top_left_corner = gp.gp_Pnt(
self.section_box['top_left_corner'][0],
self.section_box['top_left_corner'][1],
self.section_box['top_left_corner'][2])
self.section_box["top_left_corner"][0],
self.section_box["top_left_corner"][1],
self.section_box["top_left_corner"][2],
)
axis = gp.gp_Ax2(
top_left_corner,
gp.gp_Dir(
self.section_box['projection'][0],
self.section_box['projection'][1],
self.section_box['projection'][2]),
gp.gp_Dir(
self.section_box['x_axis'][0],
self.section_box['x_axis'][1],
self.section_box['x_axis'][2])
)
self.section_box["projection"][0], self.section_box["projection"][1], self.section_box["projection"][2]
),
gp.gp_Dir(self.section_box["x_axis"][0], self.section_box["x_axis"][1], self.section_box["x_axis"][2]),
)
section_box = BRepPrimAPI.BRepPrimAPI_MakeBox(
axis, self.section_box['x'], self.section_box['y'], self.section_box['z']
)
self.section_box['shape'] = section_box.Shape()
self.section_box['face'] = section_box.BottomFace()
axis, self.section_box["x"], self.section_box["y"], self.section_box["z"]
)
self.section_box["shape"] = section_box.Shape()
self.section_box["face"] = section_box.BottomFace()
source = gp.gp_Ax3(axis)
self.transformation_data = {
'top_left_corner': self.section_box['top_left_corner'],
'projection': self.section_box['projection'],
'x_axis': self.section_box['x_axis']
"top_left_corner": self.section_box["top_left_corner"],
"projection": self.section_box["projection"],
"x_axis": self.section_box["x_axis"],
}
destination = gp.gp_Ax3(
gp.gp_Pnt(0, 0, 0),
gp.gp_Dir(0, 0, -1),
gp.gp_Dir(1, 0, 0))
destination = gp.gp_Ax3(gp.gp_Pnt(0, 0, 0), gp.gp_Dir(0, 0, -1), gp.gp_Dir(1, 0, 0))
self.transformation_dest = destination
self.transformation = gp.gp_Trsf()
self.transformation.SetDisplacement(source, destination)
@@ -453,24 +440,21 @@ class IfcCutter:
for product, shape in self.product_shapes:
builder.Add(compound, shape)
print('{}/{} background elements processed ...'.format(n, total_product_shapes), end='\r', flush=True)
#print('Processing product {} '.format(product.Name))
print("{}/{} background elements processed ...".format(n, total_product_shapes), end="\r", flush=True)
# print('Processing product {} '.format(product.Name))
n += 1
intersection = BRepAlgoAPI.BRepAlgoAPI_Common(self.section_box['shape'], shape).Shape()
intersection = BRepAlgoAPI.BRepAlgoAPI_Common(self.section_box["shape"], shape).Shape()
intersection_edges = self.get_booleaned_edges(intersection)
if len(intersection_edges) <= 0:
continue
intersections.append(intersection)
transformed_intersection = BRepBuilderAPI.BRepBuilderAPI_Transform(
intersection, self.transformation)
transformed_intersection = BRepBuilderAPI.BRepBuilderAPI_Transform(intersection, self.transformation)
intersection = transformed_intersection.Shape()
edge_face_map = TopTools.TopTools_IndexedDataMapOfShapeListOfShape()
TopExp.topexp.MapShapesAndAncestors(
intersection, TopAbs.TopAbs_EDGE,
TopAbs.TopAbs_FACE, edge_face_map)
TopExp.topexp.MapShapesAndAncestors(intersection, TopAbs.TopAbs_EDGE, TopAbs.TopAbs_FACE, edge_face_map)
exp = TopExp.TopExp_Explorer(intersection, TopAbs.TopAbs_FACE)
while exp.More():
@@ -486,31 +470,29 @@ class IfcCutter:
exp.Next()
def get_raycast_hits(self, shape):
resolution = 0.1 # 5cm
resolution = 0.1 # 5cm
hits = []
current_x = 0
current_y = 0
while current_x < self.section_box['x'] /2:
while current_x < self.section_box["x"] / 2:
current_y = 0
while current_y < self.section_box['y']/4:
point = numpy.array(self.section_box['top_left_corner'])
point = numpy.add(point, current_x * numpy.array(self.section_box['x_axis']))
point = numpy.add(point, current_y * numpy.array(self.section_box['y_axis']))
while current_y < self.section_box["y"] / 4:
point = numpy.array(self.section_box["top_left_corner"])
point = numpy.add(point, current_x * numpy.array(self.section_box["x_axis"]))
point = numpy.add(point, current_y * numpy.array(self.section_box["y_axis"]))
hit = self.raycast(shape, point)
if hit:
hits.append(hit)
current_y += resolution
current_x += resolution
print('row down')
print("row down")
return hits
def raycast(self, shape, point):
raycast = IntCurvesFace.IntCurvesFace_ShapeIntersector()
raycast.Load(shape, 0.01)
line = gp.gp_Lin(
gp.gp_Pnt(float(point[0]), float(point[1]), float(point[2])),
gp.gp_Dir( 0, 0, -1))
raycast.Perform(line, 0, self.section_box['z'])
line = gp.gp_Lin(gp.gp_Pnt(float(point[0]), float(point[1]), float(point[2])), gp.gp_Dir(0, 0, -1))
raycast.Perform(line, 0, self.section_box["z"])
return raycast.NbPnt() != 0
def raycast_at_projection_dir(self, shape, point):
@@ -519,14 +501,14 @@ class IfcCutter:
line = gp.gp_Lin(
gp.gp_Pnt(float(point[0]), float(point[1]), float(point[2])),
gp.gp_Dir(
self.section_box['projection'][0],
self.section_box['projection'][1],
self.section_box['projection'][2]))
raycast.Perform(line, 0, self.section_box['z'])
self.section_box["projection"][0], self.section_box["projection"][1], self.section_box["projection"][2]
),
)
raycast.Perform(line, 0, self.section_box["z"])
if raycast.NbPnt() != 0:
# The smaller WParameter is the closer z-index
# Should be the first
return { 'face': raycast.Face(1), 'z': raycast.WParameter(1) }
return {"face": raycast.Face(1), "z": raycast.WParameter(1)}
def get_bbox(self, shape):
bbox = Bnd.Bnd_Box()
@@ -536,7 +518,7 @@ class IfcCutter:
def calculate_face_zpos(self, face):
bbox = self.get_bbox(face)
xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get()
zpos = zmin + ((zmax - zmin)/2)
zpos = zmin + ((zmax - zmin) / 2)
return zpos, zmax
def get_split_edges(self, edge_face_map, face, zmax, product):
@@ -553,24 +535,21 @@ class IfcCutter:
# because it does, sometimes.
edge_angle = 0
if edge_angle > 30 and edge_angle < 160:
newedge = self.build_new_edge(edge, zmax+0.01)
newedge = self.build_new_edge(edge, zmax + 0.01)
if newedge:
self.background_elements.append({
'raw': product,
'geometry': newedge,
'type': 'line',
'z': zmax+0.01
})
self.background_elements.append(
{"raw": product, "geometry": newedge, "type": "line", "z": zmax + 0.01}
)
exp2.Next()
def get_angle_between_faces(self, f1, f2):
return self.convert_dot_product_to_angle(
self.get_dot_product_of_normals(
self.get_normal(f1), self.get_normal(f2)))
self.get_dot_product_of_normals(self.get_normal(f1), self.get_normal(f2))
)
def get_normal(self, face):
surface = Geom.Handle_Geom_Surface(BRep.BRep_Tool.Surface(face))
props = GeomLProp.GeomLProp_SLProps(surface, 0, 0, 1, .001)
props = GeomLProp.GeomLProp_SLProps(surface, 0, 0, 1, 0.001)
return props.Normal()
def get_dot_product_of_normals(self, n1, n2):
@@ -580,9 +559,7 @@ class IfcCutter:
return math.acos(dp)
def is_same_point(self, p1, p2):
return p1.X() == p2.X() \
and p1.Y() == p2.Y() \
and p1.Z() == p2.Z()
return p1.X() == p2.X() and p1.Y() == p2.Y() and p1.Z() == p2.Z()
def build_new_edge(self, edge, zpos):
exp = TopExp.TopExp_Explorer(edge, TopAbs.TopAbs_VERTEX)
@@ -594,9 +571,7 @@ class IfcCutter:
new_vertices.append(BRepBuilderAPI.BRepBuilderAPI_MakeVertex(current_point).Vertex())
exp.Next()
try:
return BRepBuilderAPI.BRepBuilderAPI_MakeEdge(
new_vertices[0], new_vertices[1]
).Edge()
return BRepBuilderAPI.BRepBuilderAPI_MakeEdge(new_vertices[0], new_vertices[1]).Edge()
except:
return None
@@ -619,10 +594,9 @@ class IfcCutter:
previous_vertex = current_vertex
else:
try:
new_wire_builder.Add(topods.Edge(
BRepBuilderAPI.BRepBuilderAPI_MakeEdge(
previous_vertex, current_vertex
).Edge()))
new_wire_builder.Add(
topods.Edge(BRepBuilderAPI.BRepBuilderAPI_MakeEdge(previous_vertex, current_vertex).Edge())
)
previous_vertex = current_vertex
except:
pass
@@ -631,24 +605,19 @@ class IfcCutter:
# make last edge
if not wireexp.More():
try:
new_wire_builder.Add(topods.Edge(
BRepBuilderAPI.BRepBuilderAPI_MakeEdge(
current_vertex, first_vertex
).Edge()))
new_wire_builder.Add(
topods.Edge(BRepBuilderAPI.BRepBuilderAPI_MakeEdge(current_vertex, first_vertex).Edge())
)
except:
pass
try:
new_wire = new_wire_builder.Wire()
new_face = BRepBuilderAPI.BRepBuilderAPI_MakeFace(new_wire).Face()
self.background_elements.append({
'raw': product,
'geometry': new_wire,
'geometry_face': new_face,
'type': 'polygon',
'z': zpos
})
self.background_elements.append(
{"raw": product, "geometry": new_wire, "geometry_face": new_face, "type": "polygon", "z": zpos}
)
except:
#print('Could not build face')
# print('Could not build face')
pass
exp.Next()
@@ -674,21 +643,26 @@ class IfcCutter:
def get_annotation(self):
import mathutils
self.annotation_objs = []
settings_2d = ifcopenshell.geom.settings()
settings_2d.set(settings_2d.INCLUDE_CURVES, True)
settings_py = ifcopenshell.geom.settings()
settings_py.set(settings_py.USE_PYTHON_OPENCASCADE, True)
for ifc_file in self.ifc_files.values():
for element in ifc_file.by_type('IfcElement'):
for element in ifc_file.by_type("IfcElement"):
annotation_representation = None
box_representation = None
for representation in element.Representation.Representations:
if representation.ContextOfItems.ContextType == 'Plan' \
and representation.ContextOfItems.ContextIdentifier == 'Annotation':
if (
representation.ContextOfItems.ContextType == "Plan"
and representation.ContextOfItems.ContextIdentifier == "Annotation"
):
annotation_representation = representation
elif representation.ContextOfItems.ContextType == 'Model' \
and representation.ContextOfItems.ContextIdentifier == 'Box':
elif (
representation.ContextOfItems.ContextType == "Model"
and representation.ContextOfItems.ContextIdentifier == "Box"
):
box_representation = representation
if not annotation_representation or not box_representation:
continue
@@ -698,19 +672,19 @@ class IfcCutter:
# plane, then we should "continue" and not process the 2D
# wireframe. This approach works but is not very smart.
for subelement in ifc_file.traverse(box_representation):
if subelement.is_a('IfcBoundingBox'):
if subelement.is_a("IfcBoundingBox"):
block = ifc_file.createIfcBlock(
ifc_file.createIfcAxis2Placement3D(subelement.Corner, None, None),
subelement.XDim,
subelement.YDim,
subelement.ZDim
subelement.ZDim,
)
for inverse in ifc_file.get_inverse(subelement):
ifcopenshell.util.element.replace_attribute(inverse, subelement, block)
element.Representation.Representations = [box_representation]
shape = ifcopenshell.geom.create_shape(settings_py, element)
section = BRepAlgoAPI.BRepAlgoAPI_Section(self.section_box['face'], shape.geometry).Shape()
section = BRepAlgoAPI.BRepAlgoAPI_Section(self.section_box["face"], shape.geometry).Shape()
section_edges = get_booleaned_edges(section)
if len(section_edges) <= 0:
@@ -722,64 +696,66 @@ class IfcCutter:
# Monkey patch - see bug #771.
element.Representation.Representations = [annotation_representation]
shape = ifcopenshell.geom.create_shape(settings_2d, element)
if hasattr(shape, 'geometry'):
if hasattr(shape, "geometry"):
geometry = shape.geometry
else:
geometry = shape
e = geometry.edges
v = geometry.verts
m = shape.transformation.matrix.data
mat = mathutils.Matrix(([m[0], m[1], m[2], 0],
[m[3], m[4], m[5], 0],
[m[6], m[7], m[8], 0],
[m[9], m[10], m[11], 1]))
mat = mathutils.Matrix(
([m[0], m[1], m[2], 0], [m[3], m[4], m[5], 0], [m[6], m[7], m[8], 0], [m[9], m[10], m[11], 1])
)
mat.transpose()
self.annotation_objs.append({
'raw': element,
'classes': self.get_classes(element, 'annotation'),
'edges': [[e[i], e[i + 1]] for i in range(0, len(e), 2)],
'vertices': [mat @ mathutils.Vector((v[i], v[i + 1], v[i + 2])) for i in range(0, len(v), 3)]
})
self.annotation_objs.append(
{
"raw": element,
"classes": self.get_classes(element, "annotation"),
"edges": [[e[i], e[i + 1]] for i in range(0, len(e), 2)],
"vertices": [mat @ mathutils.Vector((v[i], v[i + 1], v[i + 2])) for i in range(0, len(v), 3)],
}
)
def get_cut_polygon_metadata(self):
if not self.should_extract:
if os.path.isfile(self.metadata_pickle_file):
with open(self.metadata_pickle_file, 'rb') as metadata_file:
with open(self.metadata_pickle_file, "rb") as metadata_file:
self.metadata = pickle.load(metadata_file)
for polygon in self.cut_polygons:
if polygon['global_id'] in self.metadata:
polygon['metadata'] = self.metadata[polygon['global_id']]
if polygon["global_id"] in self.metadata:
polygon["metadata"] = self.metadata[polygon["global_id"]]
return
for polygon in self.cut_polygons:
metadata = { 'classes': self.get_classes(self.get_ifc_element(polygon['global_id']), 'cut') }
self.metadata[polygon['global_id']] = metadata
polygon['metadata'] = metadata
metadata = {"classes": self.get_classes(self.get_ifc_element(polygon["global_id"]), "cut")}
self.metadata[polygon["global_id"]] = metadata
polygon["metadata"] = metadata
with open(self.metadata_pickle_file, 'wb') as metadata_file:
with open(self.metadata_pickle_file, "wb") as metadata_file:
pickle.dump(self.metadata, metadata_file, protocol=pickle.HIGHEST_PROTOCOL)
def pickle_cut_polygons(self):
with open(self.cut_pickle_file, 'wb') as pickle_file:
with open(self.cut_pickle_file, "wb") as pickle_file:
pickle.dump(self.cut_polygons, pickle_file, protocol=pickle.HIGHEST_PROTOCOL)
def get_fresh_cut_polygons(self):
process_data = [(p.GlobalId, s, self.section_box['face'], self.transformation_data) for p, s in self.product_shapes]
process_data = [
(p.GlobalId, s, self.section_box["face"], self.transformation_data) for p, s in self.product_shapes
]
import bpy
multiprocessing.set_executable(bpy.app.binary_path_python)
with multiprocessing.Pool(9) as p:
results = p.map(do_cut, process_data)
for result in results:
polygons = [p for p in result if p['points']]
polygons = [p for p in result if p["points"]]
self.cut_polygons.extend(polygons)
def get_polygon_metadata(self, polygon, position):
polygon['metadata'] = {
'classes': self.get_classes(self.get_ifc_element(polygon['global_id']), position)
}
polygon["metadata"] = {"classes": self.get_classes(self.get_ifc_element(polygon["global_id"]), position)}
return polygon
def get_ifc_element(self, global_id):
@@ -795,28 +771,29 @@ class IfcCutter:
def get_classes(self, element, position):
classes = [position, element.is_a()]
for association in element.HasAssociations:
if association.is_a('IfcRelAssociatesMaterial'):
classes.append('material-{}'.format(
re.sub('[^0-9a-zA-Z]+', '', self.get_material_name(association.RelatingMaterial))
))
classes.append('globalid-{}'.format(element.GlobalId))
if association.is_a("IfcRelAssociatesMaterial"):
classes.append(
"material-{}".format(
re.sub("[^0-9a-zA-Z]+", "", self.get_material_name(association.RelatingMaterial))
)
)
classes.append("globalid-{}".format(element.GlobalId))
for attribute in self.attributes:
result = self.selector.get_element_value(element, attribute)
if result:
classes.append('{}-{}'.format(
re.sub('[^0-9a-zA-Z]+', '', attribute),
re.sub('[^0-9a-zA-Z]+', '', result)
))
classes.append(
"{}-{}".format(re.sub("[^0-9a-zA-Z]+", "", attribute), re.sub("[^0-9a-zA-Z]+", "", result))
)
return classes
def get_material_name(self, element):
if hasattr(element, 'Name') and element.Name:
if hasattr(element, "Name") and element.Name:
return element.Name
return element.id()
def get_pickled_cut_polygons(self):
if os.path.isfile(self.cut_pickle_file):
with open(self.cut_pickle_file, 'rb') as pickle_file:
with open(self.cut_pickle_file, "rb") as pickle_file:
self.cut_polygons = pickle.load(pickle_file)
@@ -838,36 +815,35 @@ class IfcCutterDebug(IfcCutter):
self.display_background_elements()
def display_everything_with_section_plane(self):
section_face_display = ifcopenshell.geom.utils.display_shape(self.section_box['face'])
section_face_display = ifcopenshell.geom.utils.display_shape(self.section_box["face"])
ifcopenshell.geom.utils.set_shape_transparency(section_face_display, 0.8)
section_box_display = ifcopenshell.geom.utils.display_shape(self.section_box['shape'])
section_box_display = ifcopenshell.geom.utils.display_shape(self.section_box["shape"])
ifcopenshell.geom.utils.set_shape_transparency(section_box_display, 0.5)
transformed_box = BRepBuilderAPI.BRepBuilderAPI_Transform(
self.section_box['shape'], self.transformation)
transformed_box = BRepBuilderAPI.BRepBuilderAPI_Transform(self.section_box["shape"], self.transformation)
box_display = ifcopenshell.geom.utils.display_shape(transformed_box.Shape())
ifcopenshell.geom.utils.set_shape_transparency(box_display, 0.2)
for shape in self.product_shapes:
ifcopenshell.geom.utils.display_shape(shape[1])
input('Debug: showing everything with section plane.')
input("Debug: showing everything with section plane.")
def display_cut_polygons(self):
self.occ_display.EraseAll()
for polygon in self.cut_polygons:
ifcopenshell.geom.utils.display_shape(polygon['geometry'], clr='BLACK')
face = BRepBuilderAPI.BRepBuilderAPI_MakeFace(polygon['geometry']).Face()
ifcopenshell.geom.utils.display_shape(polygon["geometry"], clr="BLACK")
face = BRepBuilderAPI.BRepBuilderAPI_MakeFace(polygon["geometry"]).Face()
face_display = ifcopenshell.geom.utils.display_shape(face)
ifcopenshell.geom.utils.set_shape_transparency(face_display, 0.5)
input('Debug: showing cut polygons.')
input("Debug: showing cut polygons.")
def display_background_elements(self):
self.occ_display.EraseAll()
for element in self.background_elements:
if element['type'] == 'line':
ifcopenshell.geom.utils.display_shape(element['geometry'], clr='PURPLE')
elif element['type'] == 'polyline':
ifcopenshell.geom.utils.display_shape(element['geometry_face'], clr='RED')
elif element['type'] == 'polygon':
ifcopenshell.geom.utils.display_shape(element['geometry_face'])
input('Debug: showing background elements.')
if element["type"] == "line":
ifcopenshell.geom.utils.display_shape(element["geometry"], clr="PURPLE")
elif element["type"] == "polyline":
ifcopenshell.geom.utils.display_shape(element["geometry_face"], clr="RED")
elif element["type"] == "polygon":
ifcopenshell.geom.utils.display_shape(element["geometry_face"])
input("Debug: showing background elements.")
File diff suppressed because it is too large Load Diff
+132 -87
View File
@@ -5,7 +5,7 @@ import bpy
def get_representation_elements(ifc_file, step_id):
results = []
for child in ifc_file.traverse(ifc_file.by_id(step_id)):
if hasattr(child, 'StyledByItem') and child.StyledByItem:
if hasattr(child, "StyledByItem") and child.StyledByItem:
for styled_by_item in child.StyledByItem:
for style in styled_by_item.Styles:
for style_child in ifc_file.traverse(style):
@@ -16,49 +16,92 @@ def get_representation_elements(ifc_file, step_id):
# TODO: Deprecate this in favour of ifcopenshell.util.unit
class SIUnitHelper:
prefixes = {"EXA": 1e18, "PETA": 1e15, "TERA": 1e12, "GIGA": 1e9, "MEGA":
1e6, "KILO": 1e3, "HECTO": 1e2, "DECA": 1e1, "DECI": 1e-1, "CENTI":
1e-2, "MILLI": 1e-3, "MICRO": 1e-6, "NANO": 1e-9, "PICO": 1e-12,
"FEMTO": 1e-15, "ATTO": 1e-18}
unit_names = ["AMPERE", "BECQUEREL", "CANDELA", "COULOMB",
"CUBIC_METRE", "DEGREE CELSIUS", "FARAD", "GRAM", "GRAY", "HENRY",
"HERTZ", "JOULE", "KELVIN", "LUMEN", "LUX", "MOLE", "NEWTON", "OHM",
"PASCAL", "RADIAN", "SECOND", "SIEMENS", "SIEVERT", "SQUARE METRE",
"METRE", "STERADIAN", "TESLA", "VOLT", "WATT", "WEBER"]
prefixes = {
"EXA": 1e18,
"PETA": 1e15,
"TERA": 1e12,
"GIGA": 1e9,
"MEGA": 1e6,
"KILO": 1e3,
"HECTO": 1e2,
"DECA": 1e1,
"DECI": 1e-1,
"CENTI": 1e-2,
"MILLI": 1e-3,
"MICRO": 1e-6,
"NANO": 1e-9,
"PICO": 1e-12,
"FEMTO": 1e-15,
"ATTO": 1e-18,
}
unit_names = [
"AMPERE",
"BECQUEREL",
"CANDELA",
"COULOMB",
"CUBIC_METRE",
"DEGREE CELSIUS",
"FARAD",
"GRAM",
"GRAY",
"HENRY",
"HERTZ",
"JOULE",
"KELVIN",
"LUMEN",
"LUX",
"MOLE",
"NEWTON",
"OHM",
"PASCAL",
"RADIAN",
"SECOND",
"SIEMENS",
"SIEVERT",
"SQUARE METRE",
"METRE",
"STERADIAN",
"TESLA",
"VOLT",
"WATT",
"WEBER",
]
si_conversions = {
'inch': 0.0254,
'foot': 0.3048,
'yard': 0.914,
'mile': 1609,
'square inch': 0.0006452,
'square foot': 0.09290304,
'square yard': 0.83612736,
'acre': 4046.86,
'square mile': 2588881,
'cubic inch': 0.00001639,
'cubic foot': 0.02831684671168849,
'cubic yard': 0.7636,
'litre': 0.001,
'fluid ounce UK': 0.0000284130625,
'fluid ounce US': 0.00002957353,
'pint UK': 0.000568,
'pint US': 0.000473,
'gallon UK': 0.004546,
'gallon US': 0.003785,
'degree': math.pi/180,
'ounce': 0.02835,
'pound': 0.454,
'ton UK': 1016.0469088,
'ton US': 907.18474,
'lbf': 4.4482216153,
'kip': 4448.2216153,
'psi': 6894.7572932,
'ksi': 6894757.2932,
'minute': 60,
'hour': 3600,
'day': 86400,
'btu': 1055.056}
"inch": 0.0254,
"foot": 0.3048,
"yard": 0.914,
"mile": 1609,
"square inch": 0.0006452,
"square foot": 0.09290304,
"square yard": 0.83612736,
"acre": 4046.86,
"square mile": 2588881,
"cubic inch": 0.00001639,
"cubic foot": 0.02831684671168849,
"cubic yard": 0.7636,
"litre": 0.001,
"fluid ounce UK": 0.0000284130625,
"fluid ounce US": 0.00002957353,
"pint UK": 0.000568,
"pint US": 0.000473,
"gallon UK": 0.004546,
"gallon US": 0.003785,
"degree": math.pi / 180,
"ounce": 0.02835,
"pound": 0.454,
"ton UK": 1016.0469088,
"ton US": 907.18474,
"lbf": 4.4482216153,
"kip": 4448.2216153,
"psi": 6894.7572932,
"ksi": 6894757.2932,
"minute": 60,
"hour": 3600,
"day": 86400,
"btu": 1055.056,
}
@staticmethod
def get_prefix(text):
@@ -78,7 +121,7 @@ class SIUnitHelper:
@staticmethod
def get_unit_name(text):
for name in SIUnitHelper.unit_names:
if name in text.upper().replace('METER', 'METRE'):
if name in text.upper().replace("METER", "METRE"):
return name
@staticmethod
@@ -100,20 +143,20 @@ class SIUnitHelper:
value *= SIUnitHelper.si_conversions[from_unit]
elif from_prefix:
value *= SIUnitHelper.get_prefix_multiplier(from_prefix)
if 'SQUARE' in from_unit:
if "SQUARE" in from_unit:
value *= SIUnitHelper.get_prefix_multiplier(from_prefix)
elif 'CUBIC' in from_unit:
elif "CUBIC" in from_unit:
value *= SIUnitHelper.get_prefix_multiplier(from_prefix)
value *= SIUnitHelper.get_prefix_multiplier(from_prefix)
if to_unit in SIUnitHelper.si_conversions:
return value * (1 / SIUnitHelper.si_conversions[to_unit])
elif to_prefix:
value *= (1 / SIUnitHelper.get_prefix_multiplier(to_prefix))
if 'SQUARE' in from_unit:
value *= (1 / SIUnitHelper.get_prefix_multiplier(to_prefix))
elif 'CUBIC' in from_unit:
value *= (1 / SIUnitHelper.get_prefix_multiplier(to_prefix))
value *= (1 / SIUnitHelper.get_prefix_multiplier(to_prefix))
value *= 1 / SIUnitHelper.get_prefix_multiplier(to_prefix)
if "SQUARE" in from_unit:
value *= 1 / SIUnitHelper.get_prefix_multiplier(to_prefix)
elif "CUBIC" in from_unit:
value *= 1 / SIUnitHelper.get_prefix_multiplier(to_prefix)
value *= 1 / SIUnitHelper.get_prefix_multiplier(to_prefix)
return value
@@ -141,39 +184,38 @@ def format_distance(value, isArea=False, hide_units=True):
# Imperial Formating
if unit_system == "IMPERIAL":
precision = bpy.context.scene.BIMProperties.imperial_precision
if precision == 'NONE':
if precision == "NONE":
precision = 256
elif precision == '1':
elif precision == "1":
precision = 1
elif '/' in precision:
precision = int(precision.split('/')[1])
elif "/" in precision:
precision = int(precision.split("/")[1])
base = int(precision)
decInches = value * toInches
# Seperate ft and inches
# Unless Inches are the specified Length Unit
if unit_length != 'INCHES':
feet = math.floor(decInches/inPerFoot)
decInches -= feet*inPerFoot
if unit_length != "INCHES":
feet = math.floor(decInches / inPerFoot)
decInches -= feet * inPerFoot
else:
feet = 0
#Seperate Fractional Inches
# Seperate Fractional Inches
inches = math.floor(decInches)
if inches != 0:
frac = round(base*(decInches-inches))
frac = round(base * (decInches - inches))
else:
frac = round(base*(decInches))
frac = round(base * (decInches))
#Set proper numerator and denominator
# Set proper numerator and denominator
if frac != base:
numcycles = int(math.log2(base))
for i in range(numcycles):
if frac%2 == 0:
frac = int(frac/2)
base = int(base/2)
if frac % 2 == 0:
frac = int(frac / 2)
base = int(base / 2)
else:
break
else:
@@ -185,48 +227,52 @@ def format_distance(value, isArea=False, hide_units=True):
feet += 1
inches = 0
if inches !=0:
if inches != 0:
inchesString = str(inches)
if frac != 0: inchesString += "-"
else: inchesString += "\""
else: inchesString = ""
if frac != 0:
inchesString += "-"
else:
inchesString += '"'
else:
inchesString = ""
if feet != 0:
feetString = str(feet) + "' "
else: feetString = ""
else:
feetString = ""
if frac != 0:
fracString = str(frac) + "/" + str(base) +"\""
else: fracString = ""
fracString = str(frac) + "/" + str(base) + '"'
else:
fracString = ""
if not isArea:
tx_dist = feetString + inchesString + fracString
else:
tx_dist = str('%1.3f' % (value*toInches/inPerFoot)) + " sq. ft."
tx_dist = str("%1.3f" % (value * toInches / inPerFoot)) + " sq. ft."
# METRIC FORMATING
elif unit_system == "METRIC":
precision = bpy.context.scene.BIMProperties.metric_precision
if precision != 0:
value = precision * round(float(value)/precision)
value = precision * round(float(value) / precision)
# Meters
if unit_length == 'METERS':
fmt = '%1.3f'
if unit_length == "METERS":
fmt = "%1.3f"
if hide_units is False:
fmt += " m"
tx_dist = fmt % value
# Centimeters
elif unit_length == 'CENTIMETERS':
fmt = '%1.1f'
elif unit_length == "CENTIMETERS":
fmt = "%1.1f"
if hide_units is False:
fmt += " cm"
d_cm = value * (100)
tx_dist = fmt % d_cm
#Millimeters
elif unit_length == 'MILLIMETERS':
fmt = '%1.0f'
# Millimeters
elif unit_length == "MILLIMETERS":
fmt = "%1.0f"
if hide_units is False:
fmt += " mm"
d_mm = value * (1000)
@@ -235,19 +281,19 @@ def format_distance(value, isArea=False, hide_units=True):
# Otherwise Use Adaptive Units
else:
if round(value, 2) >= 1.0:
fmt = '%1.3f'
fmt = "%1.3f"
if hide_units is False:
fmt += " m"
tx_dist = fmt % value
else:
if round(value, 2) >= 0.01:
fmt = '%1.1f'
fmt = "%1.1f"
if hide_units is False:
fmt += " cm"
d_cm = value * (100)
tx_dist = fmt % d_cm
else:
fmt = '%1.0f'
fmt = "%1.0f"
if hide_units is False:
fmt += " mm"
d_mm = value * (1000)
@@ -257,5 +303,4 @@ def format_distance(value, isArea=False, hide_units=True):
else:
tx_dist = fmt % value
return tx_dist
+4 -3
View File
@@ -1,10 +1,11 @@
import bpy
import ifcopenshell
class IfcStore():
path = ''
class IfcStore:
path = ""
file = None
pset_template_path = ''
pset_template_path = ""
pset_template_file = None
@staticmethod
File diff suppressed because it is too large Load Diff
@@ -1,15 +1,16 @@
import json
import requests
class Api:
def login(self, username, password):
data = {
'username': username,
'password': password,
"username": username,
"password": password,
}
response_data = self.post_request('get-token', data, False)
if 'token' in response_data:
self.token = str(response_data['token'])
response_data = self.post_request("get-token", data, False)
if "token" in response_data:
self.token = str(response_data["token"])
return self.token
def post_request(self, path, data, use_token=True):
@@ -25,17 +26,16 @@ class Api:
return self._handle_response(response)
def _api_url(self, path):
return 'https://app.covetool.com/api/' + path + '/'
return "https://app.covetool.com/api/" + path + "/"
def _headers(self, use_token):
headers = {}
if use_token:
headers['Authorization'] = 'Token ' + self.token
headers["Authorization"] = "Token " + self.token
return headers
def _handle_response(self, response):
if response.ok:
return response.json()
else:
return {'result': 'error'}
return {"result": "error"}
@@ -6,98 +6,106 @@ from .api import Api
api = Api()
class Login(bpy.types.Operator):
bl_idname = 'bim.covetool_login'
bl_label = 'Login to cove.tool'
bl_idname = "bim.covetool_login"
bl_label = "Login to cove.tool"
def execute(self, context):
token = api.login(
bpy.context.scene.CoveToolProperties.username,
bpy.context.scene.CoveToolProperties.password)
token = api.login(bpy.context.scene.CoveToolProperties.username, bpy.context.scene.CoveToolProperties.password)
if token:
bpy.context.scene.CoveToolProperties.token = token
projects = api.get_request('projects')
projects = api.get_request("projects")
for project in projects:
new_project = bpy.context.scene.CoveToolProperties.projects.add()
new_project.name = project['name']
new_project.run_set = project['run_set'][0]
new_project.url = project['url']
new_project.name = project["name"]
new_project.run_set = project["run_set"][0]
new_project.url = project["url"]
else:
self.report({'ERROR'}, 'Login failed :(')
return {'FINISHED'}
self.report({"ERROR"}, "Login failed :(")
return {"FINISHED"}
class RunSimpleAnalysis(bpy.types.Operator):
bl_idname = 'bim.covetool_run_simple_analysis'
bl_label = 'Run Simple Analysis'
bl_idname = "bim.covetool_run_simple_analysis"
bl_label = "Run Simple Analysis"
def execute(self, context):
simple_analysis = bpy.context.scene.CoveToolProperties.simple_analysis
data = {
'run': bpy.context.scene.CoveToolProperties.projects[bpy.context.scene.CoveToolProperties.active_project_index].run_set,
'si_units': simple_analysis.si_units,
'building_height': simple_analysis.building_height,
'roof_area': simple_analysis.roof_area,
'floor_area': simple_analysis.floor_area,
'skylight_area': simple_analysis.skylight_area,
'wall_area_e': simple_analysis.wall_area_e,
'wall_area_ne': simple_analysis.wall_area_ne,
'wall_area_n': simple_analysis.wall_area_n,
'wall_area_nw': simple_analysis.wall_area_nw,
'wall_area_w': simple_analysis.wall_area_w,
'wall_area_sw': simple_analysis.wall_area_sw,
'wall_area_s': simple_analysis.wall_area_s,
'wall_area_se': simple_analysis.wall_area_se,
'window_area_e': simple_analysis.window_area_e,
'window_area_ne': simple_analysis.window_area_ne,
'window_area_n': simple_analysis.window_area_n,
'window_area_nw': simple_analysis.window_area_nw,
'window_area_w': simple_analysis.window_area_w,
'window_area_sw': simple_analysis.window_area_sw,
'window_area_s': simple_analysis.window_area_s,
'window_area_se': simple_analysis.window_area_se,
"run": bpy.context.scene.CoveToolProperties.projects[
bpy.context.scene.CoveToolProperties.active_project_index
].run_set,
"si_units": simple_analysis.si_units,
"building_height": simple_analysis.building_height,
"roof_area": simple_analysis.roof_area,
"floor_area": simple_analysis.floor_area,
"skylight_area": simple_analysis.skylight_area,
"wall_area_e": simple_analysis.wall_area_e,
"wall_area_ne": simple_analysis.wall_area_ne,
"wall_area_n": simple_analysis.wall_area_n,
"wall_area_nw": simple_analysis.wall_area_nw,
"wall_area_w": simple_analysis.wall_area_w,
"wall_area_sw": simple_analysis.wall_area_sw,
"wall_area_s": simple_analysis.wall_area_s,
"wall_area_se": simple_analysis.wall_area_se,
"window_area_e": simple_analysis.window_area_e,
"window_area_ne": simple_analysis.window_area_ne,
"window_area_n": simple_analysis.window_area_n,
"window_area_nw": simple_analysis.window_area_nw,
"window_area_w": simple_analysis.window_area_w,
"window_area_sw": simple_analysis.window_area_sw,
"window_area_s": simple_analysis.window_area_s,
"window_area_se": simple_analysis.window_area_se,
}
result = api.post_request('run-values', data)
covetool_results = bpy.data.texts.new('cove.tool Results')
result = api.post_request("run-values", data)
covetool_results = bpy.data.texts.new("cove.tool Results")
covetool_results.write(json.dumps(result, indent=4))
return {'FINISHED'}
return {"FINISHED"}
class RunAnalysis(bpy.types.Operator):
bl_idname = 'bim.covetool_run_analysis'
bl_label = 'Run Analysis'
bl_idname = "bim.covetool_run_analysis"
bl_label = "Run Analysis"
def execute(self, context):
self.inputs = {
'floors': [],
'walls': [],
'interior_walls': [],
'windows': [],
'skylights': [],
'roofs': [],
'shading_devices': []
"floors": [],
"walls": [],
"interior_walls": [],
"windows": [],
"skylights": [],
"roofs": [],
"shading_devices": [],
}
self.parse_objects()
data = {
'run': bpy.context.scene.CoveToolProperties.projects[bpy.context.scene.CoveToolProperties.active_project_index].run_set,
'source': 'BlenderBIM',
'rotation_angle': self.get_rotation_angle(),
**self.inputs
"run": bpy.context.scene.CoveToolProperties.projects[
bpy.context.scene.CoveToolProperties.active_project_index
].run_set,
"source": "BlenderBIM",
"rotation_angle": self.get_rotation_angle(),
**self.inputs,
}
result = api.post_request('run-values', data)
covetool_results = bpy.data.texts.new('cove.tool Results')
result = api.post_request("run-values", data)
covetool_results = bpy.data.texts.new("cove.tool Results")
covetool_results.write(json.dumps(result, indent=4))
return {'FINISHED'}
return {"FINISHED"}
def get_rotation_angle(self):
if not bpy.context.scene.BIMProperties.has_georeferencing \
or not bpy.context.scene.MapConversion.x_axis_abscissa \
or not bpy.context.scene.MapConversion.x_axis_ordinate:
if (
not bpy.context.scene.BIMProperties.has_georeferencing
or not bpy.context.scene.MapConversion.x_axis_abscissa
or not bpy.context.scene.MapConversion.x_axis_ordinate
):
return 0
rotation = -1 * degrees(atan2(
float(bpy.context.scene.MapConversion.x_axis_ordinate),
float(bpy.context.scene.MapConversion.x_axis_abscissa)))
rotation = -1 * degrees(
atan2(
float(bpy.context.scene.MapConversion.x_axis_ordinate),
float(bpy.context.scene.MapConversion.x_axis_abscissa),
)
)
if rotation < 0:
rotation = 360 - rotation
return rotation
@@ -108,86 +116,82 @@ class RunAnalysis(bpy.types.Operator):
if not covetool_category:
continue
if not self.has_triangulate_modifier(obj):
obj.modifiers.new(name='Triangulate', type='TRIANGULATE')
obj.modifiers.new(name="Triangulate", type="TRIANGULATE")
mesh = obj.evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh()
meshes = {}
for polygon in mesh.polygons:
normal = '{}|{}|{}'.format(
round(polygon.normal[0], 2),
round(polygon.normal[1], 2),
round(polygon.normal[2], 2))
meshes.setdefault(normal, {
'Mesh': {
'vertex_indices': {},
'Vertices': [],
'Triangles': []
normal = "{}|{}|{}".format(
round(polygon.normal[0], 2), round(polygon.normal[1], 2), round(polygon.normal[2], 2)
)
meshes.setdefault(
normal,
{
"Mesh": {"vertex_indices": {}, "Vertices": [], "Triangles": []},
"Center": {},
"Normal": {
"X": round(polygon.normal[0], 2),
"Y": round(polygon.normal[1], 2),
"Z": round(polygon.normal[2], 2),
},
},
'Center': {},
'Normal': {
'X': round(polygon.normal[0], 2),
'Y': round(polygon.normal[1], 2),
'Z': round(polygon.normal[2], 2)
}
})
)
for vertex in polygon.vertices:
global_vertex = obj.matrix_world @ mesh.vertices[vertex].co
meshes[normal]['Mesh']['vertex_indices'][vertex] = {
'X': global_vertex[0] * 3.28084, # Covetools require feet
'Y': global_vertex[1] * 3.28084,
'Z': global_vertex[2] * 3.28084
meshes[normal]["Mesh"]["vertex_indices"][vertex] = {
"X": global_vertex[0] * 3.28084, # Covetools require feet
"Y": global_vertex[1] * 3.28084,
"Z": global_vertex[2] * 3.28084,
}
meshes[normal]['Mesh']['Triangles'].append([
polygon.vertices[0],
polygon.vertices[1],
polygon.vertices[2]
])
meshes[normal]["Mesh"]["Triangles"].append(
[polygon.vertices[0], polygon.vertices[1], polygon.vertices[2]]
)
for normal, mesh in meshes.items():
sorted_keys = sorted(mesh['Mesh']['vertex_indices'])
mesh['Mesh']['Vertices'] = [mesh['Mesh']['vertex_indices'][k] for k in sorted_keys]
for triangle in mesh['Mesh']['Triangles']:
sorted_keys = sorted(mesh["Mesh"]["vertex_indices"])
mesh["Mesh"]["Vertices"] = [mesh["Mesh"]["vertex_indices"][k] for k in sorted_keys]
for triangle in mesh["Mesh"]["Triangles"]:
triangle[0] = sorted_keys.index(triangle[0])
triangle[1] = sorted_keys.index(triangle[1])
triangle[2] = sorted_keys.index(triangle[2])
mesh['Center'] = mesh['Mesh']['Vertices'][0] # Not correct, but just for now
del mesh['Mesh']['vertex_indices']
mesh["Center"] = mesh["Mesh"]["Vertices"][0] # Not correct, but just for now
del mesh["Mesh"]["vertex_indices"]
self.inputs[covetool_category].extend(meshes.values())
def has_triangulate_modifier(self, obj):
for modifier in obj.modifiers:
if modifier.type == 'TRIANGULATE':
if modifier.type == "TRIANGULATE":
return True
def get_covetool_category(self, obj):
if not hasattr(obj, 'data') or not isinstance(obj.data, bpy.types.Mesh):
if not hasattr(obj, "data") or not isinstance(obj.data, bpy.types.Mesh):
return
if 'IfcSlab' in obj.name:
return 'floors'
elif 'IfcRoof' in obj.name:
return 'roofs'
elif 'IfcWall' in obj.name:
if "IfcSlab" in obj.name:
return "floors"
elif "IfcRoof" in obj.name:
return "roofs"
elif "IfcWall" in obj.name:
if self.is_wall_internal(obj):
return 'interior_walls'
return 'walls'
elif 'IfcWindow' in obj.name:
return "interior_walls"
return "walls"
elif "IfcWindow" in obj.name:
if self.is_window_skylight(obj):
return 'skylights'
return 'windows'
elif 'IfcShadingDevice' in obj.name:
return 'shading_devices'
return "skylights"
return "windows"
elif "IfcShadingDevice" in obj.name:
return "shading_devices"
def is_wall_internal(self, obj):
pset_wallcommon = obj.BIMObjectProperties.psets.get('Pset_WallCommon')
pset_wallcommon = obj.BIMObjectProperties.psets.get("Pset_WallCommon")
if pset_wallcommon:
is_external = pset_wallcommon.properties.get('IsExternal')
is_external = pset_wallcommon.properties.get("IsExternal")
if is_external:
if is_external.string_value == 'True':
if is_external.string_value == "True":
return False
else:
return True
predefined_type = obj.BIMObjectProperties.attributes.get('PredefinedType')
if predefined_type and predefined_type.string_value in ['MOVABLE', 'PARTITIONING', 'PLUMBINGWALL']:
predefined_type = obj.BIMObjectProperties.attributes.get("PredefinedType")
if predefined_type and predefined_type.string_value in ["MOVABLE", "PARTITIONING", "PLUMBINGWALL"]:
return True
def is_window_skylight(self, obj):
predefined_type = obj.BIMObjectProperties.attributes.get('PredefinedType')
return predefined_type and predefined_type.string_value == 'SKYLIGHT'
predefined_type = obj.BIMObjectProperties.attributes.get("PredefinedType")
return predefined_type and predefined_type.string_value == "SKYLIGHT"
@@ -3,39 +3,39 @@ import bpy.types
class CoveToolProject(bpy.types.PropertyGroup):
name: bpy.props.StringProperty(name='Name')
run_set: bpy.props.StringProperty(name='Run Set')
url: bpy.props.StringProperty(name='URL')
name: bpy.props.StringProperty(name="Name")
run_set: bpy.props.StringProperty(name="Run Set")
url: bpy.props.StringProperty(name="URL")
class CoveToolSimpleAnalysis(bpy.types.PropertyGroup):
si_units: bpy.props.BoolProperty(name='SI Units')
building_height: bpy.props.StringProperty(name='Building Height')
roof_area: bpy.props.StringProperty(name='Roof Area')
floor_area: bpy.props.StringProperty(name='Floor Area')
skylight_area: bpy.props.StringProperty(name='Skylight Area')
wall_area_e: bpy.props.StringProperty(name='Wall Area E')
wall_area_ne: bpy.props.StringProperty(name='Wall Area NE')
wall_area_n: bpy.props.StringProperty(name='Wall Area N')
wall_area_nw: bpy.props.StringProperty(name='Wall Area NW')
wall_area_w: bpy.props.StringProperty(name='Wall Area W')
wall_area_sw: bpy.props.StringProperty(name='Wall Area SW')
wall_area_s: bpy.props.StringProperty(name='Wall Area S')
wall_area_se: bpy.props.StringProperty(name='Wall Area SE')
window_area_e: bpy.props.StringProperty(name='Window Area E')
window_area_ne: bpy.props.StringProperty(name='Window Area NE')
window_area_n: bpy.props.StringProperty(name='Window Area N')
window_area_nw: bpy.props.StringProperty(name='Window Area NW')
window_area_w: bpy.props.StringProperty(name='Window Area W')
window_area_sw: bpy.props.StringProperty(name='Window Area SW')
window_area_s: bpy.props.StringProperty(name='Window Area S')
window_area_se: bpy.props.StringProperty(name='Window Area SE')
si_units: bpy.props.BoolProperty(name="SI Units")
building_height: bpy.props.StringProperty(name="Building Height")
roof_area: bpy.props.StringProperty(name="Roof Area")
floor_area: bpy.props.StringProperty(name="Floor Area")
skylight_area: bpy.props.StringProperty(name="Skylight Area")
wall_area_e: bpy.props.StringProperty(name="Wall Area E")
wall_area_ne: bpy.props.StringProperty(name="Wall Area NE")
wall_area_n: bpy.props.StringProperty(name="Wall Area N")
wall_area_nw: bpy.props.StringProperty(name="Wall Area NW")
wall_area_w: bpy.props.StringProperty(name="Wall Area W")
wall_area_sw: bpy.props.StringProperty(name="Wall Area SW")
wall_area_s: bpy.props.StringProperty(name="Wall Area S")
wall_area_se: bpy.props.StringProperty(name="Wall Area SE")
window_area_e: bpy.props.StringProperty(name="Window Area E")
window_area_ne: bpy.props.StringProperty(name="Window Area NE")
window_area_n: bpy.props.StringProperty(name="Window Area N")
window_area_nw: bpy.props.StringProperty(name="Window Area NW")
window_area_w: bpy.props.StringProperty(name="Window Area W")
window_area_sw: bpy.props.StringProperty(name="Window Area SW")
window_area_s: bpy.props.StringProperty(name="Window Area S")
window_area_se: bpy.props.StringProperty(name="Window Area SE")
class CoveToolProperties(bpy.types.PropertyGroup):
username: bpy.props.StringProperty(name='Username')
password: bpy.props.StringProperty(name='Password')
token: bpy.props.StringProperty(name='Token')
projects: bpy.props.CollectionProperty(name='Projects', type=CoveToolProject)
active_project_index: bpy.props.IntProperty(name='Active Project Index')
username: bpy.props.StringProperty(name="Username")
password: bpy.props.StringProperty(name="Password")
token: bpy.props.StringProperty(name="Token")
projects: bpy.props.CollectionProperty(name="Projects", type=CoveToolProject)
active_project_index: bpy.props.IntProperty(name="Active Project Index")
simple_analysis: bpy.props.PointerProperty(type=CoveToolSimpleAnalysis)
@@ -1,11 +1,12 @@
import bpy.types
class BIM_PT_covetool(bpy.types.Panel):
bl_label = "cove.tool"
bl_idname = "BIM_PT_covetool"
bl_options = {'DEFAULT_CLOSED'}
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
def draw(self, context):
@@ -17,36 +18,53 @@ class BIM_PT_covetool(bpy.types.Panel):
if not props.token:
row = layout.row()
row.prop(props, 'username')
row.prop(props, "username")
row = layout.row()
row.prop(props, 'password')
row.prop(props, "password")
row = layout.row()
row.operator('bim.covetool_login')
row.operator("bim.covetool_login")
return
layout.template_list('BIM_UL_covetool_projects', '', props, 'projects', props, 'active_project_index')
layout.template_list("BIM_UL_covetool_projects", "", props, "projects", props, "active_project_index")
row = layout.row()
row.operator('bim.covetool_run_analysis')
row.operator("bim.covetool_run_analysis")
prop_names = [ 'si_units', 'building_height', 'roof_area', 'floor_area',
'skylight_area', 'wall_area_e', 'wall_area_ne', 'wall_area_n',
'wall_area_nw', 'wall_area_w', 'wall_area_sw', 'wall_area_s',
'wall_area_se', 'window_area_e', 'window_area_ne', 'window_area_n',
'window_area_nw', 'window_area_w', 'window_area_sw',
'window_area_s', 'window_area_se']
prop_names = [
"si_units",
"building_height",
"roof_area",
"floor_area",
"skylight_area",
"wall_area_e",
"wall_area_ne",
"wall_area_n",
"wall_area_nw",
"wall_area_w",
"wall_area_sw",
"wall_area_s",
"wall_area_se",
"window_area_e",
"window_area_ne",
"window_area_n",
"window_area_nw",
"window_area_w",
"window_area_sw",
"window_area_s",
"window_area_se",
]
for prop_name in prop_names:
row = layout.row()
row.prop(props.simple_analysis, prop_name)
row = layout.row()
row.operator('bim.covetool_run_simple_analysis')
row.operator("bim.covetool_run_simple_analysis")
class BIM_UL_covetool_projects(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
ob = data
if item:
layout.prop(item, 'name', text='', emboss=False)
layout.prop(item, "name", text="", emboss=False)
else:
layout.label(text='', translate=False)
layout.label(text="", translate=False)
@@ -6,42 +6,62 @@ from bpy.props import FloatVectorProperty, FloatProperty, IntProperty
from bpy_extras.object_utils import AddObjectHelper, object_data_add
from mathutils import Vector
def add_object(self, context):
guid = ifcopenshell.guid.new()
leaf_width = self.overall_width-0.045-0.045
leaf_width = self.overall_width - 0.045 - 0.045
verts = [
# Left lining
Vector((0, 0, 0)),
Vector((0, self.depth, 0)),
Vector((.04, self.depth, 0)),
Vector((.04, self.depth-.04, 0)),
Vector((.065, self.depth-.04, 0)),
Vector((.065, 0, 0)),
Vector((0.04, self.depth, 0)),
Vector((0.04, self.depth - 0.04, 0)),
Vector((0.065, self.depth - 0.04, 0)),
Vector((0.065, 0, 0)),
# Right lining
Vector((self.overall_width, 0, 0)),
Vector((self.overall_width, self.depth, 0)),
Vector((self.overall_width-.04, self.depth, 0)),
Vector((self.overall_width-.04, self.depth-.04, 0)),
Vector((self.overall_width-.065, self.depth-.04, 0)),
Vector((self.overall_width-.065, 0, 0)),
Vector((self.overall_width - 0.04, self.depth, 0)),
Vector((self.overall_width - 0.04, self.depth - 0.04, 0)),
Vector((self.overall_width - 0.065, self.depth - 0.04, 0)),
Vector((self.overall_width - 0.065, 0, 0)),
# Door panel
Vector((.045, self.depth, 0)),
Vector((.045, self.depth+leaf_width, 0)),
Vector((.080, self.depth+leaf_width, 0)),
Vector((.080, self.depth, 0)),
Vector((0.045, self.depth, 0)),
Vector((0.045, self.depth + leaf_width, 0)),
Vector((0.080, self.depth + leaf_width, 0)),
Vector((0.080, self.depth, 0)),
]
edges = [
[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], # Left lining
[6, 7], [7, 8], [8, 9], [9, 10], [10, 11], # Right lining
[12, 13], [13, 14], [14, 15], [15, 12], # Door panel
[0, 1],
[1, 2],
[2, 3],
[3, 4],
[4, 5], # Left lining
[6, 7],
[7, 8],
[8, 9],
[9, 10],
[10, 11], # Right lining
[12, 13],
[13, 14],
[14, 15],
[15, 12], # Door panel
]
# Door swing
for i in range(0, 9):
verts.append(Vector((0.045+(leaf_width*math.cos((math.pi/2)/8*i)), self.depth+(leaf_width*math.sin((math.pi/2)/8*i)), 0)))
edges.append([16+i, 17+i])
verts.append(
Vector(
(
0.045 + (leaf_width * math.cos((math.pi / 2) / 8 * i)),
self.depth + (leaf_width * math.sin((math.pi / 2) / 8 * i)),
0,
)
)
)
edges.append([16 + i, 17 + i])
edges.pop()
faces = []
mesh = bpy.data.meshes.new(name='Plan/Annotation/PLAN_VIEW/' + guid)
mesh = bpy.data.meshes.new(name="Plan/Annotation/PLAN_VIEW/" + guid)
mesh.use_fake_user = True
mesh.from_pydata(verts, edges, faces)
@@ -49,17 +69,17 @@ def add_object(self, context):
verts = [
Vector((0, 0, 0)),
Vector((0, -self.depth, 0)),
Vector((.04, -self.depth, 0)),
Vector((.04, -self.depth+.04, 0)),
Vector((.065, -self.depth+.04, 0)),
Vector((.065, 0, 0)),
Vector((0.04, -self.depth, 0)),
Vector((0.04, -self.depth + 0.04, 0)),
Vector((0.065, -self.depth + 0.04, 0)),
Vector((0.065, 0, 0)),
]
edges = []
faces = [[0, 1, 2, 3, 4, 5]]
mesh = bpy.data.meshes.new(name="Dumb Door Profile")
mesh.from_pydata(verts, edges, faces)
obj = object_data_add(context, mesh, operator=self)
bpy.ops.object.convert(target='CURVE')
bpy.ops.object.convert(target="CURVE")
# Door lining sweep
verts = [
@@ -70,99 +90,97 @@ def add_object(self, context):
]
edges = [[0, 1], [1, 2], [2, 3]]
faces = []
mesh = bpy.data.meshes.new(name='Dumb Door')
mesh = bpy.data.meshes.new(name="Dumb Door")
mesh.from_pydata(verts, edges, faces)
obj2 = object_data_add(context, mesh, operator=self)
bpy.ops.object.convert(target='CURVE')
bpy.ops.object.convert(target="CURVE")
obj2.data.dimensions = '2D'
obj2.data.dimensions = "2D"
obj2.data.bevel_object = obj
obj2.rotation_euler[0] = math.pi/2
bpy.ops.object.convert(target='MESH')
obj2.rotation_euler[0] = math.pi / 2
bpy.ops.object.convert(target="MESH")
bpy.ops.object.transform_apply(location=False)
bpy.data.objects.remove(obj, do_unlink=True)
# Door panel
verts = [
Vector((.045, self.depth, 0)),
Vector((.045, self.depth-.035, 0)),
Vector((self.overall_width-.045, self.depth-.035, 0)),
Vector((self.overall_width-.045, self.depth, 0)),
Vector((0.045, self.depth, 0)),
Vector((0.045, self.depth - 0.035, 0)),
Vector((self.overall_width - 0.045, self.depth - 0.035, 0)),
Vector((self.overall_width - 0.045, self.depth, 0)),
]
edges = []
faces = [[0, 1, 2, 3]]
mesh = bpy.data.meshes.new(name="Dumb Door Panel")
mesh.from_pydata(verts, edges, faces)
obj3 = object_data_add(context, mesh, operator=self)
modifier = obj3.modifiers.new('Panel Height', 'SOLIDIFY')
modifier = obj3.modifiers.new("Panel Height", "SOLIDIFY")
modifier.offset = 1
modifier.thickness = self.overall_height-.045
bpy.ops.object.convert(target='MESH')
modifier.thickness = self.overall_height - 0.045
bpy.ops.object.convert(target="MESH")
ctx = bpy.context.copy()
ctx['active_object'] = obj2
ctx['selected_editable_objects'] = [obj2, obj3]
ctx["active_object"] = obj2
ctx["selected_editable_objects"] = [obj2, obj3]
bpy.ops.object.join(ctx)
# Door Opening
verts = [
Vector((0, -.1, -.1)),
Vector((0, self.depth+.1, -.1)),
Vector((self.overall_width, self.depth+.1, -.1)),
Vector((self.overall_width, -.1, -.1)),
Vector((0, -0.1, -0.1)),
Vector((0, self.depth + 0.1, -0.1)),
Vector((self.overall_width, self.depth + 0.1, -0.1)),
Vector((self.overall_width, -0.1, -0.1)),
]
edges = []
faces = [[0, 1, 2, 3]]
mesh = bpy.data.meshes.new(name="Dumb Door Opening")
mesh.from_pydata(verts, edges, faces)
obj4 = object_data_add(context, mesh, operator=self)
modifier = obj4.modifiers.new('Panel Height', 'SOLIDIFY')
modifier = obj4.modifiers.new("Panel Height", "SOLIDIFY")
modifier.offset = -1
modifier.thickness = self.overall_height+.1
bpy.ops.object.convert(target='MESH')
obj4.display_type = 'WIRE'
modifier.thickness = self.overall_height + 0.1
bpy.ops.object.convert(target="MESH")
obj4.display_type = "WIRE"
obj4.parent = obj2
obj4.matrix_parent_inverse = obj2.matrix_world.inverted()
obj4.hide_render = True
obj4.name = 'IfcOpeningElement/Dumb Door Opening'
obj4.name = "IfcOpeningElement/Dumb Door Opening"
attribute = obj4.BIMObjectProperties.attributes.add()
attribute.name = 'PredefinedType'
attribute.string_value = 'OPENING'
attribute.name = "PredefinedType"
attribute.string_value = "OPENING"
obj2.name = 'IfcDoor/Dumb Door'
obj2.name = "IfcDoor/Dumb Door"
attribute = obj2.BIMObjectProperties.attributes.add()
attribute.name = 'PredefinedType'
attribute.string_value = 'DOOR'
obj2.data.name = 'Model/Body/MODEL_VIEW/' + guid
attribute.name = "PredefinedType"
attribute.string_value = "DOOR"
obj2.data.name = "Model/Body/MODEL_VIEW/" + guid
obj2.data.use_fake_user = True
rep = obj2.BIMObjectProperties.representation_contexts.add()
rep.context = 'Model'
rep.name = 'Body'
rep.target_view = 'MODEL_VIEW'
rep.context = "Model"
rep.name = "Body"
rep.target_view = "MODEL_VIEW"
rep = obj2.BIMObjectProperties.representation_contexts.add()
rep.context = 'Plan'
rep.name = 'Annotation'
rep.target_view = 'PLAN_VIEW'
rep.context = "Plan"
rep.name = "Annotation"
rep.target_view = "PLAN_VIEW"
class BIM_OT_add_object(Operator, AddObjectHelper):
bl_idname = "mesh.add_door"
bl_label = "Dumb Door"
bl_options = {'REGISTER', 'UNDO'}
bl_options = {"REGISTER", "UNDO"}
overall_width: FloatProperty(name='Overall Width', default=.85)
overall_height: FloatProperty(name='Overall Height', default=2.1)
depth: FloatProperty(name='Depth', default=.2)
overall_width: FloatProperty(name="Overall Width", default=0.85)
overall_height: FloatProperty(name="Overall Height", default=2.1)
depth: FloatProperty(name="Depth", default=0.2)
def execute(self, context):
add_object(self, context)
return {'FINISHED'}
return {"FINISHED"}
def add_object_button(self, context):
self.layout.operator(
BIM_OT_add_object.bl_idname,
icon='PLUGIN')
self.layout.operator(BIM_OT_add_object.bl_idname, icon="PLUGIN")
@@ -5,70 +5,74 @@ from bpy.props import FloatVectorProperty, FloatProperty, IntProperty
from bpy_extras.object_utils import AddObjectHelper, object_data_add
from mathutils import Vector
def add_object(self, context):
obj = object_data_add(context, None, operator=self)
obj.name = 'IfcGrid/Grid'
name = obj.name.split('/')[1]
obj.name = "IfcGrid/Grid"
name = obj.name.split("/")[1]
default_collection = obj.users_collection[0]
default_collection.objects.unlink(obj)
collection = bpy.data.collections.new('IfcGrid/' + name)
collection = bpy.data.collections.new("IfcGrid/" + name)
bpy.context.view_layer.active_layer_collection.collection.children.link(collection)
collection.objects.link(obj)
axes_collection = bpy.data.collections.new('UAxes')
axes_collection = bpy.data.collections.new("UAxes")
collection.children.link(axes_collection)
for i in range(0, self.total_u):
verts = [Vector((-2, i*self.u_spacing, 0)), Vector((((self.total_v-1)*self.v_spacing)+2, i*self.u_spacing, 0))]
verts = [
Vector((-2, i * self.u_spacing, 0)),
Vector((((self.total_v - 1) * self.v_spacing) + 2, i * self.u_spacing, 0)),
]
edges = [[0, 1]]
faces = []
mesh = bpy.data.meshes.new(name="Grid Axis")
mesh.from_pydata(verts, edges, faces)
obj = object_data_add(context, mesh, operator=self)
tag = chr(ord('A')+i)
obj.name = 'IfcGridAxis/' + tag
tag = chr(ord("A") + i)
obj.name = "IfcGridAxis/" + tag
default_collection.objects.unlink(obj)
axes_collection.objects.link(obj)
attribute = obj.BIMObjectProperties.attributes.add()
attribute.name = 'AxisTag'
attribute.name = "AxisTag"
attribute.string_value = tag
axes_collection = bpy.data.collections.new('VAxes')
axes_collection = bpy.data.collections.new("VAxes")
collection.children.link(axes_collection)
for i in range(0, self.total_v):
verts = [Vector((i*self.v_spacing, -2, 0)), Vector((i*self.v_spacing, ((self.total_u-1)*self.u_spacing)+2, 0))]
verts = [
Vector((i * self.v_spacing, -2, 0)),
Vector((i * self.v_spacing, ((self.total_u - 1) * self.u_spacing) + 2, 0)),
]
edges = [[0, 1]]
faces = []
mesh = bpy.data.meshes.new(name="Grid Axis")
mesh.from_pydata(verts, edges, faces)
obj = object_data_add(context, mesh, operator=self)
tag = str(i+1).zfill(2)
obj.name = 'IfcGridAxis/' + tag
tag = str(i + 1).zfill(2)
obj.name = "IfcGridAxis/" + tag
default_collection.objects.unlink(obj)
axes_collection.objects.link(obj)
attribute = obj.BIMObjectProperties.attributes.add()
attribute.name = 'AxisTag'
attribute.name = "AxisTag"
attribute.string_value = tag
class BIM_OT_add_object(Operator, AddObjectHelper):
bl_idname = "mesh.add_grid"
bl_label = "Grid"
bl_options = {'REGISTER', 'UNDO'}
bl_options = {"REGISTER", "UNDO"}
u_spacing: FloatProperty(name='U Spacing', default=10)
total_u: IntProperty(name='Number of U Grids', default=3)
v_spacing: FloatProperty(name='V Spacing', default=10)
total_v: IntProperty(name='Number of V Grids', default=3)
u_spacing: FloatProperty(name="U Spacing", default=10)
total_u: IntProperty(name="Number of U Grids", default=3)
v_spacing: FloatProperty(name="V Spacing", default=10)
total_v: IntProperty(name="Number of V Grids", default=3)
def execute(self, context):
add_object(self, context)
return {'FINISHED'}
return {"FINISHED"}
def add_object_button(self, context):
self.layout.operator(
BIM_OT_add_object.bl_idname,
icon='PLUGIN')
self.layout.operator(BIM_OT_add_object.bl_idname, icon="PLUGIN")
@@ -5,6 +5,7 @@ from bpy.props import FloatVectorProperty, FloatProperty, IntProperty
from bpy_extras.object_utils import AddObjectHelper, object_data_add
from mathutils import Vector
def add_object(self, context):
bm = bmesh.new()
bmesh.ops.create_cube(bm, size=self.size)
@@ -15,26 +16,24 @@ def add_object(self, context):
bm.to_mesh(mesh)
bm.free()
obj = object_data_add(context, mesh, operator=self)
obj.name = 'IfcOpening/Dumb Opening'
obj.display_type = 'WIRE'
obj.name = "IfcOpening/Dumb Opening"
obj.display_type = "WIRE"
attribute = obj.BIMObjectProperties.attributes.add()
attribute.name = 'PredefinedType'
attribute.string_value = 'OPENING'
attribute.name = "PredefinedType"
attribute.string_value = "OPENING"
class BIM_OT_add_object(Operator, AddObjectHelper):
bl_idname = "mesh.add_opening"
bl_label = "Dumb Opening"
bl_options = {'REGISTER', 'UNDO'}
bl_options = {"REGISTER", "UNDO"}
size: FloatProperty(name='Size', default=2)
size: FloatProperty(name="Size", default=2)
def execute(self, context):
add_object(self, context)
return {'FINISHED'}
return {"FINISHED"}
def add_object_button(self, context):
self.layout.operator(
BIM_OT_add_object.bl_idname,
icon='PLUGIN')
self.layout.operator(BIM_OT_add_object.bl_idname, icon="PLUGIN")
@@ -4,6 +4,7 @@ from bpy.props import FloatVectorProperty, FloatProperty, IntProperty
from bpy_extras.object_utils import AddObjectHelper, object_data_add
from mathutils import Vector
def add_object(self, context):
verts = [
Vector((0, 0, 0)),
@@ -17,31 +18,29 @@ def add_object(self, context):
mesh = bpy.data.meshes.new(name="Dumb Slab")
mesh.from_pydata(verts, edges, faces)
obj = object_data_add(context, mesh, operator=self)
modifier = obj.modifiers.new('Slab Depth', 'SOLIDIFY')
modifier = obj.modifiers.new("Slab Depth", "SOLIDIFY")
modifier.use_even_offset = True
modifier.offset = 1
modifier.thickness = self.depth
obj.name = 'IfcSlab/Dumb Slab'
obj.name = "IfcSlab/Dumb Slab"
attribute = obj.BIMObjectProperties.attributes.add()
attribute.name = 'PredefinedType'
attribute.string_value = 'FLOOR'
attribute.name = "PredefinedType"
attribute.string_value = "FLOOR"
class BIM_OT_add_object(Operator, AddObjectHelper):
bl_idname = "mesh.add_slab"
bl_label = "Dumb Slab"
bl_options = {'REGISTER', 'UNDO'}
bl_options = {"REGISTER", "UNDO"}
length: FloatProperty(name='Length', default=2)
width: FloatProperty(name='Width', default=2)
depth: FloatProperty(name='Depth', default=.2)
length: FloatProperty(name="Length", default=2)
width: FloatProperty(name="Width", default=2)
depth: FloatProperty(name="Depth", default=0.2)
def execute(self, context):
add_object(self, context)
return {'FINISHED'}
return {"FINISHED"}
def add_object_button(self, context):
self.layout.operator(
BIM_OT_add_object.bl_idname,
icon='PLUGIN')
self.layout.operator(BIM_OT_add_object.bl_idname, icon="PLUGIN")
@@ -4,6 +4,7 @@ from bpy.props import FloatVectorProperty, FloatProperty, IntProperty
from bpy_extras.object_utils import AddObjectHelper, object_data_add
from mathutils import Vector
def add_object(self, context):
if self.number_of_treads <= 0:
self.number_of_treads = 1
@@ -19,11 +20,11 @@ def add_object(self, context):
mesh = bpy.data.meshes.new(name="Dumb Stair")
mesh.from_pydata(verts, edges, faces)
obj = object_data_add(context, mesh, operator=self)
modifier = obj.modifiers.new('Stair Width', 'SOLIDIFY')
modifier = obj.modifiers.new("Stair Width", "SOLIDIFY")
modifier.use_even_offset = True
modifier.offset = 1
modifier.thickness = self.tread_depth
modifier = obj.modifiers.new('Stair Treads', 'ARRAY')
modifier = obj.modifiers.new("Stair Treads", "ARRAY")
modifier.relative_offset_displace[0] = 0
modifier.relative_offset_displace[1] = 1
modifier.use_constant_offset = True
@@ -31,31 +32,29 @@ def add_object(self, context):
modifier.count = self.number_of_treads
self.riser_height = self.height / self.number_of_treads
self.length = self.number_of_treads * self.tread_length
obj.name = 'IfcStairFlight/Dumb Stair'
obj.name = "IfcStairFlight/Dumb Stair"
attribute = obj.BIMObjectProperties.attributes.add()
attribute.name = 'PredefinedType'
attribute.string_value = 'STRAIGHT'
attribute.name = "PredefinedType"
attribute.string_value = "STRAIGHT"
class BIM_OT_add_object(Operator, AddObjectHelper):
bl_idname = "mesh.add_stair"
bl_label = "Dumb Stair"
bl_options = {'REGISTER', 'UNDO'}
bl_options = {"REGISTER", "UNDO"}
width: FloatProperty(name='Width', default=1.1)
height: FloatProperty(name='Height', default=1)
tread_depth: FloatProperty(name='Tread Depth', default=.2)
number_of_treads: IntProperty(name='Number of Treads (Goings)', default=6)
tread_length: FloatProperty(name='Tread Length (Going)', default=.25)
riser_height: FloatProperty(name='*Calculated* Riser Height')
length: FloatProperty(name='*Calculated* Length')
width: FloatProperty(name="Width", default=1.1)
height: FloatProperty(name="Height", default=1)
tread_depth: FloatProperty(name="Tread Depth", default=0.2)
number_of_treads: IntProperty(name="Number of Treads (Goings)", default=6)
tread_length: FloatProperty(name="Tread Length (Going)", default=0.25)
riser_height: FloatProperty(name="*Calculated* Riser Height")
length: FloatProperty(name="*Calculated* Length")
def execute(self, context):
add_object(self, context)
return {'FINISHED'}
return {"FINISHED"}
def add_object_button(self, context):
self.layout.operator(
BIM_OT_add_object.bl_idname,
icon='PLUGIN')
self.layout.operator(BIM_OT_add_object.bl_idname, icon="PLUGIN")
@@ -4,6 +4,7 @@ from bpy.props import FloatVectorProperty, FloatProperty, BoolProperty
from bpy_extras.object_utils import AddObjectHelper, object_data_add
from mathutils import Vector
def add_object(self, context):
if self.use_plane:
verts = [
@@ -26,7 +27,7 @@ def add_object(self, context):
mesh.from_pydata(verts, edges, faces)
obj = object_data_add(context, mesh, operator=self)
if not self.use_plane:
modifier = obj.modifiers.new('Wall Height', 'SCREW')
modifier = obj.modifiers.new("Wall Height", "SCREW")
modifier.angle = 0
modifier.screw_offset = self.height
modifier.use_smooth_shade = False
@@ -34,31 +35,29 @@ def add_object(self, context):
modifier.use_normal_flip = True
modifier.steps = 1
modifier.render_steps = 1
modifier = obj.modifiers.new('Wall Width', 'SOLIDIFY')
modifier = obj.modifiers.new("Wall Width", "SOLIDIFY")
modifier.use_even_offset = True
modifier.thickness = self.width
obj.name = 'IfcWall/Dumb Wall'
obj.name = "IfcWall/Dumb Wall"
attribute = obj.BIMObjectProperties.attributes.add()
attribute.name = 'PredefinedType'
attribute.string_value = 'STANDARD'
attribute.name = "PredefinedType"
attribute.string_value = "STANDARD"
class BIM_OT_add_object(Operator, AddObjectHelper):
bl_idname = "mesh.add_wall"
bl_label = "Dumb Wall"
bl_options = {'REGISTER', 'UNDO'}
bl_options = {"REGISTER", "UNDO"}
height: FloatProperty(name='Height', default=3)
length: FloatProperty(name='Length', default=1)
width: FloatProperty(name='Width', default=.2)
use_plane: BoolProperty(name='Use Plane', default=False)
height: FloatProperty(name="Height", default=3)
length: FloatProperty(name="Length", default=1)
width: FloatProperty(name="Width", default=0.2)
use_plane: BoolProperty(name="Use Plane", default=False)
def execute(self, context):
add_object(self, context)
return {'FINISHED'}
return {"FINISHED"}
def add_object_button(self, context):
self.layout.operator(
BIM_OT_add_object.bl_idname,
icon='PLUGIN')
self.layout.operator(BIM_OT_add_object.bl_idname, icon="PLUGIN")
@@ -6,39 +6,50 @@ from bpy.props import FloatVectorProperty, FloatProperty, IntProperty
from bpy_extras.object_utils import AddObjectHelper, object_data_add
from mathutils import Vector
def add_object(self, context):
guid = ifcopenshell.guid.new()
leaf_width = self.overall_width-0.045-0.045
leaf_width = self.overall_width - 0.045 - 0.045
verts = [
# Left lining
Vector((0, 0, 0)),
Vector((0, self.depth, 0)),
Vector((.04, self.depth, 0)),
Vector((.04, 0, 0)),
Vector((0.04, self.depth, 0)),
Vector((0.04, 0, 0)),
# Right lining
Vector((self.overall_width, 0, 0)),
Vector((self.overall_width, self.depth, 0)),
Vector((self.overall_width-.04, self.depth, 0)),
Vector((self.overall_width-.04, 0, 0)),
Vector((self.overall_width - 0.04, self.depth, 0)),
Vector((self.overall_width - 0.04, 0, 0)),
# Bottom lining
Vector((0, 0, 0)),
Vector((self.overall_width, 0, 0)),
Vector((0, self.depth, 0)),
Vector((self.overall_width, self.depth, 0)),
# Window panel
Vector((.04, (self.depth/2)+.005, 0)),
Vector((.04, (self.depth/2)-.005, 0)),
Vector((self.overall_width-.04, (self.depth/2)-.005, 0)),
Vector((self.overall_width-.04, (self.depth/2)+.005, 0)),
Vector((0.04, (self.depth / 2) + 0.005, 0)),
Vector((0.04, (self.depth / 2) - 0.005, 0)),
Vector((self.overall_width - 0.04, (self.depth / 2) - 0.005, 0)),
Vector((self.overall_width - 0.04, (self.depth / 2) + 0.005, 0)),
]
edges = [
[0, 1], [1, 2], [2, 3], [3, 0], # Left lining
[4, 5], [5, 6], [6, 7], [7, 4], # Right lining
[8, 9], [10, 11], # Bottom lining
[12, 13], [13, 14], [14, 15], [15, 12], # Window panel
[0, 1],
[1, 2],
[2, 3],
[3, 0], # Left lining
[4, 5],
[5, 6],
[6, 7],
[7, 4], # Right lining
[8, 9],
[10, 11], # Bottom lining
[12, 13],
[13, 14],
[14, 15],
[15, 12], # Window panel
]
faces = []
mesh = bpy.data.meshes.new(name='Plan/Annotation/PLAN_VIEW/' + guid)
mesh = bpy.data.meshes.new(name="Plan/Annotation/PLAN_VIEW/" + guid)
mesh.use_fake_user = True
mesh.from_pydata(verts, edges, faces)
@@ -46,15 +57,15 @@ def add_object(self, context):
verts = [
Vector((0, 0, 0)),
Vector((0, -self.depth, 0)),
Vector((.04, -self.depth, 0)),
Vector((.04, 0, 0)),
Vector((0.04, -self.depth, 0)),
Vector((0.04, 0, 0)),
]
edges = []
faces = [[0, 1, 2, 3]]
mesh = bpy.data.meshes.new(name="Dumb Window Profile")
mesh.from_pydata(verts, edges, faces)
obj = object_data_add(context, mesh, operator=self)
bpy.ops.object.convert(target='CURVE')
bpy.ops.object.convert(target="CURVE")
# Window lining sweep
verts = [
@@ -68,97 +79,95 @@ def add_object(self, context):
mesh = bpy.data.meshes.new(name="Dumb Window")
mesh.from_pydata(verts, edges, faces)
obj2 = object_data_add(context, mesh, operator=self)
bpy.ops.object.convert(target='CURVE')
bpy.ops.object.convert(target="CURVE")
obj2.data.splines[0].use_cyclic_u = True
obj2.data.dimensions = '2D'
obj2.data.dimensions = "2D"
obj2.data.bevel_object = obj
obj2.rotation_euler[0] = math.pi/2
bpy.ops.object.convert(target='MESH')
obj2.rotation_euler[0] = math.pi / 2
bpy.ops.object.convert(target="MESH")
bpy.ops.object.transform_apply(location=False)
bpy.data.objects.remove(obj, do_unlink=True)
# Window panel
verts = [
Vector((.04, (self.depth/2)+.005, .04)),
Vector((.04, (self.depth/2)-.005, .04)),
Vector((self.overall_width-.04, (self.depth/2)-.005, .04)),
Vector((self.overall_width-.04, (self.depth/2)+.005, .04)),
Vector((0.04, (self.depth / 2) + 0.005, 0.04)),
Vector((0.04, (self.depth / 2) - 0.005, 0.04)),
Vector((self.overall_width - 0.04, (self.depth / 2) - 0.005, 0.04)),
Vector((self.overall_width - 0.04, (self.depth / 2) + 0.005, 0.04)),
]
edges = []
faces = [[0, 1, 2, 3]]
mesh = bpy.data.meshes.new(name="Dumb Window Panel")
mesh.from_pydata(verts, edges, faces)
obj3 = object_data_add(context, mesh, operator=self)
modifier = obj3.modifiers.new('Panel Height', 'SOLIDIFY')
modifier = obj3.modifiers.new("Panel Height", "SOLIDIFY")
modifier.offset = 1
modifier.thickness = self.overall_height-.08
bpy.ops.object.convert(target='MESH')
modifier.thickness = self.overall_height - 0.08
bpy.ops.object.convert(target="MESH")
ctx = bpy.context.copy()
ctx['active_object'] = obj2
ctx['selected_editable_objects'] = [obj2, obj3]
ctx["active_object"] = obj2
ctx["selected_editable_objects"] = [obj2, obj3]
bpy.ops.object.join(ctx)
# Window Opening
verts = [
Vector((0, -.1, 0)),
Vector((0, self.depth+.1, 0)),
Vector((self.overall_width, self.depth+.1, 0)),
Vector((self.overall_width, -.1, 0)),
Vector((0, -0.1, 0)),
Vector((0, self.depth + 0.1, 0)),
Vector((self.overall_width, self.depth + 0.1, 0)),
Vector((self.overall_width, -0.1, 0)),
]
edges = []
faces = [[0, 1, 2, 3]]
mesh = bpy.data.meshes.new(name="Dumb Window Opening")
mesh.from_pydata(verts, edges, faces)
obj4 = object_data_add(context, mesh, operator=self)
modifier = obj4.modifiers.new('Panel Height', 'SOLIDIFY')
modifier = obj4.modifiers.new("Panel Height", "SOLIDIFY")
modifier.offset = -1
modifier.thickness = self.overall_height
bpy.ops.object.convert(target='MESH')
obj4.display_type = 'WIRE'
bpy.ops.object.convert(target="MESH")
obj4.display_type = "WIRE"
obj4.parent = obj2
obj4.matrix_parent_inverse = obj2.matrix_world.inverted()
obj4.hide_render = True
obj4.name = 'IfcOpeningElement/Dumb Window Opening'
obj4.name = "IfcOpeningElement/Dumb Window Opening"
attribute = obj4.BIMObjectProperties.attributes.add()
attribute.name = 'PredefinedType'
attribute.string_value = 'OPENING'
attribute.name = "PredefinedType"
attribute.string_value = "OPENING"
obj2.name = 'IfcWindow/Dumb Window'
obj2.name = "IfcWindow/Dumb Window"
attribute = obj2.BIMObjectProperties.attributes.add()
attribute.name = 'PredefinedType'
attribute.string_value = 'WINDOW'
obj2.data.name = 'Model/Body/MODEL_VIEW/' + guid
attribute.name = "PredefinedType"
attribute.string_value = "WINDOW"
obj2.data.name = "Model/Body/MODEL_VIEW/" + guid
obj2.data.use_fake_user = True
rep = obj2.BIMObjectProperties.representation_contexts.add()
rep.context = 'Model'
rep.name = 'Body'
rep.target_view = 'MODEL_VIEW'
rep.context = "Model"
rep.name = "Body"
rep.target_view = "MODEL_VIEW"
rep = obj2.BIMObjectProperties.representation_contexts.add()
rep.context = 'Plan'
rep.name = 'Annotation'
rep.target_view = 'PLAN_VIEW'
rep.context = "Plan"
rep.name = "Annotation"
rep.target_view = "PLAN_VIEW"
class BIM_OT_add_object(Operator, AddObjectHelper):
bl_idname = "mesh.add_window"
bl_label = "Dumb Window"
bl_options = {'REGISTER', 'UNDO'}
bl_options = {"REGISTER", "UNDO"}
overall_width: FloatProperty(name='Overall Width', default=.7)
overall_height: FloatProperty(name='Overall Height', default=1)
depth: FloatProperty(name='Depth', default=.1)
overall_width: FloatProperty(name="Overall Width", default=0.7)
overall_height: FloatProperty(name="Overall Height", default=1)
depth: FloatProperty(name="Depth", default=0.1)
def execute(self, context):
add_object(self, context)
return {'FINISHED'}
return {"FINISHED"}
def add_object_button(self, context):
self.layout.operator(
BIM_OT_add_object.bl_idname,
icon='PLUGIN')
self.layout.operator(BIM_OT_add_object.bl_idname, icon="PLUGIN")
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+26 -23
View File
@@ -1,33 +1,29 @@
from mathutils import Vector
class QtoCalculator():
class QtoCalculator:
def guess_quantity(self, prop_name, alternative_prop_names, obj):
prop_name = prop_name.lower()
alternative_prop_names = [p.lower() for p in alternative_prop_names]
if 'length' in prop_name \
and 'width' not in alternative_prop_names \
and 'height' not in alternative_prop_names:
if "length" in prop_name and "width" not in alternative_prop_names and "height" not in alternative_prop_names:
return self.get_linear_length(obj)
elif 'length' in prop_name:
elif "length" in prop_name:
return self.get_length(obj)
elif 'width' in prop_name \
and 'length' not in alternative_prop_names:
elif "width" in prop_name and "length" not in alternative_prop_names:
return self.get_length(obj)
elif 'width' in prop_name:
elif "width" in prop_name:
return self.get_width(obj)
elif 'height' in prop_name or 'depth' in prop_name:
elif "height" in prop_name or "depth" in prop_name:
return self.get_height(obj)
elif 'perimeter' in prop_name:
elif "perimeter" in prop_name:
return self.get_perimeter(obj)
elif 'area' in prop_name \
and ('footprint' in prop_name or 'section' in prop_name or 'floor' in prop_name):
elif "area" in prop_name and ("footprint" in prop_name or "section" in prop_name or "floor" in prop_name):
return self.get_footprint_area(obj)
elif 'area' in prop_name \
and 'side' in prop_name:
elif "area" in prop_name and "side" in prop_name:
return self.get_side_area(obj)
elif 'area' in prop_name:
elif "area" in prop_name:
return self.get_area(obj)
elif 'volume' in prop_name:
elif "volume" in prop_name:
return self.get_volume(obj)
def get_units(self, o, vg_index):
@@ -45,10 +41,14 @@ class QtoCalculator():
y = (Vector(o.bound_box[3]) - Vector(o.bound_box[0])).length
return max(x, y)
length = 0
edges = [e for e in o.data.edges if (
vg_index in [g.group for g in o.data.vertices[e.vertices[0]].groups] and
vg_index in [g.group for g in o.data.vertices[e.vertices[1]].groups]
)]
edges = [
e
for e in o.data.edges
if (
vg_index in [g.group for g in o.data.vertices[e.vertices[0]].groups]
and vg_index in [g.group for g in o.data.vertices[e.vertices[1]].groups]
)
]
for e in edges:
length += self.get_edge_distance(o, e)
return length
@@ -139,10 +139,13 @@ class QtoCalculator():
for tf in me.loop_triangles:
tfv = tf.vertices
if len(tf.vertices) == 3:
tf_tris = (me.vertices[tfv[0]], me.vertices[tfv[1]], me.vertices[tfv[2]]),
tf_tris = ((me.vertices[tfv[0]], me.vertices[tfv[1]], me.vertices[tfv[2]]),)
else:
tf_tris = (me.vertices[tfv[0]], me.vertices[tfv[1]], me.vertices[tfv[2]]), \
(me.vertices[tfv[2]], me.vertices[tfv[3]], me.vertices[tfv[0]])
tf_tris = (me.vertices[tfv[0]], me.vertices[tfv[1]], me.vertices[tfv[2]]), (
me.vertices[tfv[2]],
me.vertices[tfv[3]],
me.vertices[tfv[0]],
)
for tf_iter in tf_tris:
v1 = ob_mat @ tf_iter[0].co
@@ -6,19 +6,20 @@ from odf.table import Table, TableRow, TableColumn, TableCell
from odf.text import P
from odf.style import Style
class Scheduler():
class Scheduler:
def schedule(self, infile, outfile):
self.svg = svgwrite.Drawing(
outfile,
debug=False,
id='root',
id="root",
)
self.padding = 1
self.margin = 1
doc = load(infile)
styles = {}
for style in doc.getElementsByType(Style):
name = style.getAttribute('name')
name = style.getAttribute("name")
if not style.firstChild:
continue
styles[name] = {key[1]: value for key, value in style.firstChild.attributes.items()}
@@ -26,14 +27,14 @@ class Scheduler():
table = doc.getElementsByType(Table)[0]
column_widths = []
for col in table.getElementsByType(TableColumn):
style_name = col.getAttribute('stylename')
repeat = col.getAttribute('numbercolumnsrepeated')
style_name = col.getAttribute("stylename")
repeat = col.getAttribute("numbercolumnsrepeated")
repeat = int(repeat) if repeat else 1
for i in range(0, repeat):
if not style_name or 'column-width' not in styles[style_name]:
if not style_name or "column-width" not in styles[style_name]:
column_widths.append(50)
else:
column_widths.append(self.convert_to_mm(styles[style_name]['column-width']))
column_widths.append(self.convert_to_mm(styles[style_name]["column-width"]))
y = self.margin
for tri, tr in enumerate(table.getElementsByType(TableRow)):
@@ -41,54 +42,66 @@ class Scheduler():
height = 6
tdi = 0
for td in tr.getElementsByType(TableCell):
repeat = td.getAttribute('numbercolumnsrepeated')
repeat = td.getAttribute("numbercolumnsrepeated")
repeat = int(repeat) if repeat else 1
for i in range(0, repeat):
width = column_widths[tdi]
self.svg.add(self.svg.rect(insert=(x, y), size=(width, height), style='fill: #ffffff; stroke-width:.125; stroke: #000000;'))
self.svg.add(
self.svg.rect(
insert=(x, y),
size=(width, height),
style="fill: #ffffff; stroke-width:.125; stroke: #000000;",
)
)
value = td.getElementsByType(P)
if value:
self.add_text(value[0], x+self.padding, y+self.padding)
self.add_text(value[0], x + self.padding, y + self.padding)
x += width
tdi += 1
y += height
total_width = sum(column_widths) + (self.margin * 2)
self.svg['width'] = '{}mm'.format(total_width)
self.svg['height'] = '{}mm'.format(y)
self.svg['viewBox'] = '0 0 {} {}'.format(total_width, y)
self.svg["width"] = "{}mm".format(total_width)
self.svg["height"] = "{}mm".format(y)
self.svg["viewBox"] = "0 0 {} {}".format(total_width, y)
self.svg.save(pretty=True)
def add_text(self, text, x, y):
self.svg.add(self.svg.text(str(text).upper(), insert=tuple((x, y)), **{
'font-size': 4.13,
'font-family': 'OpenGost Type B TT',
'text-anchor': 'start',
'alignment-baseline': 'baseline',
'dominant-baseline': 'hanging'
}))
self.svg.add(
self.svg.text(
str(text).upper(),
insert=tuple((x, y)),
**{
"font-size": 4.13,
"font-family": "OpenGost Type B TT",
"text-anchor": "start",
"alignment-baseline": "baseline",
"dominant-baseline": "hanging",
}
)
)
def convert_to_mm(self, value):
# XSL is what defines the units of measurements in ODF
# https://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#datatype-positiveLength
# https://www.w3.org/TR/2001/REC-xsl-20011015/slice5.html#section-N8185-Definitions-of-Units-of-Measure
if 'cm' in value:
if "cm" in value:
return float(value[0:-2]) * 10
elif 'mm' in value:
elif "mm" in value:
return float(value[0:-2])
elif 'in' in value:
elif "in" in value:
return float(value[0:-2]) * 25.4
elif 'pt' in value:
return float(value[0:-2]) * (1/72) * 25.4
elif 'pc' in value:
return float(value[0:-2]) * 12 * (1/72) * 25.4
elif 'px' in value:
elif "pt" in value:
return float(value[0:-2]) * (1 / 72) * 25.4
elif "pc" in value:
return float(value[0:-2]) * 12 * (1 / 72) * 25.4
elif "px" in value:
# implementors may instead simply pick a fixed conversion factor,
# treating 'px' as an absolute unit of measurement (such as 1/92" or
# 1/72"). <-- We're picking 1/96 to match SVG. Let me know if it
# breaks anything.
return float(value[0:-2]) * (1/96) * 2.54 * 10
elif 'em' in value:
return float(value[0:-2]) * (1 / 96) * 2.54 * 10
elif "em" in value:
# This is a funny one. Since the scheduler at the moment enforces a
# font size of 2.5mm (vertically, FWIW), I'm writing this. Hopefully
# this code doesn't hurt anybody.
return float(value[0:-2]) * (1/96) * 2.54 * 10
return float(value[0:-2]) * (1 / 96) * 2.54 * 10
+36 -41
View File
@@ -6,31 +6,32 @@ from pathlib import Path
cwd = os.path.dirname(os.path.realpath(__file__))
class IfcSchema():
class IfcSchema:
def __init__(self):
self.schema_dir = os.path.join(cwd, 'schema') # TODO: make configurable
self.data_dir = os.path.join(cwd, 'data') # TODO: make configurable
self.schema_dir = os.path.join(cwd, "schema") # TODO: make configurable
self.data_dir = os.path.join(cwd, "data") # TODO: make configurable
# TODO: Make it less troublesome
self.products = [
'IfcContext',
'IfcElement',
'IfcSpatialElement',
'IfcGroup',
'IfcStructural',
'IfcPositioningElement',
'IfcMaterialDefinition',
'IfcParameterizedProfileDef',
'IfcBoundaryCondition',
'IfcElementType',
'IfcAnnotation'
"IfcContext",
"IfcElement",
"IfcSpatialElement",
"IfcGroup",
"IfcStructural",
"IfcPositioningElement",
"IfcMaterialDefinition",
"IfcParameterizedProfileDef",
"IfcBoundaryCondition",
"IfcElementType",
"IfcAnnotation",
]
self.elements = {}
self.property_files = []
property_paths = Path(os.path.join(self.data_dir, 'pset')).glob('*.ifc')
property_paths = Path(os.path.join(self.data_dir, "pset")).glob("*.ifc")
for path in property_paths:
self.property_files.append(ifcopenshell.open(path))
self.property_files.append(ifcopenshell.open(os.path.join(self.schema_dir, 'Pset_IFC4_ADD2.ifc')))
self.property_files.append(ifcopenshell.open(os.path.join(self.schema_dir, "Pset_IFC4_ADD2.ifc")))
self.classification_files = {}
self.psets = {}
@@ -42,55 +43,49 @@ class IfcSchema():
def load(self):
for product in self.products:
with open(os.path.join(self.schema_dir, f'{product}_IFC4.json')) as f:
with open(os.path.join(self.schema_dir, f"{product}_IFC4.json")) as f:
setattr(self, product, json.load(f))
self.elements.update(getattr(self, product))
with open(os.path.join(self.schema_dir, 'ifc_types_IFC4.json')) as f:
with open(os.path.join(self.schema_dir, "ifc_types_IFC4.json")) as f:
self.type_map = json.load(f)
for property_file in self.property_files:
for prop in property_file.by_type('IfcPropertySetTemplate'):
if prop.Name[0:4] == 'Qto_':
self.qtos[prop.Name] = {
'HasPropertyTemplates': {p.Name: p for p in prop.HasPropertyTemplates}}
entity = prop.ApplicableEntity if prop.ApplicableEntity else 'IfcRoot'
for prop in property_file.by_type("IfcPropertySetTemplate"):
if prop.Name[0:4] == "Qto_":
self.qtos[prop.Name] = {"HasPropertyTemplates": {p.Name: p for p in prop.HasPropertyTemplates}}
entity = prop.ApplicableEntity if prop.ApplicableEntity else "IfcRoot"
self.applicable_qtos.setdefault(entity, []).append(prop.Name)
else:
self.psets[prop.Name] = {
'HasPropertyTemplates': {p.Name: p for p in prop.HasPropertyTemplates}}
entity = prop.ApplicableEntity if prop.ApplicableEntity else 'IfcRoot'
self.psets[prop.Name] = {"HasPropertyTemplates": {p.Name: p for p in prop.HasPropertyTemplates}}
entity = prop.ApplicableEntity if prop.ApplicableEntity else "IfcRoot"
self.applicable_psets.setdefault(entity, []).append(prop.Name)
def load_classification(self, name, classification_index=None):
if name not in self.classifications:
if classification_index is not None:
self.classification_files[name] = ifcopenshell.file.from_string(
bpy.context.scene.BIMProperties.classifications[classification_index].data)
bpy.context.scene.BIMProperties.classifications[classification_index].data
)
else:
classification_path = os.path.join(self.schema_dir, 'classifications', '{}.ifc'.format(name))
classification_path = os.path.join(self.schema_dir, "classifications", "{}.ifc".format(name))
self.classification_files[name] = ifcopenshell.open(classification_path)
self.classifications[name] = self.classification_files[name].by_type('IfcClassification')[0]
self.classifications[name] = self.classification_files[name].by_type("IfcClassification")[0]
classification = self.classifications[name]
bpy.context.scene.BIMProperties.active_classification_name = self.classifications[name].Name
return {
'name': '',
'description': '',
'children': self.get_classification_references(classification)
}
return {"name": "", "description": "", "children": self.get_classification_references(classification)}
def get_classification_references(self, classification):
references = {}
if not hasattr(classification, 'HasReferences') \
or not classification.HasReferences:
if not hasattr(classification, "HasReferences") or not classification.HasReferences:
return references
for reference in classification.HasReferences:
references[reference.Identification] = {
'location': reference.Location,
'identification': reference.Identification,
'name': reference.Name,
'description': reference.Description,
'children': self.get_classification_references(reference)
"location": reference.Location,
"identification": reference.Identification,
"name": reference.Name,
"description": reference.Description,
"children": self.get_classification_references(reference),
}
return references
+114 -115
View File
@@ -6,48 +6,50 @@ import os
from shutil import copy
from xml.dom import minidom
class SheetBuilder:
def __init__(self):
self.data_dir = None
self.scale = 'NTS'
self.scale = "NTS"
def create(self, name, titleblock_name):
sheet_path = '{}sheets/{}.svg'.format(self.data_dir, name)
root = ET.Element('svg')
root.attrib['xmlns'] = 'http://www.w3.org/2000/svg'
root.attrib['xmlns:xlink'] = 'http://www.w3.org/1999/xlink'
root.attrib['id'] = 'root'
root.attrib['version'] = '1.1'
sheet_path = "{}sheets/{}.svg".format(self.data_dir, name)
root = ET.Element("svg")
root.attrib["xmlns"] = "http://www.w3.org/2000/svg"
root.attrib["xmlns:xlink"] = "http://www.w3.org/1999/xlink"
root.attrib["id"] = "root"
root.attrib["version"] = "1.1"
view_root = ET.parse(
os.path.join(self.data_dir, 'templates', 'titleblocks', titleblock_name + '.svg')).getroot()
view_width = self.convert_to_mm(view_root.attrib.get('width'))
view_height = self.convert_to_mm(view_root.attrib.get('height'))
view = ET.SubElement(root, 'g')
view.attrib['data-type'] = 'titleblock'
titleblock = ET.SubElement(view, 'image')
titleblock.attrib['xlink:href'] = '../templates/titleblocks/' + titleblock_name + '.svg'
titleblock.attrib['x'] = '0'
titleblock.attrib['y'] = '0'
titleblock.attrib['width'] = str(view_width)
titleblock.attrib['height'] = str(view_height)
os.path.join(self.data_dir, "templates", "titleblocks", titleblock_name + ".svg")
).getroot()
view_width = self.convert_to_mm(view_root.attrib.get("width"))
view_height = self.convert_to_mm(view_root.attrib.get("height"))
view = ET.SubElement(root, "g")
view.attrib["data-type"] = "titleblock"
titleblock = ET.SubElement(view, "image")
titleblock.attrib["xlink:href"] = "../templates/titleblocks/" + titleblock_name + ".svg"
titleblock.attrib["x"] = "0"
titleblock.attrib["y"] = "0"
titleblock.attrib["width"] = str(view_width)
titleblock.attrib["height"] = str(view_height)
root.attrib['width'] = '{}mm'.format(view_width)
root.attrib['height'] = '{}mm'.format(view_height)
root.attrib['viewBox'] = '0 0 {} {}'.format(view_width, view_height)
root.attrib["width"] = "{}mm".format(view_width)
root.attrib["height"] = "{}mm".format(view_height)
root.attrib["viewBox"] = "0 0 {} {}".format(view_width, view_height)
with open(sheet_path, 'w') as f:
f.write(minidom.parseString(ET.tostring(root)).toprettyxml(indent=' '))
with open(sheet_path, "w") as f:
f.write(minidom.parseString(ET.tostring(root)).toprettyxml(indent=" "))
def add_drawing(self, view_name, sheet_name):
sheet_path = os.path.join(self.data_dir, 'sheets', sheet_name + '.svg')
view_path = os.path.join(self.data_dir, 'diagrams', view_name + '.svg')
sheet_path = os.path.join(self.data_dir, "sheets", sheet_name + ".svg")
view_path = os.path.join(self.data_dir, "diagrams", view_name + ".svg")
if not os.path.isfile(view_path):
raise FileNotFoundError
ET.register_namespace('', 'http://www.w3.org/2000/svg')
ET.register_namespace('xlink', 'http://www.w3.org/1999/xlink')
ET.register_namespace("", "http://www.w3.org/2000/svg")
ET.register_namespace("xlink", "http://www.w3.org/1999/xlink")
sheet_tree = ET.parse(sheet_path)
sheet_root = sheet_tree.getroot()
@@ -58,115 +60,112 @@ class SheetBuilder:
# The view is placed into a group with a background image element.
# Although the foreground SVG already has a background, it is duplicated
# here to accommodate browsers which do not nest images.
view = ET.SubElement(sheet_root, 'g')
view.attrib['data-type'] = 'drawing'
view_width = self.convert_to_mm(view_root.attrib.get('width'))
view_height = self.convert_to_mm(view_root.attrib.get('height'))
view = ET.SubElement(sheet_root, "g")
view.attrib["data-type"] = "drawing"
view_width = self.convert_to_mm(view_root.attrib.get("width"))
view_height = self.convert_to_mm(view_root.attrib.get("height"))
background = ET.SubElement(view, 'image')
background.attrib['xlink:href'] = '../diagrams/{}.png'.format(view_name)
background.attrib['x'] = '30'
background.attrib['y'] = '30'
background.attrib['width'] = str(view_width)
background.attrib['height'] = str(view_height)
background = ET.SubElement(view, "image")
background.attrib["xlink:href"] = "../diagrams/{}.png".format(view_name)
background.attrib["x"] = "30"
background.attrib["y"] = "30"
background.attrib["width"] = str(view_width)
background.attrib["height"] = str(view_height)
foreground = ET.SubElement(view, 'image')
foreground.attrib['xlink:href'] = '../diagrams/{}.svg'.format(view_name)
foreground.attrib['x'] = '30'
foreground.attrib['y'] = '30'
foreground.attrib['width'] = str(view_width)
foreground.attrib['height'] = str(view_height)
foreground = ET.SubElement(view, "image")
foreground.attrib["xlink:href"] = "../diagrams/{}.svg".format(view_name)
foreground.attrib["x"] = "30"
foreground.attrib["y"] = "30"
foreground.attrib["width"] = str(view_width)
foreground.attrib["height"] = str(view_height)
self.add_view_title(30, view_height+35, view)
self.add_view_title(30, view_height + 35, view)
sheet_tree.write(sheet_path)
def add_schedule(self, schedule_name, sheet_name):
sheet_path = os.path.join(self.data_dir, 'sheets', sheet_name + '.svg')
view_path = os.path.join(self.data_dir, 'schedules', schedule_name + '.svg')
sheet_path = os.path.join(self.data_dir, "sheets", sheet_name + ".svg")
view_path = os.path.join(self.data_dir, "schedules", schedule_name + ".svg")
ET.register_namespace('', 'http://www.w3.org/2000/svg')
ET.register_namespace('xlink', 'http://www.w3.org/1999/xlink')
ET.register_namespace("", "http://www.w3.org/2000/svg")
ET.register_namespace("xlink", "http://www.w3.org/1999/xlink")
sheet_tree = ET.parse(sheet_path)
sheet_root = sheet_tree.getroot()
view_tree = ET.parse(view_path)
view_root = view_tree.getroot()
view_width = self.convert_to_mm(view_root.attrib.get('width'))
view_height = self.convert_to_mm(view_root.attrib.get('height'))
view_width = self.convert_to_mm(view_root.attrib.get("width"))
view_height = self.convert_to_mm(view_root.attrib.get("height"))
group = ET.SubElement(sheet_root, 'g')
group.attrib['data-type'] = 'schedule'
group = ET.SubElement(sheet_root, "g")
group.attrib["data-type"] = "schedule"
foreground = ET.SubElement(group, 'image')
foreground.attrib['xlink:href'] = '../schedules/{}.svg'.format(schedule_name)
foreground.attrib['x'] = '30'
foreground.attrib['y'] = '30'
foreground.attrib['width'] = str(view_width)
foreground.attrib['height'] = str(view_height)
foreground = ET.SubElement(group, "image")
foreground.attrib["xlink:href"] = "../schedules/{}.svg".format(schedule_name)
foreground.attrib["x"] = "30"
foreground.attrib["y"] = "30"
foreground.attrib["width"] = str(view_width)
foreground.attrib["height"] = str(view_height)
self.add_view_title(30, view_height+35, group)
self.add_view_title(30, view_height + 35, group)
sheet_tree.write(sheet_path)
def add_view_title(self, x, y, parent):
title_tree = ET.parse(os.path.join(self.data_dir, 'templates', 'view-title.svg'))
title_tree = ET.parse(os.path.join(self.data_dir, "templates", "view-title.svg"))
title_root = title_tree.getroot()
title = ET.SubElement(parent, 'image')
title.attrib['xlink:href'] = '../templates/view-title.svg'
title.attrib['x'] = str(x)
title.attrib['y'] = str(y)
title.attrib['width'] = str(self.convert_to_mm(title_root.attrib.get('width')))
title.attrib['height'] = str(self.convert_to_mm(title_root.attrib.get('height')))
title = ET.SubElement(parent, "image")
title.attrib["xlink:href"] = "../templates/view-title.svg"
title.attrib["x"] = str(x)
title.attrib["y"] = str(y)
title.attrib["width"] = str(self.convert_to_mm(title_root.attrib.get("width")))
title.attrib["height"] = str(self.convert_to_mm(title_root.attrib.get("height")))
def build(self, sheet_name):
os.makedirs('{}build/{}/'.format(self.data_dir, sheet_name), exist_ok=True)
os.makedirs("{}build/{}/".format(self.data_dir, sheet_name), exist_ok=True)
sheet_path = '{}sheets/{}.svg'.format(self.data_dir, sheet_name)
sheet_path = "{}sheets/{}.svg".format(self.data_dir, sheet_name)
ET.register_namespace('', 'http://www.w3.org/2000/svg')
ET.register_namespace('xlink', 'http://www.w3.org/1999/xlink')
ET.register_namespace("", "http://www.w3.org/2000/svg")
ET.register_namespace("xlink", "http://www.w3.org/1999/xlink")
tree = ET.parse(sheet_path)
root = tree.getroot()
titleblock = root.findall('{http://www.w3.org/2000/svg}g[@data-type="titleblock"]')[0]
image = titleblock.findall('{http://www.w3.org/2000/svg}image')[0]
titleblock.append(self.parse_embedded_svg(image, {
'number': sheet_name,
'revision': 'A'
}))
image = titleblock.findall("{http://www.w3.org/2000/svg}image")[0]
titleblock.append(self.parse_embedded_svg(image, {"number": sheet_name, "revision": "A"}))
titleblock.remove(image)
self.group_number = 1
self.build_drawings(root.findall('{http://www.w3.org/2000/svg}g[@data-type="drawing"]'), sheet_name)
self.build_schedules(root.findall('{http://www.w3.org/2000/svg}g[@data-type="schedule"]'))
with open('{}build/{}/{}.svg'.format(self.data_dir, sheet_name, sheet_name), 'wb') as output:
with open("{}build/{}/{}.svg".format(self.data_dir, sheet_name, sheet_name), "wb") as output:
tree.write(output)
def build_drawings(self, drawings, sheet_name):
for view in drawings:
images = view.findall('{http://www.w3.org/2000/svg}image')
images = view.findall("{http://www.w3.org/2000/svg}image")
background = images[0]
foreground = images[1]
view_title = images[2]
self.scale = 'NTS'
self.scale = "NTS"
# Add foreground
view.append(self.parse_embedded_svg(foreground, {}))
# Add background
background_path = '{}sheets/{}'.format(self.data_dir, self.get_href(background))
copy(background_path, '{}build/{}/'.format(self.data_dir, sheet_name))
background_path = "{}sheets/{}".format(self.data_dir, self.get_href(background))
copy(background_path, "{}build/{}/".format(self.data_dir, sheet_name))
# Add view title
foreground_path = self.get_href(foreground)
view.append(self.parse_embedded_svg(view_title, {
'no' : self.group_number,
'name': ntpath.basename(foreground_path)[0:-4],
'scale': self.scale
}))
view.append(
self.parse_embedded_svg(
view_title,
{"no": self.group_number, "name": ntpath.basename(foreground_path)[0:-4], "scale": self.scale},
)
)
for image in images:
view.remove(image)
@@ -175,19 +174,19 @@ class SheetBuilder:
def build_schedules(self, schedules):
for group in schedules:
images = group.findall('{http://www.w3.org/2000/svg}image')
images = group.findall("{http://www.w3.org/2000/svg}image")
schedule = images[0]
group_title = images[1]
self.scale = 'NTS'
self.scale = "NTS"
group.append(self.parse_embedded_svg(schedule, {}))
path = self.get_href(schedule)
group.append(self.parse_embedded_svg(group_title, {
'no' : self.group_number,
'name': ntpath.basename(path)[0:-4],
'scale': self.scale
}))
group.append(
self.parse_embedded_svg(
group_title, {"no": self.group_number, "name": ntpath.basename(path)[0:-4], "scale": self.scale}
)
)
for image in images:
group.remove(image)
@@ -195,24 +194,24 @@ class SheetBuilder:
self.group_number += 1
def get_href(self, element):
return urllib.parse.unquote(element.attrib.get('{http://www.w3.org/1999/xlink}href'))
return urllib.parse.unquote(element.attrib.get("{http://www.w3.org/1999/xlink}href"))
def parse_embedded_svg(self, image, data):
group = ET.Element('g')
group.attrib['transform'] = 'translate({},{})'.format(
self.convert_to_mm(image.attrib.get('x')),
self.convert_to_mm(image.attrib.get('y')))
group = ET.Element("g")
group.attrib["transform"] = "translate({},{})".format(
self.convert_to_mm(image.attrib.get("x")), self.convert_to_mm(image.attrib.get("y"))
)
svg_path = self.get_href(image)
with open('{}sheets/{}'.format(self.data_dir, svg_path), 'r') as template:
with open("{}sheets/{}".format(self.data_dir, svg_path), "r") as template:
embedded = ET.fromstring(pystache.render(template.read(), data))
# viewBox should not be nested
embedded.attrib['viewBox'] = ''
embedded.attrib["viewBox"] = ""
# TODO: This should not be in this function
self.scale = embedded.attrib.get('data-scale')
images = embedded.findall('{http://www.w3.org/2000/svg}image')
self.scale = embedded.attrib.get("data-scale")
images = embedded.findall("{http://www.w3.org/2000/svg}image")
for image in images:
new_href = ntpath.basename(image.attrib.get('{http://www.w3.org/1999/xlink}href'))
image.attrib['{http://www.w3.org/1999/xlink}href'] = new_href
new_href = ntpath.basename(image.attrib.get("{http://www.w3.org/1999/xlink}href"))
image.attrib["{http://www.w3.org/1999/xlink}href"] = new_href
group.append(embedded)
return group
@@ -221,20 +220,20 @@ class SheetBuilder:
# https://www.w3.org/TR/SVG/refs.html#ref-css-values-3
# https://www.w3.org/TR/css-values-3/#absolute-lengths
# The relative units are not implemented. Go fish.
if 'cm' in value:
if "cm" in value:
return float(value[0:-2]) * 10
elif 'mm' in value:
elif "mm" in value:
return float(value[0:-2])
elif 'Q' in value:
return float(value[0:-1]) * (1/40) * 10
elif 'in' in value:
elif "Q" in value:
return float(value[0:-1]) * (1 / 40) * 10
elif "in" in value:
return float(value[0:-2]) * 2.54 * 10
elif 'pc' in value:
return float(value[0:-2]) * (1/6) * 2.54 * 10
elif 'pt' in value:
return float(value[0:-2]) * (1/72) * 2.54 * 10
elif 'px' in value:
return float(value[0:-2]) * (1/96) * 2.54 * 10
elif "pc" in value:
return float(value[0:-2]) * (1 / 6) * 2.54 * 10
elif "pt" in value:
return float(value[0:-2]) * (1 / 72) * 2.54 * 10
elif "px" in value:
return float(value[0:-2]) * (1 / 96) * 2.54 * 10
return float(value)
def mm_to_px(self, value):
+296 -230
View File
@@ -16,12 +16,13 @@ try:
except ImportError:
from OCC import BRep, BRepTools, TopExp, TopAbs
class External(svgwrite.container.Group):
def __init__(self, xml, **extra):
self.xml = xml
# Remove namespace
ns = u'{http://www.w3.org/2000/svg}'
ns = u"{http://www.w3.org/2000/svg}"
nsl = len(ns)
for elem in self.xml.getiterator():
if elem.tag.startswith(ns):
@@ -33,26 +34,22 @@ class External(svgwrite.container.Group):
return self.xml
class SvgWriter():
class SvgWriter:
def __init__(self, ifc_cutter):
self.ifc_cutter = ifc_cutter
self.human_scale = 'NTS'
self.scale = 1 / 100 # 1:100
self.human_scale = "NTS"
self.scale = 1 / 100 # 1:100
def write(self):
self.calculate_scale()
self.output = os.path.join(
self.ifc_cutter.data_dir,
'diagrams',
self.ifc_cutter.diagram_name + '.svg'
)
self.output = os.path.join(self.ifc_cutter.data_dir, "diagrams", self.ifc_cutter.diagram_name + ".svg")
self.svg = svgwrite.Drawing(
self.output,
debug=False,
size=('{}mm'.format(self.width), '{}mm'.format(self.height)),
viewBox=('0 0 {} {}'.format(self.width, self.height)),
id='root',
data_scale=self.human_scale
size=("{}mm".format(self.width), "{}mm".format(self.height)),
viewBox=("0 0 {} {}".format(self.width, self.height)),
id="root",
data_scale=self.human_scale,
)
self.add_stylesheet()
@@ -66,57 +63,57 @@ class SvgWriter():
self.svg.save(pretty=True)
def calculate_scale(self):
self.scale *= 1000 # IFC is in meters, SVG is in mm
self.raw_width = self.ifc_cutter.section_box['x']
self.raw_height = self.ifc_cutter.section_box['y']
self.scale *= 1000 # IFC is in meters, SVG is in mm
self.raw_width = self.ifc_cutter.section_box["x"]
self.raw_height = self.ifc_cutter.section_box["y"]
self.width = self.raw_width * self.scale
self.height = self.raw_height * self.scale
def add_stylesheet(self):
with open('{}styles/{}.css'.format(self.ifc_cutter.data_dir, self.ifc_cutter.vector_style), 'r') as stylesheet:
with open("{}styles/{}.css".format(self.ifc_cutter.data_dir, self.ifc_cutter.vector_style), "r") as stylesheet:
self.svg.defs.add(self.svg.style(stylesheet.read()))
def add_markers(self):
tree = ET.parse('{}templates/markers.svg'.format(self.ifc_cutter.data_dir))
tree = ET.parse("{}templates/markers.svg".format(self.ifc_cutter.data_dir))
root = tree.getroot()
for child in root.getchildren():
self.svg.defs.add(External(child))
def add_symbols(self):
tree = ET.parse('{}templates/symbols.svg'.format(self.ifc_cutter.data_dir))
tree = ET.parse("{}templates/symbols.svg".format(self.ifc_cutter.data_dir))
root = tree.getroot()
for child in root.getchildren():
self.svg.defs.add(External(child))
def add_patterns(self):
tree = ET.parse('{}templates/patterns.svg'.format(self.ifc_cutter.data_dir))
tree = ET.parse("{}templates/patterns.svg".format(self.ifc_cutter.data_dir))
root = tree.getroot()
for child in root.getchildren():
self.svg.defs.add(External(child))
def draw_background_image(self):
self.svg.add(self.svg.image(
os.path.join('..', 'diagrams', os.path.basename(self.ifc_cutter.background_image)), **{
'width': self.width,
'height': self.height
}
))
self.svg.add(
self.svg.image(
os.path.join("..", "diagrams", os.path.basename(self.ifc_cutter.background_image)),
**{"width": self.width, "height": self.height}
)
)
def draw_background_elements(self):
for element in self.ifc_cutter.background_elements:
if element['type'] == 'polygon':
self.draw_polygon(element, 'background')
elif element['type'] == 'polyline':
self.draw_polyline(element, 'background')
elif element['type'] == 'line':
self.draw_line(element, 'background')
if element["type"] == "polygon":
self.draw_polygon(element, "background")
elif element["type"] == "polyline":
self.draw_polyline(element, "background")
elif element["type"] == "line":
self.draw_line(element, "background")
def draw_annotations(self):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
for obj in self.ifc_cutter.equal_objs:
self.draw_dimension_annotations(obj, text_override='EQ')
self.draw_dimension_annotations(obj, text_override="EQ")
for obj in self.ifc_cutter.dimension_objs:
self.draw_dimension_annotations(obj)
self.draw_measureit_arch_dimension_annotations()
@@ -127,7 +124,7 @@ class SvgWriter():
for grid_obj in self.ifc_cutter.grid_objs:
matrix_world = grid_obj.matrix_world
for edge in grid_obj.data.edges:
classes = ['annotation', 'grid']
classes = ["annotation", "grid"]
v0_global = matrix_world @ grid_obj.data.vertices[edge.vertices[0]].co.xyz
v1_global = matrix_world @ grid_obj.data.vertices[edge.vertices[1]].co.xyz
v0 = self.project_point_onto_camera(v0_global)
@@ -135,148 +132,198 @@ class SvgWriter():
start = Vector(((x_offset + v0.x), (y_offset - v0.y)))
end = Vector(((x_offset + v1.x), (y_offset - v1.y)))
vector = end - start
line = self.svg.add(self.svg.line(start=tuple(start * self.scale),
end=tuple(end * self.scale), class_=' '.join(classes)))
line['marker-start'] = 'url(#grid-marker)'
line['marker-end'] = 'url(#grid-marker)'
line['stroke-dasharray'] = '12.5, 3, 3, 3'
axis_tag = grid_obj.BIMObjectProperties.attributes.get('AxisTag')
line = self.svg.add(
self.svg.line(
start=tuple(start * self.scale), end=tuple(end * self.scale), class_=" ".join(classes)
)
)
line["marker-start"] = "url(#grid-marker)"
line["marker-end"] = "url(#grid-marker)"
line["stroke-dasharray"] = "12.5, 3, 3, 3"
axis_tag = grid_obj.BIMObjectProperties.attributes.get("AxisTag")
if axis_tag:
axis_tag = axis_tag.string_value
else:
axis_tag = grid_obj.name.split('/')[1]
self.svg.add(self.svg.text(axis_tag, insert=tuple(start * self.scale), **{
'font-size': annotation.Annotator.get_svg_text_size(5.0),
'font-family': 'OpenGost Type B TT',
'text-anchor': 'middle',
'alignment-baseline': 'middle',
'dominant-baseline': 'middle'
}))
self.svg.add(self.svg.text(axis_tag, insert=tuple(end * self.scale), **{
'font-size': annotation.Annotator.get_svg_text_size(5.0),
'font-family': 'OpenGost Type B TT',
'text-anchor': 'middle',
'alignment-baseline': 'middle',
'dominant-baseline': 'middle'
}))
axis_tag = grid_obj.name.split("/")[1]
self.svg.add(
self.svg.text(
axis_tag,
insert=tuple(start * self.scale),
**{
"font-size": annotation.Annotator.get_svg_text_size(5.0),
"font-family": "OpenGost Type B TT",
"text-anchor": "middle",
"alignment-baseline": "middle",
"dominant-baseline": "middle",
}
)
)
self.svg.add(
self.svg.text(
axis_tag,
insert=tuple(end * self.scale),
**{
"font-size": annotation.Annotator.get_svg_text_size(5.0),
"font-family": "OpenGost Type B TT",
"text-anchor": "middle",
"alignment-baseline": "middle",
"dominant-baseline": "middle",
}
)
)
self.draw_ifc_annotation()
for obj in self.ifc_cutter.misc_objs:
self.draw_misc_annotation(obj, ['IfcAnnotation'])
self.draw_misc_annotation(obj, ["IfcAnnotation"])
for obj_data in self.ifc_cutter.hidden_objs:
self.draw_line_annotation(obj_data, ['hidden'])
self.draw_line_annotation(obj_data, ["hidden"])
for obj_data in self.ifc_cutter.solid_objs:
self.draw_line_annotation(obj_data, ['solid'])
self.draw_line_annotation(obj_data, ["solid"])
if self.ifc_cutter.leader_obj:
self.draw_line_annotation(self.ifc_cutter.leader_obj, ['leader'])
self.draw_line_annotation(self.ifc_cutter.leader_obj, ["leader"])
if self.ifc_cutter.plan_level_obj:
matrix_world = self.ifc_cutter.plan_level_obj.matrix_world
for spline in self.ifc_cutter.plan_level_obj.data.splines:
classes = ['annotation', 'plan-level']
classes = ["annotation", "plan-level"]
points = self.get_spline_points(spline)
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
d = ' '.join(['L {} {}'.format(
(x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
for p in projected_points])
d = 'M{}'.format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=' '.join(classes)))
path['marker-end'] = 'url(#plan-level-marker)'
text_position = Vector((
(x_offset + projected_points[0].x) * self.scale,
((y_offset - projected_points[0].y) * self.scale) - 2.5
))
d = " ".join(
[
"L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
for p in projected_points
]
)
d = "M{}".format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
path["marker-end"] = "url(#plan-level-marker)"
text_position = Vector(
(
(x_offset + projected_points[0].x) * self.scale,
((y_offset - projected_points[0].y) * self.scale) - 2.5,
)
)
# TODO: allow metric to be configurable
rl = ((matrix_world @
points[0].co).xyz + self.ifc_cutter.plan_level_obj.location).z
if bpy.context.scene.unit_settings.system == 'IMPERIAL':
rl = ((matrix_world @ points[0].co).xyz + self.ifc_cutter.plan_level_obj.location).z
if bpy.context.scene.unit_settings.system == "IMPERIAL":
rl = helper.format_distance(rl)
else:
rl = '{:.3f}m'.format(rl)
rl = "{:.3f}m".format(rl)
if projected_points[0].x > projected_points[-1].x:
text_anchor = 'end'
text_anchor = "end"
else:
text_anchor = 'start'
self.svg.add(self.svg.text('RL +{}'.format(rl), insert=tuple(text_position), **{
'font-size': annotation.Annotator.get_svg_text_size(2.5),
'font-family': 'OpenGost Type B TT',
'text-anchor': text_anchor,
'alignment-baseline': 'baseline',
'dominant-baseline': 'baseline'
}))
text_anchor = "start"
self.svg.add(
self.svg.text(
"RL +{}".format(rl),
insert=tuple(text_position),
**{
"font-size": annotation.Annotator.get_svg_text_size(2.5),
"font-family": "OpenGost Type B TT",
"text-anchor": text_anchor,
"alignment-baseline": "baseline",
"dominant-baseline": "baseline",
}
)
)
if self.ifc_cutter.section_level_obj:
matrix_world = self.ifc_cutter.section_level_obj.matrix_world
for spline in self.ifc_cutter.section_level_obj.data.splines:
classes = ['annotation', 'section-level']
classes = ["annotation", "section-level"]
points = self.get_spline_points(spline)
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
d = ' '.join(['L {} {}'.format(
(x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
for p in projected_points])
d = 'M{}'.format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=' '.join(classes)))
path['marker-start'] = 'url(#section-level-marker)'
path['stroke-dasharray'] = '12.5, 3, 3, 3'
text_position = Vector((
(x_offset + projected_points[0].x) * self.scale,
((y_offset - projected_points[0].y) * self.scale) - 3.5
))
d = " ".join(
[
"L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
for p in projected_points
]
)
d = "M{}".format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
path["marker-start"] = "url(#section-level-marker)"
path["stroke-dasharray"] = "12.5, 3, 3, 3"
text_position = Vector(
(
(x_offset + projected_points[0].x) * self.scale,
((y_offset - projected_points[0].y) * self.scale) - 3.5,
)
)
# TODO: allow metric to be configurable
rl = (matrix_world @ points[0].co.xyz).z
if bpy.context.scene.unit_settings.system == 'IMPERIAL':
if bpy.context.scene.unit_settings.system == "IMPERIAL":
rl = helper.format_distance(rl)
else:
rl = '{:.3f}m'.format(rl)
self.svg.add(self.svg.text('RL +{}'.format(rl), insert=tuple(text_position), **{
'font-size': annotation.Annotator.get_svg_text_size(2.5),
'font-family': 'OpenGost Type B TT',
'text-anchor': 'start',
'alignment-baseline': 'baseline',
'dominant-baseline': 'baseline'
}))
rl = "{:.3f}m".format(rl)
self.svg.add(
self.svg.text(
"RL +{}".format(rl),
insert=tuple(text_position),
**{
"font-size": annotation.Annotator.get_svg_text_size(2.5),
"font-family": "OpenGost Type B TT",
"text-anchor": "start",
"alignment-baseline": "baseline",
"dominant-baseline": "baseline",
}
)
)
if self.ifc_cutter.stair_obj:
matrix_world = self.ifc_cutter.stair_obj.matrix_world
for spline in self.ifc_cutter.stair_obj.data.splines:
classes = ['annotation', 'stair']
classes = ["annotation", "stair"]
points = self.get_spline_points(spline)
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
d = ' '.join(['L {} {}'.format(
(x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
for p in projected_points])
d = 'M{}'.format(d[1:])
d = " ".join(
[
"L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
for p in projected_points
]
)
d = "M{}".format(d[1:])
start = Vector(((x_offset + projected_points[0].x), (y_offset - projected_points[0].y)))
next_point = Vector(((x_offset + projected_points[1].x), (y_offset - projected_points[1].y)))
text_position = (start * self.scale) - ((next_point - start).normalized() * 5)
path = self.svg.add(self.svg.path(d=d, class_=' '.join(classes)))
self.svg.add(self.svg.text('UP', insert=tuple(text_position), **{
'font-size': annotation.Annotator.get_svg_text_size(2.5),
'font-family': 'OpenGost Type B TT',
'text-anchor': 'middle',
'alignment-baseline': 'middle',
'dominant-baseline': 'middle'
}))
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
self.svg.add(
self.svg.text(
"UP",
insert=tuple(text_position),
**{
"font-size": annotation.Annotator.get_svg_text_size(2.5),
"font-family": "OpenGost Type B TT",
"text-anchor": "middle",
"alignment-baseline": "middle",
"dominant-baseline": "middle",
}
)
)
self.draw_text_annotations()
def draw_ifc_annotation(self):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
for annotation in self.ifc_cutter.annotation_objs:
for edge in annotation['edges']:
v0_global = annotation['vertices'][edge[0]]
v1_global = annotation['vertices'][edge[1]]
for edge in annotation["edges"]:
v0_global = annotation["vertices"][edge[0]]
v1_global = annotation["vertices"][edge[1]]
v0 = self.project_point_onto_camera(v0_global)
v1 = self.project_point_onto_camera(v1_global)
start = Vector(((x_offset + v0.x), (y_offset - v0.y)))
end = Vector(((x_offset + v1.x), (y_offset - v1.y)))
vector = end - start
line = self.svg.add(self.svg.line(start=tuple(start * self.scale),
end=tuple(end * self.scale), class_=' '.join(annotation['classes'])))
line = self.svg.add(
self.svg.line(
start=tuple(start * self.scale),
end=tuple(end * self.scale),
class_=" ".join(annotation["classes"]),
)
)
def draw_misc_annotation(self, obj, classes):
# We have to decide whether this should come from Blender or from IFC.
@@ -292,51 +339,50 @@ class SvgWriter():
for polygon in obj.data.polygons:
points = [obj.data.vertices[v] for v in polygon.vertices]
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
d = ' '.join(['L {} {}'.format(
(x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
for p in projected_points])
d = 'M{}'.format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=' '.join(classes)))
d = " ".join(
[
"L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
for p in projected_points
]
)
d = "M{}".format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
def get_attribute_classes(self, obj):
classes = [obj.name.split('/')[0]]
classes = [obj.name.split("/")[0]]
for slot in obj.material_slots:
if slot.material:
classes.append('material-{}'.format(
re.sub('[^0-9a-zA-Z]+', '', slot.material.name)
))
result = obj.BIMObjectProperties.attributes.get('GlobalId')
classes.append("material-{}".format(re.sub("[^0-9a-zA-Z]+", "", slot.material.name)))
result = obj.BIMObjectProperties.attributes.get("GlobalId")
if not result:
result = obj.BIMObjectProperties.attributes.add()
result.name = 'GlobalId'
result.name = "GlobalId"
result.string_value = ifcopenshell.guid.new()
classes.append('globalid-{}'.format(result.string_value))
classes.append("globalid-{}".format(result.string_value))
for attribute in self.ifc_cutter.attributes:
result = self.get_obj_value(obj, attribute)
if result:
classes.append('{}-{}'.format(
re.sub('[^0-9a-zA-Z]+', '', attribute),
re.sub('[^0-9a-zA-Z]+', '', result)
))
classes.append(
"{}-{}".format(re.sub("[^0-9a-zA-Z]+", "", attribute), re.sub("[^0-9a-zA-Z]+", "", result))
)
return classes
def get_obj_value(self, obj, key):
# This is a duplicate implementation of the IFC selector key in Blender
# In the future if all this becomes purely IFC based this can be deleted
if '.' in key \
and key.split('.')[0] == 'type':
if "." in key and key.split(".")[0] == "type":
try:
obj = obj.BIMObjectProperties.relating_type
except:
return
key = '.'.join(key.split('.')[1:])
key = ".".join(key.split(".")[1:])
result = obj.BIMObjectProperties.attributes.get(key)
if result:
return result.string_value
elif key == 'Name':
return obj.name.split('/')[1]
elif '.' in key:
pset_name, prop = key.split('.')
elif key == "Name":
return obj.name.split("/")[1]
elif "." in key:
pset_name, prop = key.split(".")
pset = obj.BIMObjectProperties.psets.get(pset_name)
if not pset:
pset = obj.BIMObjectProperties.qtos.get(pset_name)
@@ -353,18 +399,21 @@ class SvgWriter():
obj, data = obj_data
classes.extend(['annotation'])
classes.extend(["annotation"])
matrix_world = obj.matrix_world
if isinstance(data, bpy.types.Curve):
for spline in data.splines:
points = self.get_spline_points(spline)
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
d = ' '.join(['L {} {}'.format(
(x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
for p in projected_points])
d = 'M{}'.format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=' '.join(classes)))
d = " ".join(
[
"L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
for p in projected_points
]
)
d = "M{}".format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
elif isinstance(data, bpy.types.Mesh):
self.draw_edge_annotation(obj, classes)
@@ -380,8 +429,9 @@ class SvgWriter():
start = Vector(((x_offset + v0.x), (y_offset - v0.y)))
end = Vector(((x_offset + v1.x), (y_offset - v1.y)))
vector = end - start
line = self.svg.add(self.svg.line(start=tuple(start * self.scale),
end=tuple(end * self.scale), class_=' '.join(classes)))
line = self.svg.add(
self.svg.line(start=tuple(start * self.scale), end=tuple(end * self.scale), class_=" ".join(classes))
)
def draw_text_annotations(self):
x_offset = self.raw_width / 2
@@ -393,53 +443,61 @@ class SvgWriter():
local_x_axis = text_obj.matrix_world.to_quaternion() @ Vector((1, 0, 0))
projected_x_axis = self.project_point_onto_camera(text_obj.location + local_x_axis)
angle = math.degrees((Vector((x_offset + projected_x_axis.x, y_offset -
projected_x_axis.y)) - text_position).angle_signed(Vector((1, 0))))
angle = math.degrees(
(Vector((x_offset + projected_x_axis.x, y_offset - projected_x_axis.y)) - text_position).angle_signed(
Vector((1, 0))
)
)
transform = 'rotate({}, {}, {})'.format(
transform = "rotate({}, {}, {})".format(
angle,
(text_position * self.scale)[0],
(text_position * self.scale)[1],
)
if text_obj.data.BIMTextProperties.symbol != 'None':
self.svg.add(self.svg.use(
'#{}'.format(text_obj.data.BIMTextProperties.symbol),
insert=tuple(text_position * self.scale)
))
if text_obj.data.BIMTextProperties.symbol != "None":
self.svg.add(
self.svg.use(
"#{}".format(text_obj.data.BIMTextProperties.symbol), insert=tuple(text_position * self.scale)
)
)
if text_obj.data.align_x == 'CENTER':
text_anchor = 'middle'
elif text_obj.data.align_x == 'RIGHT':
text_anchor = 'end'
if text_obj.data.align_x == "CENTER":
text_anchor = "middle"
elif text_obj.data.align_x == "RIGHT":
text_anchor = "end"
else:
text_anchor = 'start'
text_anchor = "start"
if text_obj.data.align_y == 'CENTER':
alignment_baseline = 'middle'
elif text_obj.data.align_y == 'TOP':
alignment_baseline = 'hanging'
if text_obj.data.align_y == "CENTER":
alignment_baseline = "middle"
elif text_obj.data.align_y == "TOP":
alignment_baseline = "hanging"
else:
alignment_baseline = 'baseline'
alignment_baseline = "baseline"
text_body = text_obj.data.body
if text_obj.name in self.ifc_cutter.template_variables:
text_body = pystache.render(text_body, self.ifc_cutter.template_variables[text_obj.name])
for line_number, text_line in enumerate(text_body.split('\n')):
self.svg.add(self.svg.text(
text_line,
insert=tuple((text_position * self.scale) + Vector((0, 3.5*line_number))),
class_=' '.join(self.get_attribute_classes(text_obj)),
**{
'font-size': annotation.Annotator.get_svg_text_size(text_obj.data.BIMTextProperties.font_size),
'font-family': 'OpenGost Type B TT',
'text-anchor': text_anchor,
'alignment-baseline': alignment_baseline,
'dominant-baseline': alignment_baseline,
'transform': transform
}
))
for line_number, text_line in enumerate(text_body.split("\n")):
self.svg.add(
self.svg.text(
text_line,
insert=tuple((text_position * self.scale) + Vector((0, 3.5 * line_number))),
class_=" ".join(self.get_attribute_classes(text_obj)),
**{
"font-size": annotation.Annotator.get_svg_text_size(
text_obj.data.BIMTextProperties.font_size
),
"font-family": "OpenGost Type B TT",
"text-anchor": text_anchor,
"alignment-baseline": alignment_baseline,
"dominant-baseline": alignment_baseline,
"transform": transform,
}
)
)
def draw_break_annotations(self, break_obj):
x_offset = self.raw_width / 2
@@ -449,36 +507,41 @@ class SvgWriter():
for polygon in break_obj.data.polygons:
points = [break_obj.data.vertices[v] for v in polygon.vertices]
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
d = ' '.join(['L {} {}'.format(
(x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
for p in projected_points])
d = 'M{}'.format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=' '.join(['break'])))
d = " ".join(
[
"L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
for p in projected_points
]
)
d = "M{}".format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=" ".join(["break"])))
break_points = [
projected_points[0],
((projected_points[1]-projected_points[0])/2)+projected_points[0],
projected_points[1]]
d = ' '.join(['L {} {}'.format(
(x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
for p in break_points])
d = 'M{}'.format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=' '.join(['breakline'])))
((projected_points[1] - projected_points[0]) / 2) + projected_points[0],
projected_points[1],
]
d = " ".join(
["L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale) for p in break_points]
)
d = "M{}".format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=" ".join(["breakline"])))
def draw_dimension_annotations(self, dimension_obj, text_override=None):
matrix_world = dimension_obj.matrix_world
for spline in dimension_obj.data.splines:
points = self.get_spline_points(spline)
for i, p in enumerate(points):
if i+1 >= len(points):
if i + 1 >= len(points):
continue
v0_global = matrix_world @ points[i].co.xyz
v1_global = matrix_world @ points[i+1].co.xyz
v1_global = matrix_world @ points[i + 1].co.xyz
self.draw_dimension_annotation(v0_global, v1_global, text_override)
def draw_measureit_arch_dimension_annotations(self):
try:
import MeasureIt_ARCH.measureit_arch_external_utils
coords = MeasureIt_ARCH.measureit_arch_external_utils.blenderBIM_get_coords(bpy.context)
except:
return
@@ -486,7 +549,7 @@ class SvgWriter():
self.draw_dimension_annotation(Vector(coord[0]), Vector(coord[1]))
def draw_dimension_annotation(self, v0_global, v1_global, text_override=None):
classes = ['annotation', 'dimension']
classes = ["annotation", "dimension"]
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
v0 = self.project_point_onto_camera(v0_global)
@@ -498,71 +561,74 @@ class SvgWriter():
perpendicular = Vector((vector.y, -vector.x)).normalized()
dimension = (v1_global - v0_global).length
dimension = helper.format_distance(dimension)
sheet_dimension = ((end*self.scale) - (start*self.scale)).length
if sheet_dimension < 5: # annotation can't fit
sheet_dimension = ((end * self.scale) - (start * self.scale)).length
if sheet_dimension < 5: # annotation can't fit
# offset text to right of marker
text_position = (end * self.scale) + perpendicular + (3 * vector.normalized())
else:
text_position = (mid * self.scale) + perpendicular
rotation = math.degrees(vector.angle_signed(Vector((1, 0))))
line = self.svg.add(self.svg.line(start=tuple(start * self.scale),
end=tuple(end * self.scale), class_=' '.join(classes)))
line['marker-start'] = 'url(#dimension-marker-start)'
line['marker-end'] = 'url(#dimension-marker-end)'
line = self.svg.add(
self.svg.line(start=tuple(start * self.scale), end=tuple(end * self.scale), class_=" ".join(classes))
)
line["marker-start"] = "url(#dimension-marker-start)"
line["marker-end"] = "url(#dimension-marker-end)"
if text_override is not None:
text = text_override
else:
text = str(dimension)
self.svg.add(self.svg.text(text, insert=tuple(text_position), **{
'transform': 'rotate({} {} {})'.format(
rotation,
text_position.x,
text_position.y
),
'font-size': annotation.Annotator.get_svg_text_size(2.5),
'font-family': 'OpenGost Type B TT',
'text-anchor': 'middle'
}))
self.svg.add(
self.svg.text(
text,
insert=tuple(text_position),
**{
"transform": "rotate({} {} {})".format(rotation, text_position.x, text_position.y),
"font-size": annotation.Annotator.get_svg_text_size(2.5),
"font-family": "OpenGost Type B TT",
"text-anchor": "middle",
}
)
)
def project_point_onto_camera(self, point):
return self.ifc_cutter.camera_obj.matrix_world.inverted() @ geometry.intersect_line_plane(
point.xyz,
point.xyz-Vector(self.ifc_cutter.section_box['projection']),
point.xyz - Vector(self.ifc_cutter.section_box["projection"]),
self.ifc_cutter.camera_obj.location,
Vector(self.ifc_cutter.section_box['projection'])
)
Vector(self.ifc_cutter.section_box["projection"]),
)
def get_spline_points(self, spline):
return spline.bezier_points if spline.bezier_points else spline.points
def draw_cut_polygons(self):
for polygon in self.ifc_cutter.cut_polygons:
self.draw_polygon(polygon, 'cut')
self.draw_polygon(polygon, "cut")
def draw_polyline(self, element, position):
classes = self.get_classes(element['raw'], position)
exp = BRepTools.BRepTools_WireExplorer(element['geometry'])
classes = self.get_classes(element["raw"], position)
exp = BRepTools.BRepTools_WireExplorer(element["geometry"])
points = []
while exp.More():
point = BRep.BRep_Tool.Pnt(exp.CurrentVertex())
points.append((point.X() * self.scale, -point.Y() * self.scale))
exp.Next()
self.svg.add(self.svg.polyline(points=points, class_=' '.join(classes)))
self.svg.add(self.svg.polyline(points=points, class_=" ".join(classes)))
def draw_line(self, element, position):
classes = self.get_classes(element['raw'], position)
exp = TopExp.TopExp_Explorer(element['geometry'], TopAbs.TopAbs_VERTEX)
classes = self.get_classes(element["raw"], position)
exp = TopExp.TopExp_Explorer(element["geometry"], TopAbs.TopAbs_VERTEX)
points = []
while exp.More():
point = BRep.BRep_Tool.Pnt(topods.Vertex(exp.Current()))
points.append((point.X() * self.scale, -point.Y() * self.scale))
exp.Next()
self.svg.add(self.svg.line(start=points[0], end=points[1], class_=' '.join(classes)))
self.svg.add(self.svg.line(start=points[0], end=points[1], class_=" ".join(classes)))
def draw_polygon(self, polygon, position):
points = [(p[0] * self.scale, p[1] * self.scale) for p in polygon['points']]
if 'classes' in polygon['metadata']:
classes = ' '.join(polygon['metadata']['classes'])
points = [(p[0] * self.scale, p[1] * self.scale) for p in polygon["points"]]
if "classes" in polygon["metadata"]:
classes = " ".join(polygon["metadata"]["classes"])
else:
classes = ''
classes = ""
self.svg.add(self.svg.polygon(points=points, class_=classes))
File diff suppressed because it is too large Load Diff
+9 -11
View File
@@ -17,12 +17,12 @@
# -- Project information -----------------------------------------------------
project = 'IfcOpenShell'
copyright = '2020, IfcOpenShell Contributors'
author = 'IfcOpenShell Contributors'
project = "IfcOpenShell"
copyright = "2020, IfcOpenShell Contributors"
author = "IfcOpenShell Contributors"
# The full version, including alpha/beta/rc tags
release = '0.0.1'
release = "0.0.1"
# -- General configuration ---------------------------------------------------
@@ -30,17 +30,15 @@ release = '0.0.1'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc'
]
extensions = ["sphinx.ext.autodoc"]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
templates_path = ["_templates"]
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
# -- Options for HTML output -------------------------------------------------
@@ -48,9 +46,9 @@ exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'nature'
html_theme = "nature"
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
html_static_path = ["_static"]
+63 -34
View File
@@ -1,55 +1,84 @@
class Dxf2Ifc:
def execute(self):
self.create_ifc_file()
doc = ezdxf.readfile('input.dxf')
doc = ezdxf.readfile("input.dxf")
model = doc.modelspace()
products = []
for entity in model:
print(entity)
if entity.get_mode() == 'AcDbPolyFaceMesh':
if entity.get_mode() == "AcDbPolyFaceMesh":
ifc_faces = []
for face in entity.faces():
ifc_faces.append(
self.file.createIfcFace([self.file.createIfcFaceOuterBound(self.file.createIfcPolyLoop([
self.file.createIfcCartesianPoint((v.dxf.location)) for v in face[0:3]]), True)]))
representation = self.file.createIfcProductDefinitionShape(None, None, [self.file.createIfcShapeRepresentation(
self.subcontext, 'Body', 'Brep', [self.file.createIfcFacetedBrep(self.file.createIfcClosedShell(ifc_faces))])])
products.append(self.file.create_entity('IfcBuildingElementProxy', **{
'GlobalId': ifcopenshell.guid.new(),
'Name': entity.dxf.layer,
'ObjectPlacement': self.placement,
'Representation': representation
}))
self.file.createIfcFace(
[
self.file.createIfcFaceOuterBound(
self.file.createIfcPolyLoop(
[self.file.createIfcCartesianPoint((v.dxf.location)) for v in face[0:3]]
),
True,
)
]
)
)
representation = self.file.createIfcProductDefinitionShape(
None,
None,
[
self.file.createIfcShapeRepresentation(
self.subcontext,
"Body",
"Brep",
[self.file.createIfcFacetedBrep(self.file.createIfcClosedShell(ifc_faces))],
)
],
)
products.append(
self.file.create_entity(
"IfcBuildingElementProxy",
**{
"GlobalId": ifcopenshell.guid.new(),
"Name": entity.dxf.layer,
"ObjectPlacement": self.placement,
"Representation": representation,
}
)
)
else:
print('Not yet implemented')
self.file.createIfcRelContainedInSpatialStructure(ifcopenshell.guid.new(), None, None, None, products, self.site)
self.file.write('test.ifc')
print("Not yet implemented")
self.file.createIfcRelContainedInSpatialStructure(
ifcopenshell.guid.new(), None, None, None, products, self.site
)
self.file.write("test.ifc")
def create_ifc_file(self):
self.file = ifcopenshell.file()
units = self.file.createIfcUnitAssignment([
self.file.createIfcSIUnit(None, 'LENGTHUNIT', None, 'METRE')
])
units = self.file.createIfcUnitAssignment([self.file.createIfcSIUnit(None, "LENGTHUNIT", None, "METRE")])
self.origin = self.file.createIfcAxis2Placement3D(
self.file.createIfcCartesianPoint((0., 0., 0.)),
self.file.createIfcDirection((0., 0., 1.)),
self.file.createIfcDirection((1., 0., 0.)))
self.file.createIfcCartesianPoint((0.0, 0.0, 0.0)),
self.file.createIfcDirection((0.0, 0.0, 1.0)),
self.file.createIfcDirection((1.0, 0.0, 0.0)),
)
self.placement = self.file.createIfcLocalPlacement(None, self.origin)
self.context = self.file.createIfcGeometricRepresentationContext(None, 'Model', 3, 1.0E-05, self.origin)
self.context = self.file.createIfcGeometricRepresentationContext(None, "Model", 3, 1.0e-05, self.origin)
self.subcontext = self.file.createIfcGeometricRepresentationSubcontext(
'Body', 'Model', None, None, None, None, self.context, None, 'MODEL_VIEW', None)
self.project = self.file.create_entity('IfcProject', **{
'GlobalId': ifcopenshell.guid.new(),
'Name': 'DXF Conversion',
'RepresentationContexts': [self.context],
'UnitsInContext': units
})
self.site = self.file.create_entity('IfcSite', **{
'GlobalId': ifcopenshell.guid.new(),
'Name': 'DXF Conversion Site',
'ObjectPlacement': self.placement
})
"Body", "Model", None, None, None, None, self.context, None, "MODEL_VIEW", None
)
self.project = self.file.create_entity(
"IfcProject",
**{
"GlobalId": ifcopenshell.guid.new(),
"Name": "DXF Conversion",
"RepresentationContexts": [self.context],
"UnitsInContext": units,
}
)
self.site = self.file.create_entity(
"IfcSite",
**{"GlobalId": ifcopenshell.guid.new(), "Name": "DXF Conversion Site", "ObjectPlacement": self.placement}
)
self.file.createIfcRelAggregates(ifcopenshell.guid.new(), None, None, None, self.project, [self.site])
dxf2ifc = Dxf2Ifc()
dxf2ifc.execute()
+45 -47
View File
@@ -1,10 +1,11 @@
import xml.sax, json, copy, pathlib
from bs4 import BeautifulSoup
import sys
sys.setrecursionlimit(100)
class IfcElementHandler(xml.sax.ContentHandler):
class IfcElementHandler(xml.sax.ContentHandler):
def __init__(self):
self.elements = {}
self.current_element_name = None
@@ -13,90 +14,87 @@ class IfcElementHandler(xml.sax.ContentHandler):
self.attribute_stack = []
def startElement(self, name, attrs):
if name == 'xs:element' and 'substitutionGroup' in attrs:
self.elements[attrs['name']] = {
'description': self.get_description(attrs['name']),
'is_abstract': True if 'abstract' in attrs else False,
'parent': attrs['substitutionGroup'][len('ifc:'):],
'attributes': []
if name == "xs:element" and "substitutionGroup" in attrs:
self.elements[attrs["name"]] = {
"description": self.get_description(attrs["name"]),
"is_abstract": True if "abstract" in attrs else False,
"parent": attrs["substitutionGroup"][len("ifc:") :],
"attributes": [],
}
self.current_element_name = attrs['name']
elif name == 'xs:simpleType' \
and 'name' in attrs \
and 'Enum' in attrs['name']:
self.current_enum_name = attrs['name']
self.current_element_name = attrs["name"]
elif name == "xs:simpleType" and "name" in attrs and "Enum" in attrs["name"]:
self.current_enum_name = attrs["name"]
self.enums[self.current_enum_name] = []
elif name == 'xs:enumeration' and self.current_enum_name:
self.enums[self.current_enum_name].append(attrs['value'].upper())
elif name == 'xs:attribute' \
and self.current_element_name \
and 'name' in attrs \
and 'type' in attrs:
self.elements[self.current_element_name]['attributes'].append({
'name': attrs['name'],
'type': attrs['type'].replace('ifc:', ''),
})
elif name == "xs:enumeration" and self.current_enum_name:
self.enums[self.current_enum_name].append(attrs["value"].upper())
elif name == "xs:attribute" and self.current_element_name and "name" in attrs and "type" in attrs:
self.elements[self.current_element_name]["attributes"].append(
{
"name": attrs["name"],
"type": attrs["type"].replace("ifc:", ""),
}
)
def endDocument(self):
elements = {}
for name, data in self.elements.items():
for index, attribute in enumerate(data['attributes']):
data['attributes'][index] = self.resolve_enums(attribute)
for index, attribute in enumerate(data["attributes"]):
data["attributes"][index] = self.resolve_enums(attribute)
for name, data in self.elements.items():
if data['is_abstract']:
if data["is_abstract"]:
continue
if self.is_an_ifcproduct(data):
self.attribute_stack = []
self.get_parent_attributes(data)
elements[name] = copy.deepcopy(data)
elements[name]['attributes'] = copy.deepcopy(self.attribute_stack)
elements[name]["attributes"] = copy.deepcopy(self.attribute_stack)
self.elements = elements
def get_description(self, name):
try:
filenames = pathlib.Path(
'io_export_ifc/schema/ifc4-add2-tc1/ifc4-add2-tc1/html/schema/').glob(
'**/{}.htm'.format(name.lower()))
filenames = pathlib.Path("io_export_ifc/schema/ifc4-add2-tc1/ifc4-add2-tc1/html/schema/").glob(
"**/{}.htm".format(name.lower())
)
for filename in filenames:
with open(filename, 'r') as file:
soup = BeautifulSoup(file, 'html.parser')
for detail in soup.find_all('details'):
if detail.summary.string == 'Entity definition' \
and detail.p:
return str(detail.p.text.replace('\n', ' '))
with open(filename, "r") as file:
soup = BeautifulSoup(file, "html.parser")
for detail in soup.find_all("details"):
if detail.summary.string == "Entity definition" and detail.p:
return str(detail.p.text.replace("\n", " "))
return None
except:
return None
#print('Failed to get description for {}'.format(name))
# print('Failed to get description for {}'.format(name))
return None
def resolve_enums(self, attribute):
if attribute['type'] in self.enums:
attribute['is_enum'] = True
attribute['enum_values'] = self.enums[attribute['type']]
if attribute["type"] in self.enums:
attribute["is_enum"] = True
attribute["enum_values"] = self.enums[attribute["type"]]
return attribute
attribute['is_enum'] = False
attribute['enum_values'] = []
attribute["is_enum"] = False
attribute["enum_values"] = []
return attribute
def get_parent_attributes(self, data):
self.attribute_stack.extend(data['attributes'])
if data['parent'] != 'IfcProduct': # For now, we treat attributes above IfcProduct in a special way
self.get_parent_attributes(self.elements[data['parent']])
self.attribute_stack.extend(data["attributes"])
if data["parent"] != "IfcProduct": # For now, we treat attributes above IfcProduct in a special way
self.get_parent_attributes(self.elements[data["parent"]])
def is_an_ifcproduct(self, data):
if data['parent'] == 'IfcProduct':
if data["parent"] == "IfcProduct":
return True
else:
for name, parent_data in self.elements.items():
if name == data['parent']:
if name == data["parent"]:
return self.is_an_ifcproduct(parent_data)
return False
xsd_path = 'io_export_ifc/schema/IFC4.xsd'
xsd_path = "io_export_ifc/schema/IFC4.xsd"
handler = IfcElementHandler()
parser = xml.sax.make_parser()
parser.setContentHandler(handler)
+86 -81
View File
@@ -2,100 +2,101 @@ import bpy
import uuid
import math
import sys
#sys.path.append('C:\Program Files\Python37\Lib\site-packages')
# sys.path.append('C:\Program Files\Python37\Lib\site-packages')
import lxml
import bspy
from bspy import Gbxml
class GbxmlExporter():
class GbxmlExporter:
def __init__(self):
self.gbxml = Gbxml()
self.campus = None
def export(self):
print('# Start export')
self.campus = self.gbxml.add_element(self.gbxml.root(), 'Campus')
self.campus.set('id', 'campus-1')
name = self.gbxml.add_element(self.campus, 'Name', 'My project')
print("# Start export")
self.campus = self.gbxml.add_element(self.gbxml.root(), "Campus")
self.campus.set("id", "campus-1")
name = self.gbxml.add_element(self.campus, "Name", "My project")
location = self.gbxml.add_element(self.campus, 'Location')
self.gbxml.add_element(location, 'ZipcodeOrPostalCode', 'G20 0SP')
self.gbxml.add_element(location, 'Name', 'London/Heathrow')
self.gbxml.add_element(location, 'Latitude', '51.480000')
self.gbxml.add_element(location, 'Longitude', '-0.450000')
self.gbxml.add_element(location, 'Elevation', '24.000000')
location = self.gbxml.add_element(self.campus, "Location")
self.gbxml.add_element(location, "ZipcodeOrPostalCode", "G20 0SP")
self.gbxml.add_element(location, "Name", "London/Heathrow")
self.gbxml.add_element(location, "Latitude", "51.480000")
self.gbxml.add_element(location, "Longitude", "-0.450000")
self.gbxml.add_element(location, "Elevation", "24.000000")
building = self.gbxml.add_element(self.campus, 'Building')
building.set('id', str(uuid.uuid4()))
building.set('buildingType', 'Office')
building = self.gbxml.add_element(self.campus, "Building")
building.set("id", str(uuid.uuid4()))
building.set("buildingType", "Office")
for object in bpy.context.selected_objects:
self.create_space(object, building)
# hardcoded test
construction = self.gbxml.add_element(self.gbxml.root(), 'Construction')
construction.set('id', 'defaultconstruction')
self.gbxml.add_element(construction, 'Name', 'test construction name')
u_value = self.gbxml.add_element(construction, 'U-value', '0.42')
u_value.set('unit', 'WPerSquareMeterK')
layer = self.gbxml.add_element(construction, 'LayerId')
layer.set('layerIdRef', 'defaultlayer')
construction = self.gbxml.add_element(self.gbxml.root(), "Construction")
construction.set("id", "defaultconstruction")
self.gbxml.add_element(construction, "Name", "test construction name")
u_value = self.gbxml.add_element(construction, "U-value", "0.42")
u_value.set("unit", "WPerSquareMeterK")
layer = self.gbxml.add_element(construction, "LayerId")
layer.set("layerIdRef", "defaultlayer")
layer = self.gbxml.add_element(self.gbxml.root(), 'Layer')
layer.set('id', 'defaultlayer')
material = self.gbxml.add_element(layer, 'MaterialId')
material.set('materialIdRef', 'defaultmaterial')
layer = self.gbxml.add_element(self.gbxml.root(), "Layer")
layer.set("id", "defaultlayer")
material = self.gbxml.add_element(layer, "MaterialId")
material.set("materialIdRef", "defaultmaterial")
material = self.gbxml.add_element(self.gbxml.root(), 'Material')
material.set('id', 'defaultmaterial')
thickness = self.gbxml.add_element(material, 'Thickness', '0.2')
thickness.set('unit', 'Meters')
self.gbxml.add_element(material, 'Name', 'test material name')
r_value = self.gbxml.add_element(material, 'R-value', '0.13')
r_value.set('unit', 'SquareMeterKPerW')
material = self.gbxml.add_element(self.gbxml.root(), "Material")
material.set("id", "defaultmaterial")
thickness = self.gbxml.add_element(material, "Thickness", "0.2")
thickness.set("unit", "Meters")
self.gbxml.add_element(material, "Name", "test material name")
r_value = self.gbxml.add_element(material, "R-value", "0.13")
r_value.set("unit", "SquareMeterKPerW")
self.append_template('C:/cygwin64/home/moud308/Projects/New Folder/presets/light-schedule.xml')
self.append_template('C:/cygwin64/home/moud308/Projects/New Folder/presets/window-types.xml')
self.append_template("C:/cygwin64/home/moud308/Projects/New Folder/presets/light-schedule.xml")
self.append_template("C:/cygwin64/home/moud308/Projects/New Folder/presets/window-types.xml")
with open('C:/cygwin64/home/moud308/Projects/New Folder/out.xml', 'w') as out:
with open("C:/cygwin64/home/moud308/Projects/New Folder/out.xml", "w") as out:
out.write(self.gbxml.xmlstring())
print('# Validation results: {}'.format(self.gbxml.validate()))
print('# Finish export')
print("# Validation results: {}".format(self.gbxml.validate()))
print("# Finish export")
def append_template(self, file):
parser = lxml.etree.XMLParser(remove_blank_text=True)
template = lxml.etree.parse(file, parser).findall('.')[0]
template = lxml.etree.parse(file, parser).findall(".")[0]
for child in template.getchildren():
self.gbxml.root().append(child)
def create_space(self, object, building):
space = self.gbxml.add_element(building, 'Space')
space.set('id', object.name)
space.set('lightScheduleIdRef', 'aim0130') # hardcoded
self.gbxml.add_element(space, 'Name', object.name)
space = self.gbxml.add_element(building, "Space")
space.set("id", object.name)
space.set("lightScheduleIdRef", "aim0130") # hardcoded
self.gbxml.add_element(space, "Name", object.name)
light_power_per_area = self.gbxml.add_element(space, 'LightPowerPerArea', '5') # hardcoded test
light_power_per_area.set('unit', 'WattPerSquareMeter')
light_power_per_area = self.gbxml.add_element(space, "LightPowerPerArea", "5") # hardcoded test
light_power_per_area.set("unit", "WattPerSquareMeter")
calculated_area = 0
shell_geometry = self.gbxml.add_element(space, 'ShellGeometry')
shell_geometry.set('id', 'shellid')
closed_shell = self.gbxml.add_element(shell_geometry, 'ClosedShell')
shell_geometry = self.gbxml.add_element(space, "ShellGeometry")
shell_geometry.set("id", "shellid")
closed_shell = self.gbxml.add_element(shell_geometry, "ClosedShell")
vertices_in_vg = self.get_vertices_in_vg(object, 0)
for polygon in object.data.polygons:
# First vg is reserved for surfaces
if object.vertex_groups \
and not self.is_polygon_in_vg(polygon, vertices_in_vg):
if object.vertex_groups and not self.is_polygon_in_vg(polygon, vertices_in_vg):
continue
calculated_area += polygon.area
self.create_poly_loop(object, polygon, closed_shell)
self.create_space_boundary(object, polygon, space)
self.create_surface(object, polygon)
self.gbxml.add_element(space, 'Area', str(calculated_area))
self.gbxml.add_element(space, 'Volume', str(self.get_volume(object)))
self.gbxml.add_element(space, "Area", str(calculated_area))
self.gbxml.add_element(space, "Volume", str(self.get_volume(object)))
def get_vertices_in_vg(self, object, vg_index):
return [ v.index for v in object.data.vertices if vg_index in [ g.group for g in v.groups ] ]
return [v.index for v in object.data.vertices if vg_index in [g.group for g in v.groups]]
# Can move into a common Blender helper class?
def is_polygon_in_vg(self, polygon, vertices_in_vg):
@@ -105,49 +106,49 @@ class GbxmlExporter():
return True
def create_space_boundary(self, object, polygon, parent):
space_boundary = self.gbxml.add_element(parent, 'SpaceBoundary')
space_boundary.set('isSecondLevelBoundary', 'true')
space_boundary.set('surfaceIdRef', 'surface-{}-{}'.format(object.name, polygon.index))
planar_geometry = self.gbxml.add_element(space_boundary, 'PlanarGeometry')
space_boundary = self.gbxml.add_element(parent, "SpaceBoundary")
space_boundary.set("isSecondLevelBoundary", "true")
space_boundary.set("surfaceIdRef", "surface-{}-{}".format(object.name, polygon.index))
planar_geometry = self.gbxml.add_element(space_boundary, "PlanarGeometry")
self.create_poly_loop(object, polygon, planar_geometry)
def create_surface(self, object, polygon):
surface = self.gbxml.add_element(self.campus, 'Surface')
surface.set('id', 'surface-{}-{}'.format(object.name, polygon.index))
surface.set('surfaceType', 'ExteriorWall')
surface.set('constructionIdRef', 'defaultconstruction')
adjacent_space_id = self.gbxml.add_element(surface, 'AdjacentSpaceId')
adjacent_space_id.set('spaceIdRef', object.name)
rectangular_geometry = self.gbxml.add_element(surface, 'RectangularGeometry')
surface = self.gbxml.add_element(self.campus, "Surface")
surface.set("id", "surface-{}-{}".format(object.name, polygon.index))
surface.set("surfaceType", "ExteriorWall")
surface.set("constructionIdRef", "defaultconstruction")
adjacent_space_id = self.gbxml.add_element(surface, "AdjacentSpaceId")
adjacent_space_id.set("spaceIdRef", object.name)
rectangular_geometry = self.gbxml.add_element(surface, "RectangularGeometry")
self.gbxml.add_element(
rectangular_geometry, 'Azimuth',
str(math.degrees(math.atan2(polygon.normal[0], polygon.normal[1]))))
rectangular_geometry, "Azimuth", str(math.degrees(math.atan2(polygon.normal[0], polygon.normal[1])))
)
self.gbxml.add_element(
rectangular_geometry, 'Tilt',
str(math.degrees(math.atan2(polygon.normal[2], polygon.normal[1])) - 90))
planar_geometry = self.gbxml.add_element(surface, 'PlanarGeometry')
rectangular_geometry, "Tilt", str(math.degrees(math.atan2(polygon.normal[2], polygon.normal[1])) - 90)
)
planar_geometry = self.gbxml.add_element(surface, "PlanarGeometry")
self.create_poly_loop(object, polygon, planar_geometry)
for vg in object.vertex_groups:
if '/'.join(vg.name.split('/')[0:2]) == 'openings/{}'.format(polygon.index):
if "/".join(vg.name.split("/")[0:2]) == "openings/{}".format(polygon.index):
vertices_in_vg = self.get_vertices_in_vg(object, vg.index)
for p in object.data.polygons:
if self.is_polygon_in_vg(p, vertices_in_vg):
self.create_opening(object, p, surface)
def create_opening(self, object, polygon, parent):
opening = self.gbxml.add_element(parent, 'Opening')
opening.set('id', 'opening-{}-{}'.format(object.name, polygon.index))
opening.set('windowTypeIdRef', 'STD_EX11') # harcoded
opening.set('openingType', 'FixedWindow') # hardcoded
planar_geometry = self.gbxml.add_element(opening, 'PlanarGeometry')
opening = self.gbxml.add_element(parent, "Opening")
opening.set("id", "opening-{}-{}".format(object.name, polygon.index))
opening.set("windowTypeIdRef", "STD_EX11") # harcoded
opening.set("openingType", "FixedWindow") # hardcoded
planar_geometry = self.gbxml.add_element(opening, "PlanarGeometry")
self.create_poly_loop(object, polygon, planar_geometry)
def create_poly_loop(self, object, polygon, parent):
poly_loop = self.gbxml.add_element(parent, 'PolyLoop')
poly_loop = self.gbxml.add_element(parent, "PolyLoop")
for vertice in polygon.vertices:
cartesian_point = self.gbxml.add_element(poly_loop, 'CartesianPoint')
cartesian_point = self.gbxml.add_element(poly_loop, "CartesianPoint")
for coord in [0, 1, 2]:
coordinate = self.gbxml.add_element(cartesian_point, 'Coordinate')
coordinate = self.gbxml.add_element(cartesian_point, "Coordinate")
coordinate.text = str(object.data.vertices[vertice].co[coord])
def get_volume(self, o):
@@ -158,10 +159,13 @@ class GbxmlExporter():
for tf in me.loop_triangles:
tfv = tf.vertices
if len(tf.vertices) == 3:
tf_tris = (me.vertices[tfv[0]], me.vertices[tfv[1]], me.vertices[tfv[2]]),
tf_tris = ((me.vertices[tfv[0]], me.vertices[tfv[1]], me.vertices[tfv[2]]),)
else:
tf_tris = (me.vertices[tfv[0]], me.vertices[tfv[1]], me.vertices[tfv[2]]),\
(me.vertices[tfv[2]], me.vertices[tfv[3]], me.vertices[tfv[0]])
tf_tris = (me.vertices[tfv[0]], me.vertices[tfv[1]], me.vertices[tfv[2]]), (
me.vertices[tfv[2]],
me.vertices[tfv[3]],
me.vertices[tfv[0]],
)
for tf_iter in tf_tris:
v1 = ob_mat @ tf_iter[0].co
@@ -171,5 +175,6 @@ class GbxmlExporter():
volume += v1.dot(v2.cross(v3)) / 6.0
return volume
gbxml_exporter = GbxmlExporter()
gbxml_exporter.export()
+91 -72
View File
@@ -4,33 +4,32 @@ import xml.etree.ElementTree as ET
import collections
import json
class IFC4Extractor:
def __init__(self, xsd_file):
self.xsd_file = xsd_file
tree = ET.parse(self.xsd_file)
self.root = tree.getroot()
self.ns = {'xs': 'http://www.w3.org/2001/XMLSchema'}
self.ns = {"xs": "http://www.w3.org/2001/XMLSchema"}
self.elements = {}
self.filters = []
self.filtered_elements = {}
def extract(self):
for element in self.root.findall("xs:element", self.ns):
print('Processing {}'.format(element.attrib['name']))
if not 'substitutionGroup' in element.attrib \
or self.is_descendant_from_class(element, 'uos'):
print("Processing {}".format(element.attrib["name"]))
if not "substitutionGroup" in element.attrib or self.is_descendant_from_class(element, "uos"):
continue
data = {
'is_abstract': self.is_abstract(element),
'parent': element.attrib['substitutionGroup'].replace('ifc:', ''),
'attributes': self.get_attributes(element),
'complex_attributes': self.get_complex_attributes(element)
"is_abstract": self.is_abstract(element),
"parent": element.attrib["substitutionGroup"].replace("ifc:", ""),
"attributes": self.get_attributes(element),
"complex_attributes": self.get_complex_attributes(element),
}
self.elements[element.attrib['name']] = data
self.elements[element.attrib["name"]] = data
for filter in self.filters:
if self.is_descendant_from_class(element, filter) \
and not data['is_abstract']:
self.filtered_elements.setdefault(filter, {})[element.attrib['name']] = data
if self.is_descendant_from_class(element, filter) and not data["is_abstract"]:
self.filtered_elements.setdefault(filter, {})[element.attrib["name"]] = data
def export(self, filename):
final = {}
@@ -40,86 +39,100 @@ class IFC4Extractor:
file.write(json.dumps(collections.OrderedDict(sorted(final.items())), indent=4))
def is_descendant_from_class(self, element, class_name):
if element is None \
or 'substitutionGroup' not in element.attrib \
or 'type' not in element.attrib:
if element is None or "substitutionGroup" not in element.attrib or "type" not in element.attrib:
return False
if element.attrib['substitutionGroup'] == 'ifc:{}'.format(class_name) \
or element.attrib['type'] == 'ifc:{}'.format(class_name):
if element.attrib["substitutionGroup"] == "ifc:{}".format(class_name) or element.attrib[
"type"
] == "ifc:{}".format(class_name):
return True
return self.is_descendant_from_class(self.get_parent_element(element), class_name)
def is_abstract(self, element):
return True if 'abstract' in element.attrib else False
return True if "abstract" in element.attrib else False
def get_attributes(self, element, attributes = None):
def get_attributes(self, element, attributes=None):
if attributes is None:
attributes = []
if element.attrib['substitutionGroup'] != self.get_ifcroot_parent_name():
if element.attrib["substitutionGroup"] != self.get_ifcroot_parent_name():
attributes = self.get_attributes(self.get_parent_element(element), attributes)
for attribute in self.root.findall(self.get_attribute_xpath(element), self.ns):
try:
attributes.append({
'name': attribute.attrib['name'],
'type': attribute.attrib['type'].replace('ifc:', ''),
'is_enum': self.is_enum(attribute),
'enum_values': self.get_enum_values(attribute)
})
attributes.append(
{
"name": attribute.attrib["name"],
"type": attribute.attrib["type"].replace("ifc:", ""),
"is_enum": self.is_enum(attribute),
"enum_values": self.get_enum_values(attribute),
}
)
except KeyError as e:
print('Attribute {} is missing key {}'.format(attribute.attrib, e))
print("Attribute {} is missing key {}".format(attribute.attrib, e))
return attributes
def get_attribute_xpath(self, element):
return "./xs:complexType[@name='{}']/xs:complexContent/xs:extension/xs:attribute[@name][@type]".format(element.attrib['name'])
return "./xs:complexType[@name='{}']/xs:complexContent/xs:extension/xs:attribute[@name][@type]".format(
element.attrib["name"]
)
def get_ifcroot_parent_name(self):
return "ifc:Entity"
def get_complex_attributes(self, element, attributes = None):
def get_complex_attributes(self, element, attributes=None):
if attributes is None:
attributes = []
if element.attrib['substitutionGroup'] != self.get_ifcroot_parent_name():
if element.attrib["substitutionGroup"] != self.get_ifcroot_parent_name():
attributes = self.get_complex_attributes(self.get_parent_element(element), attributes)
for attribute in self.root.findall(
"./xs:complexType[@name='{}']/xs:complexContent/xs:extension/xs:sequence/xs:element[@name]".format(
element.attrib['name']
), self.ns):
if 'type' in attribute.attrib:
attributes.append({
'name': attribute.attrib['name'],
'type': attribute.attrib['type'].replace('ifc:', ''),
'is_select': False,
'select_types': []
})
element.attrib["name"]
),
self.ns,
):
if "type" in attribute.attrib:
attributes.append(
{
"name": attribute.attrib["name"],
"type": attribute.attrib["type"].replace("ifc:", ""),
"is_select": False,
"select_types": [],
}
)
else:
type_element = attribute.find('./xs:complexType/xs:sequence/xs:element[@ref]', self.ns)
type_element = attribute.find("./xs:complexType/xs:sequence/xs:element[@ref]", self.ns)
is_select = False
select_types = []
if not type_element:
# Handle select (i.e. group) attributes
type_element = attribute.find('./xs:complexType/xs:group', self.ns)
type_element = attribute.find("./xs:complexType/xs:group", self.ns)
if type_element is not None:
is_select = True
select_types = [e.attrib['ref'].replace('ifc:', '').replace('-wrapper', '') for e in
self.root.findall("./xs:group[@name='{}']/xs:choice/xs:element[@ref]".format(
type_element.attrib['ref'].replace('ifc:', '')
), self.ns)]
select_types = [
e.attrib["ref"].replace("ifc:", "").replace("-wrapper", "")
for e in self.root.findall(
"./xs:group[@name='{}']/xs:choice/xs:element[@ref]".format(
type_element.attrib["ref"].replace("ifc:", "")
),
self.ns,
)
]
if type_element is not None:
attributes.append({
'name': attribute.attrib['name'],
'type': type_element.attrib['ref'].replace('ifc:', ''),
'is_select': is_select,
'select_types': select_types
})
attributes.append(
{
"name": attribute.attrib["name"],
"type": type_element.attrib["ref"].replace("ifc:", ""),
"is_select": is_select,
"select_types": select_types,
}
)
return attributes
def get_parent_element(self, element):
return self.root.find("./xs:element[@name='{}']".format(
element.attrib["substitutionGroup"].replace('ifc:', '')
), self.ns)
return self.root.find(
"./xs:element[@name='{}']".format(element.attrib["substitutionGroup"].replace("ifc:", "")), self.ns
)
def is_enum(self, attribute):
return 'Enum' in attribute.attrib['type']
return "Enum" in attribute.attrib["type"]
def get_enum_values(self, attribute):
if not self.is_enum(attribute):
@@ -127,43 +140,49 @@ class IFC4Extractor:
values = []
for enumeration in self.root.findall(
"./xs:simpleType[@name='{}']/xs:restriction/xs:enumeration".format(
attribute.attrib['type'].replace('ifc:', '')
), self.ns):
values.append(enumeration.attrib['value'].upper())
attribute.attrib["type"].replace("ifc:", "")
),
self.ns,
):
values.append(enumeration.attrib["value"].upper())
return values
def is_ifc_version(self, version):
return version in self.xsd_file
class IFC2X3Extractor(IFC4Extractor):
# IFC2X3 seems to store regular attributes where IFC4 stores complex attributes
def get_attribute_xpath(self, element):
return "./xs:complexType[@name='{}']/xs:complexContent/xs:extension/xs:sequence/xs:element[@name]".format(element.attrib['name'])
return "./xs:complexType[@name='{}']/xs:complexContent/xs:extension/xs:sequence/xs:element[@name]".format(
element.attrib["name"]
)
def get_ifcroot_parent_name(self):
return "ex:Entity"
# IFC2X3 does not seem to store complex attributes in the XSD file
def get_complex_attributes(self, element, attributes = None):
def get_complex_attributes(self, element, attributes=None):
return []
filename_filters = {
'IfcContext_IFC4.json': ['IfcContext'],
'IfcElement_IFC4.json': ['IfcElement'],
'IfcSpatialElement_IFC4.json': ['IfcSpatialElement'],
'IfcGroup_IFC4.json': ['IfcGroup'],
'IfcStructural_IFC4.json': ['IfcStructuralActivity', 'IfcStructuralItem'],
'IfcMaterialDefinition_IFC4.json': ['IfcMaterialDefinition'],
'IfcParameterizedProfileDef_IFC4.json': ['IfcParameterizedProfileDef'],
'IfcBoundaryCondition_IFC4.json': ['IfcBoundaryCondition'],
'IfcElementType_IFC4.json': ['IfcElementType', 'IfcSpatialElementType'],
'IfcAnnotation_IFC4.json': ['IfcAnnotation'],
'IfcPositioningElement_IFC4.json': ['IfcGrid', 'IfcGridAxis'] # IfcPositioningElement in the future
"IfcContext_IFC4.json": ["IfcContext"],
"IfcElement_IFC4.json": ["IfcElement"],
"IfcSpatialElement_IFC4.json": ["IfcSpatialElement"],
"IfcGroup_IFC4.json": ["IfcGroup"],
"IfcStructural_IFC4.json": ["IfcStructuralActivity", "IfcStructuralItem"],
"IfcMaterialDefinition_IFC4.json": ["IfcMaterialDefinition"],
"IfcParameterizedProfileDef_IFC4.json": ["IfcParameterizedProfileDef"],
"IfcBoundaryCondition_IFC4.json": ["IfcBoundaryCondition"],
"IfcElementType_IFC4.json": ["IfcElementType", "IfcSpatialElementType"],
"IfcAnnotation_IFC4.json": ["IfcAnnotation"],
"IfcPositioningElement_IFC4.json": ["IfcGrid", "IfcGridAxis"], # IfcPositioningElement in the future
}
for filename, filters in filename_filters.items():
extractor = IFC4Extractor("IFC4_ADD2.xsd")
extractor.filters = filters
#extractor = IFC2X3Extractor("IFC2X3.xsd")
# extractor = IFC2X3Extractor("IFC2X3.xsd")
extractor.extract()
extractor.export(filename)
+18 -13
View File
@@ -5,12 +5,13 @@ import json
from pathlib import Path
import ifcopenshell
class Describer():
class Describer:
def describe(self):
# BuildingSMART does not provide a computer interpretable set of
# descriptions. They provide HTML docs, which contained malformed /
# invalid HTML. Therefore, this dodgy hack was written.
schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name('IFC4')
schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name("IFC4")
self.html_sources = {}
self.get_html_sources()
@@ -27,18 +28,18 @@ class Describer():
continue
if isinstance(attribute, str):
continue
if 'Enum' in attribute.name() and 'Enumeration' not in attribute.name():
if "Enum" in attribute.name() and "Enumeration" not in attribute.name():
self.get_enum_descriptions(attribute)
with open('entity_descriptions.json', 'w') as f:
with open("entity_descriptions.json", "w") as f:
f.write(json.dumps(self.entity_descriptions, indent=4))
with open('enum_descriptions.json', 'w') as f:
with open("enum_descriptions.json", "w") as f:
f.write(json.dumps(self.enum_descriptions, indent=4))
def get_html_sources(self):
html_dir = '/home/dion/Projects/IfcOpenShell/src/ifcblenderexport/descriptions/IFC4_3/RC1/HTML'
for filename in Path(html_dir).rglob('*.htm'):
if 'lexical' not in str(filename):
html_dir = "/home/dion/Projects/IfcOpenShell/src/ifcblenderexport/descriptions/IFC4_3/RC1/HTML"
for filename in Path(html_dir).rglob("*.htm"):
if "lexical" not in str(filename):
continue
name = os.path.basename(filename)[0:-4]
self.html_sources[name] = filename
@@ -48,8 +49,10 @@ class Describer():
return
with open(self.html_sources[name.lower()]) as f:
for line in f:
if 'Entity definition' in line:
self.entity_descriptions[name] = html.unescape(re.sub('<.*?>', '', line.strip().replace('Entity definition', '')))
if "Entity definition" in line:
self.entity_descriptions[name] = html.unescape(
re.sub("<.*?>", "", line.strip().replace("Entity definition", ""))
)
def get_enum_descriptions(self, enum):
if enum.name().lower() not in self.html_sources:
@@ -59,8 +62,10 @@ class Describer():
for item in enum.enumeration_items():
with open(self.html_sources[enum.name().lower()]) as f:
for line in f:
if '<td>'+item+'</td>' in line:
self.enum_descriptions.setdefault(enum.name(), {})[item] = html.unescape(re.sub('<.*?>', '', line.strip().replace(item, '')))
if "<td>" + item + "</td>" in line:
self.enum_descriptions.setdefault(enum.name(), {})[item] = html.unescape(
re.sub("<.*?>", "", line.strip().replace(item, ""))
)
describer = Describer()
+38 -33
View File
@@ -26,41 +26,44 @@ import operator
import warnings
from collections import namedtuple
try: # python 3.3+
from collections.abc import Iterable
except ModuleNotFoundError: # python 2
except ModuleNotFoundError: # python 2
from collections import Iterable
try:
from OCC.Core import TopoDS, gp, Quantity, BRepTools
from OCC.Core import TopoDS, gp, Quantity, BRepTools
try:
from OCC.Core import V3d, AIS, Graphic3d
except ImportError:
pass
except ImportError:
from OCC import TopoDS, gp, Quantity, BRepTools
from OCC import TopoDS, gp, Quantity, BRepTools
try:
from OCC import V3d, AIS, Graphic3d
except ImportError:
pass
shape_tuple = namedtuple('shape_tuple', ('data', 'geometry', 'styles'))
shape_tuple = namedtuple("shape_tuple", ("data", "geometry", "styles"))
handle, main_loop, add_menu, add_function_to_menu = None, None, None, None
DEFAULT_STYLES = {
"DEFAULT": (.7, .7, .7),
"IfcWall": (.8, .8, .8),
"IfcSite": (.75, .8, .65),
"IfcSlab": (.4, .4, .4),
"IfcWallStandardCase": (.9, .9, .9),
"IfcWall": (.9, .9, .9),
"IfcWindow": (.75, .8, .75, .3),
"IfcDoor": (.55, .3, .15),
"IfcBeam": (.75, .7, .7),
"IfcRailing": (.65, .6, .6),
"IfcMember": (.65, .6, .6),
"IfcPlate": (.8, .8, .8)
"DEFAULT": (0.7, 0.7, 0.7),
"IfcWall": (0.8, 0.8, 0.8),
"IfcSite": (0.75, 0.8, 0.65),
"IfcSlab": (0.4, 0.4, 0.4),
"IfcWallStandardCase": (0.9, 0.9, 0.9),
"IfcWall": (0.9, 0.9, 0.9),
"IfcWindow": (0.75, 0.8, 0.75, 0.3),
"IfcDoor": (0.55, 0.3, 0.15),
"IfcBeam": (0.75, 0.7, 0.7),
"IfcRailing": (0.65, 0.6, 0.6),
"IfcMember": (0.65, 0.6, 0.6),
"IfcPlate": (0.8, 0.8, 0.8),
}
@@ -119,7 +122,7 @@ def display_shape(shape, clr=None, viewer_handle=None):
if representation and not clr:
if len(set(representation.styles)) == 1:
clr = representation.styles[0]
if min(clr) < 0. or max(clr) > 1.:
if min(clr) < 0.0 or max(clr) > 1.0:
clr = DEFAULT_STYLES.get(representation.data.type, DEFAULT_STYLES["DEFAULT"])
if clr:
@@ -127,8 +130,9 @@ def display_shape(shape, clr=None, viewer_handle=None):
ais.SetMaterial(material)
if isinstance(clr, str):
qclr = getattr(Quantity, "Quantity_NOC_%s" % clr.upper(),
getattr(Quantity, "Quantity_NOC_%s1" % clr.upper(), None))
qclr = getattr(
Quantity, "Quantity_NOC_%s" % clr.upper(), getattr(Quantity, "Quantity_NOC_%s1" % clr.upper(), None)
)
if qclr is None:
raise Exception("No color named '%s'" % clr.upper())
elif isinstance(clr, Iterable):
@@ -142,8 +146,8 @@ def display_shape(shape, clr=None, viewer_handle=None):
raise Exception("Object of type %r cannot be used as a color." % type(clr))
ais.SetColor(qclr)
if isinstance(clr, tuple) and len(clr) == 4 and clr[3] < 1.:
ais.SetTransparency(1. - clr[3])
if isinstance(clr, tuple) and len(clr) == 4 and clr[3] < 1.0:
ais.SetTransparency(1.0 - clr[3])
elif representation and hasattr(AIS, "AIS_MultipleConnectedShape"):
default_style_applied = None
@@ -157,13 +161,14 @@ def display_shape(shape, clr=None, viewer_handle=None):
else:
for shp, stl in zip(subshapes, representation.styles):
subshape = AIS.AIS_Shape(shp)
if min(stl) < 0. or max(stl) > 1.:
default_style_applied = stl = DEFAULT_STYLES.get(representation.data.type,
DEFAULT_STYLES["DEFAULT"])
if min(stl) < 0.0 or max(stl) > 1.0:
default_style_applied = stl = DEFAULT_STYLES.get(
representation.data.type, DEFAULT_STYLES["DEFAULT"]
)
subshape.SetColor(Quantity.Quantity_Color(stl[0], stl[1], stl[2], Quantity.Quantity_TOC_RGB))
subshape.SetMaterial(material)
if len(stl) == 4 and stl[3] < 1.:
subshape.SetTransparency(1. - stl[3])
if len(stl) == 4 and stl[3] < 1.0:
subshape.SetTransparency(1.0 - stl[3])
ais.Connect(subshape.GetHandle())
# For some reason it is necessary to set transparency here again
@@ -171,14 +176,14 @@ def display_shape(shape, clr=None, viewer_handle=None):
applied_styles = representation.styles
if default_style_applied:
if len(default_style_applied) == 3:
default_style_applied += (1.,)
default_style_applied += (1.0,)
applied_styles += (default_style_applied,)
if len(applied_styles):
# The only way for this not to be true if is the entire shape is NULL
min_transp = min(map(operator.itemgetter(3), applied_styles))
if min_transp < 1.:
ais.SetTransparency(1.)
if min_transp < 1.0:
ais.SetTransparency(1.0)
else:
ais = AIS.AIS_Shape(shape)
@@ -201,10 +206,10 @@ def set_shape_transparency(ais, t):
def get_bounding_box_center(bbox):
bbmin = [0.] * 3
bbmax = [0.] * 3
bbmin = [0.0] * 3
bbmax = [0.0] * 3
bbmin[0], bbmin[1], bbmin[2], bbmax[0], bbmax[1], bbmax[2] = bbox.Get()
return gp.gp_Pnt(*map(lambda xy: (xy[0] + xy[1]) / 2., zip(bbmin, bbmax)))
return gp.gp_Pnt(*map(lambda xy: (xy[0] + xy[1]) / 2.0, zip(bbmin, bbmax)))
def serialize_shape(shape):
@@ -228,7 +233,7 @@ def create_shape_from_serialization(brep_object):
except BaseException:
pass
styles = tuple(styles[i:i + 4] for i in range(0, len(styles), 4))
styles = tuple(styles[i : i + 4] for i in range(0, len(styles), 4))
if not brep_data:
return shape_tuple(brep_object, None, styles)