mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-19 03:33:48 +00:00
black ifcblenderexport
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
class BcfStore():
|
||||
class BcfStore:
|
||||
topics = []
|
||||
viewpoints = []
|
||||
comments = []
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
@@ -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"]
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user