nutti / fake-bpy-module

Fake Blender Python API module collection for the code completion.
MIT License
1.37k stars 97 forks source link

Improve Object.location typing and other vector properties #158

Closed JonathanPlasse closed 4 months ago

JonathanPlasse commented 9 months ago

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.

Road-hog123 commented 8 months ago

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.

nutti commented 4 months ago

@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?

JonathanPlasse commented 4 months ago

I have to check, but I think it should be possible to use descriptors to absorb the change in mathutils.Vector.

JonathanPlasse commented 4 months ago

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
Road-hog123 commented 4 months ago

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. 👍

JonathanPlasse commented 4 months ago

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) 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. 👍

JonathanPlasse commented 4 months ago

I will try implementing this.

Road-hog123 commented 4 months ago

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. 😉

JonathanPlasse commented 4 months ago

Sequence[float] then.

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. 😉

Road-hog123 commented 4 months ago

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.

JonathanPlasse commented 4 months ago

So a protocol with __len__ and __getitem__ should be enough. This protocol would only be used as allowed type when setting a property.

Andrej730 commented 4 months ago

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
nutti commented 4 months ago

@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.

JonathanPlasse commented 4 months ago

I would like to work on it.

nutti commented 4 months ago

@JonathanPlasse

Thanks. Sure, go ahead.