Merge branch 'v0.8.0' into ifcmax/initial-refresh

This commit is contained in:
Josef Wienerroither
2026-02-26 13:24:08 +01:00
7 changed files with 44 additions and 43 deletions
@@ -466,13 +466,14 @@ class PolylineDecorator:
self.addon_prefs = tool.Blender.get_addon_preferences() self.addon_prefs = tool.Blender.get_addon_preferences()
self.font_id = 0 self.font_id = 0
font_size = tool.Blender.scale_font_size(12) 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.size(self.font_id, font_size)
blf.enable(self.font_id, blf.SHADOW) blf.enable(self.font_id, blf.SHADOW)
blf.shadow(self.font_id, 6, 0, 0, 0, 1) blf.shadow(self.font_id, 6, 0, 0, 0, 1)
color = self.addon_prefs.decorations_colour color = self.addon_prefs.decorations_colour
color_highlight = self.addon_prefs.decorator_color_special color_highlight = self.addon_prefs.decorator_color_special
offset = 20
new_line = 0 new_line = 0
for i, (key, field_name) in enumerate(texts.items()): for i, (key, field_name) in enumerate(texts.items()):
formatted_value = None formatted_value = None
@@ -480,7 +481,7 @@ class PolylineDecorator:
# Controls which options are displayed in the UI # Controls which options are displayed in the UI
if key not in self.input_ui.input_options: if key not in self.input_ui.input_options:
continue continue
new_line += 20 new_line += line_height
if self.tool_state and key != self.tool_state.input_type: if self.tool_state and key != self.tool_state.input_type:
formatted_value = self.input_ui.get_formatted_value(key) formatted_value = self.input_ui.get_formatted_value(key)
else: else:
@@ -515,7 +516,7 @@ class PolylineDecorator:
self.addon_prefs = tool.Blender.get_addon_preferences() self.addon_prefs = tool.Blender.get_addon_preferences()
self.font_id = 1 self.font_id = 1
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR") self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
font_size = tool.Blender.scale_font_size(12) font_size = tool.Blender.scale_font_size()
blf.size(self.font_id, font_size) blf.size(self.font_id, font_size)
blf.enable(self.font_id, blf.SHADOW) blf.enable(self.font_id, blf.SHADOW)
blf.shadow(self.font_id, 6, 0, 0, 0, 1) blf.shadow(self.font_id, 6, 0, 0, 0, 1)
@@ -1936,7 +1937,7 @@ class BoundingBoxDecorator:
addon_prefs = tool.Blender.get_addon_preferences() addon_prefs = tool.Blender.get_addon_preferences()
font_id = 0 font_id = 0
font_size = tool.Blender.scale_font_size(12) font_size = tool.Blender.scale_font_size()
blf.size(font_id, font_size) blf.size(font_id, font_size)
blf.enable(font_id, blf.SHADOW) blf.enable(font_id, blf.SHADOW)
blf.shadow(font_id, 6, 0, 0, 0, 1) blf.shadow(font_id, 6, 0, 0, 0, 1)
+8
View File
@@ -747,6 +747,12 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
default=".ifc.metadata.blend", 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: if TYPE_CHECKING:
svg2pdf_command: str svg2pdf_command: str
svg2dxf_command: str svg2dxf_command: str
@@ -786,6 +792,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
mass_time_units_in_wizard: bool mass_time_units_in_wizard: bool
chain_filter_with_set_operations: bool chain_filter_with_set_operations: bool
save_metadata_blend_file: bool save_metadata_blend_file: bool
decorator_font_scale: float
def draw(self, context: bpy.types.Context) -> None: def draw(self, context: bpy.types.Context) -> None:
layout = self.layout layout = self.layout
@@ -1009,6 +1016,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
else: else:
row = layout.row() row = layout.row()
row.operator("bim.manage_tab_visibility", icon="PREFERENCES") row.operator("bim.manage_tab_visibility", icon="PREFERENCES")
layout.prop(self, "decorator_font_scale")
# Scene panel groups # Scene panel groups
+7 -2
View File
@@ -1571,13 +1571,18 @@ class Blender(bonsai.core.tool.Blender):
return getattr(scene, "sun_pos_properties", None) return getattr(scene, "sun_pos_properties", None)
@classmethod @classmethod
def scale_font_size(cls, size): def scale_font_size(cls, size=None):
default_dpi = 72 default_dpi = 72
default_pixel_size = 1.0 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
platform_scale = 0.5 if sys.platform == "darwin" else 1
default_scale = default_dpi * default_pixel_size default_scale = default_dpi * default_pixel_size
system = bpy.context.preferences.system system = bpy.context.preferences.system
system_scale = system.dpi * system.pixel_size system_scale = system.dpi * system.pixel_size
return (system_scale / default_scale) * size return (system_scale / default_scale) * base_size *platform_scale * tool.Blender.get_addon_preferences().decorator_font_scale
@classmethod @classmethod
def apply_transform_as_local(cls, obj: bpy.types.Object) -> bool: def apply_transform_as_local(cls, obj: bpy.types.Object) -> bool:
+2 -13
View File
@@ -1244,18 +1244,7 @@ class Model(bonsai.core.tool.Model):
height = 100 height = 100
is_horizontal = False is_horizontal = cls.get_usage_type(element) == "LAYER3"
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
if is_horizontal: if is_horizontal:
width, height = height, width width, height = height, width
@@ -1266,7 +1255,7 @@ class Model(bonsai.core.tool.Model):
del thicknesses[-1] del thicknesses[-1]
for thickness in thicknesses: for thickness in thicknesses:
current_thickness += thickness current_thickness += thickness
if element.is_a("IfcSlabType"): if is_horizontal:
y = (current_thickness / total_thickness) * height y = (current_thickness / total_thickness) * height
line = [x_offset, y_offset + y, x_offset + width, y_offset + y] line = [x_offset, y_offset + y, x_offset + width, y_offset + y]
else: else:
+12 -19
View File
@@ -165,13 +165,6 @@ bool OpenCascadeKernel::convert(const taxonomy::loft::ptr loft, TopoDS_Shape& re
std::vector<TopoDS_Shape> shps(loft->children.size()); std::vector<TopoDS_Shape> shps(loft->children.size());
std::vector<std::vector<std::set<std::string>>> all_tags; std::vector<std::vector<std::set<std::string>>> 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 // First convert all taxonomy items to TopoDS_Wire/Face
for (auto it = loft->children.begin(); it < loft->children.end(); ++it) { for (auto it = loft->children.begin(); it < loft->children.end(); ++it) {
auto i = std::distance(loft->children.begin(), 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; 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 // @todo this approach is
// potentially incorrect as there is no guarantee that the wires for // potentially incorrect as there is no guarantee that the wires for
// subsequently placed profiles are traversed from an equivalent start vertex. // 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]); 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()) { if (!all_tags.empty()) {
// only open profiles have tags for now, so there is only one wire, no inner wires // only open profiles have tags for now, so there is only one wire, no inner wires
@@ -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""``)." "``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." "``sort({{values}})``", "``sort({{mats.Name}})``", "``Name1, Name2``", "Sorts a list of items."
"``reverse({{values}})``", "``reverse({{mats.Name}})``", "``Name2, Name1``", "Reverses 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." "``{{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. When using queries in an IfcAnnotation tag surround with backticks.
@@ -63,14 +63,15 @@ def reference_structure(
storey1 = ifcopenshell.api.root.create_entity(model, ifc_class="IfcBuildingStorey") storey1 = ifcopenshell.api.root.create_entity(model, ifc_class="IfcBuildingStorey")
storey2 = 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") 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) # 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) 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 # 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=[building], relating_object=site)
ifcopenshell.api.aggregate.assign_object(model, products=[storey], relating_object=building) ifcopenshell.api.aggregate.assign_object(model, products=[storey1,storey2,storey3], relating_object=building)
ifcopenshell.api.aggregate.assign_object(model, products=[space], relating_object=storey) ifcopenshell.api.aggregate.assign_object(model, products=[space], relating_object=storey1)
# Create a column, this column spans 3 storeys # Create a column, this column spans 3 storeys
column = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWall") column = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWall")
@@ -80,7 +81,11 @@ def reference_structure(
# And referenced in the others # And referenced in the others
ifcopenshell.api.spatial.reference_structure( 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
) )
""" """