Kivy: Как визуализировать 3D модель в заданном макете - PullRequest
0 голосов
/ 09 января 2020

Я пытаюсь сделать простой просмотрщик 3D-модели. Я начал с примера 3D-рендеринга, одного из примеров киви. Ниже приведен тестовый код, который я пробовал. (Python 3.7, Kivy 1.11.1)

Python код: test_renderer.py

import kivy
kivy.require('1.11.1')

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from renderer import Renderer
from kivy.lang import Builder
#from kivy.properties import ObjectProperty
#from kivy.uix.label import Label
#from kivy.uix.button import Button

Builder.load_file('test_win.kv')


class Test_Win(BoxLayout):
    pass

class Test_Renderer(App):
    def build(self):
        self.title = 'Test Renderer'
        return Test_Win()


if __name__ == '__main__':
    Test_Renderer().run()

и файл kv: test_win.kv

# File name: test_win.kv
#:kivy 1.11.1

<Test_Win>:
    BoxLayout:
        orientation: 'horizontal'
        BoxLayout:
            orientation: 'vertical'
            Label:
                text: 'Test Renderer'
            Button:
                text: 'Click'
        Splitter:
            Renderer:

Другие файлы (renderer.py, objloader.py, simple.glsl, monkey.obj) из примера 3D-рендеринга (main.py, objloader.py, simple.glsl, monkey.obj) представляют собой То же самое, за исключением изменения имени main.py на renderer.py, которые находятся в той же папке, что и два вышеуказанных файла.

Эти файлы можно найти по адресу https://kivy.org/doc/stable/examples/gen__3Drendering__main__py.html#

или https://github.com/kivy/kivy/tree/master/examples/3Drendering с 3D-файлом.

Полученный экран будет следующим образом

Вращающаяся головка отображается на верх макета и виджетов. Похоже, что холст дисплея - это root, а не макет сплиттера, как я планировал.

Я только начал kivy и понятия не имею, с чего начать. Может кто-нибудь помочь мне найти решение?

1 Ответ

0 голосов
/ 11 января 2020

Я придумал хак, чтобы сделать почти то, что вы хотите. Поскольку матрицы проекций задействованы, это немного сложно. Возможно, кто-то более искушенный в OGL мог бы сделать лучше. Вот мой взлом:

Сначала я хотел экстенты трехмерного объекта, поэтому я изменил метод __init__() класса ObjFile:

def __init__(self, filename, swapyz=False):
    """Loads a Wavefront OBJ file. """
    self.objects = {}
    self.vertices = []
    self.normals = []
    self.texcoords = []
    self.faces = []
    maxf = sys.float_info.max

    # initialize the minimums and maximums
    self.mins = [maxf, maxf, maxf]
    self.maxs = [-maxf, -maxf, -maxf]

    self._current_object = None

    material = None
    for line in open(filename, "r"):
        if line.startswith('#'):
            continue
        if line.startswith('s'):
            continue
        values = line.split()
        if not values:
            continue
        if values[0] == 'o':
            self.finish_object()
            self._current_object = values[1]
        # elif values[0] == 'mtllib':
        #    self.mtl = MTL(values[1])
        # elif values[0] in ('usemtl', 'usemat'):
        #    material = values[1]
        if values[0] == 'v':
            v = list(map(float, values[1:4]))
            if swapyz:
                v = v[0], v[2], v[1]

            # keep the vertex minimums and maximums
            for i in range(3):
                self.mins[i] = min(self.mins[i], v[i])
                self.maxs[i] = max(self.maxs[i], v[i])


            self.vertices.append(v)
        elif values[0] == 'vn':
            v = list(map(float, values[1:4]))
            if swapyz:
                v = v[0], v[2], v[1]
            self.normals.append(v)
        elif values[0] == 'vt':
            self.texcoords.append(map(float, values[1:3]))
        elif values[0] == 'f':
            face = []
            texcoords = []
            norms = []
            for v in values[1:]:
                w = v.split('/')
                face.append(int(w[0]))
                if len(w) >= 2 and len(w[1]) > 0:
                    texcoords.append(int(w[1]))
                else:
                    texcoords.append(-1)
                if len(w) >= 3 and len(w[2]) > 0:
                    norms.append(int(w[2]))
                else:
                    norms.append(-1)
            self.faces.append((face, norms, texcoords, material))
    self.finish_object()
    print('max :', self.maxs, ', mins:', self.mins)

И в * Класс 1008 *, я изменил два метода:

def update_glsl(self, delta):
    # Calculate new left edge for the clip matrix
    old_scene_width = self.scene.maxs[0] - self.scene.mins[0]
    app_width = App.get_running_app().root.width
    renderer_width = self.width
    ratio = app_width / renderer_width
    new_scene_width = old_scene_width * ratio
    left = self.scene.mins[0] - (new_scene_width - old_scene_width)

    # calculate new top and bottom of clip frustum that maintains monkey head aspect ratio
    scene_center_vertical = (self.scene.mins[1] + self.scene.maxs[1]) /  2.0
    old_scene_height = self.scene.maxs[1] - self.scene.mins[1]
    new_scene_height = old_scene_height * ratio
    bottom = scene_center_vertical - new_scene_height / 2.0
    top = scene_center_vertical + new_scene_height / 2.0

    # create new clip matrix
    proj = Matrix().view_clip(left, self.scene.maxs[0],
                              bottom, top,
                              1, self.scene.maxs[2] - self.scene.mins[2] - self.translate.z,
                              1)

    self.canvas['projection_mat'] = proj
    self.canvas['diffuse_light'] = (1.0, 1.0, 0.8)
    self.canvas['ambient_light'] = (0.1, 0.1, 0.1)
    self.rot.angle += delta * 100

def setup_scene(self):
    Color(1, 1, 1, 1)
    PushMatrix()

    # save a reference to the Translate matrix
    self.translate = Translate(0, 0, -3)

    self.rot = Rotate(1, 0, 1, 0)
    m = list(self.scene.objects.values())[0]
    UpdateNormalMatrix()
    self.mesh = Mesh(
        vertices=m.vertices,
        indices=m.indices,
        fmt=m.vertex_format,
        mode='triangles',
    )
    PopMatrix()

Идея состоит в том, чтобы отрегулировать матрицу отсечения так, чтобы голова обезьяны центрировалась в Renderer Widget. Для этого sh я изменил параметр left матрицы клипа так, чтобы обезьяна больше не находилась в центре усеченного усеченного конуса. Я также пересчитал параметры top и bottom усеченного конуса, чтобы избежать неравномерного масштабирования (искажения).

...