首页 > 解决方案 > 由其角点定义的边界框对象的嵌套属性

问题描述

通常对于搅拌机脚本来说,必须从 3D 点的集合中计算出一个包围盒,例如为了默认的搅拌机立方体边界盒作为输入,

coords = np.array(
     [[-1.  1. -1.],
      [-1.  1.  1.],
      [ 1. -1. -1.],
      [ 1. -1.  1.],
      [ 1.  1. -1.],
      [ 1.  1.  1.]]
 )

bfl = coords.min(axis=0)
tbr = coords.max(axis=0)

G  = np.array((bfl, tbr)).T
bbox_coords = [i for i in itertools.product(*G)]

例如,边界框坐标将是相同顺序的立方体坐标

使用上面的和寻找一些python“迭代魔法” ("left", "right"), ("front", "back"),("top", "bottom"),制作一个帮助类

>>> bbox = BBox(bfl, tbr)
>>> bbox.bottom.front.left
(-1, -1, -1)

>>> bbox.top.front
(0, -1, 1)

>> bbox.bottom
(0, 0, -1)

角顶点,边缘的中心,矩形的中心。(1、2 或 4 个角的平均总和)在搅拌机顶部是 +Z,前面是 -Y。

最初是在寻找类似用静态计算值填充嵌套字典的东西

d = {
    "front" : {
        "co" : (0, -1, 0),
        "top" : {
            "co" : (0, -1, 1),
            "left" : {"co" : (-1, -1, 1)},
            }
        }   
    }

嵌套字典的类对象属性访问

编辑

为了避免发布XY Problem以我一直在处理的方式发布问题,在下面添加了一个答案,其中包含我在哪里使用它。抱歉,我忘了提到,可以改为选择 x 和 y 轴方向的北、南、东和西,并希望能够改变。

感觉循环超过 8 个角顶点是重新制作以顶点索引作为叶节点的“swizzle”字典的方法。“正面”面或右下角的顶点索引不会改变。

它使用它作为使用坐标或 bfl 实例化的类的基础,tbr 是无论我做什么,我总是觉得有比我现在做的“更好”的方法。

标签: pythonnumpynestediteratorattributes

解决方案


这里有两个类似的版本。两者的想法是您始终返回一个BBox对象,并且只更改一个变量,该变量指示您通过, , ...x 指定的维度。最后,您有一个函数用于计算剩余角的中心。leftrightx

第一种方法使用函数,因此您必须调用它们bbox.bottom().front().left().c()。这里的主要区别是不是所有的组合

top
top left
top right
top left front
...

在创建对象时计算,但仅在您调用它们时计算。


import numpy as np
import itertools

class BBox:
    """
    ("left", "right"), -x, +x
    ("front", "back"), -y, +y
    ("bottom", "top"), -z, +z
    """
    def __init__(self, bfl, tbr):
        self.bfl = bfl
        self.tbr = tbr

        self.g = np.array((bfl, tbr)).T

        self.x = [[0, 1], [0, 1], [0, 1]]

    def c(self):  # get center coordinates
        return np.mean([i for i in itertools.product(*[self.g[i][self.x[i]] for i in range(3)])], axis=0)

    def part(self, i, xi):
        assert len(self.x[i]) == 2
        b2 = BBox(bfl=self.bfl, tbr=self.tbr)
        b2.x = self.x.copy()
        b2.x[i] = [xi]
        return b2

    def left(self):
        return self.part(i=0, xi=0)

    def right(self):
        return self.part(i=0, xi=1)

    def front(self):
        return self.part(i=1, xi=0)

    def back(self):
        return self.part(i=1, xi=1)

    def bottom(self):
        return self.part(i=2, xi=0)

    def top(self):
        return self.part(i=2, xi=1)


bbox = BBox(bfl=[-1, -1, -1], tbr=[1, 1, 1])
>>> bbox.bottom().front().left().c()
(-1, -1, -1)

>>> bbox.top().front().c()
(0, -1, 1)

>>> bbox.bottom().c()
(0, 0, -1)

第二种方法使用本身就是BBox对象的属性。当您取消注释init函数中的 print 语句时,您会了解在构造过程中发生的所有递归调用。因此,虽然查看这里发生的事情可能会更复杂,但在访问属性时会更方便。

class BBox:
    def __init__(self, bfl, tbr, x=None):
        self.bfl = bfl
        self.tbr = tbr
        self.g = np.array((bfl, tbr)).T

        self.x = [[0, 1], [0, 1], [0, 1]] if x is None else x
        
        # print(self.x)  # Debugging 
        self.left = self.part(i=0, xi=0)
        self.right = self.part(i=0, xi=1)
        self.front = self.part(i=1, xi=0)
        self.back = self.part(i=1, xi=1)
        self.bottom = self.part(i=2, xi=0)
        self.top = self.part(i=2, xi=1)

    def c(self):  # get center coordinates
        return np.mean([i for i in itertools.product(*[self.g[i][self.x[i]] 
                        for i in range(3)])], axis=0)

    def part(self, i, xi):
        if len(self.x[i]) < 2:
            return None
        x2 = self.x.copy()
        x2[i] = [xi]
        return BBox(bfl=self.bfl, tbr=self.tbr, x=x2)

bbox = BBox(bfl=[-1, -1, -1], tbr=[1, 1, 1])
>>> bbox.bottom.front.left.c()
(-1, -1, -1)

您还可以在构造函数的末尾添加类似的内容,以删除无效属性。(以防止类似的东西bbox.right.left.c())。它们是None以前的,但AttributeError可能更合适。

   def __init__(self, bfl, tbr, x=None):
       ...
       for name in ['left', 'right', 'front', 'back', 'bottom', 'top']:
           if getattr(self, name) is None:
               delattr(self, name)

你也可以添加一个__repr__()方法:

    def __repr__(self):
        return repr(self.get_vertices())

    def get_vertices(self):
        return [i for i in itertools.product(*[self.g[i][self.x[i]]
                                               for i in range(3)])]

    def c(self):  # get center coordinates
        return np.mean(self.get_vertices(), axis=0)


bbox.left.front
# [(-1, -1, -1), (-1, -1, 1)]
bbox.left.front.c()
# array([-1., -1.,  0.])

编辑

一段时间后回到这个问题后,我认为最好只添加相关属性而不添加所有属性,然后删除其中的一半。所以我能想到的最紧凑/最方便的课程是:

class BBox:
    def __init__(self, bfl, tbr, x=None):
        self.bfl, self.tbr = bfl, tbr
        self.g = np.array((bfl, tbr)).T
        self.x = [[0, 1], [0, 1], [0, 1]] if x is None else x

        for j, name in enumerate(['left', 'right', 'front', 'back', 'bottom', 'top']):
            temp = self.part(i=j//2, xi=j%2)
            if temp is not None:
                setattr(self, name, temp)

    def c(self):  # get center coordinates
        return np.mean([x for x in itertools.product(*[self.g[i][self.x[i]]
                                                       for i in range(3)])], axis=0)

    def part(self, i, xi):
        if len(self.x[i]) == 2:
            x2, x2[i] = self.x.copy(), [xi]
            return BBox(bfl=self.bfl, tbr=self.tbr, x=x2)

推荐阅读