python三维建模可视化与交互,python三维建模可视化

这篇具有很好参考价值的文章主要介绍了python三维建模可视化与交互,python三维建模可视化。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本篇文章给大家谈谈python三维建模可视化与交互,以及python三维建模可视化,希望对各位有所帮助,不要忘了收藏本站喔。

python三维建模可视化与交互,python三维建模可视化,人工智能

Python 实现三维建模工具

一、 内容介绍

人类是那么得有创造力,我们创造、发明、设计、生产了一切大自然没有直接给予我们的东西使我们的生活变得更轻松更美好。在过去,我们只能在图纸上进行产品的创造与设计,而现在,有了计算机的帮助,有了 CAD(计算机辅助设计)软件,大大节省了我们的精力与时间成本,使我们的工作更高效,能够拥有更多时间去思考设计本身python建议自学吗。

那么 CAD 软件是如何写出来的呢?CAD 软件种类繁多,但它们有一个共同的特点,就是对三维世界的建模,对三维世界中物体的控制,对三维设计的展示。

课程知识点

本课程项目完成过程中,我们将学习:

 OpenGL 坐标系的转换

 实现简单的用户输入事件回调机制

 设计模式中组合模式的使用

 基于包围盒的碰撞检测

二、 实现原理及步骤

1.用户接口

新建 interaction.py 文件,用户接口在 Interaction 类中实现。

导入需要的库:

    from collections import defaultdict
    from OpenGL.GLUT import glutGet, glutKeyboardFunc, glutMotionFunc, glutMouseFunc, glutPassiveMotionFunc, \
                            glutPostRedisplay, glutSpecialFunc
    from OpenGL.GLUT import GLUT_LEFT_BUTTON, GLUT_RIGHT_BUTTON, GLUT_MIDDLE_BUTTON, \
                            GLUT_WINDOW_HEIGHT, GLUT_WINDOW_WIDTH, \
                            GLUT_DOWN, GLUT_KEY_UP, GLUT_KEY_DOWN, GLUT_KEY_LEFT, GLUT_KEY_RIGHT
    import trackball

初始化 Interaction 类,注册 glut 的事件回调函数。

    class Interaction(object):
        def __init__(self):
            """ 处理用户接口 """
            #被按下的键
            self.pressed = None
            #轨迹球,会在之后进行说明
            self.trackball = trackball.Trackball(theta = -25, distance=15)
            #当前鼠标位置
            self.mouse_loc = None
            #回调函数词典
            self.callbacks = defaultdict(list)

            self.register()

        def register(self):
            """ 注册glut的事件回调函数 """
            glutMouseFunc(self.handle_mouse_button)
            glutMotionFunc(self.handle_mouse_move)
            glutKeyboardFunc(self.handle_keystroke)
            glutSpecialFunc(self.handle_keystroke)

回调函数的实现:

def handle_mouse_button(self, button, mode, x, y):
    """ 当鼠标按键被点击或者释放的时候调用 """
    xSize, ySize = glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT)
    y = ySize - y  # OpenGL原点在窗口左下角,窗口原点在左上角,所以需要这种转换。
    self.mouse_loc = (x, y)

    if mode == GLUT_DOWN:
        #鼠标按键按下的时候
        self.pressed = button
        if button == GLUT_RIGHT_BUTTON:
            pass
        elif button == GLUT_LEFT_BUTTON:
            self.trigger('pick', x, y)
    else:  # 鼠标按键被释放的时候
        self.pressed = None
    #标记当前窗口需要重新绘制
    glutPostRedisplay()

def handle_mouse_move(self, x, screen_y):
    """ 鼠标移动时调用 """
    xSize, ySize = glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT)
    y = ySize - screen_y
    if self.pressed is not None:
        dx = x - self.mouse_loc[0]
        dy = y - self.mouse_loc[1]
        if self.pressed == GLUT_RIGHT_BUTTON and self.trackball is not None:
            # 变化场景的角度
            self.trackball.drag_to(self.mouse_loc[0], self.mouse_loc[1], dx, dy)
        elif self.pressed == GLUT_LEFT_BUTTON:
            self.trigger('move', x, y)
        elif self.pressed == GLUT_MIDDLE_BUTTON:
            self.translate(dx/60.0, dy/60.0, 0)
        else:
            pass
        glutPostRedisplay()
    self.mouse_loc = (x, y)

def handle_keystroke(self, key, x, screen_y):
    """ 键盘输入时调用 """
    xSize, ySize = glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT)
    y = ySize - screen_y
    if key == 's':
        self.trigger('place', 'sphere', x, y)
    elif key == 'c':
        self.trigger('place', 'cube', x, y)
    elif key == GLUT_KEY_UP:
        self.trigger('scale', up=True)
    elif key == GLUT_KEY_DOWN:
        self.trigger('scale', up=False)
    elif key == GLUT_KEY_LEFT:
        self.trigger('rotate_color', forward=True)
    elif key == GLUT_KEY_RIGHT:
        self.trigger('rotate_color', forward=False)
    glutPostRedisplay()
2.回调函数

初始化 Interaction 类,注册 glut 的事件回调函数。

    class Interaction(object):
        def __init__(self):
            """ 处理用户接口 """
            #被按下的键
            self.pressed = None
            #轨迹球,会在之后进行说明
            self.trackball = trackball.Trackball(theta = -25, distance=15)
            #当前鼠标位置
            self.mouse_loc = None
            #回调函数词典
            self.callbacks = defaultdict(list)

            self.register()

        def register(self):
            """ 注册glut的事件回调函数 """
            glutMouseFunc(self.handle_mouse_button)
            glutMotionFunc(self.handle_mouse_move)
            glutKeyboardFunc(self.handle_keystroke)
            glutSpecialFunc(self.handle_keystroke)

回调函数的实现:

    def handle_mouse_button(self, button, mode, x, y):
        """ 当鼠标按键被点击或者释放的时候调用 """
        xSize, ySize = glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT)
        y = ySize - y  # OpenGL原点在窗口左下角,窗口原点在左上角,所以需要这种转换。
        self.mouse_loc = (x, y)

        if mode == GLUT_DOWN:
            #鼠标按键按下的时候
            self.pressed = button
            if button == GLUT_RIGHT_BUTTON:
                pass
            elif button == GLUT_LEFT_BUTTON:
                self.trigger('pick', x, y)
        else:  # 鼠标按键被释放的时候
            self.pressed = None
        #标记当前窗口需要重新绘制
        glutPostRedisplay()

    def handle_mouse_move(self, x, screen_y):
        """ 鼠标移动时调用 """
        xSize, ySize = glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT)
        y = ySize - screen_y
        if self.pressed is not None:
            dx = x - self.mouse_loc[0]
            dy = y - self.mouse_loc[1]
            if self.pressed == GLUT_RIGHT_BUTTON and self.trackball is not None:
                # 变化场景的角度
                self.trackball.drag_to(self.mouse_loc[0], self.mouse_loc[1], dx, dy)
            elif self.pressed == GLUT_LEFT_BUTTON:
                self.trigger('move', x, y)
            elif self.pressed == GLUT_MIDDLE_BUTTON:
                self.translate(dx/60.0, dy/60.0, 0)
            else:
                pass
            glutPostRedisplay()
        self.mouse_loc = (x, y)

    def handle_keystroke(self, key, x, screen_y):
        """ 键盘输入时调用 """
        xSize, ySize = glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT)
        y = ySize - screen_y
        if key == 's':
            self.trigger('place', 'sphere', x, y)
        elif key == 'c':
            self.trigger('place', 'cube', x, y)
        elif key == GLUT_KEY_UP:
            self.trigger('scale', up=True)
        elif key == GLUT_KEY_DOWN:
            self.trigger('scale', up=False)
        elif key == GLUT_KEY_LEFT:
            self.trigger('rotate_color', forward=True)
        elif key == GLUT_KEY_RIGHT:
            self.trigger('rotate_color', forward=False)
        glutPostRedisplay()

针对用户行为会调用 self.trigger 方法,它的第一个参数指明行为期望的效果,后续参数为该效果的参数,trigger 的实现如下:

    def trigger(self, name, *args, **kwargs):
        for func in self.callbacks[name]:
            func(*args, **kwargs)

将方法添加进 callbacks ,需要实现一个注册回调函数的方法:

    def register_callback(self, name, func):
        self.callbacks[name].append(func)

Viewer 中未实现的 self.init_interaction() ,在这里注册回调函数的,补完 init_interaction

    from interaction import Interaction
    ...
    class Viewer(object):
        ...
        def init_interaction(self):
            self.interaction = Interaction()
            self.interaction.register_callback('pick', self.pick)
            self.interaction.register_callback('move', self.move)
            self.interaction.register_callback('place', self.place)
            self.interaction.register_callback('rotate_color', self.rotate_color)
            self.interaction.register_callback('scale', self.scale)

        def pick(self, x, y):
            """ 鼠标选中一个节点 """
            pass

        def move(self, x, y):
            """ 移动当前选中的节点 """
            pass

        def place(self, shape, x, y):
            """ 在鼠标的位置上新放置一个节点 """
            pass

        def rotate_color(self, forward):
            """ 更改选中节点的颜色 """
            pass

        def scale(self, up):
            """ 改变选中节点的大小 """
            pass
3.与场景交互

旋转场景 在这个项目中摄像机是固定的,我们主要靠移动场景来观察不同角度下的 3D 模型。摄像机固定在距离原点 15 个单位的位置,面对世界坐标系的原点。

使用轨迹球 使用轨迹球算法来完成场景的旋转,旋转的方法理解起来很简单,想象一个可以向任意角度围绕球心旋转的地球仪,你的视线是不变的,但是通过你的手在拨这个球,你可以想看哪里拨哪里。在我们的项目中,这个拨球的手就是鼠标右键,点着右键拖动就能实现这个旋转场景的效果。

在这个项目中,我们使用 Glumpy 中轨迹球的实现。

下载 trackball.py 文件,并将其置于工作目录下:

wget http://labfile.oss.aliyuncs.com/courses/561/trackball.py

drag_to 方法实现与轨迹球的交互,它会比对之前的鼠标位置和移动后的鼠标位置来更新旋转矩阵。

self.trackball.drag_to(self.mouse_loc[0], self.mouse_loc[1], dx, dy)

得到的旋转矩阵保存在 viewer 的 trackball.matrix 中。

更新 viewer.py 下的 ModelView 矩阵:

    class Viewer(object):
        ...
        def render(self):
            self.init_view()

            glEnable(GL_LIGHTING)
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

            # 将ModelView矩阵设为轨迹球的旋转矩阵
            glMatrixMode(GL_MODELVIEW)
            glPushMatrix()
            glLoadIdentity()
            glMultMatrixf(self.interaction.trackball.matrix)

            # 存储ModelView矩阵与其逆矩阵之后做坐标系转换用
            currentModelView = numpy.array(glGetFloatv(GL_MODELVIEW_MATRIX))
            self.modelView = numpy.transpose(currentModelView)
            self.inverseModelView = inv(numpy.transpose(currentModelView))

            self.scene.render()

            glDisable(GL_LIGHTING)
            glCallList(G_OBJ_PLANE)
            glPopMatrix()

            glFlush()
4.选择场景中的对象

新建 aabb.py,编写包围盒类:

    from OpenGL.GL import glCallList, glMatrixMode, glPolygonMode, glPopMatrix, glPushMatrix, glTranslated, \
                          GL_FILL, GL_FRONT_AND_BACK, GL_LINE, GL_MODELVIEW
    from primitive import G_OBJ_CUBE
    import numpy
    import math

    #判断误差
    EPSILON = 0.000001

    class AABB(object):

        def __init__(self, center, size):
            self.center = numpy.array(center)
            self.size = numpy.array(size)

        def scale(self, scale):
            self.size *= scale

        def ray_hit(self, origin, direction, modelmatrix):
            """ 返回真则表示激光射中了包盒
                参数说明:  origin, distance -> 激光源点与方向
                          modelmatrix      -> 世界坐标到局部对象坐标的转换矩阵 """
            aabb_min = self.center - self.size
            aabb_max = self.center + self.size
            tmin = 0.0
            tmax = 100000.0

            obb_pos_worldspace = numpy.array([modelmatrix[0, 3], modelmatrix[1, 3], modelmatrix[2, 3]])
            delta = (obb_pos_worldspace - origin)

            # test intersection with 2 planes perpendicular to OBB's x-axis
            xaxis = numpy.array((modelmatrix[0, 0], modelmatrix[0, 1], modelmatrix[0, 2]))

            e = numpy.dot(xaxis, delta)
            f = numpy.dot(direction, xaxis)
            if math.fabs(f) > 0.0 + EPSILON:
                t1 = (e + aabb_min[0])/f
                t2 = (e + aabb_max[0])/f
                if t1 > t2:
                    t1, t2 = t2, t1
                if t2 < tmax:
                    tmax = t2
                if t1 > tmin:
                    tmin = t1
                if tmax < tmin:
                    return (False, 0)
            else:
                if (-e + aabb_min[0] > 0.0 + EPSILON) or (-e+aabb_max[0] < 0.0 - EPSILON):
                    return False, 0

            yaxis = numpy.array((modelmatrix[1, 0], modelmatrix[1, 1], modelmatrix[1, 2]))
            e = numpy.dot(yaxis, delta)
            f = numpy.dot(direction, yaxis)
            # intersection in y
            if math.fabs(f) > 0.0 + EPSILON:
                t1 = (e + aabb_min[1])/f
                t2 = (e + aabb_max[1])/f
                if t1 > t2:
                    t1, t2 = t2, t1
                if t2 < tmax:
                    tmax = t2
                if t1 > tmin:
                    tmin = t1
                if tmax < tmin:
                    return (False, 0)
            else:
                if (-e + aabb_min[1] > 0.0 + EPSILON) or (-e+aabb_max[1] < 0.0 - EPSILON):
                    return False, 0

            # intersection in z
            zaxis = numpy.array((modelmatrix[2, 0], modelmatrix[2, 1], modelmatrix[2, 2]))
            e = numpy.dot(zaxis, delta)
            f = numpy.dot(direction, zaxis)
            if math.fabs(f) > 0.0 + EPSILON:
                t1 = (e + aabb_min[2])/f
                t2 = (e + aabb_max[2])/f
                if t1 > t2:
                    t1, t2 = t2, t1
                if t2 < tmax:
                    tmax = t2
                if t1 > tmin:
                    tmin = t1
                if tmax < tmin:
                    return (False, 0)
            else:
                if (-e + aabb_min[2] > 0.0 + EPSILON) or (-e+aabb_max[2] < 0.0 - EPSILON):
                    return False, 0

            return True, tmin

        def render(self):
            """ 渲染显示包围盒,可在调试的时候使用 """
            glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
            glMatrixMode(GL_MODELVIEW)
            glPushMatrix()
            glTranslated(self.center[0], self.center[1], self.center[2])
            glCallList(G_OBJ_CUBE)
            glPopMatrix()
            glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)

更新 Node 类与 Scene 类,加入与选中节点有关的内容。

更新 Node 类:

    from aabb import AABB
    ...
    class Node(object):

        def __init__(self):
            self.color_index = random.randint(color.MIN_COLOR, color.MAX_COLOR)
            self.aabb = AABB([0.0, 0.0, 0.0], [0.5, 0.5, 0.5])
            self.translation_matrix = numpy.identity(4)
            self.scaling_matrix = numpy.identity(4)
            self.selected = False
        ...

        def render(self):
            glPushMatrix()
            glMultMatrixf(numpy.transpose(self.translation_matrix))
            glMultMatrixf(self.scaling_matrix)
            cur_color = color.COLORS[self.color_index]
            glColor3f(cur_color[0], cur_color[1], cur_color[2])
            if self.selected:  # 选中的对象会发光
                glMaterialfv(GL_FRONT, GL_EMISSION, [0.3, 0.3, 0.3])

            self.render_self()
            if self.selected:
                glMaterialfv(GL_FRONT, GL_EMISSION, [0.0, 0.0, 0.0])

            glPopMatrix()

        def select(self, select=None):
            if select is not None:
                self.selected = select
            else:
                self.selected = not self.selected

更新scene类:

    class Scene(object):
        def __init__(self):
            self.node_list = list()
            self.selected_node = None

在 Viewer 类中实现通过鼠标位置获取激光的函数以及 pick 函数:

        # class Viewer
        def get_ray(self, x, y):
            """
            返回光源和激光方向
            """
            self.init_view()

            glMatrixMode(GL_MODELVIEW)
            glLoadIdentity()

            # 得到激光的起始点
            start = numpy.array(gluUnProject(x, y, 0.001))
            end = numpy.array(gluUnProject(x, y, 0.999))

            # 得到激光的方向
            direction = end - start
            direction = direction / norm(direction)

            return (start, direction)

        def pick(self, x, y):
            """ 是否被选中以及哪一个被选中交由Scene下的pick处理 """
            start, direction = self.get_ray(x, y)
            self.scene.pick(start, direction, self.modelView)

为了确定是哪个对象被选中,我们会遍历场景下的所有对象,检查激光是否与该对象相交,取离摄像机最近的对象为选中对象。

    # Scene 下实现
    def pick(self, start, direction, mat):
        """
        参数中的mat为当前ModelView的逆矩阵,作用是计算激光在局部(对象)坐标系中的坐标
        """
        import sys

        if self.selected_node is not None:
            self.selected_node.select(False)
            self.selected_node = None

        # 找出激光击中的最近的节点。
        mindist = sys.maxsize
        closest_node = None
        for node in self.node_list:
            hit, distance = node.pick(start, direction, mat)
            if hit and distance < mindist:
                mindist, closest_node = distance, node

        # 如果找到了,选中它
        if closest_node is not None:
            closest_node.select()
            closest_node.depth = mindist
            closest_node.selected_loc = start + direction * mindist
            self.selected_node = closest_node

    # Node下的实现
    def pick(self, start, direction, mat):

        # 将modelview矩阵乘上节点的变换矩阵
        newmat = numpy.dot(
            numpy.dot(mat, self.translation_matrix),
            numpy.linalg.inv(self.scaling_matrix)
        )
        results = self.aabb.ray_hit(start, direction, newmat)
        return results
5.操作场景中的对象

对对象的操作主要包括在场景中加入新对象,移动对象、改变对象的颜色与改变对象的大小。因为这部分的实现较为简单,所以仅实现加入新对象与移动对象的操作。

加入新对象的代码如下:

    # Viewer下的实现
    def place(self, shape, x, y):
        start, direction = self.get_ray(x, y)
        self.scene.place(shape, start, direction, self.inverseModelView)

    # Scene下的实现
    import numpy
    from node import Sphere, Cube, SnowFigure
    ...
    def place(self, shape, start, direction, inv_modelview):
        new_node = None
        if shape == 'sphere': new_node = Sphere()
        elif shape == 'cube': new_node = Cube()
        elif shape == 'figure': new_node = SnowFigure()

        self.add_node(new_node)

        # 得到在摄像机坐标系中的坐标
        translation = (start + direction * self.PLACE_DEPTH)

        # 转换到世界坐标系
        pre_tran = numpy.array([translation[0], translation[1], translation[2], 1])
        translation = inv_modelview.dot(pre_tran)

        new_node.translate(translation[0], translation[1], translation[2])

移动目标对象的代码如下:

    # Viewer下的实现
    def move(self, x, y):
        start, direction = self.get_ray(x, y)
        self.scene.move_selected(start, direction, self.inverseModelView)

    # Scene下的实现
    def move_selected(self, start, direction, inv_modelview):

        if self.selected_node is None: return

        # 找到选中节点的坐标与深度(距离)
        node = self.selected_node
        depth = node.depth
        oldloc = node.selected_loc

        # 新坐标的深度保持不变
        newloc = (start + direction * depth)

        # 得到世界坐标系中的移动坐标差
        translation = newloc - oldloc
        pre_tran = numpy.array([translation[0], translation[1], translation[2], 0])
        translation = inv_modelview.dot(pre_tran)

        # 节点做平移变换
        node.translate(translation[0], translation[1], translation[2])
        node.selected_loc = newloc

三、 源代码

viewer.py 代码:
    from OpenGL.GL import glCallList, glClear, glClearColor, glColorMaterial, glCullFace, glDepthFunc, glDisable, glEnable,\
                          glFlush, glGetFloatv, glLightfv, glLoadIdentity, glMatrixMode, glMultMatrixf, glPopMatrix, \
                          glPushMatrix, glTranslated, glViewport, \
                          GL_AMBIENT_AND_DIFFUSE, GL_BACK, GL_CULL_FACE, GL_COLOR_BUFFER_BIT, GL_COLOR_MATERIAL, \
                          GL_DEPTH_BUFFER_BIT, GL_DEPTH_TEST, GL_FRONT_AND_BACK, GL_LESS, GL_LIGHT0, GL_LIGHTING, \
                          GL_MODELVIEW, GL_MODELVIEW_MATRIX, GL_POSITION, GL_PROJECTION, GL_SPOT_DIRECTION
    from OpenGL.constants import GLfloat_3, GLfloat_4
    from OpenGL.GLU import gluPerspective, gluUnProject
    from OpenGL.GLUT import glutCreateWindow, glutDisplayFunc, glutGet, glutInit, glutInitDisplayMode, \
                            glutInitWindowSize, glutMainLoop, \
                            GLUT_SINGLE, GLUT_RGB, GLUT_WINDOW_HEIGHT, GLUT_WINDOW_WIDTH, glutCloseFunc
    import numpy
    from numpy.linalg import norm, inv
    import random
    from OpenGL.GL import glBegin, glColor3f, glEnd, glEndList, glLineWidth, glNewList, glNormal3f, glVertex3f, \
                          GL_COMPILE, GL_LINES, GL_QUADS
    from OpenGL.GLU import gluDeleteQuadric, gluNewQuadric, gluSphere

    import color
    from scene import Scene
    from primitive import init_primitives, G_OBJ_PLANE
    from node import Sphere, Cube, SnowFigure


    class Viewer(object):
        def __init__(self):
            """ Initialize the viewer. """
            #初始化接口,创建窗口并注册渲染函数
            self.init_interface()
            #初始化opengl的配置
            self.init_opengl()
            #初始化3d场景
            self.init_scene()
            #初始化交互操作相关的代码
            self.init_interaction()
            init_primitives()

        def init_interface(self):
            """ 初始化窗口并注册渲染函数 """
            glutInit()
            glutInitWindowSize(640, 480)
            glutCreateWindow("3D Modeller")
            glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB)
            #注册窗口渲染函数
            glutDisplayFunc(self.render)

        def init_opengl(self):
            """ 初始化opengl的配置 """
            #模型视图矩阵
            self.inverseModelView = numpy.identity(4)
            #模型视图矩阵的逆矩阵
            self.modelView = numpy.identity(4)

            #开启剔除操作效果
            glEnable(GL_CULL_FACE)
            #取消对多边形背面进行渲染的计算(看不到的部分不渲染)
            glCullFace(GL_BACK)
            #开启深度测试
            glEnable(GL_DEPTH_TEST)
            #测试是否被遮挡,被遮挡的物体不予渲染
            glDepthFunc(GL_LESS)
            #启用0号光源
            glEnable(GL_LIGHT0)
            #设置光源的位置
            glLightfv(GL_LIGHT0, GL_POSITION, GLfloat_4(0, 0, 1, 0))
            #设置光源的照射方向
            glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, GLfloat_3(0, 0, -1))
            #设置材质颜色
            glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)
            glEnable(GL_COLOR_MATERIAL)
            #设置清屏的颜色
            glClearColor(0.4, 0.4, 0.4, 0.0)

        def init_scene(self):
            #创建一个场景实例
            self.scene = Scene()
            #初始化场景内的对象
            self.create_sample_scene()

        def create_sample_scene(self):
            cube_node = Cube()
            cube_node.translate(2, 0, 2)
            cube_node.color_index = 1
            self.scene.add_node(cube_node)

            sphere_node = Sphere()
            sphere_node.translate(-2, 0, 2)
            sphere_node.color_index = 3
            self.scene.add_node(sphere_node)

            hierarchical_node = SnowFigure()
            hierarchical_node.translate(-2, 0, -2)
            self.scene.add_node(hierarchical_node)

        def init_interaction(self):
            #初始化交互操作相关的代码,之后实现
            pass

        def main_loop(self):
            #程序主循环开始
            glutMainLoop()

        def render(self):
            #初始化投影矩阵
            self.init_view()

            #启动光照
            glEnable(GL_LIGHTING)
            #清空颜色缓存与深度缓存
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

            #设置模型视图矩阵,这节课先用单位矩阵就行了。
            glMatrixMode(GL_MODELVIEW)
            glPushMatrix()
            glLoadIdentity()

            #渲染场景
            self.scene.render()

            #每次渲染后复位光照状态
            glDisable(GL_LIGHTING)
            glCallList(G_OBJ_PLANE)
            glPopMatrix()
            #把数据刷新到显存上
            glFlush()

        def init_view(self):
            """ 初始化投影矩阵 """
            xSize, ySize = glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT)
            #得到屏幕宽高比
            aspect_ratio = float(xSize) / float(ySize)

            #设置投影矩阵
            glMatrixMode(GL_PROJECTION)
            glLoadIdentity()

            #设置视口,应与窗口重合
            glViewport(0, 0, xSize, ySize)
            #设置透视,摄像机上下视野幅度70度
            #视野范围到距离摄像机1000个单位为止。
            gluPerspective(70, aspect_ratio, 0.1, 1000.0)
            #摄像机镜头从原点后退15个单位
            glTranslated(0, 0, -15)

    if __name__ == "__main__":
        viewer = Viewer()
        viewer.main_loop()

四、 结果及分析

python三维建模可视化与交互,python三维建模可视化,人工智能

到这里我们就已经实现了一个简单的 3D 建模工具了,想一下这个程序还能在什么地方进行改进,或是增加一些新的功能?比如说:

-编写新的节点类,支持三角形网格能够组合成任意形状。

-增加一个撤销栈,支持撤销命令功能。

-能够保存/加载 3D 设计,比如保存为 DXF 3D 文件格式

-改进程序,选中目标更精准。文章来源地址https://www.toymoban.com/news/detail-818299.html

Python 实现三维建模工具

一、 内容介绍

人类是那么得有创造力,我们创造、发明、设计、生产了一切大自然没有直接给予我们的东西使我们的生活变得更轻松更美好。在过去,我们只能在图纸上进行产品的创造与设计,而现在,有了计算机的帮助,有了 CAD(计算机辅助设计)软件,大大节省了我们的精力与时间成本,使我们的工作更高效,能够拥有更多时间去思考设计本身python建议自学吗。

那么 CAD 软件是如何写出来的呢?CAD 软件种类繁多,但它们有一个共同的特点,就是对三维世界的建模,对三维世界中物体的控制,对三维设计的展示。

课程知识点

本课程项目完成过程中,我们将学习:

 OpenGL 坐标系的转换

 实现简单的用户输入事件回调机制

 设计模式中组合模式的使用

 基于包围盒的碰撞检测

二、 实现原理及步骤

1.用户接口

新建 interaction.py 文件,用户接口在 Interaction 类中实现。

导入需要的库:

    from collections import defaultdict
    from OpenGL.GLUT import glutGet, glutKeyboardFunc, glutMotionFunc, glutMouseFunc, glutPassiveMotionFunc, \
                            glutPostRedisplay, glutSpecialFunc
    from OpenGL.GLUT import GLUT_LEFT_BUTTON, GLUT_RIGHT_BUTTON, GLUT_MIDDLE_BUTTON, \
                            GLUT_WINDOW_HEIGHT, GLUT_WINDOW_WIDTH, \
                            GLUT_DOWN, GLUT_KEY_UP, GLUT_KEY_DOWN, GLUT_KEY_LEFT, GLUT_KEY_RIGHT
    import trackball

初始化 Interaction 类,注册 glut 的事件回调函数。

    class Interaction(object):
        def __init__(self):
            """ 处理用户接口 """
            #被按下的键
            self.pressed = None
            #轨迹球,会在之后进行说明
            self.trackball = trackball.Trackball(theta = -25, distance=15)
            #当前鼠标位置
            self.mouse_loc = None
            #回调函数词典
            self.callbacks = defaultdict(list)

            self.register()

        def register(self):
            """ 注册glut的事件回调函数 """
            glutMouseFunc(self.handle_mouse_button)
            glutMotionFunc(self.handle_mouse_move)
            glutKeyboardFunc(self.handle_keystroke)
            glutSpecialFunc(self.handle_keystroke)

回调函数的实现:

def handle_mouse_button(self, button, mode, x, y):
    """ 当鼠标按键被点击或者释放的时候调用 """
    xSize, ySize = glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT)
    y = ySize - y  # OpenGL原点在窗口左下角,窗口原点在左上角,所以需要这种转换。
    self.mouse_loc = (x, y)

    if mode == GLUT_DOWN:
        #鼠标按键按下的时候
        self.pressed = button
        if button == GLUT_RIGHT_BUTTON:
            pass
        elif button == GLUT_LEFT_BUTTON:
            self.trigger('pick', x, y)
    else:  # 鼠标按键被释放的时候
        self.pressed = None
    #标记当前窗口需要重新绘制
    glutPostRedisplay()

def handle_mouse_move(self, x, screen_y):
    """ 鼠标移动时调用 """
    xSize, ySize = glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT)
    y = ySize - screen_y
    if self.pressed is not None:
        dx = x - self.mouse_loc[0]
        dy = y - self.mouse_loc[1]
        if self.pressed == GLUT_RIGHT_BUTTON and self.trackball is not None:
            # 变化场景的角度
            self.trackball.drag_to(self.mouse_loc[0], self.mouse_loc[1], dx, dy)
        elif self.pressed == GLUT_LEFT_BUTTON:
            self.trigger('move', x, y)
        elif self.pressed == GLUT_MIDDLE_BUTTON:
            self.translate(dx/60.0, dy/60.0, 0)
        else:
            pass
        glutPostRedisplay()
    self.mouse_loc = (x, y)

def handle_keystroke(self, key, x, screen_y):
    """ 键盘输入时调用 """
    xSize, ySize = glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT)
    y = ySize - screen_y
    if key == 's':
        self.trigger('place', 'sphere', x, y)
    elif key == 'c':
        self.trigger('place', 'cube', x, y)
    elif key == GLUT_KEY_UP:
        self.trigger('scale', up=True)
    elif key == GLUT_KEY_DOWN:
        self.trigger('scale', up=False)
    elif key == GLUT_KEY_LEFT:
        self.trigger('rotate_color', forward=True)
    elif key == GLUT_KEY_RIGHT:
        self.trigger('rotate_color', forward=False)
    glutPostRedisplay()
2.回调函数

初始化 Interaction 类,注册 glut 的事件回调函数。

    class Interaction(object):
        def __init__(self):
            """ 处理用户接口 """
            #被按下的键
            self.pressed = None
            #轨迹球,会在之后进行说明
            self.trackball = trackball.Trackball(theta = -25, distance=15)
            #当前鼠标位置
            self.mouse_loc = None
            #回调函数词典
            self.callbacks = defaultdict(list)

            self.register()

        def register(self):
            """ 注册glut的事件回调函数 """
            glutMouseFunc(self.handle_mouse_button)
            glutMotionFunc(self.handle_mouse_move)
            glutKeyboardFunc(self.handle_keystroke)
            glutSpecialFunc(self.handle_keystroke)

回调函数的实现:

    def handle_mouse_button(self, button, mode, x, y):
        """ 当鼠标按键被点击或者释放的时候调用 """
        xSize, ySize = glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT)
        y = ySize - y  # OpenGL原点在窗口左下角,窗口原点在左上角,所以需要这种转换。
        self.mouse_loc = (x, y)

        if mode == GLUT_DOWN:
            #鼠标按键按下的时候
            self.pressed = button
            if button == GLUT_RIGHT_BUTTON:
                pass
            elif button == GLUT_LEFT_BUTTON:
                self.trigger('pick', x, y)
        else:  # 鼠标按键被释放的时候
            self.pressed = None
        #标记当前窗口需要重新绘制
        glutPostRedisplay()

    def handle_mouse_move(self, x, screen_y):
        """ 鼠标移动时调用 """
        xSize, ySize = glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT)
        y = ySize - screen_y
        if self.pressed is not None:
            dx = x - self.mouse_loc[0]
            dy = y - self.mouse_loc[1]
            if self.pressed == GLUT_RIGHT_BUTTON and self.trackball is not None:
                # 变化场景的角度
                self.trackball.drag_to(self.mouse_loc[0], self.mouse_loc[1], dx, dy)
            elif self.pressed == GLUT_LEFT_BUTTON:
                self.trigger('move', x, y)
            elif self.pressed == GLUT_MIDDLE_BUTTON:
                self.translate(dx/60.0, dy/60.0, 0)
            else:
                pass
            glutPostRedisplay()
        self.mouse_loc = (x, y)

    def handle_keystroke(self, key, x, screen_y):
        """ 键盘输入时调用 """
        xSize, ySize = glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT)
        y = ySize - screen_y
        if key == 's':
            self.trigger('place', 'sphere', x, y)
        elif key == 'c':
            self.trigger('place', 'cube', x, y)
        elif key == GLUT_KEY_UP:
            self.trigger('scale', up=True)
        elif key == GLUT_KEY_DOWN:
            self.trigger('scale', up=False)
        elif key == GLUT_KEY_LEFT:
            self.trigger('rotate_color', forward=True)
        elif key == GLUT_KEY_RIGHT:
            self.trigger('rotate_color', forward=False)
        glutPostRedisplay()

针对用户行为会调用 self.trigger 方法,它的第一个参数指明行为期望的效果,后续参数为该效果的参数,trigger 的实现如下:

    def trigger(self, name, *args, **kwargs):
        for func in self.callbacks[name]:
            func(*args, **kwargs)

将方法添加进 callbacks ,需要实现一个注册回调函数的方法:

    def register_callback(self, name, func):
        self.callbacks[name].append(func)

Viewer 中未实现的 self.init_interaction() ,在这里注册回调函数的,补完 init_interaction

    from interaction import Interaction
    ...
    class Viewer(object):
        ...
        def init_interaction(self):
            self.interaction = Interaction()
            self.interaction.register_callback('pick', self.pick)
            self.interaction.register_callback('move', self.move)
            self.interaction.register_callback('place', self.place)
            self.interaction.register_callback('rotate_color', self.rotate_color)
            self.interaction.register_callback('scale', self.scale)

        def pick(self, x, y):
            """ 鼠标选中一个节点 """
            pass

        def move(self, x, y):
            """ 移动当前选中的节点 """
            pass

        def place(self, shape, x, y):
            """ 在鼠标的位置上新放置一个节点 """
            pass

        def rotate_color(self, forward):
            """ 更改选中节点的颜色 """
            pass

        def scale(self, up):
            """ 改变选中节点的大小 """
            pass
3.与场景交互

旋转场景 在这个项目中摄像机是固定的,我们主要靠移动场景来观察不同角度下的 3D 模型。摄像机固定在距离原点 15 个单位的位置,面对世界坐标系的原点。

使用轨迹球 使用轨迹球算法来完成场景的旋转,旋转的方法理解起来很简单,想象一个可以向任意角度围绕球心旋转的地球仪,你的视线是不变的,但是通过你的手在拨这个球,你可以想看哪里拨哪里。在我们的项目中,这个拨球的手就是鼠标右键,点着右键拖动就能实现这个旋转场景的效果。

在这个项目中,我们使用 Glumpy 中轨迹球的实现。

下载 trackball.py 文件,并将其置于工作目录下:

wget http://labfile.oss.aliyuncs.com/courses/561/trackball.py

drag_to 方法实现与轨迹球的交互,它会比对之前的鼠标位置和移动后的鼠标位置来更新旋转矩阵。

self.trackball.drag_to(self.mouse_loc[0], self.mouse_loc[1], dx, dy)

得到的旋转矩阵保存在 viewer 的 trackball.matrix 中。

更新 viewer.py 下的 ModelView 矩阵:

    class Viewer(object):
        ...
        def render(self):
            self.init_view()

            glEnable(GL_LIGHTING)
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

            # 将ModelView矩阵设为轨迹球的旋转矩阵
            glMatrixMode(GL_MODELVIEW)
            glPushMatrix()
            glLoadIdentity()
            glMultMatrixf(self.interaction.trackball.matrix)

            # 存储ModelView矩阵与其逆矩阵之后做坐标系转换用
            currentModelView = numpy.array(glGetFloatv(GL_MODELVIEW_MATRIX))
            self.modelView = numpy.transpose(currentModelView)
            self.inverseModelView = inv(numpy.transpose(currentModelView))

            self.scene.render()

            glDisable(GL_LIGHTING)
            glCallList(G_OBJ_PLANE)
            glPopMatrix()

            glFlush()
4.选择场景中的对象

新建 aabb.py,编写包围盒类:

    from OpenGL.GL import glCallList, glMatrixMode, glPolygonMode, glPopMatrix, glPushMatrix, glTranslated, \
                          GL_FILL, GL_FRONT_AND_BACK, GL_LINE, GL_MODELVIEW
    from primitive import G_OBJ_CUBE
    import numpy
    import math

    #判断误差
    EPSILON = 0.000001

    class AABB(object):

        def __init__(self, center, size):
            self.center = numpy.array(center)
            self.size = numpy.array(size)

        def scale(self, scale):
            self.size *= scale

        def ray_hit(self, origin, direction, modelmatrix):
            """ 返回真则表示激光射中了包盒
                参数说明:  origin, distance -> 激光源点与方向
                          modelmatrix      -> 世界坐标到局部对象坐标的转换矩阵 """
            aabb_min = self.center - self.size
            aabb_max = self.center + self.size
            tmin = 0.0
            tmax = 100000.0

            obb_pos_worldspace = numpy.array([modelmatrix[0, 3], modelmatrix[1, 3], modelmatrix[2, 3]])
            delta = (obb_pos_worldspace - origin)

            # test intersection with 2 planes perpendicular to OBB's x-axis
            xaxis = numpy.array((modelmatrix[0, 0], modelmatrix[0, 1], modelmatrix[0, 2]))

            e = numpy.dot(xaxis, delta)
            f = numpy.dot(direction, xaxis)
            if math.fabs(f) > 0.0 + EPSILON:
                t1 = (e + aabb_min[0])/f
                t2 = (e + aabb_max[0])/f
                if t1 > t2:
                    t1, t2 = t2, t1
                if t2 < tmax:
                    tmax = t2
                if t1 > tmin:
                    tmin = t1
                if tmax < tmin:
                    return (False, 0)
            else:
                if (-e + aabb_min[0] > 0.0 + EPSILON) or (-e+aabb_max[0] < 0.0 - EPSILON):
                    return False, 0

            yaxis = numpy.array((modelmatrix[1, 0], modelmatrix[1, 1], modelmatrix[1, 2]))
            e = numpy.dot(yaxis, delta)
            f = numpy.dot(direction, yaxis)
            # intersection in y
            if math.fabs(f) > 0.0 + EPSILON:
                t1 = (e + aabb_min[1])/f
                t2 = (e + aabb_max[1])/f
                if t1 > t2:
                    t1, t2 = t2, t1
                if t2 < tmax:
                    tmax = t2
                if t1 > tmin:
                    tmin = t1
                if tmax < tmin:
                    return (False, 0)
            else:
                if (-e + aabb_min[1] > 0.0 + EPSILON) or (-e+aabb_max[1] < 0.0 - EPSILON):
                    return False, 0

            # intersection in z
            zaxis = numpy.array((modelmatrix[2, 0], modelmatrix[2, 1], modelmatrix[2, 2]))
            e = numpy.dot(zaxis, delta)
            f = numpy.dot(direction, zaxis)
            if math.fabs(f) > 0.0 + EPSILON:
                t1 = (e + aabb_min[2])/f
                t2 = (e + aabb_max[2])/f
                if t1 > t2:
                    t1, t2 = t2, t1
                if t2 < tmax:
                    tmax = t2
                if t1 > tmin:
                    tmin = t1
                if tmax < tmin:
                    return (False, 0)
            else:
                if (-e + aabb_min[2] > 0.0 + EPSILON) or (-e+aabb_max[2] < 0.0 - EPSILON):
                    return False, 0

            return True, tmin

        def render(self):
            """ 渲染显示包围盒,可在调试的时候使用 """
            glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
            glMatrixMode(GL_MODELVIEW)
            glPushMatrix()
            glTranslated(self.center[0], self.center[1], self.center[2])
            glCallList(G_OBJ_CUBE)
            glPopMatrix()
            glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)

更新 Node 类与 Scene 类,加入与选中节点有关的内容。

更新 Node 类:

    from aabb import AABB
    ...
    class Node(object):

        def __init__(self):
            self.color_index = random.randint(color.MIN_COLOR, color.MAX_COLOR)
            self.aabb = AABB([0.0, 0.0, 0.0], [0.5, 0.5, 0.5])
            self.translation_matrix = numpy.identity(4)
            self.scaling_matrix = numpy.identity(4)
            self.selected = False
        ...

        def render(self):
            glPushMatrix()
            glMultMatrixf(numpy.transpose(self.translation_matrix))
            glMultMatrixf(self.scaling_matrix)
            cur_color = color.COLORS[self.color_index]
            glColor3f(cur_color[0], cur_color[1], cur_color[2])
            if self.selected:  # 选中的对象会发光
                glMaterialfv(GL_FRONT, GL_EMISSION, [0.3, 0.3, 0.3])

            self.render_self()
            if self.selected:
                glMaterialfv(GL_FRONT, GL_EMISSION, [0.0, 0.0, 0.0])

            glPopMatrix()

        def select(self, select=None):
            if select is not None:
                self.selected = select
            else:
                self.selected = not self.selected

更新scene类:

    class Scene(object):
        def __init__(self):
            self.node_list = list()
            self.selected_node = None

在 Viewer 类中实现通过鼠标位置获取激光的函数以及 pick 函数:

        # class Viewer
        def get_ray(self, x, y):
            """
            返回光源和激光方向
            """
            self.init_view()

            glMatrixMode(GL_MODELVIEW)
            glLoadIdentity()

            # 得到激光的起始点
            start = numpy.array(gluUnProject(x, y, 0.001))
            end = numpy.array(gluUnProject(x, y, 0.999))

            # 得到激光的方向
            direction = end - start
            direction = direction / norm(direction)

            return (start, direction)

        def pick(self, x, y):
            """ 是否被选中以及哪一个被选中交由Scene下的pick处理 """
            start, direction = self.get_ray(x, y)
            self.scene.pick(start, direction, self.modelView)

为了确定是哪个对象被选中,我们会遍历场景下的所有对象,检查激光是否与该对象相交,取离摄像机最近的对象为选中对象。

    # Scene 下实现
    def pick(self, start, direction, mat):
        """
        参数中的mat为当前ModelView的逆矩阵,作用是计算激光在局部(对象)坐标系中的坐标
        """
        import sys

        if self.selected_node is not None:
            self.selected_node.select(False)
            self.selected_node = None

        # 找出激光击中的最近的节点。
        mindist = sys.maxsize
        closest_node = None
        for node in self.node_list:
            hit, distance = node.pick(start, direction, mat)
            if hit and distance < mindist:
                mindist, closest_node = distance, node

        # 如果找到了,选中它
        if closest_node is not None:
            closest_node.select()
            closest_node.depth = mindist
            closest_node.selected_loc = start + direction * mindist
            self.selected_node = closest_node

    # Node下的实现
    def pick(self, start, direction, mat):

        # 将modelview矩阵乘上节点的变换矩阵
        newmat = numpy.dot(
            numpy.dot(mat, self.translation_matrix),
            numpy.linalg.inv(self.scaling_matrix)
        )
        results = self.aabb.ray_hit(start, direction, newmat)
        return results
5.操作场景中的对象

对对象的操作主要包括在场景中加入新对象,移动对象、改变对象的颜色与改变对象的大小。因为这部分的实现较为简单,所以仅实现加入新对象与移动对象的操作。

加入新对象的代码如下:

    # Viewer下的实现
    def place(self, shape, x, y):
        start, direction = self.get_ray(x, y)
        self.scene.place(shape, start, direction, self.inverseModelView)

    # Scene下的实现
    import numpy
    from node import Sphere, Cube, SnowFigure
    ...
    def place(self, shape, start, direction, inv_modelview):
        new_node = None
        if shape == 'sphere': new_node = Sphere()
        elif shape == 'cube': new_node = Cube()
        elif shape == 'figure': new_node = SnowFigure()

        self.add_node(new_node)

        # 得到在摄像机坐标系中的坐标
        translation = (start + direction * self.PLACE_DEPTH)

        # 转换到世界坐标系
        pre_tran = numpy.array([translation[0], translation[1], translation[2], 1])
        translation = inv_modelview.dot(pre_tran)

        new_node.translate(translation[0], translation[1], translation[2])

移动目标对象的代码如下:

    # Viewer下的实现
    def move(self, x, y):
        start, direction = self.get_ray(x, y)
        self.scene.move_selected(start, direction, self.inverseModelView)

    # Scene下的实现
    def move_selected(self, start, direction, inv_modelview):

        if self.selected_node is None: return

        # 找到选中节点的坐标与深度(距离)
        node = self.selected_node
        depth = node.depth
        oldloc = node.selected_loc

        # 新坐标的深度保持不变
        newloc = (start + direction * depth)

        # 得到世界坐标系中的移动坐标差
        translation = newloc - oldloc
        pre_tran = numpy.array([translation[0], translation[1], translation[2], 0])
        translation = inv_modelview.dot(pre_tran)

        # 节点做平移变换
        node.translate(translation[0], translation[1], translation[2])
        node.selected_loc = newloc

三、 源代码

viewer.py 代码:
    from OpenGL.GL import glCallList, glClear, glClearColor, glColorMaterial, glCullFace, glDepthFunc, glDisable, glEnable,\
                          glFlush, glGetFloatv, glLightfv, glLoadIdentity, glMatrixMode, glMultMatrixf, glPopMatrix, \
                          glPushMatrix, glTranslated, glViewport, \
                          GL_AMBIENT_AND_DIFFUSE, GL_BACK, GL_CULL_FACE, GL_COLOR_BUFFER_BIT, GL_COLOR_MATERIAL, \
                          GL_DEPTH_BUFFER_BIT, GL_DEPTH_TEST, GL_FRONT_AND_BACK, GL_LESS, GL_LIGHT0, GL_LIGHTING, \
                          GL_MODELVIEW, GL_MODELVIEW_MATRIX, GL_POSITION, GL_PROJECTION, GL_SPOT_DIRECTION
    from OpenGL.constants import GLfloat_3, GLfloat_4
    from OpenGL.GLU import gluPerspective, gluUnProject
    from OpenGL.GLUT import glutCreateWindow, glutDisplayFunc, glutGet, glutInit, glutInitDisplayMode, \
                            glutInitWindowSize, glutMainLoop, \
                            GLUT_SINGLE, GLUT_RGB, GLUT_WINDOW_HEIGHT, GLUT_WINDOW_WIDTH, glutCloseFunc
    import numpy
    from numpy.linalg import norm, inv
    import random
    from OpenGL.GL import glBegin, glColor3f, glEnd, glEndList, glLineWidth, glNewList, glNormal3f, glVertex3f, \
                          GL_COMPILE, GL_LINES, GL_QUADS
    from OpenGL.GLU import gluDeleteQuadric, gluNewQuadric, gluSphere

    import color
    from scene import Scene
    from primitive import init_primitives, G_OBJ_PLANE
    from node import Sphere, Cube, SnowFigure


    class Viewer(object):
        def __init__(self):
            """ Initialize the viewer. """
            #初始化接口,创建窗口并注册渲染函数
            self.init_interface()
            #初始化opengl的配置
            self.init_opengl()
            #初始化3d场景
            self.init_scene()
            #初始化交互操作相关的代码
            self.init_interaction()
            init_primitives()

        def init_interface(self):
            """ 初始化窗口并注册渲染函数 """
            glutInit()
            glutInitWindowSize(640, 480)
            glutCreateWindow("3D Modeller")
            glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB)
            #注册窗口渲染函数
            glutDisplayFunc(self.render)

        def init_opengl(self):
            """ 初始化opengl的配置 """
            #模型视图矩阵
            self.inverseModelView = numpy.identity(4)
            #模型视图矩阵的逆矩阵
            self.modelView = numpy.identity(4)

            #开启剔除操作效果
            glEnable(GL_CULL_FACE)
            #取消对多边形背面进行渲染的计算(看不到的部分不渲染)
            glCullFace(GL_BACK)
            #开启深度测试
            glEnable(GL_DEPTH_TEST)
            #测试是否被遮挡,被遮挡的物体不予渲染
            glDepthFunc(GL_LESS)
            #启用0号光源
            glEnable(GL_LIGHT0)
            #设置光源的位置
            glLightfv(GL_LIGHT0, GL_POSITION, GLfloat_4(0, 0, 1, 0))
            #设置光源的照射方向
            glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, GLfloat_3(0, 0, -1))
            #设置材质颜色
            glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)
            glEnable(GL_COLOR_MATERIAL)
            #设置清屏的颜色
            glClearColor(0.4, 0.4, 0.4, 0.0)

        def init_scene(self):
            #创建一个场景实例
            self.scene = Scene()
            #初始化场景内的对象
            self.create_sample_scene()

        def create_sample_scene(self):
            cube_node = Cube()
            cube_node.translate(2, 0, 2)
            cube_node.color_index = 1
            self.scene.add_node(cube_node)

            sphere_node = Sphere()
            sphere_node.translate(-2, 0, 2)
            sphere_node.color_index = 3
            self.scene.add_node(sphere_node)

            hierarchical_node = SnowFigure()
            hierarchical_node.translate(-2, 0, -2)
            self.scene.add_node(hierarchical_node)

        def init_interaction(self):
            #初始化交互操作相关的代码,之后实现
            pass

        def main_loop(self):
            #程序主循环开始
            glutMainLoop()

        def render(self):
            #初始化投影矩阵
            self.init_view()

            #启动光照
            glEnable(GL_LIGHTING)
            #清空颜色缓存与深度缓存
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

            #设置模型视图矩阵,这节课先用单位矩阵就行了。
            glMatrixMode(GL_MODELVIEW)
            glPushMatrix()
            glLoadIdentity()

            #渲染场景
            self.scene.render()

            #每次渲染后复位光照状态
            glDisable(GL_LIGHTING)
            glCallList(G_OBJ_PLANE)
            glPopMatrix()
            #把数据刷新到显存上
            glFlush()

        def init_view(self):
            """ 初始化投影矩阵 """
            xSize, ySize = glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT)
            #得到屏幕宽高比
            aspect_ratio = float(xSize) / float(ySize)

            #设置投影矩阵
            glMatrixMode(GL_PROJECTION)
            glLoadIdentity()

            #设置视口,应与窗口重合
            glViewport(0, 0, xSize, ySize)
            #设置透视,摄像机上下视野幅度70度
            #视野范围到距离摄像机1000个单位为止。
            gluPerspective(70, aspect_ratio, 0.1, 1000.0)
            #摄像机镜头从原点后退15个单位
            glTranslated(0, 0, -15)

    if __name__ == "__main__":
        viewer = Viewer()
        viewer.main_loop()

四、 结果及分析

python三维建模可视化与交互,python三维建模可视化,人工智能

到这里我们就已经实现了一个简单的 3D 建模工具了,想一下这个程序还能在什么地方进行改进,或是增加一些新的功能?比如说:

-编写新的节点类,支持三角形网格能够组合成任意形状。

-增加一个撤销栈,支持撤销命令功能。

-能够保存/加载 3D 设计,比如保存为 DXF 3D 文件格式

-改进程序,选中目标更精准。

到了这里,关于python三维建模可视化与交互,python三维建模可视化的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • MATLAB数学建模:数据图形可视化-三维绘图函数

    在 MATLAB 中, 我们可使用函数 surf 和 surfc 绘制三维曲面图. 调用格式如下: 以矩阵 ZZZ 所指定的参数创建一个渐变的三维曲面. 坐标 $x = 1:n, y = 1:m, $ 其中 [m,n]=size(Z)[m,n] = size(Z)[m,n]=size(Z) 以 ZZZ 确定的曲面高度和颜色, 按照 X,YX,YX,Y 形成的格点矩阵, 创建一个渐变的三维曲面. X,

    2024年02月06日
    浏览(61)
  • 三维交互可视化平台(智慧海上牧场平台)学习开发之Vue(一)

    最近找导师重新更换了研究方向,学的东西还是蛮杂的,本来就是一个代码菜鸟,捣鼓一大堆,全栈开发、各种语言、区块链开发等等,之前总是想要学会一项,完成一样功能才记录。目前我要完成的项目是一个智慧海洋牧场平台,前期学习了Flask+mysql+echarts的可视化展示,现

    2023年04月09日
    浏览(49)
  • 用人工智能提升智能安全监察的可视化和交互性

    作者:禅与计算机程序设计艺术 《22. 用人工智能提升智能安全监察的可视化和交互性》 1.1. 背景介绍 随着互联网技术的快速发展,智能安全监察作为保障网络安全的重要手段,越来越受到关注。然而,传统的智能安全监察手段主要依赖于人工检查和分析,过程繁琐且易出错

    2024年02月07日
    浏览(59)
  • 使用Python进行三维可视化

    使用Python进行三维可视化 Python是一种易于学习和使用的编程语言,它拥有强大的图形处理能力。在科学、工程或数据分析等领域,Python可以用来处理和生成各种图表和图像。本文将介绍如何使用Python进行三维可视化,并提供相应的源代码。 首先,我们需要安装必要的Python库

    2024年02月14日
    浏览(48)
  • python数学建模--绘图动态可视化图表

    本博客的灵感来源自笔者最近研究的最优化问题 在使用 模拟退火算法、遗传算法 求二元函数最值的过程中,虽然笔者已经能够通过算法得到不错的结果,但是笔者还是比较好奇算法的执行过程中,变量是怎样更新的,显然可视化是一种很好的方法 在上一篇博客【python数学建

    2024年02月06日
    浏览(42)
  • 数据可视化——用python绘制气泡图、三维散点图、多重柱形图案例

    目录 前言 一、气泡图的绘制 1、什么是气泡图?他适用于什么数据? 2、图形效果展示 3、导入需要用到的库 4、读取要分析的数据 5、检查数据是否有问题 6、将要对比数据提取出来 7、画图 二、三维散点图的绘制 1、什么是三维散点图? 2、导入需要用到的数据库 3、画图 三

    2024年02月06日
    浏览(63)
  • 【复杂网络建模】——Python可视化重要节点识别(PageRank算法)

    🤵‍♂️ 个人主页:@Lingxw_w的个人主页 ✍🏻作者简介:计算机科学与技术研究生在读 🐋 希望大家多多支持,我们一起进步!😄 如果文章对你有帮助的话, 欢迎评论 💬点赞👍🏻 收藏 📂加关注+ 目录 一、复杂网络建模 二、建模的算法

    2024年02月06日
    浏览(48)
  • open3d教程(二):可视化三维模型,并转换成点云(Python版本)

    可以自己用建模软件建立一个模型 从free3d免费下载 open3d.visualization. draw_geometries 参数: geometry_list ( List[open3d.geometry.Geometry]) : 要可视化的几何体列表. window_name( str ,  optional ,  default=\\\'Open3D\\\'): 展示模型的可视化窗口名称,默认是Open3d. width: 

    2024年02月11日
    浏览(50)
  • 掌握Python库的Bokeh,就能让你的交互炫目可视化

    本文分享自华为云社区《Bokeh图形魔法:掌握绘图基础与高级技巧,定制炫目可视化》,作者: 柠檬味拥抱。 Bokeh是一个用于创建交互式可视化图形的强大Python库。它不仅易于使用,而且功能强大,适用于各种数据可视化需求。本文将介绍Bokeh库的绘图可视化基础入门,重点

    2024年03月15日
    浏览(50)
  • Python数据可视化库Matplotlib绘图学习(二维)&数学建模

    如果没有出现错误,就说明安装成功。 一元二次函数图像: 运行效果: 解释: as: 重命名,将长串的函数库改一个容易书写的名字 range函数: 生成范围内所有的数字 列表推导式: 列表推导式(List Comprehension)是一种简洁地创建新列表的方法,它可以基于现有的列表、集合

    2024年02月07日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包