fake-bpy-module icon indicating copy to clipboard operation
fake-bpy-module copied to clipboard

Improve Object.location typing and other vector properties

Open JonathanPlasse opened this issue 1 year ago • 1 comments

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.

JonathanPlasse avatar Dec 17 '23 16:12 JonathanPlasse

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.

Road-hog123 avatar Feb 08 '24 20:02 Road-hog123

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

nutti avatar May 12 '24 12:05 nutti

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

JonathanPlasse avatar May 12 '24 12:05 JonathanPlasse

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

JonathanPlasse avatar May 19 '24 14:05 JonathanPlasse

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

Road-hog123 avatar May 19 '24 16:05 Road-hog123

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 avatar May 19 '24 17:05 JonathanPlasse

I will try implementing this.

JonathanPlasse avatar May 19 '24 17:05 JonathanPlasse

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 avatar May 19 '24 17:05 Road-hog123

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

JonathanPlasse avatar May 19 '24 18:05 JonathanPlasse

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.

Road-hog123 avatar May 19 '24 18:05 Road-hog123

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

JonathanPlasse avatar May 19 '24 18:05 JonathanPlasse

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

Andrej730 avatar May 24 '24 06:05 Andrej730

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

nutti avatar May 27 '24 11:05 nutti

I would like to work on it.

JonathanPlasse avatar May 27 '24 11:05 JonathanPlasse

@JonathanPlasse

Thanks. Sure, go ahead.

nutti avatar May 27 '24 11:05 nutti