Django / Blender / Celery / Channels

Я нашел это хранилище / видео на youtube

https://www.youtube.com/watch?v=42nvLjOv8Ng

https://github.com/hophead-ninja/django_blender

Но Channels довольно сильно изменился, и я признаюсь, что никогда раньше не пробовал ничего подобного с Django. Я попытался воссоздать это, используя Channels 3.0 - я могу запустить веб-страницу, и кнопка Render "click" возвращает 200 с сервера - ничего не выходит из строя - но monkey.blend не рендерится. Я не вижу, что мой Worker принимает нажатие, я просто получаю 200 обратно от сервера

Вот мой код - возможно, вместе мы сможем заставить это работать. Мой код также является открытым исходным кодом и будет доступен, если мы заставим его работать

Вот все файлы и код - если вам нужно что-то еще, пожалуйста, дайте мне знать. Я очень ценю вашу помощь и хотел бы, чтобы monkey.blend открывался в браузере! Спасибо за ваше время.

Скриншот отрендеренной HTML-страницы /viewer/ html страница

В ответ я получаю статус 200 и IP-адрес REDIS

HTTP GET /viewer/? 200 [0.00, 172.18.0.1:39096]

merlin/settings.py

CHANNEL_LAYERS = {
   "default": {
       "BACKEND": 
"channels_redis.core.RedisChannelLayer",
       "CONFIG": {
           "hosts": [os.environ.get('REDIS_URL', 
'redis://redis:6379')]
       },
    },
}

BLENDER_FILE = ("monkey.blend")
BLENDER_RENDER_TMP_DIR = '/tmp/django_blender'
BLENDER_RENDER_SYNC_INTERVAL = 0.2
BLENDER_USE_GPU = False
BLENDER_GPU_DEVICE = "CUDA"

merlin/routing.py

from channels import route
from viewer import consumers

channel_routing = [
    route("websocket.connect", consumers.connect, 
path=r"^/viewer/"),
    route("websocket.receive", consumers.receive, 
path=r"^/viewer/"),
]

viewer/consumers.py

import json
from channels.generic.websocket import 
WebsocketConsumer
from .tasks import render

class Monkey(WebsocketConsumer):
    def connect(self, message):
        self.accept({
           "type": "websocket.accept",
           "text": json.dumps({
           "action": "reply_channel",
           "reply_channel": 
message.reply_channel.name,
            })
        })

    def receive(self, message, event):
        try:
            data = json.loads(message['text'])
        except ValueError:
            return

       if data:
            reply_channel = 
message.reply_channel.name

            if data['action'] == "start_render":             
                start_render(data, reply_channel)

        self.send({
            "type": "websocket.send",
            "text": event["Render Finished"],
        })

    def start_render(self, data, reply_channel):
        task = render.delay(data, reply_channel)
        self.send({
        "text": json.dumps({
        "action": "started",
        "task_id": task.id
            })
        })

    def disconnect(self, message):
        pass

viewer/tasks.py

import base64
import json
import os
import time
import threading
from merlin.celery import app, get_blender
from django.conf import settings
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer

channel_layer = get_channel_layer()

@app.task(bind=True, track_started=True)
def render(task, data, reply_channel):
    bpy = get_blender()
    setup_scene(bpy, data)

    context = {'rendering': True, 'filepath': 
    os.path.join(settings.BLENDER_RENDER_TMP_DIR, task.request.id)}
    sync_thread = threading.Thread(target=sync_render, args=(bpy, context, reply_channel))
    sync_thread.start()
    bpy.ops.render.render()
    context['rendering'] = False
    sync_thread.join()

    if os.path.exists(context['filepath']):
        os.remove(context['filepath'])

    if reply_channel is not None:
        async_to_sync(channel_layer.send) 

(reply_channel, { 'text': json.dumps({ 'action': 'render_finished' }) })

def setup_scene(bpy, data):
    try:
        levels = int(data['subsurf'])
    except ValueError:
        levels = 0
    bpy.context.object.modifiers[0].render_levels = levels

def sync_render(bpy, context, reply_channel):
    while context['rendering']:

     time.sleep(settings.BLENDER_RENDER_SYNC_INTERVAL)

    image = bpy.data.images['Render Result']
        
    image.save_render(filepath=context['filepath'])

    with open(context['filepath'], "rb") as image_file:
            encoded_string = base64.b64encode(image_file.read())

         if reply_channel is not None:
            async_to_sync(channel_layer.send)(reply_channel, {
            'text': json.dumps({
            'action': 'render_finished'
        })
    })

viewer/urls.py

from django.contrib import admin
from django.conf.urls import url
from django.urls import path, include
from . import views

urlpatterns = [
    url('viewer/', views.index),
]

viewer/views.py

from django.shortcuts import render

def index(request):
    return render(request, '3D/index.html')

3D/index.html

<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <meta http-equiv="x-ua-compatible" content="ie=edge">

  <title>Django - Channels - Celery - Blender</title>

  <script
          src="https://code.jquery.com/jquery-2.2.4.min.js"
          integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44="
          crossorigin="anonymous"></script>
</head>
<body>
<div>
    <form id="render_form">
        <button id="render_button" type="submit" id="go">Render</button>
        <label>Subsurf level</label>
        <input id="subsurf" type="number" value="0">
    </form>
    Task: <span id="render_task">---</span>
    Status: <span id="render_status">---</span>
</div>
<div>
    <img id="render_result" style="width:600px; height:600px; margin:auto; display:block" />
</div>

<script>
$(function() {
    var ws_scheme = window.location.protocol == "http:" ? "wss" : "ws";
    var ws_path = ws_scheme + '://' + window.location.host + '/viewer/';

    var socket = new WebSocket(ws_path);

    socket.onmessage = function(message) {
        var data = JSON.parse(message.data);
        if (data.action == "started") {
            $('#render_task').text(data.task_id);
            $('#render_status').text('Waiting for render node(worker)');
        }
        else if (data.action == "sync_render"){
            $('#render_result').attr('src', 'data:image/png;base64,'+data.image);
            $('#render_status').text('Rendering');
        }
        else if(data.action == "render_finished"){
            $('#render_button').attr('disabled', false);
            $('#render_status').text('Finished');
        }
    };

    $("#render_form").on("submit", function(event) {
        $('#render_button').attr('disabled', true);
        var message = {
            action: "start_render",
            subsurf: $('#subsurf').val()
        };
        socket.send(JSON.stringify(message));
        return false;
    });
});
</script>
</body>
</html>
Вернуться на верх