Live patch: move to dedicated module and use msgbus
This commit is contained in:
parent
3b72fff76d
commit
07ffe06c1d
|
@ -6,13 +6,14 @@ import bpy
|
|||
from bpy.app.handlers import persistent
|
||||
|
||||
import arm.api
|
||||
import arm.live_patch as live_patch
|
||||
import arm.logicnode.arm_nodes as arm_nodes
|
||||
import arm.nodes_logic
|
||||
import arm.make as make
|
||||
import arm.make_state as state
|
||||
import arm.props as props
|
||||
import arm.utils
|
||||
|
||||
|
||||
@persistent
|
||||
def on_depsgraph_update_post(self):
|
||||
if state.proc_build != None:
|
||||
|
@ -40,12 +41,10 @@ def on_depsgraph_update_post(self):
|
|||
|
||||
# Send last operator to Krom
|
||||
wrd = bpy.data.worlds['Arm']
|
||||
if state.proc_play != None and \
|
||||
state.target == 'krom' and \
|
||||
wrd.arm_live_patch:
|
||||
if state.proc_play is not None and state.target == 'krom' and wrd.arm_live_patch:
|
||||
ops = bpy.context.window_manager.operators
|
||||
if len(ops) > 0 and ops[-1] != None:
|
||||
send_operator(ops[-1])
|
||||
if len(ops) > 0 and ops[-1] is not None:
|
||||
live_patch.on_operator(ops[-1].bl_idname)
|
||||
|
||||
# Hacky solution to update armory props after operator executions
|
||||
last_operator = bpy.context.active_operator
|
||||
|
@ -71,24 +70,6 @@ def on_operator_post(operator_id: str) -> None:
|
|||
target_obj.arm_rb_collision_filter_mask = source_obj.arm_rb_collision_filter_mask
|
||||
|
||||
|
||||
def send_operator(op):
|
||||
if hasattr(bpy.context, 'object') and bpy.context.object != None:
|
||||
obj = bpy.context.object.name
|
||||
if op.name == 'Move':
|
||||
vec = bpy.context.object.location
|
||||
js = 'var o = iron.Scene.active.getChild("' + obj + '"); o.transform.loc.set(' + str(vec[0]) + ', ' + str(vec[1]) + ', ' + str(vec[2]) + '); o.transform.dirty = true;'
|
||||
make.write_patch(js)
|
||||
elif op.name == 'Resize':
|
||||
vec = bpy.context.object.scale
|
||||
js = 'var o = iron.Scene.active.getChild("' + obj + '"); o.transform.scale.set(' + str(vec[0]) + ', ' + str(vec[1]) + ', ' + str(vec[2]) + '); o.transform.dirty = true;'
|
||||
make.write_patch(js)
|
||||
elif op.name == 'Rotate':
|
||||
vec = bpy.context.object.rotation_euler.to_quaternion()
|
||||
js = 'var o = iron.Scene.active.getChild("' + obj + '"); o.transform.rot.set(' + str(vec[1]) + ', ' + str(vec[2]) + ', ' + str(vec[3]) + ' ,' + str(vec[0]) + '); o.transform.dirty = true;'
|
||||
make.write_patch(js)
|
||||
else: # Rebuild
|
||||
make.patch()
|
||||
|
||||
def always():
|
||||
# Force ui redraw
|
||||
if state.redraw_ui and context_screen != None:
|
||||
|
@ -102,9 +83,11 @@ def always():
|
|||
space.node_tree.arm_cached = False
|
||||
return 0.5
|
||||
|
||||
|
||||
appended_py_paths = []
|
||||
context_screen = None
|
||||
|
||||
|
||||
@persistent
|
||||
def on_load_post(context):
|
||||
global appended_py_paths
|
||||
|
|
134
blender/arm/live_patch.py
Normal file
134
blender/arm/live_patch.py
Normal file
|
@ -0,0 +1,134 @@
|
|||
import os
|
||||
import shutil
|
||||
from typing import Type
|
||||
|
||||
import bpy
|
||||
|
||||
import arm.assets as assets
|
||||
from arm.exporter import ArmoryExporter
|
||||
import arm.log as log
|
||||
import arm.make as make
|
||||
import arm.make_state as state
|
||||
import arm.utils
|
||||
|
||||
# Current patch id
|
||||
patch_id = 0
|
||||
|
||||
# Any object can act as a message bus owner
|
||||
msgbus_owner = object()
|
||||
|
||||
|
||||
def start():
|
||||
log.debug("Live patch session started")
|
||||
|
||||
listen(bpy.types.Object, "location", "obj_location")
|
||||
listen(bpy.types.Object, "rotation_euler", "obj_rotation")
|
||||
listen(bpy.types.Object, "scale", "obj_scale")
|
||||
|
||||
|
||||
def patch_export():
|
||||
"""Re-export the current scene and update the game accordingly."""
|
||||
if state.proc_build is not None:
|
||||
return
|
||||
|
||||
assets.invalidate_enabled = False
|
||||
fp = arm.utils.get_fp()
|
||||
|
||||
with arm.utils.WorkingDir(fp):
|
||||
asset_path = arm.utils.get_fp_build() + '/compiled/Assets/' + arm.utils.safestr(bpy.context.scene.name) + '.arm'
|
||||
ArmoryExporter.export_scene(bpy.context, asset_path, scene=bpy.context.scene)
|
||||
|
||||
if not os.path.isdir(arm.utils.build_dir() + '/compiled/Shaders/std'):
|
||||
raw_shaders_path = arm.utils.get_sdk_path() + '/armory/Shaders/'
|
||||
shutil.copytree(raw_shaders_path + 'std', arm.utils.build_dir() + '/compiled/Shaders/std')
|
||||
node_path = arm.utils.get_node_path()
|
||||
khamake_path = arm.utils.get_khamake_path()
|
||||
|
||||
cmd = [
|
||||
node_path, khamake_path, 'krom',
|
||||
'--shaderversion', '330',
|
||||
'--parallelAssetConversion', '4',
|
||||
'--to', arm.utils.build_dir() + '/debug',
|
||||
'--nohaxe',
|
||||
'--noproject'
|
||||
]
|
||||
|
||||
assets.invalidate_enabled = True
|
||||
state.proc_build = make.run_proc(cmd, patch_done)
|
||||
|
||||
|
||||
def patch_done():
|
||||
"""Signal Iron to reload the running scene after a re-export."""
|
||||
js = 'iron.Scene.patch();'
|
||||
write_patch(js)
|
||||
state.proc_build = None
|
||||
bpy.msgbus.clear_by_owner(msgbus_owner)
|
||||
|
||||
|
||||
def write_patch(js: str):
|
||||
"""Write the given javascript code to 'krom.patch'."""
|
||||
global patch_id
|
||||
with open(arm.utils.get_fp_build() + '/debug/krom/krom.patch', 'w') as f:
|
||||
patch_id += 1
|
||||
f.write(str(patch_id) + '\n')
|
||||
f.write(js)
|
||||
|
||||
|
||||
def listen(rna_type: Type[bpy.types.Struct], prop: str, event_id: str):
|
||||
"""Subscribe to '<rna_type>.<prop>'. The event_id can be choosen
|
||||
freely but must match with the id used in send_event().
|
||||
"""
|
||||
bpy.msgbus.subscribe_rna(
|
||||
key=(rna_type, prop),
|
||||
owner=msgbus_owner,
|
||||
args=(event_id, ),
|
||||
notify=send_event
|
||||
# options={"PERSISTENT"}
|
||||
)
|
||||
|
||||
|
||||
def send_event(event_id: str):
|
||||
"""Send the result of the given event to Krom."""
|
||||
if hasattr(bpy.context, 'object') and bpy.context.object is not None:
|
||||
obj = bpy.context.object.name
|
||||
|
||||
if bpy.context.object.mode == "OBJECT":
|
||||
if event_id == "obj_location":
|
||||
vec = bpy.context.object.location
|
||||
js = f'var o = iron.Scene.active.getChild("{obj}"); o.transform.loc.set({vec[0]}, {vec[1]}, {vec[2]}); o.transform.dirty = true;'
|
||||
write_patch(js)
|
||||
|
||||
elif event_id == 'obj_scale':
|
||||
vec = bpy.context.object.scale
|
||||
js = f'var o = iron.Scene.active.getChild("{obj}"); o.transform.scale.set({vec[0]}, {vec[1]}, {vec[2]}); o.transform.dirty = true;'
|
||||
write_patch(js)
|
||||
|
||||
elif event_id == 'obj_rotation':
|
||||
vec = bpy.context.object.rotation_euler.to_quaternion()
|
||||
js = f'var o = iron.Scene.active.getChild("{obj}"); o.transform.rot.set({vec[1]}, {vec[2]}, {vec[3]}, {vec[0]}); o.transform.dirty = true;'
|
||||
write_patch(js)
|
||||
|
||||
else:
|
||||
patch_export()
|
||||
|
||||
|
||||
def on_operator(operator_id: str):
|
||||
"""As long as bpy.msgbus doesn't listen to changes made by
|
||||
operators (*), additionally notify the callback manually.
|
||||
|
||||
(*) https://developer.blender.org/T72109
|
||||
"""
|
||||
# Don't re-export the scene for the following operators
|
||||
if operator_id in ("VIEW3D_OT_select", "OUTLINER_OT_item_activate", "OBJECT_OT_editmode_toggle"):
|
||||
return
|
||||
|
||||
if operator_id == "TRANSFORM_OT_translate":
|
||||
send_event("obj_location")
|
||||
elif operator_id == "TRANSFORM_OT_rotate":
|
||||
send_event("obj_rotation")
|
||||
elif operator_id == "TRANSFORM_OT_resize":
|
||||
send_event("obj_scale")
|
||||
|
||||
# Rebuild
|
||||
else:
|
||||
patch_export()
|
|
@ -17,6 +17,7 @@ import arm.assets as assets
|
|||
from arm.exporter import ArmoryExporter
|
||||
import arm.lib.make_datas
|
||||
import arm.lib.server
|
||||
import arm.live_patch as live_patch
|
||||
import arm.log as log
|
||||
import arm.make_logic as make_logic
|
||||
import arm.make_renderpath as make_renderpath
|
||||
|
@ -445,40 +446,6 @@ def build_done():
|
|||
else:
|
||||
log.error('Build failed, check console')
|
||||
|
||||
def patch():
|
||||
if state.proc_build != None:
|
||||
return
|
||||
assets.invalidate_enabled = False
|
||||
fp = arm.utils.get_fp()
|
||||
os.chdir(fp)
|
||||
asset_path = arm.utils.get_fp_build() + '/compiled/Assets/' + arm.utils.safestr(bpy.context.scene.name) + '.arm'
|
||||
ArmoryExporter.export_scene(bpy.context, asset_path, scene=bpy.context.scene)
|
||||
if not os.path.isdir(arm.utils.build_dir() + '/compiled/Shaders/std'):
|
||||
raw_shaders_path = arm.utils.get_sdk_path() + '/armory/Shaders/'
|
||||
shutil.copytree(raw_shaders_path + 'std', arm.utils.build_dir() + '/compiled/Shaders/std')
|
||||
node_path = arm.utils.get_node_path()
|
||||
khamake_path = arm.utils.get_khamake_path()
|
||||
|
||||
cmd = [node_path, khamake_path, 'krom']
|
||||
cmd.extend(('--shaderversion', '330', '--parallelAssetConversion', '4',
|
||||
'--to', arm.utils.build_dir() + '/debug', '--nohaxe', '--noproject'))
|
||||
|
||||
assets.invalidate_enabled = True
|
||||
state.proc_build = run_proc(cmd, patch_done)
|
||||
|
||||
def patch_done():
|
||||
js = 'iron.Scene.patch();'
|
||||
write_patch(js)
|
||||
state.proc_build = None
|
||||
|
||||
patch_id = 0
|
||||
|
||||
def write_patch(js):
|
||||
global patch_id
|
||||
with open(arm.utils.get_fp_build() + '/debug/krom/krom.patch', 'w') as f:
|
||||
patch_id += 1
|
||||
f.write(str(patch_id) + '\n')
|
||||
f.write(js)
|
||||
|
||||
def runtime_to_target():
|
||||
wrd = bpy.data.worlds['Arm']
|
||||
|
@ -545,6 +512,7 @@ def build_success():
|
|||
webbrowser.open(html5_app_path)
|
||||
elif wrd.arm_runtime == 'Krom':
|
||||
if wrd.arm_live_patch:
|
||||
live_patch.start()
|
||||
open(arm.utils.get_fp_build() + '/debug/krom/krom.patch', 'w').close()
|
||||
krom_location, krom_path = arm.utils.krom_paths()
|
||||
os.chdir(krom_location)
|
||||
|
|
Loading…
Reference in a new issue