fake-bpy-module
fake-bpy-module copied to clipboard
Improve Object.location typing and other vector properties
The Object.location
can be assigns a Vector
, list[float]
, or tuple[float, float, float]
but only ever returns Vector
.
The type hints should reflect this.
More generally, all Vector
properties should be using this.
What motivated this change?
When getting the value of Object.location
the type deduced by Pyright is the union instead of Vector
.
This means that in a typed codebase, every get on a vector property need to be cast.
The following code would now be valid with Pyright.
import bpy
from mathutils import Vector
o = bpy.context.active_object
o.location = (0, 4, 5)
v: Vector = o.location
This is the proposed changes for Object.location
.
All other vector properties could use the same typing.
class Object:
# Old
# location: typing.Union[typing.List[float], typing.Tuple[float, float, float], "mathutils.Vector"]
# New
@property
def location(self) -> "mathutils.Vector":
...
@location.setter
def location(self, value: typing.Union[typing.List[float], typing.Tuple[float, float, float], "mathutils.Vector"]) -> None:
...
In the current state, Mypy raise an error for the getter, with this change it would raise an error on the setter because of its limited support for setters.
In the current state, Pyright raise an error for the getter, with this change it would not raise any error.
I think the input type should be mathutils.Vector | typing.Sequence[float]
—perhaps this could be defined as a type alias, thereby giving it a nice clean name?
object.location = range(1, 4)
presently raises a false error because range
is a Sequence
that is neither list
nor tuple
. Sequence[float]
on its own is insufficient as mathutils.Vector
is missing several Sequence
methods.
The same should probably be done for Euler
, Quaternion
and Matrix
.
@JonathanPlasse
Tweaking each attribute is a bit costly for the generation.
Is it possible to absorb the change in mathutils.Vector
or bpy.types.bpy_prop_array
?
I have to check, but I think it should be possible to use descriptors to absorb the change in mathutils.Vector
.
Here is the version using descriptor.
This absorbs the change in mathutils.Vector
.
The only inconvenient is that when assigning a tuple to Vector
, any size of float tuple is accepted, but this is already the case when assigning a Vector
of different size.
class Vector:
def __get__(self, instance, owner) -> "Vector": ...
def __set__(self, instance, value: "Vector | list[float] | tuple[float, ...]") -> None: ...
class Object:
# Old
# location: typing.Union[typing.List[float], typing.Tuple[float, float, float], "mathutils.Vector"]
# New
location: Vector
# The following code is valid with both Pyright and MyPy
o = Object()
o.location = (0, 4, 5)
v: Vector = o.location
That looks like a promising solution! 😄
I wonder if it would be worth defining a Protocol for Vector-like input? o.location = range(3)
and o.location = np.array((0, 4, 5))
should also type check okay.
any size of float tuple is accepted
Sequence length checking is not within the scope of a type checker, so this is not an issue. 👍
Using Iterable[float]
should work.
That looks like a promising solution! 😄
I wonder if it would be worth defining a Protocol for Vector-like input?
o.location = range(3)
ando.location = np.array((0, 4, 5))
should also type check okay.any size of float tuple is accepted Sequence length checking is not within the scope of a type checker, so this is not an issue. 👍
I will try implementing this.
Using
Iterable[float]
should work.
Ah, but (n for n in range(3))
is an Iterable
that should not type check okay—__len__
is required. 😉
Sequence[float]
then.
Using
Iterable[float]
should work.Ah, but
(n for n in range(3))
is anIterable
that should not type check okay—__len__
is required. 😉
Sequence[float]
then.
Ah, but np.array
is not a Sequence
😢
Perhaps I will dive into the source code and work out exactly what methods are required—__len__
and __getitem__
for sure.
So a protocol with __len__
and __getitem__
should be enough.
This protocol would only be used as allowed type when setting a property.
Related issue with other mathutils types: Color, Euler, Quaternion, Matrix.
from mathutils import Matrix, Vector, Euler
import bpy
obj: bpy.types.Object = None
m = obj.matrix_world
v = Vector()
# Operator "@" not supported for types "list[list[float]] | tuple[tuple[float, float, float, float], tuple[float, float, float, float], tuple[float, float, float, float], tuple[float, float, float, float]] | Matrix" and "Vector"
# Operator "@" not supported for types "list[list[float]]" and "Vector"
# Operator "@" not supported for types "tuple[tuple[float, float, float, float], tuple[float, float, float, float], tuple[float, float, float, float], tuple[float, float, float, float]]" and "Vector"
new_v = m @ v
quat = obj.rotation_quaternion
# Operator "@" not supported for types "list[float] | tuple[float, float, float, float] | Quaternion" and "Vector"
# Operator "@" not supported for types "list[float]" and "Vector"
# Operator "@" not supported for types "tuple[float, float, float, float]" and "Vector"
new_v = quat @ v
# Cannot access attribute "rotate" for class "list[float]"
# Cannot access attribute "rotate" for class "tuple[float, float, float]"
obj.rotation_euler.rotate(Euler())
mat: bpy.types.Material = None
# Cannot access attribute "hsv" for class "list[float]"
# Cannot access attribute "hsv" for class "tuple[float, float, float]"
mat.specular_color.hsv
@JonathanPlasse @Road-hog123
Thank you for diving into the solution!
BTW, do you want to contribute this issue? Fortunately, this can be fixed by tweaking mod file. https://github.com/nutti/fake-bpy-module/blob/master/src/mods/common/analyzer/append/mathutils.mod.rst?plain=1
Perhaps, we may need to modify data_type_refiner.py
.
I would like to work on it.
@JonathanPlasse
Thanks. Sure, go ahead.