Python 在matplot库中绘制三维凸闭合区域

Python 在matplot库中绘制三维凸闭合区域,python,numpy,matplotlib,scipy,mplot3d,Python,Numpy,Matplotlib,Scipy,Mplot3d,我试图在3D中绘制一个由一组不等式定义的多面体。本质上,我试图在matplotlib中重现此matlab库的功能 我的方法是获取相交顶点,构造它们的凸包,然后获取并绘制生成的面(单纯形) 问题是许多单体是共面的,它们无缘无故地使绘图非常繁忙(请参见下面绘图中的所有对角线) 有没有简单的方法可以直接打印多面体的“外”边,而不必一个接一个地合并所有的共面单纯形 多谢各位 from scipy.spatial import HalfspaceIntersection from scipy.spatia

我试图在3D中绘制一个由一组不等式定义的多面体。本质上,我试图在matplotlib中重现此matlab库的功能

我的方法是获取相交顶点,构造它们的凸包,然后获取并绘制生成的面(单纯形)

问题是许多单体是共面的,它们无缘无故地使绘图非常繁忙(请参见下面绘图中的所有对角线)

有没有简单的方法可以直接打印多面体的“外”边,而不必一个接一个地合并所有的共面单纯形

多谢各位

from scipy.spatial import HalfspaceIntersection
from scipy.spatial import ConvexHull
import scipy as sp
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d as a3
import matplotlib.colors as colors


w = np.array([1., 1., 1.])


# ∑ᵢ hᵢ wᵢ qᵢ - ∑ᵢ gᵢ wᵢ <= 0 
#  qᵢ - ubᵢ <= 0
# -qᵢ + lbᵢ <= 0 
halfspaces = np.array([
                    [1.*w[0], 1.*w[1], 1.*w[2], -10 ],
                    [ 1.,  0.,  0., -4],
                    [ 0.,  1.,  0., -4],
                    [ 0.,  0.,  1., -4],
                    [-1.,  0.,  0.,  0],
                    [ 0., -1.,  0.,  0],
                    [ 0.,  0., -1.,  0]
                    ])
feasible_point = np.array([0.1, 0.1, 0.1])
hs = HalfspaceIntersection(halfspaces, feasible_point)
verts = hs.intersections
hull = ConvexHull(verts)
faces = hull.simplices

ax = a3.Axes3D(plt.figure())
ax.dist=10
ax.azim=30
ax.elev=10
ax.set_xlim([0,5])
ax.set_ylim([0,5])
ax.set_zlim([0,5])

for s in faces:
    sq = [
        [verts[s[0], 0], verts[s[0], 1], verts[s[0], 2]],
        [verts[s[1], 0], verts[s[1], 1], verts[s[1], 2]],
        [verts[s[2], 0], verts[s[2], 1], verts[s[2], 2]]
    ]

    f = a3.art3d.Poly3DCollection([sq])
    f.set_color(colors.rgb2hex(sp.rand(3)))
    f.set_edgecolor('k')
    f.set_alpha(0.1)
    ax.add_collection3d(f)

plt.show()
从scipy.spatial导入半空间交点
从scipy.spatial导入convxhull
将scipy作为sp导入
将numpy作为np导入
将matplotlib.pyplot作为plt导入
将mpl_toolkits.mplot3d作为a3导入
将matplotlib.colors导入为颜色
w=np.数组([1,1,1.]))

# ∑ᵢ Hᵢ Wᵢ Qᵢ - ∑ᵢ Gᵢ Wᵢ 很确定matplotlib中没有任何本机内容。不过,找到属于一起的面孔并不特别困难。下面实现的基本思想是创建一个图,其中每个节点都是一个三角形。然后连接共面且相邻的三角形。最后,找到图形的连接组件以确定哪些三角形构成面

将numpy导入为np
从sympy导入平面,点3D
将networkx导入为nx
def简化(三角形):
"""
简化一组三角形,使相邻三角形和共面三角形形成一个面。
每个三角形是三维空间中3个点的集合。
"""
#创建一个节点表示三角形的图形;
#如果相应的三角形相邻且共面,则连接节点
G=nx.Graph()
G.从(范围(三角形))添加节点
对于ii,枚举中的a(三角形):
对于jj,枚举中的b(三角形):
if(iireturn(角度-公差_in_弧度以下是我的解决方案版本。它类似于@Paul的解决方案,它采用三角形,按它们所属的面对它们进行分组,并将它们连接到一个面

区别主要在于此解决方案不使用
nx
simpy
。许多必要的操作都是通过重新索引、广泛使用
unique
和一些线性代数来执行的。
最终面顶点的顺序由凸面决定。我认为这不应该是一个限制,因为(我认为)任何半空间相交都应该只产生凸面形状。但是,我还添加了另一种方法,如果形状不是凸面,可以使用它(基于来自的想法)

从scipy.spatial导入半空间交点
从scipy.spatial导入convxhull
将numpy作为np导入
将matplotlib.pyplot作为plt导入
将mpl_toolkits.mplot3d作为a3导入
w=np.数组([1,1,1.]))

# ∑ᵢ Hᵢ Wᵢ Qᵢ - ∑ᵢ Gᵢ Wᵢ 我猜“不太难”是一个委婉的说法?还有什么比最初的问题Qualified as更需要两个额外的包和更多的代码行吗?;-)我昨天尝试了类似的东西,但没有
nx
,在第5个子循环中迷路后放弃了。哦,哇!非常感谢各位的努力!不是MaMattLIB的专家,但我认为这个主题中描述的功能非常有用(至少是足够有用的),以保证图书馆中的一个位置。也许我们应该打电话给开发商?再次感谢你!对我的生活圆满了!在@ImportanceOfBeingErnest被难倒的地方,我获胜了。我现在可以休息了但是,如果不是因为舍入误差,
是共面的
会短得多,并且重新排列节点以获得凸面片几乎是剩余直线的一半。连接组件的整洁位非常短。@nikferrari:解决方案取决于
networkx
sympy
。我敢肯定,那些太大的依赖性,MatMattLIB团队考虑纳入MaMattLIB的主分支。替换
是共面的
,因此清除sympy库可能非常简单(那天下午晚些时候我不相信我的数学);不过,实现一个图形结构并找到连接的组件至少需要一页代码。仔细检查,实际上可能没那么糟糕。scipy.sparse中已经有一个连接组件实现<代码>重新排序
仍然需要变得健壮,但是,我目前没有一个好主意。有什么建议吗,@ImportanceOfBeingErnest(当我们得到你的注意时)?
import numpy as np
from sympy import Plane, Point3D
import networkx as nx


def simplify(triangles):
    """
    Simplify an iterable of triangles such that adjacent and coplanar triangles form a single face.
    Each triangle is a set of 3 points in 3D space.
    """

    # create a graph in which nodes represent triangles;
    # nodes are connected if the corresponding triangles are adjacent and coplanar
    G = nx.Graph()
    G.add_nodes_from(range(len(triangles)))
    for ii, a in enumerate(triangles):
        for jj, b in enumerate(triangles):
            if (ii < jj): # test relationships only in one way as adjacency and co-planarity are bijective
                if is_adjacent(a, b):
                    if is_coplanar(a, b, np.pi / 180.):
                        G.add_edge(ii,jj)

    # triangles that belong to a connected component can be combined
    components = list(nx.connected_components(G))
    simplified = [set(flatten(triangles[index] for index in component)) for component in components]

    # need to reorder nodes so that patches are plotted correctly
    reordered = [reorder(face) for face in simplified]

    return reordered


def is_adjacent(a, b):
    return len(set(a) & set(b)) == 2 # i.e. triangles share 2 points and hence a side


def is_coplanar(a, b, tolerance_in_radians=0):
    a1, a2, a3 = a
    b1, b2, b3 = b
    plane_a = Plane(Point3D(a1), Point3D(a2), Point3D(a3))
    plane_b = Plane(Point3D(b1), Point3D(b2), Point3D(b3))
    if not tolerance_in_radians: # only accept exact results
        return plane_a.is_coplanar(plane_b)
    else:
        angle = plane_a.angle_between(plane_b).evalf()
        angle %= np.pi # make sure that angle is between 0 and np.pi
        return (angle - tolerance_in_radians <= 0.) or \
            ((np.pi - angle) - tolerance_in_radians <= 0.)


flatten = lambda l: [item for sublist in l for item in sublist]


def reorder(vertices):
    """
    Reorder nodes such that the resulting path corresponds to the "hull" of the set of points.

    Note:
    -----
    Not tested on edge cases, and likely to break.
    Probably only works for convex shapes.

    """
    if len(vertices) <= 3: # just a triangle
        return vertices
    else:
        # take random vertex (here simply the first)
        reordered = [vertices.pop()]
        # get next closest vertex that is not yet reordered
        # repeat until only one vertex remains in original list
        vertices = list(vertices)
        while len(vertices) > 1:
            idx = np.argmin(get_distance(reordered[-1], vertices))
            v = vertices.pop(idx)
            reordered.append(v)
        # add remaining vertex to output
        reordered += vertices
        return reordered


def get_distance(v1, v2):
    v2 = np.array(list(v2))
    difference = v2 - v1
    ssd = np.sum(difference**2, axis=1)
    return np.sqrt(ssd)
from scipy.spatial import HalfspaceIntersection
from scipy.spatial import ConvexHull
import scipy as sp
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d as a3
import matplotlib.colors as colors


w = np.array([1., 1., 1.])


# ∑ᵢ hᵢ wᵢ qᵢ - ∑ᵢ gᵢ wᵢ <= 0
#  qᵢ - ubᵢ <= 0
# -qᵢ + lbᵢ <= 0
halfspaces = np.array([
                    [1.*w[0], 1.*w[1], 1.*w[2], -10 ],
                    [ 1.,  0.,  0., -4],
                    [ 0.,  1.,  0., -4],
                    [ 0.,  0.,  1., -4],
                    [-1.,  0.,  0.,  0],
                    [ 0., -1.,  0.,  0],
                    [ 0.,  0., -1.,  0]
                    ])
feasible_point = np.array([0.1, 0.1, 0.1])
hs = HalfspaceIntersection(halfspaces, feasible_point)
verts = hs.intersections
hull = ConvexHull(verts)
faces = hull.simplices

ax = a3.Axes3D(plt.figure())
ax.dist=10
ax.azim=30
ax.elev=10
ax.set_xlim([0,5])
ax.set_ylim([0,5])
ax.set_zlim([0,5])

triangles = []
for s in faces:
    sq = [
        (verts[s[0], 0], verts[s[0], 1], verts[s[0], 2]),
        (verts[s[1], 0], verts[s[1], 1], verts[s[1], 2]),
        (verts[s[2], 0], verts[s[2], 1], verts[s[2], 2])
    ]
    triangles.append(sq)

new_faces = simplify(triangles)
for sq in new_faces:
    f = a3.art3d.Poly3DCollection([sq])
    f.set_color(colors.rgb2hex(sp.rand(3)))
    f.set_edgecolor('k')
    f.set_alpha(0.1)
    ax.add_collection3d(f)

# # rotate the axes and update
# for angle in range(0, 360):
#     ax.view_init(30, angle)
#     plt.draw()
#     plt.pause(.001)
from scipy.spatial import HalfspaceIntersection
from scipy.spatial import ConvexHull
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d as a3

w = np.array([1., 1., 1.])
# ∑ᵢ hᵢ wᵢ qᵢ - ∑ᵢ gᵢ wᵢ <= 0 
#  qᵢ - ubᵢ <= 0
# -qᵢ + lbᵢ <= 0 
halfspaces = np.array([
                    [1.*w[0], 1.*w[1], 1.*w[2], -10 ],
                    [ 1.,  0.,  0., -4],
                    [ 0.,  1.,  0., -4],
                    [ 0.,  0.,  1., -4],
                    [-1.,  0.,  0.,  0],
                    [ 0., -1.,  0.,  0],
                    [ 0.,  0., -1.,  0]
                    ])
feasible_point = np.array([0.1, 0.1, 0.1])
hs = HalfspaceIntersection(halfspaces, feasible_point)
verts = hs.intersections
hull = ConvexHull(verts)
simplices = hull.simplices

org_triangles = [verts[s] for s in simplices]

class Faces():
    def __init__(self,tri, sig_dig=12, method="convexhull"):
        self.method=method
        self.tri = np.around(np.array(tri), sig_dig)
        self.grpinx = list(range(len(tri)))
        norms = np.around([self.norm(s) for s in self.tri], sig_dig)
        _, self.inv = np.unique(norms,return_inverse=True, axis=0)

    def norm(self,sq):
        cr = np.cross(sq[2]-sq[0],sq[1]-sq[0])
        return np.abs(cr/np.linalg.norm(cr))

    def isneighbor(self, tr1,tr2):
        a = np.concatenate((tr1,tr2), axis=0)
        return len(a) == len(np.unique(a, axis=0))+2

    def order(self, v):
        if len(v) <= 3:
            return v
        v = np.unique(v, axis=0)
        n = self.norm(v[:3])
        y = np.cross(n,v[1]-v[0])
        y = y/np.linalg.norm(y)
        c = np.dot(v, np.c_[v[1]-v[0],y])
        if self.method == "convexhull":
            h = ConvexHull(c)
            return v[h.vertices]
        else:
            mean = np.mean(c,axis=0)
            d = c-mean
            s = np.arctan2(d[:,0], d[:,1])
            return v[np.argsort(s)]

    def simplify(self):
        for i, tri1 in enumerate(self.tri):
            for j,tri2 in enumerate(self.tri):
                if j > i: 
                    if self.isneighbor(tri1,tri2) and \
                       self.inv[i]==self.inv[j]:
                        self.grpinx[j] = self.grpinx[i]
        groups = []
        for i in np.unique(self.grpinx):
            u = self.tri[self.grpinx == i]
            u = np.concatenate([d for d in u])
            u = self.order(u)
            groups.append(u)
        return groups


f = Faces(org_triangles)
g = f.simplify()

ax = a3.Axes3D(plt.figure())

colors = list(map("C{}".format, range(len(g))))

pc = a3.art3d.Poly3DCollection(g,  facecolor=colors, 
                                   edgecolor="k", alpha=0.9)
ax.add_collection3d(pc)

ax.dist=10
ax.azim=30
ax.elev=10
ax.set_xlim([0,5])
ax.set_ylim([0,5])
ax.set_zlim([0,5])
plt.show()