mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-10-01 20:49:44 +00:00
Provide manual length, area, volume calc operators in qto UI. See #898.
This commit is contained in:
@@ -160,6 +160,13 @@ if bpy is not None:
|
|||||||
operator.PropagateTextData,
|
operator.PropagateTextData,
|
||||||
operator.PushRepresentation,
|
operator.PushRepresentation,
|
||||||
operator.ConvertLocalToGlobal,
|
operator.ConvertLocalToGlobal,
|
||||||
|
operator.GetObjectLength,
|
||||||
|
operator.GetObjectWidth,
|
||||||
|
operator.GetObjectHeight,
|
||||||
|
operator.GetObjectFootprintArea,
|
||||||
|
operator.GetObjectSideArea,
|
||||||
|
operator.GetObjectArea,
|
||||||
|
operator.GetObjectVolume,
|
||||||
prop.StrProperty,
|
prop.StrProperty,
|
||||||
prop.Variable,
|
prop.Variable,
|
||||||
prop.Role,
|
prop.Role,
|
||||||
|
|||||||
@@ -19,59 +19,6 @@ class ArrayModifier:
|
|||||||
offset: Vector
|
offset: Vector
|
||||||
|
|
||||||
|
|
||||||
class QtoCalculator():
|
|
||||||
def get_units(self, o, vg_index):
|
|
||||||
return len([v for v in o.data.vertices if vg_index in [g.group for g in v.groups]])
|
|
||||||
|
|
||||||
def get_length(self, o, vg_index):
|
|
||||||
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]
|
|
||||||
)]
|
|
||||||
for e in edges:
|
|
||||||
length += self.get_edge_distance(o, e)
|
|
||||||
return length
|
|
||||||
|
|
||||||
def get_edge_distance(self, obj, edge):
|
|
||||||
return (obj.data.vertices[edge.vertices[1]].co - obj.data.vertices[edge.vertices[0]].co).length
|
|
||||||
|
|
||||||
def get_area(self, o, vg_index):
|
|
||||||
area = 0
|
|
||||||
vertices_in_vg = [v.index for v in o.data.vertices if vg_index in [g.group for g in v.groups]]
|
|
||||||
for polygon in o.data.polygons:
|
|
||||||
if self.is_polygon_in_vg(polygon, vertices_in_vg):
|
|
||||||
area += polygon.area
|
|
||||||
return area
|
|
||||||
|
|
||||||
def is_polygon_in_vg(self, polygon, vertices_in_vg):
|
|
||||||
for v in polygon.vertices:
|
|
||||||
if v not in vertices_in_vg:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def get_volume(self, o, vg_index):
|
|
||||||
volume = 0
|
|
||||||
ob_mat = o.matrix_world
|
|
||||||
me = o.data
|
|
||||||
me.calc_loop_triangles()
|
|
||||||
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]]),
|
|
||||||
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]])
|
|
||||||
|
|
||||||
for tf_iter in tf_tris:
|
|
||||||
v1 = ob_mat @ tf_iter[0].co
|
|
||||||
v2 = ob_mat @ tf_iter[1].co
|
|
||||||
v3 = ob_mat @ tf_iter[2].co
|
|
||||||
|
|
||||||
volume += v1.dot(v2.cross(v3)) / 6.0
|
|
||||||
return volume
|
|
||||||
|
|
||||||
|
|
||||||
class IfcParser():
|
class IfcParser():
|
||||||
def __init__(self, ifc_export_settings):
|
def __init__(self, ifc_export_settings):
|
||||||
self.data_dir = ifc_export_settings.data_dir
|
self.data_dir = ifc_export_settings.data_dir
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import ifcopenshell
|
|||||||
import tempfile
|
import tempfile
|
||||||
from . import export_ifc
|
from . import export_ifc
|
||||||
from . import import_ifc
|
from . import import_ifc
|
||||||
|
from . import qto
|
||||||
from . import cut_ifc
|
from . import cut_ifc
|
||||||
from . import svgwriter
|
from . import svgwriter
|
||||||
from . import sheeter
|
from . import sheeter
|
||||||
@@ -63,7 +64,7 @@ class ExportIFC(bpy.types.Operator):
|
|||||||
output_file = bpy.path.ensure_ext(self.filepath, '.ifc')
|
output_file = bpy.path.ensure_ext(self.filepath, '.ifc')
|
||||||
ifc_export_settings = export_ifc.IfcExportSettings.factory(context, output_file, logger)
|
ifc_export_settings = export_ifc.IfcExportSettings.factory(context, output_file, logger)
|
||||||
ifc_parser = export_ifc.IfcParser(ifc_export_settings)
|
ifc_parser = export_ifc.IfcParser(ifc_export_settings)
|
||||||
qto_calculator = export_ifc.QtoCalculator()
|
qto_calculator = qto.QtoCalculator()
|
||||||
ifc_exporter = export_ifc.IfcExporter(ifc_export_settings, ifc_parser, qto_calculator)
|
ifc_exporter = export_ifc.IfcExporter(ifc_export_settings, ifc_parser, qto_calculator)
|
||||||
ifc_export_settings.logger.info('Starting export')
|
ifc_export_settings.logger.info('Starting export')
|
||||||
ifc_exporter.export(context.selected_objects)
|
ifc_exporter.export(context.selected_objects)
|
||||||
@@ -3026,7 +3027,7 @@ class PushRepresentation(bpy.types.Operator):
|
|||||||
ifc_export_settings = export_ifc.IfcExportSettings.factory(context, output_file, logger)
|
ifc_export_settings = export_ifc.IfcExportSettings.factory(context, output_file, logger)
|
||||||
ifc_parser = export_ifc.IfcParser(ifc_export_settings)
|
ifc_parser = export_ifc.IfcParser(ifc_export_settings)
|
||||||
ifc_parser.parse([bpy.context.active_object])
|
ifc_parser.parse([bpy.context.active_object])
|
||||||
qto_calculator = export_ifc.QtoCalculator()
|
qto_calculator = qto.QtoCalculator()
|
||||||
self.ifc_exporter = export_ifc.IfcExporter(ifc_export_settings, ifc_parser, qto_calculator)
|
self.ifc_exporter = export_ifc.IfcExporter(ifc_export_settings, ifc_parser, qto_calculator)
|
||||||
self.ifc_exporter.file = ifcopenshell.file(schema=self.file.schema)
|
self.ifc_exporter.file = ifcopenshell.file(schema=self.file.schema)
|
||||||
self.ifc_exporter.create_origin()
|
self.ifc_exporter.create_origin()
|
||||||
@@ -3145,3 +3146,87 @@ class ConvertLocalToGlobal(bpy.types.Operator):
|
|||||||
|
|
||||||
bpy.context.scene.cursor.location = (eastings, northings, height)
|
bpy.context.scene.cursor.location = (eastings, northings, height)
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
class GetObjectVolume(bpy.types.Operator):
|
||||||
|
bl_idname = 'bim.get_object_volume'
|
||||||
|
bl_label = 'Get Object Volume'
|
||||||
|
qto_index: bpy.props.IntProperty()
|
||||||
|
prop_index: bpy.props.IntProperty()
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
qto_calculator = qto.QtoCalculator()
|
||||||
|
bpy.context.active_object.BIMObjectProperties.qtos[self.qto_index].properties[self.prop_index].string_value = str(round(qto_calculator.get_volume(bpy.context.active_object), 3))
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
class GetObjectFootprintArea(bpy.types.Operator):
|
||||||
|
bl_idname = 'bim.get_object_footprint_area'
|
||||||
|
bl_label = 'Get Object Footprint Area'
|
||||||
|
qto_index: bpy.props.IntProperty()
|
||||||
|
prop_index: bpy.props.IntProperty()
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
qto_calculator = qto.QtoCalculator()
|
||||||
|
bpy.context.active_object.BIMObjectProperties.qtos[self.qto_index].properties[self.prop_index].string_value = str(round(qto_calculator.get_footprint_area(bpy.context.active_object), 3))
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
class GetObjectSideArea(bpy.types.Operator):
|
||||||
|
bl_idname = 'bim.get_object_side_area'
|
||||||
|
bl_label = 'Get Object Side Area'
|
||||||
|
qto_index: bpy.props.IntProperty()
|
||||||
|
prop_index: bpy.props.IntProperty()
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
qto_calculator = qto.QtoCalculator()
|
||||||
|
bpy.context.active_object.BIMObjectProperties.qtos[self.qto_index].properties[self.prop_index].string_value = str(round(qto_calculator.get_side_area(bpy.context.active_object), 3))
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
class GetObjectArea(bpy.types.Operator):
|
||||||
|
bl_idname = 'bim.get_object_area'
|
||||||
|
bl_label = 'Get Object Area'
|
||||||
|
qto_index: bpy.props.IntProperty()
|
||||||
|
prop_index: bpy.props.IntProperty()
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
qto_calculator = qto.QtoCalculator()
|
||||||
|
bpy.context.active_object.BIMObjectProperties.qtos[self.qto_index].properties[self.prop_index].string_value = str(round(qto_calculator.get_area(bpy.context.active_object), 3))
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
class GetObjectLength(bpy.types.Operator):
|
||||||
|
bl_idname = 'bim.get_object_length'
|
||||||
|
bl_label = 'Get Object Length'
|
||||||
|
qto_index: bpy.props.IntProperty()
|
||||||
|
prop_index: bpy.props.IntProperty()
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
qto_calculator = qto.QtoCalculator()
|
||||||
|
bpy.context.active_object.BIMObjectProperties.qtos[self.qto_index].properties[self.prop_index].string_value = str(round(qto_calculator.get_length(bpy.context.active_object), 3))
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
class GetObjectWidth(bpy.types.Operator):
|
||||||
|
bl_idname = 'bim.get_object_width'
|
||||||
|
bl_label = 'Get Object Width'
|
||||||
|
qto_index: bpy.props.IntProperty()
|
||||||
|
prop_index: bpy.props.IntProperty()
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
qto_calculator = qto.QtoCalculator()
|
||||||
|
bpy.context.active_object.BIMObjectProperties.qtos[self.qto_index].properties[self.prop_index].string_value = str(round(qto_calculator.get_width(bpy.context.active_object), 3))
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
class GetObjectHeight(bpy.types.Operator):
|
||||||
|
bl_idname = 'bim.get_object_height'
|
||||||
|
bl_label = 'Get Object Height'
|
||||||
|
qto_index: bpy.props.IntProperty()
|
||||||
|
prop_index: bpy.props.IntProperty()
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
qto_calculator = qto.QtoCalculator()
|
||||||
|
bpy.context.active_object.BIMObjectProperties.qtos[self.qto_index].properties[self.prop_index].string_value = str(round(qto_calculator.get_height(bpy.context.active_object), 3))
|
||||||
|
return {'FINISHED'}
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
from mathutils import Vector
|
||||||
|
|
||||||
|
class QtoCalculator():
|
||||||
|
def get_units(self, o, vg_index):
|
||||||
|
return len([v for v in o.data.vertices if vg_index in [g.group for g in v.groups]])
|
||||||
|
|
||||||
|
def get_length(self, o, vg_index=None):
|
||||||
|
if vg_index is None:
|
||||||
|
x = (Vector(o.bound_box[4]) - Vector(o.bound_box[0])).length
|
||||||
|
y = (Vector(o.bound_box[3]) - Vector(o.bound_box[0])).length
|
||||||
|
return x if x > y else 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]
|
||||||
|
)]
|
||||||
|
for e in edges:
|
||||||
|
length += self.get_edge_distance(o, e)
|
||||||
|
return length
|
||||||
|
|
||||||
|
def get_width(self, o):
|
||||||
|
x = (Vector(o.bound_box[4]) - Vector(o.bound_box[0])).length
|
||||||
|
y = (Vector(o.bound_box[3]) - Vector(o.bound_box[0])).length
|
||||||
|
return x if x < y else y
|
||||||
|
|
||||||
|
def get_height(self, o):
|
||||||
|
return (Vector(o.bound_box[1]) - Vector(o.bound_box[0])).length
|
||||||
|
|
||||||
|
def get_edge_distance(self, obj, edge):
|
||||||
|
return (obj.data.vertices[edge.vertices[1]].co - obj.data.vertices[edge.vertices[0]].co).length
|
||||||
|
|
||||||
|
def get_footprint_area(self, o):
|
||||||
|
area = 0
|
||||||
|
for polygon in o.data.polygons:
|
||||||
|
if round(polygon.center[2], 3) == 0.0 \
|
||||||
|
and round(o.data.vertices[polygon.vertices[0]].co[2], 3) == 0.0:
|
||||||
|
area += polygon.area
|
||||||
|
return area
|
||||||
|
|
||||||
|
def get_side_area(self, o):
|
||||||
|
# There are a few dumb options for this, but this seems the dumbest
|
||||||
|
# until I get more practical experience on what works best.
|
||||||
|
x = (Vector(o.bound_box[4]) - Vector(o.bound_box[0])).length
|
||||||
|
y = (Vector(o.bound_box[3]) - Vector(o.bound_box[0])).length
|
||||||
|
z = (Vector(o.bound_box[1]) - Vector(o.bound_box[0])).length
|
||||||
|
a1 = x * z
|
||||||
|
a2 = y * z
|
||||||
|
return a1 if a1 > a2 else a2
|
||||||
|
|
||||||
|
def get_area(self, o, vg_index=None):
|
||||||
|
if vg_index is None:
|
||||||
|
area = 0
|
||||||
|
for polygon in o.data.polygons:
|
||||||
|
area += polygon.area
|
||||||
|
return area
|
||||||
|
area = 0
|
||||||
|
vertices_in_vg = [v.index for v in o.data.vertices if vg_index in [g.group for g in v.groups]]
|
||||||
|
for polygon in o.data.polygons:
|
||||||
|
if self.is_polygon_in_vg(polygon, vertices_in_vg):
|
||||||
|
area += polygon.area
|
||||||
|
return area
|
||||||
|
|
||||||
|
def is_polygon_in_vg(self, polygon, vertices_in_vg):
|
||||||
|
for v in polygon.vertices:
|
||||||
|
if v not in vertices_in_vg:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_volume(self, o, vg_index=None):
|
||||||
|
volume = 0
|
||||||
|
ob_mat = o.matrix_world
|
||||||
|
me = o.data
|
||||||
|
me.calc_loop_triangles()
|
||||||
|
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]]),
|
||||||
|
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]])
|
||||||
|
|
||||||
|
for tf_iter in tf_tris:
|
||||||
|
v1 = ob_mat @ tf_iter[0].co
|
||||||
|
v2 = ob_mat @ tf_iter[1].co
|
||||||
|
v3 = ob_mat @ tf_iter[2].co
|
||||||
|
|
||||||
|
volume += v1.dot(v2.cross(v3)) / 6.0
|
||||||
|
return volume
|
||||||
@@ -99,10 +99,45 @@ class BIM_PT_object(Panel):
|
|||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.prop(qto, 'name', text='')
|
row.prop(qto, 'name', text='')
|
||||||
row.operator('bim.remove_qto', icon='X', text='').index = index
|
row.operator('bim.remove_qto', icon='X', text='').index = index
|
||||||
for prop in qto.properties:
|
for index2, prop in enumerate(qto.properties):
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.prop(prop, 'name', text='')
|
row.prop(prop, 'name', text='')
|
||||||
row.prop(prop, 'string_value', text='')
|
row.prop(prop, 'string_value', text='')
|
||||||
|
if 'length' in prop.name.lower():
|
||||||
|
op = row.operator('bim.get_object_length', icon='IPO_EASE_IN_OUT', text='')
|
||||||
|
op.qto_index = index
|
||||||
|
op.prop_index = index2
|
||||||
|
elif 'width' in prop.name.lower() \
|
||||||
|
and 'length' not in [p.name for p in qto.properties]:
|
||||||
|
op = row.operator('bim.get_object_length', icon='IPO_EASE_IN_OUT', text='')
|
||||||
|
op.qto_index = index
|
||||||
|
op.prop_index = index2
|
||||||
|
elif 'width' in prop.name.lower():
|
||||||
|
op = row.operator('bim.get_object_width', icon='IPO_EASE_IN_OUT', text='')
|
||||||
|
op.qto_index = index
|
||||||
|
op.prop_index = index2
|
||||||
|
elif 'height' in prop.name.lower():
|
||||||
|
op = row.operator('bim.get_object_height', icon='IPO_EASE_IN_OUT', text='')
|
||||||
|
op.qto_index = index
|
||||||
|
op.prop_index = index2
|
||||||
|
elif 'area' in prop.name.lower() \
|
||||||
|
and 'footprint' in prop.name.lower():
|
||||||
|
op = row.operator('bim.get_object_footprint_area', icon='MESH_CIRCLE', text='')
|
||||||
|
op.qto_index = index
|
||||||
|
op.prop_index = index2
|
||||||
|
elif 'area' in prop.name.lower() \
|
||||||
|
and 'side' in prop.name.lower():
|
||||||
|
op = row.operator('bim.get_object_side_area', icon='MESH_CIRCLE', text='')
|
||||||
|
op.qto_index = index
|
||||||
|
op.prop_index = index2
|
||||||
|
elif 'area' in prop.name.lower():
|
||||||
|
op = row.operator('bim.get_object_area', icon='MESH_CIRCLE', text='')
|
||||||
|
op.qto_index = index
|
||||||
|
op.prop_index = index2
|
||||||
|
elif 'volume' in prop.name.lower():
|
||||||
|
op = row.operator('bim.get_object_volume', icon='SPHERE', text='')
|
||||||
|
op.qto_index = index
|
||||||
|
op.prop_index = index2
|
||||||
|
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
row.prop(props, 'material_type')
|
row.prop(props, 'material_type')
|
||||||
|
|||||||
Reference in New Issue
Block a user