2022-04-28 21:16:12 +00:00
bl_info = {
" name " : " osci-render " ,
" author " : " James Ball " ,
2025-01-21 21:34:25 +00:00
" version " : ( 1 , 1 , 0 ) ,
2022-04-28 21:16:12 +00:00
" blender " : ( 3 , 1 , 2 ) ,
" location " : " View3D " ,
2022-04-28 22:53:09 +00:00
" description " : " Addon to send gpencil frames over to osci-render " ,
" warning " : " Requires a camera and gpencil object " ,
2022-04-28 21:16:12 +00:00
" wiki_url " : " https://github.com/jameshball/osci-render " ,
" category " : " Development " ,
}
import bpy
2024-04-24 15:00:47 +00:00
import os
2022-04-28 21:16:12 +00:00
import bmesh
import socket
import json
2022-04-30 17:51:56 +00:00
import atexit
2025-01-21 21:34:25 +00:00
import struct
2025-01-22 13:37:45 +00:00
import base64
2024-04-26 19:34:50 +00:00
from bpy . props import StringProperty
2022-04-30 09:18:32 +00:00
from bpy . app . handlers import persistent
2024-04-24 15:00:47 +00:00
from bpy_extras . io_utils import ImportHelper
2022-04-28 21:16:12 +00:00
HOST = " localhost "
2025-01-23 15:38:28 +00:00
PORT = 51677
2022-04-28 21:16:12 +00:00
sock = None
2025-01-21 21:34:25 +00:00
GPLA_MAJOR = 2
GPLA_MINOR = 0
GPLA_PATCH = 0
2022-04-28 21:16:12 +00:00
class OBJECT_PT_osci_render_settings ( bpy . types . Panel ) :
bl_idname = " OBJECT_PT_osci_render_settings "
2025-01-24 15:34:24 +00:00
bl_label = " osci-render settings "
2022-04-28 21:16:12 +00:00
bl_space_type = " PROPERTIES "
bl_region_type = " WINDOW "
bl_context = " render "
def draw_header ( self , context ) :
layout = self . layout
def draw ( self , context ) :
2025-01-24 12:38:28 +00:00
self . layout . prop ( context . scene , " oscirenderPort " )
2022-04-28 21:16:12 +00:00
global sock
if sock is None :
2024-04-24 15:00:47 +00:00
self . layout . operator ( " render.osci_render_connect " , text = " Connect to osci-render instance " )
2022-04-28 21:16:12 +00:00
else :
self . layout . operator ( " render.osci_render_close " , text = " Close osci-render connection " )
2024-04-24 15:00:47 +00:00
self . layout . operator ( " render.osci_render_save " , text = " Save line art to file " )
2022-04-28 21:16:12 +00:00
class osci_render_connect ( bpy . types . Operator ) :
bl_label = " Connect to osci-render "
bl_idname = " render.osci_render_connect "
bl_description = " Connect to osci-render "
def execute ( self , context ) :
global sock
if sock is None :
try :
sock = socket . socket ( socket . AF_INET , socket . SOCK_STREAM )
2022-04-30 17:51:56 +00:00
sock . settimeout ( 1 )
2025-01-24 12:38:28 +00:00
sock . connect ( ( HOST , context . scene . oscirenderPort ) )
2022-04-28 21:16:12 +00:00
send_scene_to_osci_render ( bpy . context . scene )
2022-04-30 17:51:56 +00:00
except socket . error as exp :
2022-04-28 21:16:12 +00:00
sock = None
2022-04-30 17:51:56 +00:00
self . report ( { " WARNING " } , " Failed to connect to osci-render - make sure it is running first! " )
return { " CANCELLED " }
2022-04-28 21:16:12 +00:00
return { " FINISHED " }
2024-04-24 15:00:47 +00:00
class osci_render_save ( bpy . types . Operator , ImportHelper ) :
bl_label = " Save Line Art "
bl_idname = " render.osci_render_save "
bl_description = " Save line art to the chosen file "
2024-04-26 19:34:50 +00:00
filename_ext = " .gpla "
filter_glob : StringProperty (
2024-04-24 17:06:17 +00:00
default = " *.gpla " ,
options = { " HIDDEN " }
2024-04-24 15:00:47 +00:00
)
2024-04-26 19:34:50 +00:00
def execute ( self , context ) :
2024-04-24 15:00:47 +00:00
FilePath = self . filepath
filename , extension = os . path . splitext ( self . filepath )
2024-04-26 19:34:50 +00:00
if extension != " .gpla " :
2024-04-24 15:00:47 +00:00
extension = " .gpla "
FilePath = FilePath + " .gpla "
2024-04-26 19:34:50 +00:00
2024-04-24 15:00:47 +00:00
self . report ( { " INFO " } , FilePath )
2024-04-26 19:34:50 +00:00
2024-04-24 15:00:47 +00:00
if filename is not None and extension is not None :
fin = save_scene_to_file ( bpy . context . scene , FilePath )
2024-04-26 19:34:50 +00:00
if fin == 0 :
2024-04-24 15:00:47 +00:00
self . report ( { " INFO " } , " File write successful! " )
return { " FINISHED " }
else :
self . report ( { " WARNING " } , " Something went wrong in saving the file " )
else :
filename = None
extension = None
self . report ( { " WARNING " } , " The filename or extension isn ' t right, action stopped for your own safety " )
return { " CANCELLED " }
2022-04-28 21:16:12 +00:00
class osci_render_close ( bpy . types . Operator ) :
bl_label = " Close osci-render connection "
2022-04-28 22:53:09 +00:00
bl_idname = " render.osci_render_close "
2022-04-28 21:16:12 +00:00
def execute ( self , context ) :
2022-04-30 17:51:56 +00:00
close_osci_render ( )
2022-04-28 21:16:12 +00:00
return { " FINISHED " }
2022-04-30 17:51:56 +00:00
@persistent
def close_osci_render ( ) :
global sock
if sock is not None :
try :
sock . send ( " CLOSE \n " . encode ( ' utf-8 ' ) )
sock . close ( )
except socket . error as exp :
sock = None
2022-04-28 21:16:12 +00:00
2025-01-22 13:37:45 +00:00
def get_gpla_file_allframes ( scene ) :
2025-01-21 21:34:25 +00:00
bin = bytearray ( )
# header
bin . extend ( ( " GPLA " ) . encode ( " utf8 " ) )
bin . extend ( GPLA_MAJOR . to_bytes ( 8 , " little " ) )
bin . extend ( GPLA_MINOR . to_bytes ( 8 , " little " ) )
bin . extend ( GPLA_PATCH . to_bytes ( 8 , " little " ) )
2024-04-24 15:00:47 +00:00
2025-01-21 21:34:25 +00:00
# file info
bin . extend ( ( " FILE " ) . encode ( " utf8 " ) )
bin . extend ( ( " fCount " ) . encode ( " utf8 " ) )
bin . extend ( ( scene . frame_end - scene . frame_start + 1 ) . to_bytes ( 8 , " little " ) )
bin . extend ( ( " fRate " ) . encode ( " utf8 " ) )
bin . extend ( scene . render . fps . to_bytes ( 8 , " little " ) )
bin . extend ( ( " DONE " ) . encode ( " utf8 " ) )
for frame in range ( 0 , scene . frame_end - scene . frame_start + 1 ) :
2024-04-24 15:00:47 +00:00
scene . frame_set ( frame + scene . frame_start )
2025-01-21 21:34:25 +00:00
bin . extend ( get_frame_info_binary ( ) )
bin . extend ( ( " END GPLA " ) . encode ( " utf8 " ) )
2024-04-24 15:00:47 +00:00
2025-01-22 13:37:45 +00:00
return bin
def get_gpla_file ( scene ) :
bin = bytearray ( )
# header
bin . extend ( ( " GPLA " ) . encode ( " utf8 " ) )
bin . extend ( GPLA_MAJOR . to_bytes ( 8 , " little " ) )
bin . extend ( GPLA_MINOR . to_bytes ( 8 , " little " ) )
bin . extend ( GPLA_PATCH . to_bytes ( 8 , " little " ) )
# file info
bin . extend ( ( " FILE " ) . encode ( " utf8 " ) )
bin . extend ( ( " fCount " ) . encode ( " utf8 " ) )
bin . extend ( ( scene . frame_end - scene . frame_start + 1 ) . to_bytes ( 8 , " little " ) )
bin . extend ( ( " fRate " ) . encode ( " utf8 " ) )
bin . extend ( scene . render . fps . to_bytes ( 8 , " little " ) )
bin . extend ( ( " DONE " ) . encode ( " utf8 " ) )
bin . extend ( get_frame_info_binary ( ) )
bin . extend ( ( " END GPLA " ) . encode ( " utf8 " ) )
return bin
@persistent
def save_scene_to_file ( scene , file_path ) :
return_frame = scene . frame_current
bin = get_gpla_file_allframes ( scene )
2024-04-26 18:27:28 +00:00
if file_path is not None :
2025-01-21 21:34:25 +00:00
with open ( file_path , " wb " ) as f :
f . write ( bytes ( bin ) )
2024-04-24 15:00:47 +00:00
else :
return 1
2025-01-21 21:34:25 +00:00
2024-04-26 18:27:28 +00:00
scene . frame_set ( return_frame )
2024-04-24 15:00:47 +00:00
return 0
2022-04-28 21:16:12 +00:00
2025-01-21 21:34:25 +00:00
def get_frame_info_binary ( ) :
frame_info = bytearray ( )
frame_info . extend ( ( " FRAME " ) . encode ( " utf8 " ) )
frame_info . extend ( ( " focalLen " ) . encode ( " utf8 " ) )
frame_info . extend ( struct . pack ( " d " , - 0.05 * bpy . data . cameras [ 0 ] . lens ) )
frame_info . extend ( ( " OBJECTS " ) . encode ( " utf8 " ) )
if ( bpy . app . version [ 0 ] > 4 ) or ( bpy . app . version [ 0 ] == 4 and bpy . app . version [ 1 ] > = 3 ) :
2025-01-21 22:17:31 +00:00
for object in bpy . data . objects :
if object . visible_get ( ) and object . type == ' GREASEPENCIL ' :
dg = bpy . context . evaluated_depsgraph_get ( )
obj = object . evaluated_get ( dg )
2025-01-23 15:38:28 +00:00
frame_info . extend ( ( " OBJECT " ) . encode ( " utf8 " ) )
# matrix
frame_info . extend ( ( " MATRIX " ) . encode ( " utf8 " ) )
camera_space = bpy . context . scene . camera . matrix_world . inverted ( ) @ obj . matrix_world
for i in range ( 4 ) :
for j in range ( 4 ) :
frame_info . extend ( struct . pack ( " d " , camera_space [ i ] [ j ] ) )
frame_info . extend ( ( " DONE " ) . encode ( " utf8 " ) )
# strokes
frame_info . extend ( ( " STROKES " ) . encode ( " utf8 " ) )
2025-01-23 15:58:55 +00:00
layers = obj . data . layers
for layer in layers :
strokes = layer . frames . data . current_frame ( ) . drawing . strokes
for stroke in strokes :
frame_info . extend ( ( " STROKE " ) . encode ( " utf8 " ) )
2025-01-23 13:25:45 +00:00
2025-01-23 15:58:55 +00:00
frame_info . extend ( ( " vertexCt " ) . encode ( " utf8 " ) )
frame_info . extend ( len ( stroke . points ) . to_bytes ( 8 , " little " ) )
2025-01-23 13:25:45 +00:00
2025-01-23 15:58:55 +00:00
frame_info . extend ( ( " VERTICES " ) . encode ( " utf8 " ) )
for vert in stroke . points :
frame_info . extend ( struct . pack ( " d " , vert . position . x ) )
frame_info . extend ( struct . pack ( " d " , vert . position . y ) )
frame_info . extend ( struct . pack ( " d " , vert . position . z ) )
# VERTICES
frame_info . extend ( ( " DONE " ) . encode ( " utf8 " ) )
2025-01-23 15:38:28 +00:00
2025-01-23 15:58:55 +00:00
# STROKE
frame_info . extend ( ( " DONE " ) . encode ( " utf8 " ) )
2025-01-23 15:38:28 +00:00
# STROKES
frame_info . extend ( ( " DONE " ) . encode ( " utf8 " ) )
# OBJECT
frame_info . extend ( ( " DONE " ) . encode ( " utf8 " ) )
2025-01-21 21:34:25 +00:00
else :
2025-01-21 22:17:31 +00:00
for object in bpy . data . objects :
2025-01-26 16:28:35 +00:00
if object . visible_get ( ) and object . type == ' GPENCIL ' :
2025-01-21 22:17:31 +00:00
dg = bpy . context . evaluated_depsgraph_get ( )
obj = object . evaluated_get ( dg )
2025-01-21 21:34:25 +00:00
frame_info . extend ( ( " OBJECT " ) . encode ( " utf8 " ) )
# matrix
frame_info . extend ( ( " MATRIX " ) . encode ( " utf8 " ) )
camera_space = bpy . context . scene . camera . matrix_world . inverted ( ) @ obj . matrix_world
for i in range ( 4 ) :
for j in range ( 4 ) :
2025-01-26 16:28:35 +00:00
frame_info . extend ( struct . pack ( " d " , camera_space [ i ] [ j ] ) )
2025-01-21 21:34:25 +00:00
# MATRIX
frame_info . extend ( ( " DONE " ) . encode ( " utf8 " ) )
# strokes
frame_info . extend ( ( " STROKES " ) . encode ( " utf8 " ) )
2025-01-23 15:58:55 +00:00
layers = obj . data . layers
for layer in layers :
strokes = layer . frames . data . active_frame . strokes
for stroke in strokes :
frame_info . extend ( ( " STROKE " ) . encode ( " utf8 " ) )
frame_info . extend ( ( " vertexCt " ) . encode ( " utf8 " ) )
frame_info . extend ( len ( stroke . points ) . to_bytes ( 8 , " little " ) )
frame_info . extend ( ( " VERTICES " ) . encode ( " utf8 " ) )
for vert in stroke . points :
frame_info . extend ( struct . pack ( " d " , vert . co [ 0 ] ) )
frame_info . extend ( struct . pack ( " d " , vert . co [ 1 ] ) )
frame_info . extend ( struct . pack ( " d " , vert . co [ 2 ] ) )
# VERTICES
frame_info . extend ( ( " DONE " ) . encode ( " utf8 " ) )
2025-01-21 21:34:25 +00:00
2025-01-23 15:58:55 +00:00
# STROKE
frame_info . extend ( ( " DONE " ) . encode ( " utf8 " ) )
2025-01-21 21:34:25 +00:00
# STROKES
frame_info . extend ( ( " DONE " ) . encode ( " utf8 " ) )
# OBJECT
frame_info . extend ( ( " DONE " ) . encode ( " utf8 " ) )
# OBJECTS
frame_info . extend ( ( " DONE " ) . encode ( " utf8 " ) )
# FRAME
frame_info . extend ( ( " DONE " ) . encode ( " utf8 " ) )
return frame_info
2022-04-28 21:16:12 +00:00
2022-04-30 09:18:32 +00:00
@persistent
2022-04-28 21:16:12 +00:00
def send_scene_to_osci_render ( scene ) :
2022-04-30 17:51:56 +00:00
global sock
2022-04-28 21:16:12 +00:00
if sock is not None :
2025-01-22 13:37:45 +00:00
bin = get_gpla_file ( scene )
2022-04-30 17:51:56 +00:00
try :
2025-01-22 13:37:45 +00:00
sock . sendall ( base64 . b64encode ( bytes ( bin ) ) + " \n " . encode ( " utf8 " ) )
2022-04-30 17:51:56 +00:00
except socket . error as exp :
sock = None
2022-04-28 21:16:12 +00:00
2024-04-24 15:00:47 +00:00
operations = [ OBJECT_PT_osci_render_settings , osci_render_connect , osci_render_close , osci_render_save ]
2022-04-28 21:16:12 +00:00
def register ( ) :
2025-01-24 12:38:28 +00:00
bpy . types . Scene . oscirenderPort = bpy . props . IntProperty ( name = " osci-render port " , description = " The port through which osci-render will connect " , min = 51600 , max = 51699 , default = 51677 )
2022-04-28 21:16:12 +00:00
bpy . app . handlers . frame_change_pre . append ( send_scene_to_osci_render )
bpy . app . handlers . depsgraph_update_post . append ( send_scene_to_osci_render )
2022-04-30 17:51:56 +00:00
atexit . register ( close_osci_render )
2022-04-28 21:16:12 +00:00
for operation in operations :
bpy . utils . register_class ( operation )
def unregister ( ) :
2025-01-24 12:38:28 +00:00
del bpy . types . Object . oscirenderPort
2022-04-30 09:18:32 +00:00
bpy . app . handlers . frame_change_pre . remove ( send_scene_to_osci_render )
bpy . app . handlers . depsgraph_update_post . remove ( send_scene_to_osci_render )
2022-04-30 17:51:56 +00:00
atexit . unregister ( close_osci_render )
2022-04-30 09:18:32 +00:00
for operation in reversed ( operations ) :
2022-04-28 21:16:12 +00:00
bpy . utils . unregister_class ( operation )
if __name__ == " __main__ " :
2025-01-11 14:22:57 +00:00
register ( )