mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 09:48:32 +00:00
Text annotation can now extract dynamic variables from IFC data
This commit is contained in:
@@ -122,7 +122,10 @@ if bpy is not None:
|
||||
operator.SelectDocIfcFile,
|
||||
operator.AddAnnotation,
|
||||
operator.ResizeText,
|
||||
operator.AddVariable,
|
||||
operator.RemoveVariable,
|
||||
prop.StrProperty,
|
||||
prop.Variable,
|
||||
prop.Classification,
|
||||
prop.ClassificationReference,
|
||||
prop.ClassificationView,
|
||||
|
||||
@@ -4,6 +4,17 @@ from mathutils import Vector
|
||||
|
||||
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',
|
||||
}
|
||||
return float(sizes[str(size)])
|
||||
|
||||
@staticmethod
|
||||
def add_text():
|
||||
curve = bpy.data.curves.new(type='FONT', name='Text')
|
||||
|
||||
@@ -3,8 +3,10 @@ import math
|
||||
import time
|
||||
import numpy
|
||||
import pickle
|
||||
import pystache
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from . import annotation
|
||||
mathutils = sys.modules.get('mathutils')
|
||||
if mathutils is not None:
|
||||
from mathutils import Vector
|
||||
@@ -121,17 +123,21 @@ def do_cut(process_data):
|
||||
|
||||
class IfcCutter:
|
||||
def __init__(self):
|
||||
self.ifc_attribute_extractor = None
|
||||
self.product_shapes = []
|
||||
self.background_elements = []
|
||||
self.cut_polygons = []
|
||||
self.template_variables = {}
|
||||
self.data_dir = ''
|
||||
self.ifc_files = []
|
||||
self.unit = None
|
||||
self.resolved_pixels = set()
|
||||
self.should_get_background = False
|
||||
self.shapes_pickle_file = 'shapes.pickle'
|
||||
self.text_pickle_file = 'text.pickle'
|
||||
self.cut_pickle_file = 'cut.pickle'
|
||||
self.should_recut = True
|
||||
self.should_extract = True
|
||||
self.diagram_name = None
|
||||
self.background_image = None
|
||||
self.section_box = {
|
||||
@@ -152,6 +158,10 @@ class IfcCutter:
|
||||
self.load_ifc_files()
|
||||
print('# Timer logged at {:.2f} seconds'.format(time.time() - start_time))
|
||||
start_time = time.time()
|
||||
print('# Extract template variables')
|
||||
self.get_template_variables()
|
||||
print('# Timer logged at {:.2f} seconds'.format(time.time() - start_time))
|
||||
start_time = time.time()
|
||||
print('# Get product shapes')
|
||||
self.get_product_shapes()
|
||||
print('# Timer logged at {:.2f} seconds'.format(time.time() - start_time))
|
||||
@@ -185,7 +195,7 @@ class IfcCutter:
|
||||
print('# Timer logged at {:.2f} seconds'.format(time.time() - start_time))
|
||||
|
||||
def load_ifc_files(self):
|
||||
if not self.should_recut:
|
||||
if not self.should_recut and not self.should_extract:
|
||||
return
|
||||
|
||||
loaded_files = []
|
||||
@@ -195,6 +205,29 @@ class IfcCutter:
|
||||
loaded_files.append(ifcopenshell.open(ifc_file))
|
||||
self.ifc_files = loaded_files
|
||||
|
||||
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:
|
||||
self.template_variables = pickle.load(text_file)
|
||||
return
|
||||
|
||||
data = {}
|
||||
for text_obj in self.text_objs:
|
||||
text_obj_data = {}
|
||||
text_body = text_obj.data.body
|
||||
for variable in text_obj.data.BIMTextProperties.variables:
|
||||
element = self.get_ifc_element(variable.global_id)
|
||||
if element:
|
||||
text_obj_data[variable.name] = self.ifc_attribute_extractor.get_element_key(element, variable.prop_key)
|
||||
if text_obj_data:
|
||||
data[text_obj.name] = text_obj_data
|
||||
|
||||
with open(self.text_pickle_file, 'wb') as text_file:
|
||||
pickle.dump(data, text_file, protocol=pickle.HIGHEST_PROTOCOL)
|
||||
|
||||
self.template_variables = data
|
||||
|
||||
def get_product_shapes(self):
|
||||
if not self.should_recut:
|
||||
return
|
||||
@@ -797,14 +830,14 @@ class SvgWriter():
|
||||
line['marker-end'] = 'url(#grid-marker)'
|
||||
line['stroke-dasharray'] = '12.5, 3, 3, 3'
|
||||
self.svg.add(self.svg.text(grid_obj.name.split('/')[1], insert=tuple(start * self.scale), **{
|
||||
'font-size': '8.25', # 5
|
||||
'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(grid_obj.name.split('/')[1], insert=tuple(end * self.scale), **{
|
||||
'font-size': '8.25', # 5
|
||||
'font-size': annotation.Annotator.get_svg_text_size(5.0),
|
||||
'font-family': 'OpenGost Type B TT',
|
||||
'text-anchor': 'middle',
|
||||
'alignment-baseline': 'middle',
|
||||
@@ -859,7 +892,7 @@ class SvgWriter():
|
||||
else:
|
||||
text_anchor = 'start'
|
||||
self.svg.add(self.svg.text('RL +{:.3f}m'.format(rl), insert=tuple(text_position), **{
|
||||
'font-size': '4.13', # 2.5
|
||||
'font-size': annotation.Annotator.get_svg_text_size(2.5),
|
||||
'font-family': 'OpenGost Type B TT',
|
||||
'text-anchor': text_anchor,
|
||||
'alignment-baseline': 'baseline',
|
||||
@@ -886,7 +919,7 @@ class SvgWriter():
|
||||
# TODO: unhardcode m unit
|
||||
rl = (matrix_world @ points[0].co.xyz).z
|
||||
self.svg.add(self.svg.text('RL +{:.3f}m'.format(rl), insert=tuple(text_position), **{
|
||||
'font-size': '4.13', # 2.5
|
||||
'font-size': annotation.Annotator.get_svg_text_size(2.5),
|
||||
'font-family': 'OpenGost Type B TT',
|
||||
'text-anchor': 'start',
|
||||
'alignment-baseline': 'baseline',
|
||||
@@ -906,7 +939,7 @@ class SvgWriter():
|
||||
path['marker-start'] = 'url(#stair-marker-start)'
|
||||
path['marker-end'] = 'url(#stair-marker-end)'
|
||||
self.svg.add(self.svg.text('UP', insert=tuple(text_position), **{
|
||||
'font-size': '4.13', # 2.5
|
||||
'font-size': annotation.Annotator.get_svg_text_size(2.5),
|
||||
'font-family': 'OpenGost Type B TT',
|
||||
'text-anchor': 'middle',
|
||||
'alignment-baseline': 'middle',
|
||||
@@ -944,12 +977,16 @@ class SvgWriter():
|
||||
else:
|
||||
alignment_baseline = 'baseline'
|
||||
|
||||
for line_number, text_line in enumerate(text_obj.data.body.split('\n')):
|
||||
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))),
|
||||
**{
|
||||
'font-size': '4.13', # 2.5
|
||||
'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,
|
||||
@@ -990,8 +1027,6 @@ class SvgWriter():
|
||||
end=tuple(end * self.scale), class_=' '.join(classes)))
|
||||
line['marker-start'] = 'url(#dimension-marker-start)'
|
||||
line['marker-end'] = 'url(#dimension-marker-end)'
|
||||
# Standard font sizes 1.8, 2.5, 3.5, 5, 7
|
||||
# Equivalent for OpenGost Type B: 2.97, 4.13, 5.78, 8.25, 11.55
|
||||
if text_override is not None:
|
||||
text = text_override
|
||||
else:
|
||||
@@ -1002,7 +1037,7 @@ class SvgWriter():
|
||||
text_position.x,
|
||||
text_position.y
|
||||
),
|
||||
'font-size': '4.13', # 2.5
|
||||
'font-size': annotation.Annotator.get_svg_text_size(2.5),
|
||||
'font-family': 'OpenGost Type B TT',
|
||||
'text-anchor': 'middle'
|
||||
}))
|
||||
|
||||
@@ -1575,6 +1575,8 @@ class CutSection(bpy.types.Operator):
|
||||
y_axis = camera.matrix_world.to_quaternion() @ Vector((0, -1, 0))
|
||||
top_left_corner = location - (width / 2 * x_axis) - (height / 2 * y_axis)
|
||||
ifc_cutter = cut_ifc.IfcCutter()
|
||||
import ifccsv
|
||||
ifc_cutter.ifc_attribute_extractor = ifccsv.IfcAttributeExtractor
|
||||
ifc_cutter.ifc_files = [i.name for i in bpy.context.scene.DocProperties.ifc_files]
|
||||
ifc_cutter.data_dir = bpy.context.scene.BIMProperties.data_dir
|
||||
ifc_cutter.diagram_name = self.diagram_name
|
||||
@@ -1625,6 +1627,7 @@ class CutSection(bpy.types.Operator):
|
||||
ifc_cutter.shapes_pickle_file = os.path.join(ifc_cutter.data_dir, 'shapes.pickle')
|
||||
ifc_cutter.cut_pickle_file = os.path.join(ifc_cutter.data_dir, '{}-cut.pickle'.format(self.diagram_name))
|
||||
ifc_cutter.should_recut = bpy.context.scene.DocProperties.should_recut
|
||||
ifc_cutter.should_extract = bpy.context.scene.DocProperties.should_extract
|
||||
svg_writer = cut_ifc.SvgWriter(ifc_cutter)
|
||||
numerator, denominator = camera.data.BIMCameraProperties.diagram_scale.split(':')
|
||||
if camera.data.BIMCameraProperties.is_nts:
|
||||
@@ -2421,3 +2424,22 @@ class ResizeText(bpy.types.Operator):
|
||||
if isinstance(obj.data, bpy.types.TextCurve):
|
||||
annotation.Annotator.resize_text(obj)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class AddVariable(bpy.types.Operator):
|
||||
bl_idname = 'bim.add_variable'
|
||||
bl_label = 'Add Variable'
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.active_object.data.BIMTextProperties.variables.add()
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class RemoveVariable(bpy.types.Operator):
|
||||
bl_idname = 'bim.remove_variable'
|
||||
bl_label = 'Remove Variable'
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.active_object.data.BIMTextProperties.variables.remove(self.index)
|
||||
return {'FINISHED'}
|
||||
|
||||
@@ -375,6 +375,12 @@ class StrProperty(PropertyGroup):
|
||||
pass
|
||||
|
||||
|
||||
class Variable(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
global_id: StringProperty(name="Global ID")
|
||||
prop_key: StringProperty(name="Property Key")
|
||||
|
||||
|
||||
class Subcontext(PropertyGroup):
|
||||
name: StringProperty(name='Name')
|
||||
target_view: StringProperty(name='Target View')
|
||||
@@ -383,6 +389,7 @@ class Subcontext(PropertyGroup):
|
||||
class DocProperties(PropertyGroup):
|
||||
should_recut: BoolProperty(name="Should Recut", default=True)
|
||||
should_render: BoolProperty(name="Should Render", default=True)
|
||||
should_extract: BoolProperty(name="Should Extract", default=True)
|
||||
view_name: StringProperty(name="View Name")
|
||||
available_views: EnumProperty(items=getViews, name="Available Views")
|
||||
sheet_name: StringProperty(name="Sheet Name", update=refreshSheets)
|
||||
@@ -398,16 +405,17 @@ class BIMCameraProperties(PropertyGroup):
|
||||
|
||||
class BIMTextProperties(PropertyGroup):
|
||||
font_size: EnumProperty(items=[
|
||||
('1.8', '1.8', ''),
|
||||
('2.5', '2.5', ''),
|
||||
('3.5', '3.5', ''),
|
||||
('5.0', '5.0', ''),
|
||||
('7.0', '7.0', ''),
|
||||
('1.8', '1.8 - Small', ''),
|
||||
('2.5', '2.5 - Regular', ''),
|
||||
('3.5', '3.5 - Large', ''),
|
||||
('5.0', '5.0 - Header', ''),
|
||||
('7.0', '7.0 - Title', ''),
|
||||
], update=refreshFontSize, name='Font Size')
|
||||
symbol: EnumProperty(items=[
|
||||
('None', 'None', ''),
|
||||
('rectangle-tag', 'Rectangle Tag', ''),
|
||||
], update=refreshFontSize, name='Symbol')
|
||||
variables: CollectionProperty(name='Variables', type=Variable)
|
||||
|
||||
|
||||
class DocumentInformation(PropertyGroup):
|
||||
|
||||
@@ -626,9 +626,10 @@ class BIM_PT_camera(Panel):
|
||||
|
||||
row = layout.row()
|
||||
row.prop(dprops, 'should_recut')
|
||||
|
||||
row = layout.row()
|
||||
row.prop(dprops, 'should_render')
|
||||
row = layout.row()
|
||||
row.prop(dprops, 'should_extract')
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, 'is_nts')
|
||||
@@ -663,6 +664,18 @@ class BIM_PT_text(Panel):
|
||||
row = layout.row()
|
||||
row.prop(props, 'symbol')
|
||||
|
||||
row = layout.row()
|
||||
row.operator('bim.add_variable')
|
||||
|
||||
for index, variable in enumerate(props.variables):
|
||||
row = layout.row(align=True)
|
||||
row.prop(variable, 'name')
|
||||
row.operator('bim.remove_variable', icon='X', text='').index = index
|
||||
row = layout.row()
|
||||
row.prop(variable, 'global_id')
|
||||
row = layout.row()
|
||||
row.prop(variable, 'prop_key')
|
||||
|
||||
|
||||
class BIM_PT_owner(Panel):
|
||||
bl_label = "Owner History"
|
||||
|
||||
Reference in New Issue
Block a user