mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-13 10:57:49 +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.PushRepresentation,
|
||||
operator.ConvertLocalToGlobal,
|
||||
operator.GetObjectLength,
|
||||
operator.GetObjectWidth,
|
||||
operator.GetObjectHeight,
|
||||
operator.GetObjectFootprintArea,
|
||||
operator.GetObjectSideArea,
|
||||
operator.GetObjectArea,
|
||||
operator.GetObjectVolume,
|
||||
prop.StrProperty,
|
||||
prop.Variable,
|
||||
prop.Role,
|
||||
|
||||
@@ -19,59 +19,6 @@ class ArrayModifier:
|
||||
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():
|
||||
def __init__(self, ifc_export_settings):
|
||||
self.data_dir = ifc_export_settings.data_dir
|
||||
|
||||
@@ -8,6 +8,7 @@ import ifcopenshell
|
||||
import tempfile
|
||||
from . import export_ifc
|
||||
from . import import_ifc
|
||||
from . import qto
|
||||
from . import cut_ifc
|
||||
from . import svgwriter
|
||||
from . import sheeter
|
||||
@@ -63,7 +64,7 @@ class ExportIFC(bpy.types.Operator):
|
||||
output_file = bpy.path.ensure_ext(self.filepath, '.ifc')
|
||||
ifc_export_settings = export_ifc.IfcExportSettings.factory(context, output_file, logger)
|
||||
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_export_settings.logger.info('Starting export')
|
||||
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_parser = export_ifc.IfcParser(ifc_export_settings)
|
||||
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.file = ifcopenshell.file(schema=self.file.schema)
|
||||
self.ifc_exporter.create_origin()
|
||||
@@ -3145,3 +3146,87 @@ class ConvertLocalToGlobal(bpy.types.Operator):
|
||||
|
||||
bpy.context.scene.cursor.location = (eastings, northings, height)
|
||||
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.prop(qto, 'name', text='')
|
||||
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.prop(prop, 'name', 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.prop(props, 'material_type')
|
||||
|
||||
Reference in New Issue
Block a user