diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index b62f7eb5e2..af55393134 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -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, diff --git a/src/ifcblenderexport/blenderbim/bim/annotation.py b/src/ifcblenderexport/blenderbim/bim/annotation.py index 8277d243f1..7b0b66d4f1 100644 --- a/src/ifcblenderexport/blenderbim/bim/annotation.py +++ b/src/ifcblenderexport/blenderbim/bim/annotation.py @@ -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') diff --git a/src/ifcblenderexport/blenderbim/bim/cut_ifc.py b/src/ifcblenderexport/blenderbim/bim/cut_ifc.py index 1d6a94ac30..1541d807ba 100644 --- a/src/ifcblenderexport/blenderbim/bim/cut_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/cut_ifc.py @@ -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' })) diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index d853a820af..ac97c1dfbc 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -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'} diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index e600141f90..7875d111e2 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -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): diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index c57361a46f..c9773b797f 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -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"