From 82b201a14ea63514700cde3158dd0a0891bfa9f6 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 12 Feb 2023 15:01:47 +1100 Subject: [PATCH] Fix #2352. Text styling is now possible with custom classes. --- .../bim/data/pset/EPset_Annotation.ifc | 6 +- .../blenderbim/bim/data/styles/default.css | 7 +- .../bim/module/drawing/annotation.py | 11 -- .../bim/module/drawing/svgwriter.py | 25 +--- .../blenderbim/bim/module/drawing/ui.py | 5 - src/blenderbim/blenderbim/bim/operator.py | 2 + src/blenderbim/blenderbim/tool/drawing.py | 4 +- .../ifcopenshell/util/element.py | 126 +++++++++++++++++- 8 files changed, 135 insertions(+), 51 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/data/pset/EPset_Annotation.ifc b/src/blenderbim/blenderbim/bim/data/pset/EPset_Annotation.ifc index 78742d16b2..8aabdfd00b 100644 --- a/src/blenderbim/blenderbim/bim/data/pset/EPset_Annotation.ifc +++ b/src/blenderbim/blenderbim/bim/data/pset/EPset_Annotation.ifc @@ -5,9 +5,9 @@ FILE_NAME('EPset_Annotation.ifc','2020-01-01T00:00:00',(),(),'EPset_Annotation', FILE_SCHEMA(('IFC4')); ENDSEC; DATA; -#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3)); +#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4)); #2=IFCSIMPLEPROPERTYTEMPLATE('2P7JN79n96Q9pElZ83LKe4',$,'ZIndex','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.); -#3=IFCSIMPLEPROPERTYTEMPLATE('1Wpx_r2xj1_9w5JpI0QRJy',$,'Symbol','',.P_ENUMERATEDVALUE.,'IfcLabel',$,#4,$,$,$,.READWRITE.); -#4=IFCPROPERTYENUMERATION('Symbol',(IFCLABEL('rectangle-tag'), IFCLABEL('door-tag')),$); +#3=IFCSIMPLEPROPERTYTEMPLATE('1Wpx_r2xj1_9w5JpI0QRJy',$,'Symbol','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.); +#4=IFCSIMPLEPROPERTYTEMPLATE('3q0oxMUKP47vZ4jnyG$dDb',$,'Classes','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.); ENDSEC; END-ISO-10303-21; diff --git a/src/blenderbim/blenderbim/bim/data/styles/default.css b/src/blenderbim/blenderbim/bim/data/styles/default.css index e87a177590..36f81edcd3 100644 --- a/src/blenderbim/blenderbim/bim/data/styles/default.css +++ b/src/blenderbim/blenderbim/bim/data/styles/default.css @@ -19,7 +19,7 @@ */ * { stroke-linecap: round; stroke-linejoin: round; } -text { fill: black; stroke: none; } +text { /* 2.5mm */ fill: black; stroke: none; font-family: 'OpenGost Type B TT'; font-size: 4.13px; dominant-baseline: baseline; text-anchor: start; } .cut { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; fill-rule: evenodd; } .projection { fill: white; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; } .annotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; } @@ -44,6 +44,11 @@ text { fill: black; stroke: none; } .PredefinedType-TEXT { fill: black; stroke: none; } path.PredefinedType-TEXTLEADER { marker-end: url(#leader-marker); } text.PredefinedType-TEXTLEADER { fill: black; stroke: none; } +text.title { /* 7mm */ fill: black; stroke: none; font-family: 'OpenGost Type B TT'; font-size: 11.55px; } +text.header { /* 5mm */ fill: black; stroke: none; font-family: 'OpenGost Type B TT'; font-size: 8.25px; } +text.large { /* 3.5mm */ fill: black; stroke: none; font-family: 'OpenGost Type B TT'; font-size: 5.78px; } +text.regular { /* 2.5mm */ fill: black; stroke: none; font-family: 'OpenGost Type B TT'; font-size: 4.13px; } +text.small { /* 1.8mm */ fill: black; stroke: none; font-family: 'OpenGost Type B TT'; font-size: 2.97px; } .material-blank { fill: white; } .material-diagonal1 { fill: url(#diagonal1); } .material-diagonal2 { fill: url(#diagonal2); } diff --git a/src/blenderbim/blenderbim/bim/module/drawing/annotation.py b/src/blenderbim/blenderbim/bim/module/drawing/annotation.py index d3e7a7aa94..6dc2ee095d 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/annotation.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/annotation.py @@ -23,17 +23,6 @@ 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(related_element=None): curve = bpy.data.curves.new(type="FONT", name="Text") diff --git a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py index b0b48209ce..3a1810d5c1 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py @@ -202,8 +202,6 @@ class SvgWriter: rl *= unit_scale rl = "{:.3f}m".format(rl) text_style = { - "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", @@ -236,8 +234,6 @@ class SvgWriter: "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", @@ -270,8 +266,6 @@ class SvgWriter: axis_tag, insert=tuple(start * self.svg_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", @@ -283,8 +277,6 @@ class SvgWriter: axis_tag, insert=tuple(end * self.svg_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", @@ -325,6 +317,9 @@ class SvgWriter: str(ifcopenshell.util.element.get_predefined_type(element)) ) classes = [global_id, element.is_a(), predefined_type] + custom_classes = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Classes") + if custom_classes: + classes.extend(custom_classes.split()) for key in self.metadata: value = ifcopenshell.util.selector.Selector.get_element_value(element, key) if value: @@ -413,8 +408,6 @@ class SvgWriter: reference_id, sheet_id = self.get_reference_and_sheet_id_from_annotation(tool.Ifc.get_entity(obj)) text_position = list(symbol_position * self.svg_scale) text_style = { - "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", @@ -446,8 +439,6 @@ class SvgWriter: reference_id, sheet_id = self.get_reference_and_sheet_id_from_annotation(tool.Ifc.get_entity(obj)) text_position = list(symbol_position * self.svg_scale) text_style = { - "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", @@ -544,8 +535,6 @@ class SvgWriter: insert=tuple((text_position * self.svg_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.BIMTextProperties.font_size), - "font-family": "OpenGost Type B TT", "text-anchor": text_anchor, "alignment-baseline": alignment_baseline, "dominant-baseline": alignment_baseline, @@ -618,8 +607,6 @@ class SvgWriter: "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", @@ -740,8 +727,6 @@ class SvgWriter: text_position += text_offset text_style = { - "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", @@ -808,8 +793,6 @@ class SvgWriter: text_position += text_offset text_style = { - "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", @@ -898,8 +881,6 @@ class SvgWriter: 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", }, ) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/ui.py b/src/blenderbim/blenderbim/bim/module/drawing/ui.py index 6ded9c3307..f9a7fa257a 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/ui.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/ui.py @@ -355,11 +355,6 @@ class BIM_PT_text(Panel): row.label(text=attribute["name"]) row.label(text=attribute["value"]) - row = self.layout.row() - row.prop(props, "font_size") - row = self.layout.row() - row.prop(props, "symbol") - class BIM_PT_annotation_utilities(Panel): bl_idname = "BIM_PT_annotation_utilities" diff --git a/src/blenderbim/blenderbim/bim/operator.py b/src/blenderbim/blenderbim/bim/operator.py index 5cefab007f..b9546623e1 100644 --- a/src/blenderbim/blenderbim/bim/operator.py +++ b/src/blenderbim/blenderbim/bim/operator.py @@ -160,6 +160,8 @@ class OpenUpstream(bpy.types.Operator): webbrowser.open("https://wiki.osarch.org/index.php?title=Category:BlenderBIM_Add-on") elif self.page == "community": webbrowser.open("https://community.osarch.org/") + elif self.page == "fund": + webbrowser.open("https://opencollective.com/opensourcebim") return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/tool/drawing.py b/src/blenderbim/blenderbim/tool/drawing.py index 1d9e0563a2..1ac064ca57 100644 --- a/src/blenderbim/blenderbim/tool/drawing.py +++ b/src/blenderbim/blenderbim/tool/drawing.py @@ -962,11 +962,11 @@ class Drawing(blenderbim.core.tool.Drawing): @classmethod def get_annotation_z_index(cls, drawing): - return ifcopenshell.util.element.get_psets(drawing).get("EPset_Annotation", {}).get("ZIndex", 0) + return ifcopenshell.util.element.get_pset(drawing, "EPset_Annotation", "ZIndex") or 0 @classmethod def get_annotation_symbol(cls, drawing): - return ifcopenshell.util.element.get_psets(drawing).get("EPset_Annotation", {}).get("Symbol", None) + return ifcopenshell.util.element.get_pset(drawing, "EPset_Annotation", "Symbol") @classmethod def has_linework(cls, drawing): diff --git a/src/ifcopenshell-python/ifcopenshell/util/element.py b/src/ifcopenshell-python/ifcopenshell/util/element.py index 4861af986a..9e4d7e6e7b 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/element.py +++ b/src/ifcopenshell-python/ifcopenshell/util/element.py @@ -19,20 +19,95 @@ import ifcopenshell -def get_psets(element, psets_only=False, qtos_only=False, should_inherit=True): - """Retrieve property sets, their related properties' names & values and ids. +def get_pset(element, name, prop=None, should_inherit=True): + """Retrieve a single property set or single property + + This is more efficient than ifcopenshell.util.element.get_psets if you know + exactly which property set and property you are after. :param element: The IFC Element entity - :param psets_only: Default as False. Set to true if only property sets are needed. - :param qtos_only: Default as False. Set to true if only quantities are needed. + :type element: ifcopenshell.entity_instance.entity_instance + :param name: The name of the pset + :type name: str + :param prop: The name of the property + :type name: str,optional :param should_inherit: Default as True. Set to false if you don't want to inherit property sets from the Type. - :return: dictionnary: key, value pair of psets' names and their properties' names & values + :type should_inherit: bool,optional + :return: A dictionary of property names and values, or a single value if a + property is specified. + :rtype: dict Example: .. code:: python - element = ifcopenshell.by_type("IfcBuildingElement")[0] + element = ifcopenshell.by_type("IfcWall")[0] + psets_and_qtos = ifcopenshell.util.element.get_pset(element, "Pset_WallCommon") + """ + pset = None + if element.is_a("IfcTypeObject"): + for definition in element.HasPropertySets or []: + if definition.Name == name: + pset = definition + break + elif element.is_a("IfcMaterialDefinition") or element.is_a("IfcProfileDef"): + for definition in element.HasProperties or []: + if definition.Name == name: + pset = definition + break + elif hasattr(element, "IsDefinedBy"): + element_type = ifcopenshell.util.element.get_type(element) + if element_type and should_inherit: + result = get_pset(element_type, name, prop, should_inherit=False) + if result: + return result + for relationship in element.IsDefinedBy: + if relationship.is_a("IfcRelDefinesByProperties"): + definition = relationship.RelatingPropertyDefinition + if definition.Name == name: + pset = definition + break + + if not pset: + return + + if not prop: + return get_property_definition(pset) + + if definition.is_a("IfcElementQuantity"): + return get_quantity(definition.Quantities, prop) + elif definition.is_a("IfcPropertySet"): + return get_property(definition.HasProperties, prop) + elif definition.is_a("IfcMaterialProperties") or definition.is_a("IfcProfileProperties"): + return get_property(definition.Properties, prop) + else: + # Entity introduced in IFC4 + # definition.is_a('IfcPreDefinedPropertySet'): + for i in range(4, len(definition)): + if definition[i] is not None: + if definition.attribute_name(i) == prop: + return definition[i] + + +def get_psets(element, psets_only=False, qtos_only=False, should_inherit=True): + """Retrieve property sets, their related properties' names & values and ids. + + :param element: The IFC Element entity + :type element: ifcopenshell.entity_instance.entity_instance + :param psets_only: Default as False. Set to true if only property sets are needed. + :type psets_only: bool,optional + :param qtos_only: Default as False. Set to true if only quantities are needed. + :type qtos_only: bool,optional + :param should_inherit: Default as True. Set to false if you don't want to inherit property sets from the Type. + :type should_inherit: bool,optional + :return: Key, value pair of psets' names and their properties' names & values + :rtype: dict + + Example: + + .. code:: python + + element = ifcopenshell.by_type("IfcWall")[0] psets = ifcopenshell.util.element.get_psets(element, psets_only=True) qsets = ifcopenshell.util.element.get_psets(element, qtos_only=True) psets_and_qtos = ifcopenshell.util.element.get_psets(element) @@ -84,7 +159,21 @@ def get_property_definition(definition): return props -def get_quantities(quantities): +def get_quantity(quantities, name): + for quantity in quantities or []: + if quantity.Name != name: + continue + if quantity.is_a("IfcPhysicalSimpleQuantity"): + return quantity[3] + results[quantity.Name] = quantity[3] + elif quantity.is_a("IfcPhysicalComplexQuantity"): + data = {k: v for k, v in quantity.get_info().items() if v is not None and k != "Name"} + data["properties"] = get_quantities(quantity.HasQuantities) + del data["HasQuantities"] + return data + + +def get_quantities(quantities, name=None): results = {} for quantity in quantities or []: if quantity.is_a("IfcPhysicalSimpleQuantity"): @@ -97,6 +186,29 @@ def get_quantities(quantities): return results +def get_property(properties, name): + for prop in properties or []: + if prop.Name != name: + continue + if prop.is_a("IfcPropertySingleValue"): + return prop.NominalValue.wrappedValue if prop.NominalValue else None + elif prop.is_a("IfcPropertyEnumeratedValue"): + return [v.wrappedValue for v in prop.EnumerationValues] if prop.EnumerationValues else None + elif prop.is_a("IfcPropertyListValue"): + return [v.wrappedValue for v in prop.ListValues] or None + elif prop.is_a("IfcPropertyBoundedValue"): + data = prop.get_info() + del data["Unit"] + return data + elif prop.is_a("IfcPropertyTableValue"): + return prop.get_info() + elif prop.is_a("IfcComplexProperty"): + data = {k: v for k, v in prop.get_info().items() if v is not None and k != "Name"} + data["properties"] = get_properties(prop.HasProperties) + del data["HasProperties"] + return data + + def get_properties(properties): results = {} for prop in properties or []: