由其角点定义的边界框对象的嵌套属性
Nested Attributes for a Bounding Box Object defined by its Corner Points
Blender 脚本通常必须从一组 3D 点计算包围边界框,例如,为了将默认的 Blender 立方体边界框作为输入,
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")
的“迭代魔法”,来制作一个助手 class
>>> 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)},
}
}
}
Object-like attribute access for nested dictionary
编辑
为了避免发布 XY Problem,ie 以我处理这个问题的方式发布问题,在下面添加了一个答案,说明我当时的处理方式.抱歉,我忘了提及可以改为选择北、南、东和西作为 x 和 y 轴方向,并希望能够改变。
感觉循环 8 个角顶点是重新制作以顶点索引作为叶节点的“swizzle”字典的方法。 “前”面或右上角下角的顶点索引不变。
它使用它作为 class 的基础,用坐标或 bfl 实例化,tbr 是无论我做什么我总是觉得有比我做的“更好”的方法现在正在做。
这里有两个相似的版本。两者的想法是你总是 return
一个 BBox
对象,只改变一个变量 x
,它指示您通过 left
、right
、...指定的维度
最后你有一个使用 x
来计算中心的函数
剩余角球。
第一种方法使用函数,因此您必须调用它们 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是:
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)
这是使用迭代方法创建字典的另一种解决方案:
import numpy
import itertools
directions = ['left', 'right', 'front', 'back', 'bottom', 'top']
dims = np.array([ 0, 0, 1, 1, 2, 2]) # xyz
def get_vertices(bfl, tbr, x):
g = np.array((bfl, tbr)).T
return [v for v in itertools.product(*[g[ii][x[ii]] for ii in range(3)])]
bfl = [-1, -1, -1]
tbr = [1, 1, 1]
d = {}
for i in range(6):
x = [[0, 1], [0, 1], [0, 1]]
x[i//2] = [i % 2] # x[dim[i] = min or max
d_i = dict(c=np.mean(get_vertices(bfl=bfl, tbr=tbr, x=x), axis=0))
for j in np.nonzero(dims != dims[i])[0]:
x[j//2] = [j % 2]
d_ij = dict(c=np.mean(get_vertices(bfl=bfl, tbr=tbr, x=x), axis=0))
for k in np.nonzero(np.logical_and(dims != dims[i], dims != dims[j]))[0]:
x[k//2] = [k % 2]
d_ij[directions[k]] = dict(c=np.mean(get_vertices(bfl=bfl, tbr=tbr, x=x), axis=0))
d_i[directions[j]] = d_ij
d[directions[i]] = d_i
d
# {'left': {'c': array([-1., 0., 0.]),
# 'front': {'c': array([-1., -1., 0.]),
# 'bottom': {'c': array([-1., -1., -1.])},
# 'top': {'c': array([-1., -1., 1.])}},
# 'back': {'c': array([-1., 1., 1.]),
# 'bottom': {'c': array([-1., 1., -1.])},
# 'top': {'c': array([-1., 1., 1.])}},
# ....
您可以将此与您的链接问题结合起来,通过 d.key1.key2
访问字典的键。
我到了哪里。
以某种方式将此添加为答案以更好地解释我的问题
循环遍历立方体的 8 个顶点,将 3 个名称匹配到每个有效角。
“swizzle”是构成角的三个轴方向的排列。
直接输入自嵌套字典d[i][j][k] = value
是一种轻松创建它们的方法。 (下图pprint(d)
)
很高兴从那里开始它变得丑陋,一些鸭子输入从简单的 8 vert truth table.
获取和获取元素索引
由于没有特别的原因,returns 生成的方法 class 是一个包装器,但我并没有这样使用它。
import numpy as np
import pprint
import operator
from itertools import product, permutations
from functools import reduce
from collections import defaultdict
class NestedDefaultDict(defaultdict):
def __init__(self, *args, **kwargs):
super(NestedDefaultDict, self).__init__(NestedDefaultDict, *args, **kwargs)
def __repr__(self):
return repr(dict(self))
def set_by_path(root, items, value):
reduce(operator.getitem, items[:-1], root)[items[-1]] = value
def create_bbox_swizzle(cls, dirx=("left", "right"), diry=("front", "back"), dirz=("bottom", "top")):
d = NestedDefaultDict()
data = {}
for i, cnr in enumerate(product(*(dirx, diry, dirz))):
vert = {"index": i}
data[frozenset(cnr)] = i
for perm in permutations(cnr, 3):
set_by_path(d, perm, vert)
pprint.pprint(d)
def wire_up(names, d):
class Mbox:
@property
def co(self):
return self.coords[self.vertices].mean(axis=0)
def __init__(self, coords):
self.coords = np.array(coords)
self.vertices = [v for k, v in data.items() if k.issuperset(names)]
pass
def __repr__(self):
if len(names) == 1:
return f"<BBFace {self.vertices}/>"
elif len(names) == 2:
return f"<BBEdge {self.vertices}/>"
elif len(names) == 3:
return f"<BBVert {self.vertices}/>"
return "<BBox/>"
pass
def f(k, v):
def g(self):
return wire_up(names + [k], v)(self.coords)
return property(g)
for k, v in d.items():
if isinstance(v, dict):
setattr(Mbox, k, (f(k, v)))
else:
setattr(Mbox, k, v)
return Mbox
return wire_up([], d)
@create_bbox_swizzle
class BBox:
def __init__(self, *coords, **kwargs):
pass
试驾:
>>> bbox = BBox(coords) # used coords instead of corners
>>> bbox.co
array([ 5.96046448e-08, -1.19209290e-07, 0.00000000e+00])
>>> bbox.left.bottom
<BBEdge [0, 2]/>
>>> bbox.left.bottom.vertices
[0, 2]
>>> bbox.left.bottom.co
array([-1.00000036e+00, -1.19209290e-07, 0.00000000e+00])
Blender 脚本通常必须从一组 3D 点计算包围边界框,例如,为了将默认的 Blender 立方体边界框作为输入,
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")
的“迭代魔法”,来制作一个助手 class
>>> 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)},
}
}
}
Object-like attribute access for nested dictionary
编辑
为了避免发布 XY Problem,ie 以我处理这个问题的方式发布问题,在下面添加了一个答案,说明我当时的处理方式.抱歉,我忘了提及可以改为选择北、南、东和西作为 x 和 y 轴方向,并希望能够改变。
感觉循环 8 个角顶点是重新制作以顶点索引作为叶节点的“swizzle”字典的方法。 “前”面或右上角下角的顶点索引不变。
它使用它作为 class 的基础,用坐标或 bfl 实例化,tbr 是无论我做什么我总是觉得有比我做的“更好”的方法现在正在做。
这里有两个相似的版本。两者的想法是你总是 return
一个 BBox
对象,只改变一个变量 x
,它指示您通过 left
、right
、...指定的维度
最后你有一个使用 x
来计算中心的函数
剩余角球。
第一种方法使用函数,因此您必须调用它们 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是:
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)
这是使用迭代方法创建字典的另一种解决方案:
import numpy
import itertools
directions = ['left', 'right', 'front', 'back', 'bottom', 'top']
dims = np.array([ 0, 0, 1, 1, 2, 2]) # xyz
def get_vertices(bfl, tbr, x):
g = np.array((bfl, tbr)).T
return [v for v in itertools.product(*[g[ii][x[ii]] for ii in range(3)])]
bfl = [-1, -1, -1]
tbr = [1, 1, 1]
d = {}
for i in range(6):
x = [[0, 1], [0, 1], [0, 1]]
x[i//2] = [i % 2] # x[dim[i] = min or max
d_i = dict(c=np.mean(get_vertices(bfl=bfl, tbr=tbr, x=x), axis=0))
for j in np.nonzero(dims != dims[i])[0]:
x[j//2] = [j % 2]
d_ij = dict(c=np.mean(get_vertices(bfl=bfl, tbr=tbr, x=x), axis=0))
for k in np.nonzero(np.logical_and(dims != dims[i], dims != dims[j]))[0]:
x[k//2] = [k % 2]
d_ij[directions[k]] = dict(c=np.mean(get_vertices(bfl=bfl, tbr=tbr, x=x), axis=0))
d_i[directions[j]] = d_ij
d[directions[i]] = d_i
d
# {'left': {'c': array([-1., 0., 0.]),
# 'front': {'c': array([-1., -1., 0.]),
# 'bottom': {'c': array([-1., -1., -1.])},
# 'top': {'c': array([-1., -1., 1.])}},
# 'back': {'c': array([-1., 1., 1.]),
# 'bottom': {'c': array([-1., 1., -1.])},
# 'top': {'c': array([-1., 1., 1.])}},
# ....
您可以将此与您的链接问题结合起来,通过 d.key1.key2
访问字典的键。
我到了哪里。
以某种方式将此添加为答案以更好地解释我的问题
循环遍历立方体的 8 个顶点,将 3 个名称匹配到每个有效角。
“swizzle”是构成角的三个轴方向的排列。
直接输入自嵌套字典d[i][j][k] = value
是一种轻松创建它们的方法。 (下图pprint(d)
)
很高兴从那里开始它变得丑陋,一些鸭子输入从简单的 8 vert truth table.
获取和获取元素索引由于没有特别的原因,returns 生成的方法 class 是一个包装器,但我并没有这样使用它。
import numpy as np
import pprint
import operator
from itertools import product, permutations
from functools import reduce
from collections import defaultdict
class NestedDefaultDict(defaultdict):
def __init__(self, *args, **kwargs):
super(NestedDefaultDict, self).__init__(NestedDefaultDict, *args, **kwargs)
def __repr__(self):
return repr(dict(self))
def set_by_path(root, items, value):
reduce(operator.getitem, items[:-1], root)[items[-1]] = value
def create_bbox_swizzle(cls, dirx=("left", "right"), diry=("front", "back"), dirz=("bottom", "top")):
d = NestedDefaultDict()
data = {}
for i, cnr in enumerate(product(*(dirx, diry, dirz))):
vert = {"index": i}
data[frozenset(cnr)] = i
for perm in permutations(cnr, 3):
set_by_path(d, perm, vert)
pprint.pprint(d)
def wire_up(names, d):
class Mbox:
@property
def co(self):
return self.coords[self.vertices].mean(axis=0)
def __init__(self, coords):
self.coords = np.array(coords)
self.vertices = [v for k, v in data.items() if k.issuperset(names)]
pass
def __repr__(self):
if len(names) == 1:
return f"<BBFace {self.vertices}/>"
elif len(names) == 2:
return f"<BBEdge {self.vertices}/>"
elif len(names) == 3:
return f"<BBVert {self.vertices}/>"
return "<BBox/>"
pass
def f(k, v):
def g(self):
return wire_up(names + [k], v)(self.coords)
return property(g)
for k, v in d.items():
if isinstance(v, dict):
setattr(Mbox, k, (f(k, v)))
else:
setattr(Mbox, k, v)
return Mbox
return wire_up([], d)
@create_bbox_swizzle
class BBox:
def __init__(self, *coords, **kwargs):
pass
试驾:
>>> bbox = BBox(coords) # used coords instead of corners
>>> bbox.co
array([ 5.96046448e-08, -1.19209290e-07, 0.00000000e+00])
>>> bbox.left.bottom
<BBEdge [0, 2]/>
>>> bbox.left.bottom.vertices
[0, 2]
>>> bbox.left.bottom.co
array([-1.00000036e+00, -1.19209290e-07, 0.00000000e+00])