Hi! I ran 2.6.7 on Blender 5.1.1 (Python 3.13) and hit three more issues. I've patched all of them locally with Claude, let me know if this can help you to push a new version.
TL;DR
# | Severity | File | Symptom on Blender 5.1.1
-- | -- | -- | --
1 | Blocker | core/utilities.py, resources/extensions/instance_assets.py | AttributeError: 'Action' object has no attribute 'fcurves'
2 | Blocker | dependencies/rpc/factory.py | SyntaxError: expected 'else' after 'if' expression when calling any remote Unreal method
3 | Cosmetic | dependencies/rpc/factory.py | SyntaxWarning: invalid escape sequence '\.'
Issues 1 and 2 each prevent any export. Issue 3 is just a warning under Python 3.12+.
1. action.fcurves removed in Blender 5.0
Blender 5.0 finalised the slotted-action migration started in 4.4 and removed the legacy proxy properties action.fcurves, action.groups, and action.id_root. F-Curves now live on the channelbag of each action slot:
https://developer.blender.org/docs/release_notes/5.0/python_api/
https://developer.blender.org/docs/release_notes/4.4/upgrading/slotted_actions/
Send2UE accesses action.fcurves directly in six places:
core/utilities.py — get_custom_property_fcurve_data, remove_object_scale_keyframes, round_keyframes, scale_object_actions
resources/extensions/instance_assets.py — post_import (anim sequence branch)
Fix
I added two small back-compat helpers at the top of core/utilities.py and routed every existing call through them:
python
def _iter_action_fcurves(action):
"""Yield all F-Curves of an Action; transparent on 4.x and 5.x."""
if action is None:
return
if bpy.app.version[0] >= 5:
try:
for layer in action.layers:
for strip in layer.strips:
for channelbag in strip.channelbags:
for fcurve in channelbag.fcurves:
yield fcurve
return
except AttributeError:
pass
for fcurve in action.fcurves:
yield fcurve
def _remove_action_fcurve(action, fcurve):
"""Remove an F-Curve from an Action; transparent on 4.x and 5.x."""
if bpy.app.version[0] >= 5:
try:
for layer in action.layers:
for strip in layer.strips:
for channelbag in strip.channelbags:
if fcurve in list(channelbag.fcurves):
channelbag.fcurves.remove(fcurve)
return
return
except AttributeError:
pass
action.fcurves.remove(fcurve)
For instance, remove_object_scale_keyframes becomes:
python
for action in actions:
scale_fcurves = [fc for fc in _iter_action_fcurves(action) if fc.data_path == 'scale']
for fcurve in scale_fcurves:
_remove_action_fcurve(action, fcurve)
instance_assets.py imports _iter_action_fcurves from send2ue.core.utilities and uses it in the for fcurve in action.fcurves loop inside post_import.
2. _get_docstring / _get_code in dependencies/rpc/factory.py
When the user clicks Send to Unreal, validation calls a remote method (e.g. UnrealRemoteCalls.is_using_legacy_fbx_importer()). On Blender 5.1.1 the call fails twice in a row:
2a) Client-side exec() in _get_docstring
python
exec('\n'.join(code))
function_instance = locals().copy().get(function_name)
return function_instance.__doc__
This re-execs the function source just to read __doc__. Python 3.13's parser raises SyntaxError: expected 'else' after 'if' expression on several UnrealRemoteCalls method sources (the methods themselves are fine — it's the round-trip through inspect.getsource → textwrap.dedent → split-and-filter → join that produces something the new parser doesn't like in edge cases involving comprehensions and conditional expressions).
The exec is unnecessary: at the only call site we already hold the function object. I changed it to:
python
@staticmethod
def _get_docstring(code, function_name, function=None):
if function is not None and getattr(function, "__doc__", None) is not None:
return function.__doc__
# Fallback for the rare case where the function object isn't supplied:
# extract the first triple-quoted block from the source without exec.
import re
source = '\n'.join(code)
m = re.search(r'(?P<q>\"{3}|\'{3})(?P<body>.*?)(?P=q)', source, re.DOTALL)
return m.group('body') if m else None
…and the single caller forwards the function:
python
doc_string = self._get_docstring(code, function.__name__, function=function)
2b) Server-side exec() chokes on the stripped docstring
After 2a was fixed, Send to Unreal then crashed on the server side:
File "send2ue/dependencies/rpc/base_server.py", line 208, in add_new_callable
exec(code)
File "<string>", line 3
Checks to see if a directory exist in unreal.
^^^^^^^^
SyntaxError: expected 'else' after 'if' expression
The culprit is the docstring-stripping logic in _get_code:
python
if doc_string:
code = '\n'.join(code).replace(doc_string, '')
code = [line for line in code.split('\n')
if not all([char == '"' or char == "'" for char in line.strip()])]
function.__doc__ preserves the source's leading indentation, but inspect.getsource is fed through textwrap.dedent first, so the indentation no longer matches. str.replace silently fails to remove anything. Then the second filter strips lines composed entirely of triple-quotes — which means the opening and closing """ get removed but the body stays. The body becomes naked text in the function (Checks to see if a directory exist in unreal.) and Python 3.13 sees the if and demands an else.
The simplest robust fix is to stop trying to strip docstrings. They're valid Python and add only a handful of bytes per RPC call. I replaced the block with:
python
# Docstrings are valid Python; leaving them in is harmless and avoids a
# fragile indentation-sensitive string replace.
_ = doc_string
return code
After this, Send to Unreal completes end-to-end on Blender 5.1.1.
3. SyntaxWarning: invalid escape sequence '\.'
Just re.split('\.|\(| ', line.strip()) at factory.py:126 — needs an r prefix. Not blocking, but noisy in the console on Python 3.12+.
python
if key in re.split(r'\.|\(| ', line.strip()):
Testing
I tested on:
- Blender 5.1.1 with Python 3.13, Unreal 5.7.4
Static mesh, skeletal mesh, animation export, and instance-assets extension all work.
Kelit
Hi! I ran 2.6.7 on Blender 5.1.1 (Python 3.13) and hit three more issues. I've patched all of them locally with Claude, let me know if this can help you to push a new version.
TL;DR
Issues 1 and 2 each prevent any export. Issue 3 is just a warning under Python 3.12+.
1.
action.fcurvesremoved in Blender 5.0Blender 5.0 finalised the slotted-action migration started in 4.4 and removed the legacy proxy properties
action.fcurves,action.groups, andaction.id_root. F-Curves now live on the channelbag of each action slot: https://developer.blender.org/docs/release_notes/5.0/python_api/ https://developer.blender.org/docs/release_notes/4.4/upgrading/slotted_actions/Send2UE accesses
action.fcurvesdirectly in six places:core/utilities.py—get_custom_property_fcurve_data,remove_object_scale_keyframes,round_keyframes,scale_object_actionsresources/extensions/instance_assets.py—post_import(anim sequence branch)Fix
I added two small back-compat helpers at the top of
core/utilities.pyand routed every existing call through them:For instance,
remove_object_scale_keyframesbecomes:instance_assets.pyimports_iter_action_fcurvesfromsend2ue.core.utilitiesand uses it in thefor fcurve in action.fcurvesloop insidepost_import.2.
_get_docstring/_get_codeindependencies/rpc/factory.pyWhen the user clicks Send to Unreal, validation calls a remote method (e.g.
UnrealRemoteCalls.is_using_legacy_fbx_importer()). On Blender 5.1.1 the call fails twice in a row:2a) Client-side
exec()in_get_docstringThis re-execs the function source just to read
__doc__. Python 3.13's parser raisesSyntaxError: expected 'else' after 'if' expressionon severalUnrealRemoteCallsmethod sources (the methods themselves are fine — it's the round-trip throughinspect.getsource→textwrap.dedent→ split-and-filter → join that produces something the new parser doesn't like in edge cases involving comprehensions and conditional expressions).The exec is unnecessary: at the only call site we already hold the function object. I changed it to:
…and the single caller forwards the function:
2b) Server-side
exec()chokes on the stripped docstringAfter 2a was fixed,
Send to Unrealthen crashed on the server side:The culprit is the docstring-stripping logic in
_get_code:function.__doc__preserves the source's leading indentation, butinspect.getsourceis fed throughtextwrap.dedentfirst, so the indentation no longer matches.str.replacesilently fails to remove anything. Then the second filter strips lines composed entirely of triple-quotes — which means the opening and closing"""get removed but the body stays. The body becomes naked text in the function (Checks to see if a directory exist in unreal.) and Python 3.13 sees theifand demands anelse.The simplest robust fix is to stop trying to strip docstrings. They're valid Python and add only a handful of bytes per RPC call. I replaced the block with:
After this, Send to Unreal completes end-to-end on Blender 5.1.1.
3.
SyntaxWarning: invalid escape sequence '\.'Just
re.split('\.|\(| ', line.strip())atfactory.py:126— needs anrprefix. Not blocking, but noisy in the console on Python 3.12+.Testing
I tested on:
Static mesh, skeletal mesh, animation export, and instance-assets extension all work.
Kelit