From 456adf453dad83d339a107b427561bea2737f775 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 21 Oct 2021 12:43:16 +1100 Subject: [PATCH] New feature to calculate the radius of a polygonal circle --- .../blenderbim/bim/module/model/wall.py | 4 +- .../blenderbim/bim/module/qto/__init__.py | 1 + .../blenderbim/bim/module/qto/operator.py | 16 ++++++++ .../blenderbim/bim/module/qto/ui.py | 2 + src/blenderbim/blenderbim/core/qto.py | 23 +++++++++++ src/blenderbim/blenderbim/core/tool.py | 6 +++ src/blenderbim/blenderbim/tool/__init__.py | 1 + src/blenderbim/blenderbim/tool/qto.py | 37 +++++++++++++++++ src/blenderbim/pytest.ini | 1 + src/blenderbim/test/bim/feature/qto.feature | 30 ++++++++++++++ src/blenderbim/test/bim/test_feature.py | 2 +- src/blenderbim/test/core/bootstrap.py | 15 +++++-- src/blenderbim/test/core/test_qto.py | 27 ++++++++++++ src/blenderbim/test/tool/test_qto.py | 41 +++++++++++++++++++ 14 files changed, 200 insertions(+), 6 deletions(-) create mode 100644 src/blenderbim/blenderbim/core/qto.py create mode 100644 src/blenderbim/blenderbim/tool/qto.py create mode 100644 src/blenderbim/test/bim/feature/qto.feature create mode 100644 src/blenderbim/test/core/test_qto.py create mode 100644 src/blenderbim/test/tool/test_qto.py diff --git a/src/blenderbim/blenderbim/bim/module/model/wall.py b/src/blenderbim/blenderbim/bim/module/model/wall.py index d51e8e616f..9ff3af2b1e 100644 --- a/src/blenderbim/blenderbim/bim/module/model/wall.py +++ b/src/blenderbim/blenderbim/bim/module/model/wall.py @@ -169,7 +169,9 @@ class SplitWall(bpy.types.Operator): bl_idname = "bim.split_wall" bl_label = "Split Wall" bl_options = {"REGISTER", "UNDO"} - bl_description = "Split selected wall into two walls in correspondence of Blender cursor. The cursor must be in the wall volume" + bl_description = ( + "Split selected wall into two walls in correspondence of Blender cursor. The cursor must be in the wall volume" + ) @classmethod def poll(cls, context): diff --git a/src/blenderbim/blenderbim/bim/module/qto/__init__.py b/src/blenderbim/blenderbim/bim/module/qto/__init__.py index 6583deae6a..aab4c49a2a 100644 --- a/src/blenderbim/blenderbim/bim/module/qto/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/qto/__init__.py @@ -20,6 +20,7 @@ import bpy from . import ui, prop, operator classes = ( + operator.CalculateCircleRadius, operator.CalculateEdgeLengths, operator.CalculateFaceAreas, operator.CalculateObjectVolumes, diff --git a/src/blenderbim/blenderbim/bim/module/qto/operator.py b/src/blenderbim/blenderbim/bim/module/qto/operator.py index 0a40caddcc..0054dda845 100644 --- a/src/blenderbim/blenderbim/bim/module/qto/operator.py +++ b/src/blenderbim/blenderbim/bim/module/qto/operator.py @@ -19,11 +19,27 @@ import bpy import ifcopenshell import ifcopenshell.api +import blenderbim.tool as tool +import blenderbim.core.qto as core from blenderbim.bim.ifc import IfcStore from blenderbim.bim.module.qto import helper from ifcopenshell.api.pset.data import Data as PsetData +class CalculateCircleRadius(bpy.types.Operator): + bl_idname = "bim.calculate_circle_radius" + bl_label = "Calculate Circle Radius" + bl_options = {"REGISTER", "UNDO"} + + @classmethod + def poll(cls, context): + return context.active_object + + def execute(self, context): + core.calculate_circle_radius(tool.Qto, obj=context.active_object) + return {"FINISHED"} + + class CalculateEdgeLengths(bpy.types.Operator): bl_idname = "bim.calculate_edge_lengths" bl_label = "Calculate Edge Lengths" diff --git a/src/blenderbim/blenderbim/bim/module/qto/ui.py b/src/blenderbim/blenderbim/bim/module/qto/ui.py index 0b021e66ff..58699f609b 100644 --- a/src/blenderbim/blenderbim/bim/module/qto/ui.py +++ b/src/blenderbim/blenderbim/bim/module/qto/ui.py @@ -34,6 +34,8 @@ class BIM_PT_qto_utilities(Panel): row = layout.row() row.prop(props, "qto_result", text="Results") + row = layout.row(align=True) + row.operator("bim.calculate_circle_radius") row = layout.row(align=True) row.operator("bim.calculate_edge_lengths") row = layout.row(align=True) diff --git a/src/blenderbim/blenderbim/core/qto.py b/src/blenderbim/blenderbim/core/qto.py new file mode 100644 index 0000000000..28c061f9da --- /dev/null +++ b/src/blenderbim/blenderbim/core/qto.py @@ -0,0 +1,23 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + + +def calculate_circle_radius(qto, obj=None): + result = qto.get_radius_of_selected_vertices(obj) + qto.set_qto_result(result) + return result diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index e653194800..6a70b536ad 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -135,6 +135,12 @@ class Owner: def set_user(cls, user): pass +@interface +class Qto: + def get_radius_of_selected_vertices(cls, obj): pass + def set_qto_result(cls, result): pass + + @interface class Root: def add_dynamic_opening_voids(cls, element, obj): pass diff --git a/src/blenderbim/blenderbim/tool/__init__.py b/src/blenderbim/blenderbim/tool/__init__.py index 3c8c505572..521b4d1854 100644 --- a/src/blenderbim/blenderbim/tool/__init__.py +++ b/src/blenderbim/blenderbim/tool/__init__.py @@ -25,6 +25,7 @@ from blenderbim.tool.ifc import Ifc from blenderbim.tool.material import Material from blenderbim.tool.misc import Misc from blenderbim.tool.owner import Owner +from blenderbim.tool.qto import Qto from blenderbim.tool.root import Root from blenderbim.tool.spatial import Spatial from blenderbim.tool.style import Style diff --git a/src/blenderbim/blenderbim/tool/qto.py b/src/blenderbim/blenderbim/tool/qto.py new file mode 100644 index 0000000000..732193f818 --- /dev/null +++ b/src/blenderbim/blenderbim/tool/qto.py @@ -0,0 +1,37 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + +import bpy +import blenderbim.core.tool +import blenderbim.tool as tool +from mathutils import Vector + + +class Qto(blenderbim.core.tool.Qto): + @classmethod + def get_radius_of_selected_vertices(cls, obj): + selected_verts = [v.co for v in obj.data.vertices if v.select] + total = Vector() + for v in selected_verts: + total += v + circle_center = total / len(selected_verts) + return max([(v - circle_center).length for v in selected_verts]) + + @classmethod + def set_qto_result(cls, result): + bpy.context.scene.BIMQtoProperties.qto_result = str(round(result, 3)) diff --git a/src/blenderbim/pytest.ini b/src/blenderbim/pytest.ini index 397268bda9..07f53e6d6d 100644 --- a/src/blenderbim/pytest.ini +++ b/src/blenderbim/pytest.ini @@ -9,6 +9,7 @@ markers = patch project pset_template + qto root spatial style diff --git a/src/blenderbim/test/bim/feature/qto.feature b/src/blenderbim/test/bim/feature/qto.feature new file mode 100644 index 0000000000..3f2a737489 --- /dev/null +++ b/src/blenderbim/test/bim/feature/qto.feature @@ -0,0 +1,30 @@ +@qto +Feature: Qto + +Scenario: Calculate circle radius + Given an empty Blender session + And I add a cube + And the object "Cube" is selected + When I press "bim.calculate_circle_radius" + Then "scene.BIMQtoProperties.qto_result" is "1.732" + +Scenario: Calculate edge lengths + Given an empty Blender session + And I add a cube + And the object "Cube" is selected + When I press "bim.calculate_edge_lengths" + Then "scene.BIMQtoProperties.qto_result" is "24.0" + +Scenario: Calculate face areas + Given an empty Blender session + And I add a cube + And the object "Cube" is selected + When I press "bim.calculate_face_areas" + Then "scene.BIMQtoProperties.qto_result" is "24.0" + +Scenario: Calculate object volumes + Given an empty Blender session + And I add a cube + And the object "Cube" is selected + When I press "bim.calculate_object_volumes" + Then "scene.BIMQtoProperties.qto_result" is "8.0" diff --git a/src/blenderbim/test/bim/test_feature.py b/src/blenderbim/test/bim/test_feature.py index 8a3c36c61d..b896ecf337 100644 --- a/src/blenderbim/test/bim/test_feature.py +++ b/src/blenderbim/test/bim/test_feature.py @@ -358,7 +358,7 @@ def the_object_name_is_at_location(name, location): @then(parsers.parse('the file "{name}" should contain "{value}"')) -def the_file_name_should_contain_value(name, value): +def the_file_name_should_contain_value(name, value): name = replace_variables(name) with open(name, "r") as f: assert value in f.read() diff --git a/src/blenderbim/test/core/bootstrap.py b/src/blenderbim/test/core/bootstrap.py index 8982f8d9b9..261337e63b 100644 --- a/src/blenderbim/test/core/bootstrap.py +++ b/src/blenderbim/test/core/bootstrap.py @@ -84,6 +84,13 @@ def owner(): prophet.verify() +@pytest.fixture +def qto(): + prophet = Prophecy(blenderbim.core.tool.Qto) + yield prophet + prophet.verify() + + @pytest.fixture def root(): prophet = Prophecy(blenderbim.core.tool.Root) @@ -127,15 +134,15 @@ def type(): @pytest.fixture -def voider(): - prophet = Prophecy(blenderbim.core.tool.Voider) +def unit(): + prophet = Prophecy(blenderbim.core.tool.Unit) yield prophet prophet.verify() @pytest.fixture -def unit(): - prophet = Prophecy(blenderbim.core.tool.Unit) +def voider(): + prophet = Prophecy(blenderbim.core.tool.Voider) yield prophet prophet.verify() diff --git a/src/blenderbim/test/core/test_qto.py b/src/blenderbim/test/core/test_qto.py new file mode 100644 index 0000000000..07ca33cda5 --- /dev/null +++ b/src/blenderbim/test/core/test_qto.py @@ -0,0 +1,27 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + +import blenderbim.core.qto as subject +from test.core.bootstrap import qto + + +class TestCalculateCircleRadius: + def test_run(self, qto): + qto.get_radius_of_selected_vertices("obj").should_be_called().will_return("radius") + qto.set_qto_result("radius").should_be_called() + assert subject.calculate_circle_radius(qto, obj="obj") == "radius" diff --git a/src/blenderbim/test/tool/test_qto.py b/src/blenderbim/test/tool/test_qto.py new file mode 100644 index 0000000000..bc16ad5e9c --- /dev/null +++ b/src/blenderbim/test/tool/test_qto.py @@ -0,0 +1,41 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + +import bpy +import ifcopenshell +import test.bim.bootstrap +import blenderbim.core.tool +import blenderbim.tool as tool +from blenderbim.tool.qto import Qto as subject + + +class TestImplementsTool(test.bim.bootstrap.NewFile): + def test_run(self): + assert isinstance(subject(), blenderbim.core.tool.Qto) + + +class TestGetRadiusOfSelectedVertices(test.bim.bootstrap.NewFile): + def test_run(self): + bpy.ops.mesh.primitive_circle_add() + assert round(subject.get_radius_of_selected_vertices(bpy.data.objects.get("Circle")), 3) == 1 + + +class TestSetQtoResult(test.bim.bootstrap.NewFile): + def test_run(self): + subject.set_qto_result(123.4567) + assert bpy.context.scene.BIMQtoProperties.qto_result == "123.457"