From 9ff4a7f0e0c23b85491c3478f1fa90d66088822d Mon Sep 17 00:00:00 2001 From: Ryan Schultz Date: Sun, 22 Feb 2026 15:10:29 -0600 Subject: [PATCH 01/16] Table was not rendering correctly. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix Sphinx docs: replace csv-table with list-table for formatting functions The documentation table of formatting/query functions was not rendering because `.. csv-table::` requires strict RFC4180 CSV escaping. The table contains nested quotes, inch marks (e.g. `3' - 0"`), backticks, and code examples, which cause the CSV parser in docutils to treat rows as malformed and drop the entire directive. Replaced the directive with `.. list-table::`, which parses reStructuredText instead of CSV and safely supports inline code, quotes, and multi-line cells. Also moved the examples text outside the directive block and ensured a blank line after the table so Sphinx does not interpret following paragraphs as table rows. No content changes — documentation now renders correctly. Generated with the assistance of an AI coding tool. --- .../ifcopenshell-python/selector_syntax.rst | 92 ++++++++++++++++--- 1 file changed, 77 insertions(+), 15 deletions(-) diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python/selector_syntax.rst b/src/ifcopenshell-python/docs/ifcopenshell-python/selector_syntax.rst index 2d0cc9e071..4f994daced 100644 --- a/src/ifcopenshell-python/docs/ifcopenshell-python/selector_syntax.rst +++ b/src/ifcopenshell-python/docs/ifcopenshell-python/selector_syntax.rst @@ -240,22 +240,84 @@ in spreadsheets. For example ``upper("foo")`` will produce ``FOO``. You may nest formulas, for example ``concat(title("foo"), lower("Bar"))`` will produce ``Foobar``. Strings must be double quoted. -.. csv-table:: - :header: "Function", "Example", "Result", "Description" +.. list-table:: + :header-rows: 1 + :widths: 28 28 16 28 + + * - Function + - Example + - Result + - Description + + * - ``upper({{value}})`` + - ``upper("Foo")`` + - ``FOO`` + - Uppercases a string. + + * - ``lower({{value}})`` + - ``lower("Foo")`` + - ``foo`` + - Lowercases a string. + + * - ``title({{value}})`` + - ``title("foo")`` + - ``Foo`` + - Titlecases a string. + + * - ``concat({{value}}[, {{value2}}]*)`` + - ``concat("foo", "bar")`` + - ``foobar`` + - Concatenates two or more strings. + + * - ``round({{value}}, {{precision}})`` + - ``round(3.123, 0.1)`` + - ``3.1`` + - Rounds ``{{value}}`` to the nearest ``{{precision}}``. + + * - ``int({{value}})`` + - ``int(3.123)`` + - ``3`` + - Truncates the decimal part of the ``{{value}}``. + + * - ``number({{value}}[, {{decimal_separator}}[, {{thousands_separator}}]])`` + - ``number(1234.56, ",", ".")`` + - ``1.234,56`` + - Formats ``{{value}}`` with an optional custom ``{{decimal_separator}}`` and ``{{thousands_separator}}``. The default separators are ``.`` and ``,``. + + * - ``metric_length({{value}}, {{precision}}, {{decimals}})`` + - ``metric_length(3.123, 0.1, 2)`` + - ``3.10`` + - Rounds ``{{value}}`` to the nearest ``{{precision}}`` then displays using a certain amount of decimal places. + + * - ``imperial_length({{value}}, {{precision}}, {{input_unit}}, {{output_unit}}, {{suppress_zero_inches}})`` + - ``imperial_length(3.0, 4, "foot", "foot", true)`` + OR + ``imperial_length(3.0, 4, "foot", "foot", false)`` + - ``3'`` + OR + ``3' - 0"`` + - The ``{{value}}`` may be specified either as ``foot`` or ``inch`` depending on ``{{input_unit}}``. The ``{{value}}`` is rounded to the nearest ``1/{{precision}}`` inch, then formatted using fractional feet and inches if ``{{output_unit}}`` is ``foot``, or just inches if ``{{output_unit}}`` is ``inch``. When ``{{suppress_zero_inches}}`` is ``true`` (default), measurements with zero inches omit the inch portion (e.g., ``3'`` instead of ``3' - 0"``). + + * - ``sort({{values}})`` + - ``sort({{mats.Name}})`` + - ``Name1, Name2`` + - Sorts a list of items. + + * - ``reverse({{values}})`` + - ``reverse({{mats.Name}})`` + - ``Name2, Name1`` + - Reverses a list of items. + + * - ``join({{separator}}, {{values}})`` + - ``join("-", {{mats.Name}})`` + - ``Name1-Name2`` + - Joins a list of items with a custom separator. By default, lists are rendered as comma separated. + + * - ``{{value1}}[+-*/]{{value2}}`` + - ``{{z}}+3`` + - ``5`` + - Does arithmetic. Operators such as ``+``, ``-``, ``*``, and ``/`` are allowed and can be mixed with variables and formatting functions. - "``upper({{value}})``", "``upper(""Foo"")``", "``FOO``", "Uppercases a string." - "``lower({{value}})``", "``lower(""Foo"")``", "``foo``", "Lowercases a string." - "``title({{value}})``", "``title(""foo"")``", "``Foo``", "Titlecases a string." - "``concat({{value}}[, {{value2}}]*)``", "``concat(""foo"", ""bar"")``", "``foobar``", "Concatenates two or more strings." - "``round({{value}}, {{precision}})``", "``round(3.123, 0.1)``", "``3.1``", "Rounds ``{{value}}`` to the nearest ``{{precision}}``." - "``int({{value}})``", "``int(3.123)``", "``3``", "Truncates the decimal part of the ``{{value}}``." - "``number({{value}}[, {{decimal_separator}}[, {{thousands_separator}}]])``", "``number(1234.56, "","", ""."")``", "``1.234,56``", "Formats {{value}} with an optional custom {{decimal_separator}} and {{thousands_separator}}. The default separators are ``.`` and ``,``." - "``metric_length({{value}}, {{precision}}, {{decimals}})``", "``metric_length(3.123, 0.1, 2)``", "``3.10``", "Rounds ``{{value}}`` to the nearest ``{{precision}}`` then displays using a certain amount of decimal places." - "``imperial_length({{value}}, {{precision}}, {{input_unit}}, {{output_unit}}, {{suppress_zero_inches}})``", "``imperial_length(3.0, 4, ""foot"", ""foot"", true)`` OR ``imperial_length(3.0, 4, ""foot"", ""foot"", false)``", "``3'`` OR ``3' - 0""``", "The ``{{value}}`` may be specified either as ``foot`` or ``inch`` depending on ``{{input_unit}}``. The ``{{value}}`` is then rounded to the nearest ``1/{{precision}}`` inch, then formatted using fractional feet and inches if ``{{output_unit}}`` is set to ``foot``, or just inches if ``{{output_unit}}`` is set to ``inch``. When ``{{suppress_zero_inches}}`` is ``true`` (default), measurements with zero inches will omit the inch portion (e.g., ``3'`` instead of ``3' - 0""``)." - "``sort({{values}})``", "``sort({{mats.Name}})``", "``Name1, Name2``", "Sorts a list of items." - "``reverse({{values}})``", "``reverse({{mats.Name}})``", "``Name2, Name1``", "Reverses a list of items." - "``join({{separator}}, {{values}})``", "``join("-", {{mats.Name}})``", "``Name1-Name2``", "Joins a list of items with a custom separator. By default, all lists a rendered as comma separated." - "``{{value1}}[+-*/]{{value2}}``", "``{{z}}+3``", "``5``", "Does arithmetic. Typical operators such as +, -, \*, and / are allowed and can be mixed with other variables and formatting functions." When using queries in an IfcAnnotation tag surround with backticks. Examples: From 514cbb49cc9d6d21d36c58ef545ca5e1f82dfb03 Mon Sep 17 00:00:00 2001 From: Ryan Schultz Date: Sun, 22 Feb 2026 17:46:10 -0600 Subject: [PATCH 02/16] Fix #7646: Fix layer thumbnail orientation for IFC types Use EPset_Parametric.LayerSetDirection exclusively to determine horizontal vs vertical layer rendering in type thumbnails, rather than hardcoding IfcSlabType checks. Also fix line drawing to use the is_horizontal flag consistently. Generated with the assistance of an AI coding tool. --- src/bonsai/bonsai/tool/model.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/bonsai/bonsai/tool/model.py b/src/bonsai/bonsai/tool/model.py index 148f4a6b75..c9cca803ec 100644 --- a/src/bonsai/bonsai/tool/model.py +++ b/src/bonsai/bonsai/tool/model.py @@ -1245,9 +1245,6 @@ class Model(bonsai.core.tool.Model): height = 100 is_horizontal = False - if element.is_a("IfcSlabType"): - is_horizontal = True - parametric = ifcopenshell.util.element.get_psets(element).get("EPset_Parametric") if parametric: layer_set_direction = parametric.get("LayerSetDirection", None) @@ -1266,7 +1263,7 @@ class Model(bonsai.core.tool.Model): del thicknesses[-1] for thickness in thicknesses: current_thickness += thickness - if element.is_a("IfcSlabType"): + if is_horizontal: y = (current_thickness / total_thickness) * height line = [x_offset, y_offset + y, x_offset + width, y_offset + y] else: From 7d8c7a2c3d1b18666e1483cdf2372604e2b86e95 Mon Sep 17 00:00:00 2001 From: Ryan Schultz Date: Mon, 23 Feb 2026 07:09:18 -0600 Subject: [PATCH 03/16] Fix #7681: Fix isolate_objects ignoring hide_select/hide_viewport (#7710) Objects with hide_select=True could not be selected during isolation, causing hide_view_set to incorrectly hide them. Objects with hide_viewport=True had their H-key hide state modified as a side effect of hide_view_clear/hide_view_set. Both are now left unaffected by bim.activate_drawing. Generated with the assistance of an AI coding tool. --- src/bonsai/bonsai/tool/blender.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py index a2a0f09ed8..d05f3356ec 100644 --- a/src/bonsai/bonsai/tool/blender.py +++ b/src/bonsai/bonsai/tool/blender.py @@ -1908,14 +1908,29 @@ class Blender(bonsai.core.tool.Blender): previously_active = bpy.context.view_layer.objects.active override = cls.get_viewport_context() + + # Save H-key hide state for globally-restricted objects so we don't change it. + # hide_viewport=True means the object is globally hidden via the outliner restriction; + # hide_view_clear/hide_view_set should not add or remove an additional H-key hide on them. + viewport_restricted = {obj: obj.hide_get() for obj in bpy.context.view_layer.objects if obj.hide_viewport} + with bpy.context.temp_override(**override): bpy.ops.object.hide_view_clear(select=False) bpy.ops.object.select_all(action="DESELECT") + hide_select_objs = [] for obj in objs: + if obj.hide_select: + hide_select_objs.append(obj) + obj.hide_select = False obj.select_set(True) with bpy.context.temp_override(**override): bpy.ops.object.hide_view_set(unselected=True) + for obj in hide_select_objs: + obj.hide_select = True + + for obj, was_hidden in viewport_restricted.items(): + obj.hide_set(was_hidden) bpy.ops.object.select_all(action="DESELECT") for name in previously_selected: From 2f5c71588ede9bbf5a892433a1b8a378e69f3969 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 24 Feb 2026 09:53:24 +1100 Subject: [PATCH 04/16] Revert "Table was not rendering correctly." This reverts commit 9ff4a7f0e0c23b85491c3478f1fa90d66088822d. --- .../ifcopenshell-python/selector_syntax.rst | 92 +++---------------- 1 file changed, 15 insertions(+), 77 deletions(-) diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python/selector_syntax.rst b/src/ifcopenshell-python/docs/ifcopenshell-python/selector_syntax.rst index 4f994daced..2d0cc9e071 100644 --- a/src/ifcopenshell-python/docs/ifcopenshell-python/selector_syntax.rst +++ b/src/ifcopenshell-python/docs/ifcopenshell-python/selector_syntax.rst @@ -240,84 +240,22 @@ in spreadsheets. For example ``upper("foo")`` will produce ``FOO``. You may nest formulas, for example ``concat(title("foo"), lower("Bar"))`` will produce ``Foobar``. Strings must be double quoted. -.. list-table:: - :header-rows: 1 - :widths: 28 28 16 28 - - * - Function - - Example - - Result - - Description - - * - ``upper({{value}})`` - - ``upper("Foo")`` - - ``FOO`` - - Uppercases a string. - - * - ``lower({{value}})`` - - ``lower("Foo")`` - - ``foo`` - - Lowercases a string. - - * - ``title({{value}})`` - - ``title("foo")`` - - ``Foo`` - - Titlecases a string. - - * - ``concat({{value}}[, {{value2}}]*)`` - - ``concat("foo", "bar")`` - - ``foobar`` - - Concatenates two or more strings. - - * - ``round({{value}}, {{precision}})`` - - ``round(3.123, 0.1)`` - - ``3.1`` - - Rounds ``{{value}}`` to the nearest ``{{precision}}``. - - * - ``int({{value}})`` - - ``int(3.123)`` - - ``3`` - - Truncates the decimal part of the ``{{value}}``. - - * - ``number({{value}}[, {{decimal_separator}}[, {{thousands_separator}}]])`` - - ``number(1234.56, ",", ".")`` - - ``1.234,56`` - - Formats ``{{value}}`` with an optional custom ``{{decimal_separator}}`` and ``{{thousands_separator}}``. The default separators are ``.`` and ``,``. - - * - ``metric_length({{value}}, {{precision}}, {{decimals}})`` - - ``metric_length(3.123, 0.1, 2)`` - - ``3.10`` - - Rounds ``{{value}}`` to the nearest ``{{precision}}`` then displays using a certain amount of decimal places. - - * - ``imperial_length({{value}}, {{precision}}, {{input_unit}}, {{output_unit}}, {{suppress_zero_inches}})`` - - ``imperial_length(3.0, 4, "foot", "foot", true)`` - OR - ``imperial_length(3.0, 4, "foot", "foot", false)`` - - ``3'`` - OR - ``3' - 0"`` - - The ``{{value}}`` may be specified either as ``foot`` or ``inch`` depending on ``{{input_unit}}``. The ``{{value}}`` is rounded to the nearest ``1/{{precision}}`` inch, then formatted using fractional feet and inches if ``{{output_unit}}`` is ``foot``, or just inches if ``{{output_unit}}`` is ``inch``. When ``{{suppress_zero_inches}}`` is ``true`` (default), measurements with zero inches omit the inch portion (e.g., ``3'`` instead of ``3' - 0"``). - - * - ``sort({{values}})`` - - ``sort({{mats.Name}})`` - - ``Name1, Name2`` - - Sorts a list of items. - - * - ``reverse({{values}})`` - - ``reverse({{mats.Name}})`` - - ``Name2, Name1`` - - Reverses a list of items. - - * - ``join({{separator}}, {{values}})`` - - ``join("-", {{mats.Name}})`` - - ``Name1-Name2`` - - Joins a list of items with a custom separator. By default, lists are rendered as comma separated. - - * - ``{{value1}}[+-*/]{{value2}}`` - - ``{{z}}+3`` - - ``5`` - - Does arithmetic. Operators such as ``+``, ``-``, ``*``, and ``/`` are allowed and can be mixed with variables and formatting functions. +.. csv-table:: + :header: "Function", "Example", "Result", "Description" + "``upper({{value}})``", "``upper(""Foo"")``", "``FOO``", "Uppercases a string." + "``lower({{value}})``", "``lower(""Foo"")``", "``foo``", "Lowercases a string." + "``title({{value}})``", "``title(""foo"")``", "``Foo``", "Titlecases a string." + "``concat({{value}}[, {{value2}}]*)``", "``concat(""foo"", ""bar"")``", "``foobar``", "Concatenates two or more strings." + "``round({{value}}, {{precision}})``", "``round(3.123, 0.1)``", "``3.1``", "Rounds ``{{value}}`` to the nearest ``{{precision}}``." + "``int({{value}})``", "``int(3.123)``", "``3``", "Truncates the decimal part of the ``{{value}}``." + "``number({{value}}[, {{decimal_separator}}[, {{thousands_separator}}]])``", "``number(1234.56, "","", ""."")``", "``1.234,56``", "Formats {{value}} with an optional custom {{decimal_separator}} and {{thousands_separator}}. The default separators are ``.`` and ``,``." + "``metric_length({{value}}, {{precision}}, {{decimals}})``", "``metric_length(3.123, 0.1, 2)``", "``3.10``", "Rounds ``{{value}}`` to the nearest ``{{precision}}`` then displays using a certain amount of decimal places." + "``imperial_length({{value}}, {{precision}}, {{input_unit}}, {{output_unit}}, {{suppress_zero_inches}})``", "``imperial_length(3.0, 4, ""foot"", ""foot"", true)`` OR ``imperial_length(3.0, 4, ""foot"", ""foot"", false)``", "``3'`` OR ``3' - 0""``", "The ``{{value}}`` may be specified either as ``foot`` or ``inch`` depending on ``{{input_unit}}``. The ``{{value}}`` is then rounded to the nearest ``1/{{precision}}`` inch, then formatted using fractional feet and inches if ``{{output_unit}}`` is set to ``foot``, or just inches if ``{{output_unit}}`` is set to ``inch``. When ``{{suppress_zero_inches}}`` is ``true`` (default), measurements with zero inches will omit the inch portion (e.g., ``3'`` instead of ``3' - 0""``)." + "``sort({{values}})``", "``sort({{mats.Name}})``", "``Name1, Name2``", "Sorts a list of items." + "``reverse({{values}})``", "``reverse({{mats.Name}})``", "``Name2, Name1``", "Reverses a list of items." + "``join({{separator}}, {{values}})``", "``join("-", {{mats.Name}})``", "``Name1-Name2``", "Joins a list of items with a custom separator. By default, all lists a rendered as comma separated." + "``{{value1}}[+-*/]{{value2}}``", "``{{z}}+3``", "``5``", "Does arithmetic. Typical operators such as +, -, \*, and / are allowed and can be mixed with other variables and formatting functions." When using queries in an IfcAnnotation tag surround with backticks. Examples: From f69ea8278976ec9c4c025ef67960f67a0a87b2a0 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 24 Feb 2026 09:53:27 +1100 Subject: [PATCH 05/16] Revert "Fix #7646: Fix layer thumbnail orientation for IFC types" This reverts commit 514cbb49cc9d6d21d36c58ef545ca5e1f82dfb03. --- src/bonsai/bonsai/tool/model.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/bonsai/bonsai/tool/model.py b/src/bonsai/bonsai/tool/model.py index c9cca803ec..148f4a6b75 100644 --- a/src/bonsai/bonsai/tool/model.py +++ b/src/bonsai/bonsai/tool/model.py @@ -1245,6 +1245,9 @@ class Model(bonsai.core.tool.Model): height = 100 is_horizontal = False + if element.is_a("IfcSlabType"): + is_horizontal = True + parametric = ifcopenshell.util.element.get_psets(element).get("EPset_Parametric") if parametric: layer_set_direction = parametric.get("LayerSetDirection", None) @@ -1263,7 +1266,7 @@ class Model(bonsai.core.tool.Model): del thicknesses[-1] for thickness in thicknesses: current_thickness += thickness - if is_horizontal: + if element.is_a("IfcSlabType"): y = (current_thickness / total_thickness) * height line = [x_offset, y_offset + y, x_offset + width, y_offset + y] else: From 41afaaec0d49aa06a840ee34e14ac59c57bb61e1 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 24 Feb 2026 09:53:28 +1100 Subject: [PATCH 06/16] Revert "Fix #7681: Fix isolate_objects ignoring hide_select/hide_viewport (#7710)" This reverts commit 7d8c7a2c3d1b18666e1483cdf2372604e2b86e95. --- src/bonsai/bonsai/tool/blender.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py index d05f3356ec..a2a0f09ed8 100644 --- a/src/bonsai/bonsai/tool/blender.py +++ b/src/bonsai/bonsai/tool/blender.py @@ -1908,29 +1908,14 @@ class Blender(bonsai.core.tool.Blender): previously_active = bpy.context.view_layer.objects.active override = cls.get_viewport_context() - - # Save H-key hide state for globally-restricted objects so we don't change it. - # hide_viewport=True means the object is globally hidden via the outliner restriction; - # hide_view_clear/hide_view_set should not add or remove an additional H-key hide on them. - viewport_restricted = {obj: obj.hide_get() for obj in bpy.context.view_layer.objects if obj.hide_viewport} - with bpy.context.temp_override(**override): bpy.ops.object.hide_view_clear(select=False) bpy.ops.object.select_all(action="DESELECT") - hide_select_objs = [] for obj in objs: - if obj.hide_select: - hide_select_objs.append(obj) - obj.hide_select = False obj.select_set(True) with bpy.context.temp_override(**override): bpy.ops.object.hide_view_set(unselected=True) - for obj in hide_select_objs: - obj.hide_select = True - - for obj, was_hidden in viewport_restricted.items(): - obj.hide_set(was_hidden) bpy.ops.object.select_all(action="DESELECT") for name in previously_selected: From 0d382119dddded963348012fb5c802edd89c8421 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 24 Feb 2026 09:53:57 +1100 Subject: [PATCH 07/16] Fix docs table for selector syntax --- .../docs/ifcopenshell-python/selector_syntax.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python/selector_syntax.rst b/src/ifcopenshell-python/docs/ifcopenshell-python/selector_syntax.rst index 2d0cc9e071..4e28e83ed4 100644 --- a/src/ifcopenshell-python/docs/ifcopenshell-python/selector_syntax.rst +++ b/src/ifcopenshell-python/docs/ifcopenshell-python/selector_syntax.rst @@ -254,7 +254,7 @@ nest formulas, for example ``concat(title("foo"), lower("Bar"))`` will produce "``imperial_length({{value}}, {{precision}}, {{input_unit}}, {{output_unit}}, {{suppress_zero_inches}})``", "``imperial_length(3.0, 4, ""foot"", ""foot"", true)`` OR ``imperial_length(3.0, 4, ""foot"", ""foot"", false)``", "``3'`` OR ``3' - 0""``", "The ``{{value}}`` may be specified either as ``foot`` or ``inch`` depending on ``{{input_unit}}``. The ``{{value}}`` is then rounded to the nearest ``1/{{precision}}`` inch, then formatted using fractional feet and inches if ``{{output_unit}}`` is set to ``foot``, or just inches if ``{{output_unit}}`` is set to ``inch``. When ``{{suppress_zero_inches}}`` is ``true`` (default), measurements with zero inches will omit the inch portion (e.g., ``3'`` instead of ``3' - 0""``)." "``sort({{values}})``", "``sort({{mats.Name}})``", "``Name1, Name2``", "Sorts a list of items." "``reverse({{values}})``", "``reverse({{mats.Name}})``", "``Name2, Name1``", "Reverses a list of items." - "``join({{separator}}, {{values}})``", "``join("-", {{mats.Name}})``", "``Name1-Name2``", "Joins a list of items with a custom separator. By default, all lists a rendered as comma separated." + "``join({{separator}}, {{values}})``", "``join(""-"", {{mats.Name}})``", "``Name1-Name2``", "Joins a list of items with a custom separator. By default, all lists a rendered as comma separated." "``{{value1}}[+-*/]{{value2}}``", "``{{z}}+3``", "``5``", "Does arithmetic. Typical operators such as +, -, \*, and / are allowed and can be mixed with other variables and formatting functions." When using queries in an IfcAnnotation tag surround with backticks. From dcc25038f909b1827cd091c2899b52d55ab6f1eb Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 24 Feb 2026 10:03:33 +1100 Subject: [PATCH 08/16] Fix #7646. Bug with layer thumbnail orientation. --- src/bonsai/bonsai/tool/model.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/bonsai/bonsai/tool/model.py b/src/bonsai/bonsai/tool/model.py index 148f4a6b75..0f3f4e45ad 100644 --- a/src/bonsai/bonsai/tool/model.py +++ b/src/bonsai/bonsai/tool/model.py @@ -1244,18 +1244,7 @@ class Model(bonsai.core.tool.Model): height = 100 - is_horizontal = False - if element.is_a("IfcSlabType"): - is_horizontal = True - - parametric = ifcopenshell.util.element.get_psets(element).get("EPset_Parametric") - if parametric: - layer_set_direction = parametric.get("LayerSetDirection", None) - if layer_set_direction == "AXIS2": - is_horizontal = False - elif layer_set_direction == "AXIS3": - is_horizontal = True - + is_horizontal = cls.get_usage_type(element) == "LAYER3" if is_horizontal: width, height = height, width @@ -1266,7 +1255,7 @@ class Model(bonsai.core.tool.Model): del thicknesses[-1] for thickness in thicknesses: current_thickness += thickness - if element.is_a("IfcSlabType"): + if is_horizontal: y = (current_thickness / total_thickness) * height line = [x_offset, y_offset + y, x_offset + width, y_offset + y] else: From 2d7a556dd5c24f1ba2d7ab0beb58078ebfbf5669 Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Tue, 24 Feb 2026 11:26:18 +0100 Subject: [PATCH 09/16] Fix sectioned solid cap #7674 --- src/ifcgeom/kernels/opencascade/loft.cpp | 31 +++++++++--------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/src/ifcgeom/kernels/opencascade/loft.cpp b/src/ifcgeom/kernels/opencascade/loft.cpp index 74c9c187df..b055b7743b 100644 --- a/src/ifcgeom/kernels/opencascade/loft.cpp +++ b/src/ifcgeom/kernels/opencascade/loft.cpp @@ -165,13 +165,6 @@ bool OpenCascadeKernel::convert(const taxonomy::loft::ptr loft, TopoDS_Shape& re std::vector shps(loft->children.size()); std::vector>> all_tags; - - std::ostringstream oss; - loft->children[0]->print(oss); - loft->children[1]->print(oss); - auto s = oss.str(); - std::wcout << s.c_str() << std::endl; - // First convert all taxonomy items to TopoDS_Wire/Face for (auto it = loft->children.begin(); it < loft->children.end(); ++it) { auto i = std::distance(loft->children.begin(), it); @@ -267,6 +260,18 @@ bool OpenCascadeKernel::convert(const taxonomy::loft::ptr loft, TopoDS_Shape& re return true; */ + if (shps.size() < 2) { + Logger::Error("Not enough sections to loft"); + return false; + } + + if (shps[0].ShapeType() == TopAbs_FACE) { + // When processing a sectioned *surface* there are no + // begin and end caps that need to be added. + BB.Add(comp, shps.front().Reversed()); + BB.Add(comp, shps.back()); + } + // @todo this approach is // potentially incorrect as there is no guarantee that the wires for // subsequently placed profiles are traversed from an equivalent start vertex. @@ -292,18 +297,6 @@ bool OpenCascadeKernel::convert(const taxonomy::loft::ptr loft, TopoDS_Shape& re ws[0][i] = TopoDS::Wire(*fa[i]); } } - if (it->ShapeType() == TopAbs_FACE) { - // When processing a sectioned *surface* there are no - // begin and end caps that need to be added. - if (it == shps.begin()) { - // faces.Append(shps[0]); - BB.Add(comp, shps[0]); - } - if (jt == shps.end() - 1) { - // faces.Append(shps[1]); - BB.Add(comp, shps[1]); - } - } if (!all_tags.empty()) { // only open profiles have tags for now, so there is only one wire, no inner wires From ece7d6b97f0446d69a5782aac2cef3874f5409a7 Mon Sep 17 00:00:00 2001 From: Richard Brice <37087370+RickBrice@users.noreply.github.com> Date: Tue, 24 Feb 2026 14:54:26 -0800 Subject: [PATCH 10/16] Fixes example in documentation --- .../ifcopenshell/api/spatial/reference_structure.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/ifcopenshell-python/ifcopenshell/api/spatial/reference_structure.py b/src/ifcopenshell-python/ifcopenshell/api/spatial/reference_structure.py index 4446971f0d..f19be2360b 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/spatial/reference_structure.py +++ b/src/ifcopenshell-python/ifcopenshell/api/spatial/reference_structure.py @@ -63,14 +63,15 @@ def reference_structure( storey1 = ifcopenshell.api.root.create_entity(model, ifc_class="IfcBuildingStorey") storey2 = ifcopenshell.api.root.create_entity(model, ifc_class="IfcBuildingStorey") storey3 = ifcopenshell.api.root.create_entity(model, ifc_class="IfcBuildingStorey") + space = ifcopenshell.api.root.create_entity(model, ifc_class="IfcSpace") # The project contains a site (note that project aggregation is a special case in IFC) ifcopenshell.api.aggregate.assign_object(model, products=[site], relating_object=project) # The site has a building, the building has a storey, and the storey has a space ifcopenshell.api.aggregate.assign_object(model, products=[building], relating_object=site) - ifcopenshell.api.aggregate.assign_object(model, products=[storey], relating_object=building) - ifcopenshell.api.aggregate.assign_object(model, products=[space], relating_object=storey) + ifcopenshell.api.aggregate.assign_object(model, products=[storey1,storey2,storey3], relating_object=building) + ifcopenshell.api.aggregate.assign_object(model, products=[space], relating_object=storey1) # Create a column, this column spans 3 storeys column = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWall") @@ -80,7 +81,11 @@ def reference_structure( # And referenced in the others ifcopenshell.api.spatial.reference_structure( - model, products=[column], relating_structure=[storey2, storey3] + model, products=[column], relating_structure=storey2 + ) + + ifcopenshell.api.spatial.reference_structure( + model, products=[column], relating_structure=storey3 ) """ From 8df4b2cd562439b9499e50371c2903d6603c28a4 Mon Sep 17 00:00:00 2001 From: falken10vdl Date: Wed, 14 Jan 2026 01:06:51 +0100 Subject: [PATCH 11/16] Scale font size in PolylineDecorator and BoundingBoxDecorator based on Blender's UI preferences --- .../bonsai/bim/module/model/decorator.py | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/model/decorator.py b/src/bonsai/bonsai/bim/module/model/decorator.py index 328804d4c6..a6338a05e7 100644 --- a/src/bonsai/bonsai/bim/module/model/decorator.py +++ b/src/bonsai/bonsai/bim/module/model/decorator.py @@ -29,6 +29,7 @@ import bpy import gpu import ifcopenshell import mathutils +import sys from bpy.types import SpaceView3D from bpy_extras import view3d_utils from bpy_extras.view3d_utils import location_3d_to_region_2d @@ -466,13 +467,19 @@ class PolylineDecorator: self.addon_prefs = tool.Blender.get_addon_preferences() self.font_id = 0 - font_size = tool.Blender.scale_font_size(12) + ui_style = context.preferences.ui_styles[0] + widget_font_points = ui_style.widget.points + ui_scale = context.preferences.view.ui_scale + platform_scale = 2 if sys.platform == 'darwin' else 1 + + font_size = widget_font_points * ui_scale * platform_scale + offset = widget_font_points * ui_scale * (1.5 * platform_scale) + line_height = widget_font_points * ui_scale * (1.25 * platform_scale) blf.size(self.font_id, font_size) blf.enable(self.font_id, blf.SHADOW) blf.shadow(self.font_id, 6, 0, 0, 0, 1) color = self.addon_prefs.decorations_colour color_highlight = self.addon_prefs.decorator_color_special - offset = 20 new_line = 0 for i, (key, field_name) in enumerate(texts.items()): formatted_value = None @@ -480,7 +487,7 @@ class PolylineDecorator: # Controls which options are displayed in the UI if key not in self.input_ui.input_options: continue - new_line += 20 + new_line += line_height if self.tool_state and key != self.tool_state.input_type: formatted_value = self.input_ui.get_formatted_value(key) else: @@ -515,7 +522,12 @@ class PolylineDecorator: self.addon_prefs = tool.Blender.get_addon_preferences() self.font_id = 1 self.shader = gpu.shader.from_builtin("UNIFORM_COLOR") - font_size = tool.Blender.scale_font_size(12) + ui_style = context.preferences.ui_styles[0] + widget_font_points = ui_style.widget.points + ui_scale = context.preferences.view.ui_scale + platform_scale = 2 if sys.platform == 'darwin' else 1 + + font_size = widget_font_points * ui_scale * platform_scale blf.size(self.font_id, font_size) blf.enable(self.font_id, blf.SHADOW) blf.shadow(self.font_id, 6, 0, 0, 0, 1) @@ -1936,7 +1948,13 @@ class BoundingBoxDecorator: addon_prefs = tool.Blender.get_addon_preferences() font_id = 0 - font_size = tool.Blender.scale_font_size(12) + # Get Blender's default UI widget font size from preferences + ui_style = context.preferences.ui_styles[0] + widget_font_points = ui_style.widget.points + ui_scale = context.preferences.view.ui_scale + platform_scale = 2 if sys.platform == 'darwin' else 1 + + font_size = widget_font_points * ui_scale * platform_scale blf.size(font_id, font_size) blf.enable(font_id, blf.SHADOW) blf.shadow(font_id, 6, 0, 0, 0, 1) From dde6e2d62b6bbbbeebec8faceb89088cc807615d Mon Sep 17 00:00:00 2001 From: falken10vdl Date: Wed, 14 Jan 2026 01:13:28 +0100 Subject: [PATCH 12/16] black --- src/bonsai/bonsai/bim/module/model/decorator.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/model/decorator.py b/src/bonsai/bonsai/bim/module/model/decorator.py index a6338a05e7..6fd5a9020f 100644 --- a/src/bonsai/bonsai/bim/module/model/decorator.py +++ b/src/bonsai/bonsai/bim/module/model/decorator.py @@ -470,8 +470,8 @@ class PolylineDecorator: ui_style = context.preferences.ui_styles[0] widget_font_points = ui_style.widget.points ui_scale = context.preferences.view.ui_scale - platform_scale = 2 if sys.platform == 'darwin' else 1 - + platform_scale = 2 if sys.platform == "darwin" else 1 + font_size = widget_font_points * ui_scale * platform_scale offset = widget_font_points * ui_scale * (1.5 * platform_scale) line_height = widget_font_points * ui_scale * (1.25 * platform_scale) @@ -525,8 +525,8 @@ class PolylineDecorator: ui_style = context.preferences.ui_styles[0] widget_font_points = ui_style.widget.points ui_scale = context.preferences.view.ui_scale - platform_scale = 2 if sys.platform == 'darwin' else 1 - + platform_scale = 2 if sys.platform == "darwin" else 1 + font_size = widget_font_points * ui_scale * platform_scale blf.size(self.font_id, font_size) blf.enable(self.font_id, blf.SHADOW) @@ -1952,8 +1952,8 @@ class BoundingBoxDecorator: ui_style = context.preferences.ui_styles[0] widget_font_points = ui_style.widget.points ui_scale = context.preferences.view.ui_scale - platform_scale = 2 if sys.platform == 'darwin' else 1 - + platform_scale = 2 if sys.platform == "darwin" else 1 + font_size = widget_font_points * ui_scale * platform_scale blf.size(font_id, font_size) blf.enable(font_id, blf.SHADOW) From ecaea5f7766303540fb0c836ec78253395eefd2c Mon Sep 17 00:00:00 2001 From: falken10vdl Date: Thu, 22 Jan 2026 09:19:57 +0100 Subject: [PATCH 13/16] refactor scale_font_size as per developers feedback --- .../bonsai/bim/module/model/decorator.py | 27 ++++--------------- src/bonsai/bonsai/tool/blender.py | 12 ++++----- 2 files changed, 11 insertions(+), 28 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/model/decorator.py b/src/bonsai/bonsai/bim/module/model/decorator.py index 6fd5a9020f..bad611b1d7 100644 --- a/src/bonsai/bonsai/bim/module/model/decorator.py +++ b/src/bonsai/bonsai/bim/module/model/decorator.py @@ -29,7 +29,6 @@ import bpy import gpu import ifcopenshell import mathutils -import sys from bpy.types import SpaceView3D from bpy_extras import view3d_utils from bpy_extras.view3d_utils import location_3d_to_region_2d @@ -467,14 +466,9 @@ class PolylineDecorator: self.addon_prefs = tool.Blender.get_addon_preferences() self.font_id = 0 - ui_style = context.preferences.ui_styles[0] - widget_font_points = ui_style.widget.points - ui_scale = context.preferences.view.ui_scale - platform_scale = 2 if sys.platform == "darwin" else 1 - - font_size = widget_font_points * ui_scale * platform_scale - offset = widget_font_points * ui_scale * (1.5 * platform_scale) - line_height = widget_font_points * ui_scale * (1.25 * platform_scale) + font_size = tool.Blender.scale_font_size(None) + offset = tool.Blender.scale_font_size(None) * 1.5 + line_height = tool.Blender.scale_font_size(None) * 1.25 blf.size(self.font_id, font_size) blf.enable(self.font_id, blf.SHADOW) blf.shadow(self.font_id, 6, 0, 0, 0, 1) @@ -522,12 +516,7 @@ class PolylineDecorator: self.addon_prefs = tool.Blender.get_addon_preferences() self.font_id = 1 self.shader = gpu.shader.from_builtin("UNIFORM_COLOR") - ui_style = context.preferences.ui_styles[0] - widget_font_points = ui_style.widget.points - ui_scale = context.preferences.view.ui_scale - platform_scale = 2 if sys.platform == "darwin" else 1 - - font_size = widget_font_points * ui_scale * platform_scale + font_size = tool.Blender.scale_font_size(None) blf.size(self.font_id, font_size) blf.enable(self.font_id, blf.SHADOW) blf.shadow(self.font_id, 6, 0, 0, 0, 1) @@ -1948,13 +1937,7 @@ class BoundingBoxDecorator: addon_prefs = tool.Blender.get_addon_preferences() font_id = 0 - # Get Blender's default UI widget font size from preferences - ui_style = context.preferences.ui_styles[0] - widget_font_points = ui_style.widget.points - ui_scale = context.preferences.view.ui_scale - platform_scale = 2 if sys.platform == "darwin" else 1 - - font_size = widget_font_points * ui_scale * platform_scale + font_size = tool.Blender.scale_font_size(None) blf.size(font_id, font_size) blf.enable(font_id, blf.SHADOW) blf.shadow(font_id, 6, 0, 0, 0, 1) diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py index a2a0f09ed8..b10f88b3f2 100644 --- a/src/bonsai/bonsai/tool/blender.py +++ b/src/bonsai/bonsai/tool/blender.py @@ -1572,12 +1572,12 @@ class Blender(bonsai.core.tool.Blender): @classmethod def scale_font_size(cls, size): - default_dpi = 72 - default_pixel_size = 1.0 - default_scale = default_dpi * default_pixel_size - system = bpy.context.preferences.system - system_scale = system.dpi * system.pixel_size - return (system_scale / default_scale) * size + ui_style = bpy.context.preferences.ui_styles[0] + base_size = ui_style.widget.points if size is None else size + ui_scale = bpy.context.preferences.view.ui_scale + platform_scale = 2 if sys.platform == "darwin" else 1 + + return base_size * ui_scale * platform_scale @classmethod def apply_transform_as_local(cls, obj: bpy.types.Object) -> bool: From c0857c715aa09fcfe0c47013db8be9365c6fef56 Mon Sep 17 00:00:00 2001 From: falken10vdl Date: Wed, 28 Jan 2026 08:58:35 +0100 Subject: [PATCH 14/16] Refactor scale_font_size to improve DPI and pixel size handling for better font scaling --- src/bonsai/bonsai/tool/blender.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py index b10f88b3f2..4aa67685a7 100644 --- a/src/bonsai/bonsai/tool/blender.py +++ b/src/bonsai/bonsai/tool/blender.py @@ -1572,12 +1572,17 @@ class Blender(bonsai.core.tool.Blender): @classmethod def scale_font_size(cls, size): + default_dpi = 72 + default_pixel_size = 1.0 ui_style = bpy.context.preferences.ui_styles[0] base_size = ui_style.widget.points if size is None else size - ui_scale = bpy.context.preferences.view.ui_scale - platform_scale = 2 if sys.platform == "darwin" else 1 - - return base_size * ui_scale * platform_scale + platform_scale = 0.5 if sys.platform == "darwin" else 1 + + default_scale = default_dpi * default_pixel_size + system = bpy.context.preferences.system + system_scale = system.dpi * system.pixel_size + return (system_scale / default_scale) * base_size *platform_scale + @classmethod def apply_transform_as_local(cls, obj: bpy.types.Object) -> bool: From 1b784e22af5e8851835f000fbb61db49c6588917 Mon Sep 17 00:00:00 2001 From: falken10vdl Date: Wed, 28 Jan 2026 09:11:08 +0100 Subject: [PATCH 15/16] Add decorator font scale property addon setting --- src/bonsai/bonsai/bim/ui.py | 8 ++++++++ src/bonsai/bonsai/tool/blender.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/bonsai/bonsai/bim/ui.py b/src/bonsai/bonsai/bim/ui.py index 30ce93f076..26c06c5b8d 100644 --- a/src/bonsai/bonsai/bim/ui.py +++ b/src/bonsai/bonsai/bim/ui.py @@ -747,6 +747,12 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences): default=".ifc.metadata.blend", ) + decorator_font_scale: bpy.props.FloatProperty( + name="Decorator Font Scale", + description="Scale factor for decorator font size.", + default=1.0, + ) + if TYPE_CHECKING: svg2pdf_command: str svg2dxf_command: str @@ -786,6 +792,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences): mass_time_units_in_wizard: bool chain_filter_with_set_operations: bool save_metadata_blend_file: bool + decorator_font_scale: float def draw(self, context: bpy.types.Context) -> None: layout = self.layout @@ -1009,6 +1016,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences): else: row = layout.row() row.operator("bim.manage_tab_visibility", icon="PREFERENCES") + layout.prop(self, "decorator_font_scale") # Scene panel groups diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py index 4aa67685a7..d2f9b420a6 100644 --- a/src/bonsai/bonsai/tool/blender.py +++ b/src/bonsai/bonsai/tool/blender.py @@ -1581,7 +1581,7 @@ class Blender(bonsai.core.tool.Blender): default_scale = default_dpi * default_pixel_size system = bpy.context.preferences.system system_scale = system.dpi * system.pixel_size - return (system_scale / default_scale) * base_size *platform_scale + return (system_scale / default_scale) * base_size *platform_scale * tool.Blender.get_addon_preferences().decorator_font_scale @classmethod From 0be348707c84dad9a387c78627c707cb7e65c20e Mon Sep 17 00:00:00 2001 From: falken10vdl Date: Wed, 25 Feb 2026 09:29:39 +0100 Subject: [PATCH 16/16] Update scale_font_size method to accept a None parameter so it is cleaner the calls from the rest of the code base --- src/bonsai/bonsai/bim/module/model/decorator.py | 10 +++++----- src/bonsai/bonsai/tool/blender.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/model/decorator.py b/src/bonsai/bonsai/bim/module/model/decorator.py index bad611b1d7..f5a1e534da 100644 --- a/src/bonsai/bonsai/bim/module/model/decorator.py +++ b/src/bonsai/bonsai/bim/module/model/decorator.py @@ -466,9 +466,9 @@ class PolylineDecorator: self.addon_prefs = tool.Blender.get_addon_preferences() self.font_id = 0 - font_size = tool.Blender.scale_font_size(None) - offset = tool.Blender.scale_font_size(None) * 1.5 - line_height = tool.Blender.scale_font_size(None) * 1.25 + font_size = tool.Blender.scale_font_size() + offset = tool.Blender.scale_font_size() * 1.5 + line_height = tool.Blender.scale_font_size() * 1.25 blf.size(self.font_id, font_size) blf.enable(self.font_id, blf.SHADOW) blf.shadow(self.font_id, 6, 0, 0, 0, 1) @@ -516,7 +516,7 @@ class PolylineDecorator: self.addon_prefs = tool.Blender.get_addon_preferences() self.font_id = 1 self.shader = gpu.shader.from_builtin("UNIFORM_COLOR") - font_size = tool.Blender.scale_font_size(None) + font_size = tool.Blender.scale_font_size() blf.size(self.font_id, font_size) blf.enable(self.font_id, blf.SHADOW) blf.shadow(self.font_id, 6, 0, 0, 0, 1) @@ -1937,7 +1937,7 @@ class BoundingBoxDecorator: addon_prefs = tool.Blender.get_addon_preferences() font_id = 0 - font_size = tool.Blender.scale_font_size(None) + font_size = tool.Blender.scale_font_size() blf.size(font_id, font_size) blf.enable(font_id, blf.SHADOW) blf.shadow(font_id, 6, 0, 0, 0, 1) diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py index d2f9b420a6..9471cad28c 100644 --- a/src/bonsai/bonsai/tool/blender.py +++ b/src/bonsai/bonsai/tool/blender.py @@ -1571,7 +1571,7 @@ class Blender(bonsai.core.tool.Blender): return getattr(scene, "sun_pos_properties", None) @classmethod - def scale_font_size(cls, size): + def scale_font_size(cls, size=None): default_dpi = 72 default_pixel_size = 1.0 ui_style = bpy.context.preferences.ui_styles[0]