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>