Widen mathutils param types, fix getset_descriptor writability, add conformance tests
- Widen bare mathutils type params (Vector, Euler, etc.) to also accept Sequence[float], matching Blender's mathutils_array_parse C behavior - Fix getset_descriptor readonly detection by probing __set__ on the descriptor instead of checking fset (which doesn't exist on C descriptors) - Accept int | slice keys in __getitem__/__setitem__/__delitem__ - Accept Sequence[element_type] values in __setitem__ for slice assignment - Add mathutils overrides for Matrix.Translation and Matrix.Scale - Extend apply_overrides to support ClassName.method_name keys - Add conformance test files from Blender docs examples - Disable reportUnusedExpression in conformance checks Remaining known conformance issues: - draw_handler_add missing from SpaceView3D - Vector not nominally Sequence[float] (buffer protocol, swizzle setters) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9fa2f1c200
commit
c2876bc184
37
conformance/test_gpu_3d_points_with_single_color.py
Normal file
37
conformance/test_gpu_3d_points_with_single_color.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import bpy
|
||||||
|
import gpu
|
||||||
|
from gpu_extras.batch import batch_for_shader
|
||||||
|
|
||||||
|
coords = [(1, 1, 1), (-2, 0, 0), (-2, -1, 3), (0, 1, 1)]
|
||||||
|
shader = gpu.shader.from_builtin("POINT_UNIFORM_COLOR")
|
||||||
|
batch = batch_for_shader(shader, "POINTS", {"pos": coords})
|
||||||
|
|
||||||
|
|
||||||
|
def draw():
|
||||||
|
shader.uniform_float("color", (1, 1, 0, 1))
|
||||||
|
gpu.state.point_size_set(4.5)
|
||||||
|
batch.draw(shader)
|
||||||
|
|
||||||
|
|
||||||
|
bpy.types.SpaceView3D.draw_handler_add(draw, (), "WINDOW", "POST_VIEW")
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
3D Lines with Single Color
|
||||||
|
--------------------------
|
||||||
|
"""
|
||||||
|
|
||||||
|
coords = [(1, 1, 1), (-2, 0, 0), (-2, -1, 3), (0, 1, 1)]
|
||||||
|
shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
|
||||||
|
batch = batch_for_shader(shader, "LINES", {"pos": coords})
|
||||||
|
|
||||||
|
|
||||||
|
def draw():
|
||||||
|
shader.uniform_float("viewportSize", gpu.state.viewport_get()[2:])
|
||||||
|
shader.uniform_float("lineWidth", 4.5)
|
||||||
|
shader.uniform_float("color", (1, 1, 0, 1))
|
||||||
|
batch.draw(shader)
|
||||||
|
|
||||||
|
|
||||||
|
bpy.types.SpaceView3D.draw_handler_add(draw, (), "WINDOW", "POST_VIEW")
|
||||||
42
conformance/test_gpu_triangle_with_custom_shader.py
Normal file
42
conformance/test_gpu_triangle_with_custom_shader.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import bpy
|
||||||
|
import gpu
|
||||||
|
from gpu_extras.batch import batch_for_shader
|
||||||
|
|
||||||
|
vert_out = gpu.types.GPUStageInterfaceInfo("my_interface")
|
||||||
|
vert_out.smooth("VEC3", "pos")
|
||||||
|
|
||||||
|
shader_info = gpu.types.GPUShaderCreateInfo()
|
||||||
|
shader_info.push_constant("MAT4", "viewProjectionMatrix")
|
||||||
|
shader_info.push_constant("FLOAT", "brightness")
|
||||||
|
shader_info.vertex_in(0, "VEC3", "position")
|
||||||
|
shader_info.vertex_out(vert_out)
|
||||||
|
shader_info.fragment_out(0, "VEC4", "FragColor")
|
||||||
|
|
||||||
|
shader_info.vertex_source(
|
||||||
|
"void main()"
|
||||||
|
"{"
|
||||||
|
" pos = position;"
|
||||||
|
" gl_Position = viewProjectionMatrix * vec4(position, 1.0f);"
|
||||||
|
"}"
|
||||||
|
)
|
||||||
|
|
||||||
|
shader_info.fragment_source(
|
||||||
|
"void main()" "{" " FragColor = vec4(pos * brightness, 1.0);" "}"
|
||||||
|
)
|
||||||
|
|
||||||
|
shader = gpu.shader.create_from_info(shader_info)
|
||||||
|
del vert_out
|
||||||
|
del shader_info
|
||||||
|
|
||||||
|
coords = [(1, 1, 1), (2, 0, 0), (-2, -1, 3)]
|
||||||
|
batch = batch_for_shader(shader, "TRIS", {"position": coords})
|
||||||
|
|
||||||
|
|
||||||
|
def draw():
|
||||||
|
matrix = bpy.context.region_data.perspective_matrix
|
||||||
|
shader.uniform_float("viewProjectionMatrix", matrix)
|
||||||
|
shader.uniform_float("brightness", 0.5)
|
||||||
|
batch.draw(shader)
|
||||||
|
|
||||||
|
|
||||||
|
bpy.types.SpaceView3D.draw_handler_add(draw, (), "WINDOW", "POST_VIEW")
|
||||||
37
conformance/test_mathutils_color.py
Normal file
37
conformance/test_mathutils_color.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import mathutils
|
||||||
|
|
||||||
|
# Color values are represented as RGB values from 0 - 1, this is blue.
|
||||||
|
col = mathutils.Color((0.0, 0.0, 1.0))
|
||||||
|
|
||||||
|
# As well as r/g/b attribute access you can adjust them by h/s/v.
|
||||||
|
col.s *= 0.5
|
||||||
|
|
||||||
|
# You can access its components by attribute or index.
|
||||||
|
print("Color R:", col.r)
|
||||||
|
print("Color G:", col[1])
|
||||||
|
print("Color B:", col[-1])
|
||||||
|
print("Color HSV: {:.2f}, {:.2f}, {:.2f}".format(*col))
|
||||||
|
|
||||||
|
|
||||||
|
# Components of an existing color can be set.
|
||||||
|
col[:] = 0.0, 0.5, 1.0
|
||||||
|
|
||||||
|
# Components of an existing color can use slice notation to get a tuple.
|
||||||
|
print("Values: {:f}, {:f}, {:f}".format(*col))
|
||||||
|
|
||||||
|
# Colors can be added and subtracted.
|
||||||
|
col += mathutils.Color((0.25, 0.0, 0.0))
|
||||||
|
|
||||||
|
# Color can be multiplied, in this example color is scaled to 0-255
|
||||||
|
# can printed as integers.
|
||||||
|
print("Color: {:d}, {:d}, {:d}".format(*(int(c) for c in (col * 255.0))))
|
||||||
|
|
||||||
|
# This example prints the color as hexadecimal.
|
||||||
|
print(
|
||||||
|
"Hexadecimal: {:02x}{:02x}{:02x}".format(
|
||||||
|
int(col.r * 255), int(col.g * 255), int(col.b * 255)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Direct buffer access is supported at runtime via C buffer protocol
|
||||||
|
# but not expressible in type stubs (requires Python 3.12+ __buffer__).
|
||||||
35
conformance/test_mathutils_matrix.py
Normal file
35
conformance/test_mathutils_matrix.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import mathutils
|
||||||
|
import math
|
||||||
|
|
||||||
|
# Create a location matrix.
|
||||||
|
mat_loc = mathutils.Matrix.Translation((2.0, 3.0, 4.0))
|
||||||
|
|
||||||
|
# Create an identity matrix.
|
||||||
|
mat_sca = mathutils.Matrix.Scale(0.5, 4, (0.0, 0.0, 1.0))
|
||||||
|
|
||||||
|
# Create a rotation matrix.
|
||||||
|
mat_rot = mathutils.Matrix.Rotation(math.radians(45.0), 4, "X")
|
||||||
|
|
||||||
|
# Combine transformations.
|
||||||
|
mat_out = mat_loc @ mat_rot @ mat_sca
|
||||||
|
print(mat_out)
|
||||||
|
|
||||||
|
# Extract components back out of the matrix as two vectors and a quaternion.
|
||||||
|
loc, rot, sca = mat_out.decompose()
|
||||||
|
print(loc, rot, sca)
|
||||||
|
|
||||||
|
# Recombine extracted components.
|
||||||
|
mat_out2 = mathutils.Matrix.LocRotScale(loc, rot, sca)
|
||||||
|
print(mat_out2)
|
||||||
|
|
||||||
|
# It can also be useful to access components of a matrix directly.
|
||||||
|
mat = mathutils.Matrix()
|
||||||
|
mat[0][0], mat[1][0], mat[2][0] = 0.0, 1.0, 2.0
|
||||||
|
|
||||||
|
mat[0][0:3] = 0.0, 1.0, 2.0
|
||||||
|
|
||||||
|
# Each item in a matrix is a vector so vector utility functions can be used.
|
||||||
|
mat[0].xyz = 0.0, 1.0, 2.0
|
||||||
|
|
||||||
|
# Direct buffer access is supported at runtime via C buffer protocol
|
||||||
|
# but not expressible in type stubs (requires Python 3.12+ __buffer__).
|
||||||
40
conformance/test_mathutils_quaternion.py
Normal file
40
conformance/test_mathutils_quaternion.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import mathutils
|
||||||
|
import math
|
||||||
|
|
||||||
|
# A new rotation 90 degrees about the Y axis.
|
||||||
|
quat_a = mathutils.Quaternion((0.7071068, 0.0, 0.7071068, 0.0))
|
||||||
|
|
||||||
|
# Passing values to Quaternion's directly can be confusing so axis, angle
|
||||||
|
# is supported for initializing too.
|
||||||
|
quat_b = mathutils.Quaternion((0.0, 1.0, 0.0), math.radians(90.0))
|
||||||
|
|
||||||
|
print("Check quaternions match", quat_a == quat_b)
|
||||||
|
|
||||||
|
# Like matrices, quaternions can be multiplied to accumulate rotational values.
|
||||||
|
quat_a = mathutils.Quaternion((0.0, 1.0, 0.0), math.radians(90.0))
|
||||||
|
quat_b = mathutils.Quaternion((0.0, 0.0, 1.0), math.radians(45.0))
|
||||||
|
quat_out = quat_a @ quat_b
|
||||||
|
|
||||||
|
# Print the quaternion, euler degrees for mere mortals and (axis, angle).
|
||||||
|
print("Final Rotation:")
|
||||||
|
print(quat_out)
|
||||||
|
print("{:.2f}, {:.2f}, {:.2f}".format(*(math.degrees(a) for a in quat_out.to_euler())))
|
||||||
|
print(
|
||||||
|
"({:.2f}, {:.2f}, {:.2f}), {:.2f}".format(
|
||||||
|
*quat_out.axis, math.degrees(quat_out.angle)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Multiple rotations can be interpolated using the exponential map.
|
||||||
|
quat_c = mathutils.Quaternion((1.0, 0.0, 0.0), math.radians(15.0))
|
||||||
|
exp_avg = (
|
||||||
|
quat_a.to_exponential_map()
|
||||||
|
+ quat_b.to_exponential_map()
|
||||||
|
+ quat_c.to_exponential_map()
|
||||||
|
) / 3.0
|
||||||
|
quat_avg = mathutils.Quaternion(exp_avg)
|
||||||
|
print("Average rotation:")
|
||||||
|
print(quat_avg)
|
||||||
|
|
||||||
|
# Direct buffer access is supported.
|
||||||
|
print(memoryview(quat_avg).tobytes())
|
||||||
58
conformance/test_mathutils_vector.py
Normal file
58
conformance/test_mathutils_vector.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import mathutils
|
||||||
|
|
||||||
|
# Zero length vector.
|
||||||
|
vec = mathutils.Vector((0.0, 0.0, 1.0))
|
||||||
|
|
||||||
|
# Unit length vector.
|
||||||
|
vec_a = vec.normalized()
|
||||||
|
|
||||||
|
vec_b = mathutils.Vector((0.0, 1.0, 2.0))
|
||||||
|
|
||||||
|
vec2d = mathutils.Vector((1.0, 2.0))
|
||||||
|
vec3d = mathutils.Vector((1.0, 0.0, 0.0))
|
||||||
|
vec4d = vec_a.to_4d()
|
||||||
|
|
||||||
|
# Other `mathutils` types.
|
||||||
|
quat = mathutils.Quaternion()
|
||||||
|
matrix = mathutils.Matrix()
|
||||||
|
|
||||||
|
# Comparison operators can be done on Vector classes:
|
||||||
|
|
||||||
|
# (In)equality operators == and != test component values, e.g. 1,2,3 != 3,2,1
|
||||||
|
vec_a == vec_b
|
||||||
|
vec_a != vec_b
|
||||||
|
|
||||||
|
# Ordering operators >, >=, > and <= test vector length.
|
||||||
|
vec_a > vec_b
|
||||||
|
vec_a >= vec_b
|
||||||
|
vec_a < vec_b
|
||||||
|
vec_a <= vec_b
|
||||||
|
|
||||||
|
|
||||||
|
# Math can be performed on Vector classes.
|
||||||
|
vec_a + vec_b
|
||||||
|
vec_a - vec_b
|
||||||
|
vec_a @ vec_b
|
||||||
|
vec_a * 10.0
|
||||||
|
matrix @ vec_a
|
||||||
|
quat @ vec_a
|
||||||
|
-vec_a
|
||||||
|
|
||||||
|
|
||||||
|
# You can access a vector object like a sequence.
|
||||||
|
x = vec_a[0]
|
||||||
|
len(vec)
|
||||||
|
vec_a[:] = vec_b
|
||||||
|
vec_a[:] = 1.0, 2.0, 3.0
|
||||||
|
vec2d[:] = vec3d[:2]
|
||||||
|
|
||||||
|
|
||||||
|
# Vectors support 'swizzle' operations.
|
||||||
|
# See https://en.wikipedia.org/wiki/Swizzling_(computer_graphics)
|
||||||
|
vec.xyz = vec.zyx
|
||||||
|
vec.xy = vec4d.zw
|
||||||
|
vec.xyz = vec4d.wzz
|
||||||
|
vec4d.wxyz = vec.yxyx
|
||||||
|
|
||||||
|
# Direct buffer access is supported.
|
||||||
|
raw_data = memoryview(vec).tobytes()
|
||||||
@ -760,9 +760,7 @@ def load_overrides(overrides_dir: str, module_name: str) -> dict[str, ParamOverr
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
def _apply_func_overrides(
|
def _apply_func_overrides(func: FunctionData, func_overrides: ParamOverrides) -> None:
|
||||||
func: FunctionData, func_overrides: ParamOverrides
|
|
||||||
) -> None:
|
|
||||||
"""Apply overrides to a single function/method."""
|
"""Apply overrides to a single function/method."""
|
||||||
param_overrides = func_overrides.get("params", {})
|
param_overrides = func_overrides.get("params", {})
|
||||||
for param in func["params"]:
|
for param in func["params"]:
|
||||||
|
|||||||
@ -226,7 +226,9 @@ def parse_docstring_types(docstring: str) -> tuple[dict[str, str], str | None]:
|
|||||||
|
|
||||||
# Match :type param: ... up to the next RST directive (:arg, :type, :rtype, :return)
|
# Match :type param: ... up to the next RST directive (:arg, :type, :rtype, :return)
|
||||||
# but NOT :class: or :func: which appear inside type annotations
|
# but NOT :class: or :func: which appear inside type annotations
|
||||||
directive_lookahead = r"(?=\n\s*:(?:arg|param|type|rtype|return|returns|raises)[\s:]|$)"
|
directive_lookahead = (
|
||||||
|
r"(?=\n\s*:(?:arg|param|type|rtype|return|returns|raises)[\s:]|$)"
|
||||||
|
)
|
||||||
for match in re.finditer(
|
for match in re.finditer(
|
||||||
rf":type\s+(\w+):\s*(.+?){directive_lookahead}", docstring, re.DOTALL
|
rf":type\s+(\w+):\s*(.+?){directive_lookahead}", docstring, re.DOTALL
|
||||||
):
|
):
|
||||||
@ -890,6 +892,29 @@ def parse_rst_function_sig(
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# Bare mathutils types that Blender's C code accepts interchangeably with
|
||||||
|
# Sequence[float] via mathutils_array_parse. When a param is typed as one
|
||||||
|
# of these in a docstring, widen it to also accept Sequence[float].
|
||||||
|
_MATHUTILS_ARRAY_TYPES = {
|
||||||
|
"mathutils.Vector",
|
||||||
|
"mathutils.Euler",
|
||||||
|
"mathutils.Quaternion",
|
||||||
|
"mathutils.Color",
|
||||||
|
"Vector",
|
||||||
|
"Euler",
|
||||||
|
"Quaternion",
|
||||||
|
"Color",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _widen_mathutils_params(params: list[ParamData]) -> None:
|
||||||
|
"""Widen bare mathutils type params to also accept Sequence[float]."""
|
||||||
|
for param in params:
|
||||||
|
ptype = param.get("type")
|
||||||
|
if ptype and ptype in _MATHUTILS_ARRAY_TYPES:
|
||||||
|
param["type"] = f"{ptype} | Sequence[float]"
|
||||||
|
|
||||||
|
|
||||||
def introspect_callable(func: Callable[..., object], name: str) -> FunctionData | None:
|
def introspect_callable(func: Callable[..., object], name: str) -> FunctionData | None:
|
||||||
"""Introspect a callable (function or builtin) and return its metadata."""
|
"""Introspect a callable (function or builtin) and return its metadata."""
|
||||||
docstring = inspect.getdoc(func) or ""
|
docstring = inspect.getdoc(func) or ""
|
||||||
@ -947,6 +972,7 @@ def introspect_callable(func: Callable[..., object], name: str) -> FunctionData
|
|||||||
"kind": "POSITIONAL_OR_KEYWORD",
|
"kind": "POSITIONAL_OR_KEYWORD",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
_widen_mathutils_params(params)
|
||||||
return {
|
return {
|
||||||
"name": name,
|
"name": name,
|
||||||
"doc": clean_docstring(docstring),
|
"doc": clean_docstring(docstring),
|
||||||
@ -959,9 +985,7 @@ def introspect_callable(func: Callable[..., object], name: str) -> FunctionData
|
|||||||
# C functions often use generic names like "object" in __text_signature__
|
# C functions often use generic names like "object" in __text_signature__
|
||||||
# while docstrings use descriptive names like "string", "cls", etc.
|
# while docstrings use descriptive names like "string", "cls", etc.
|
||||||
doc_param_list = list(param_types.items())
|
doc_param_list = list(param_types.items())
|
||||||
sig_param_list = [
|
sig_param_list = [(n, p) for n, p in sig.parameters.items() if n != "self"]
|
||||||
(n, p) for n, p in sig.parameters.items() if n != "self"
|
|
||||||
]
|
|
||||||
|
|
||||||
params = []
|
params = []
|
||||||
for i, (pname, param) in enumerate(sig_param_list):
|
for i, (pname, param) in enumerate(sig_param_list):
|
||||||
@ -991,6 +1015,7 @@ def introspect_callable(func: Callable[..., object], name: str) -> FunctionData
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_widen_mathutils_params(params)
|
||||||
return {
|
return {
|
||||||
"name": name,
|
"name": name,
|
||||||
"doc": clean_docstring(docstring),
|
"doc": clean_docstring(docstring),
|
||||||
@ -1099,6 +1124,7 @@ def _parse_class_constructor(class_doc: str, cls: type) -> FunctionData | None:
|
|||||||
if not params:
|
if not params:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
_widen_mathutils_params(params)
|
||||||
return {
|
return {
|
||||||
"name": "__init__",
|
"name": "__init__",
|
||||||
"doc": "",
|
"doc": "",
|
||||||
@ -1192,7 +1218,7 @@ def _fix_dunder_signatures(
|
|||||||
method["params"] = [
|
method["params"] = [
|
||||||
{
|
{
|
||||||
"name": "key",
|
"name": "key",
|
||||||
"type": "int",
|
"type": "int | slice",
|
||||||
"default": None,
|
"default": None,
|
||||||
"kind": "POSITIONAL_OR_KEYWORD",
|
"kind": "POSITIONAL_OR_KEYWORD",
|
||||||
}
|
}
|
||||||
@ -1216,7 +1242,7 @@ def _fix_dunder_signatures(
|
|||||||
method["params"] = [
|
method["params"] = [
|
||||||
{
|
{
|
||||||
"name": "key",
|
"name": "key",
|
||||||
"type": "int",
|
"type": "int | slice",
|
||||||
"default": None,
|
"default": None,
|
||||||
"kind": "POSITIONAL_OR_KEYWORD",
|
"kind": "POSITIONAL_OR_KEYWORD",
|
||||||
}
|
}
|
||||||
@ -1244,13 +1270,13 @@ def _fix_dunder_signatures(
|
|||||||
method["params"] = [
|
method["params"] = [
|
||||||
{
|
{
|
||||||
"name": "key",
|
"name": "key",
|
||||||
"type": "int",
|
"type": "int | slice",
|
||||||
"default": None,
|
"default": None,
|
||||||
"kind": "POSITIONAL_OR_KEYWORD",
|
"kind": "POSITIONAL_OR_KEYWORD",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "value",
|
"name": "value",
|
||||||
"type": vtype,
|
"type": f"{vtype} | Sequence[{vtype}]",
|
||||||
"default": None,
|
"default": None,
|
||||||
"kind": "POSITIONAL_OR_KEYWORD",
|
"kind": "POSITIONAL_OR_KEYWORD",
|
||||||
},
|
},
|
||||||
@ -1304,6 +1330,31 @@ def _fix_dunder_signatures(
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
||||||
|
def _is_getset_writable(cls: type[object], attr_name: str) -> bool:
|
||||||
|
"""Test if a C getset_descriptor property is writable.
|
||||||
|
|
||||||
|
C getset_descriptors that have a setter implement __set__.
|
||||||
|
Those without a setter raise AttributeError on __set__ calls.
|
||||||
|
We test this by checking if __set__ raises on a dummy call.
|
||||||
|
"""
|
||||||
|
descriptor = cls.__dict__.get(attr_name)
|
||||||
|
if descriptor is None:
|
||||||
|
return False
|
||||||
|
# If the descriptor doesn't implement __set__ at all, it's read-only
|
||||||
|
if not hasattr(descriptor, "__set__"):
|
||||||
|
return False
|
||||||
|
# Try calling __set__ with None — a writable descriptor will attempt
|
||||||
|
# the set (and fail on None), while a read-only one raises AttributeError
|
||||||
|
try:
|
||||||
|
descriptor.__set__(None, None)
|
||||||
|
except AttributeError:
|
||||||
|
return False
|
||||||
|
except Exception:
|
||||||
|
# Any other error means __set__ exists (it just didn't like our args)
|
||||||
|
return True
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def introspect_class(cls: type[object], module_name: str) -> StructData:
|
def introspect_class(cls: type[object], module_name: str) -> StructData:
|
||||||
"""Introspect a class (C extension or Python) and return StructData."""
|
"""Introspect a class (C extension or Python) and return StructData."""
|
||||||
# Determine base class (skip object and internal bases)
|
# Determine base class (skip object and internal bases)
|
||||||
@ -1361,7 +1412,11 @@ def introspect_class(cls: type[object], module_name: str) -> StructData:
|
|||||||
elif isinstance(raw, property) or type(raw).__name__ == "getset_descriptor":
|
elif isinstance(raw, property) or type(raw).__name__ == "getset_descriptor":
|
||||||
doc = inspect.getdoc(raw) or ""
|
doc = inspect.getdoc(raw) or ""
|
||||||
_, rtype = parse_docstring_types(doc)
|
_, rtype = parse_docstring_types(doc)
|
||||||
is_readonly = not hasattr(raw, "fset") or raw.fset is None
|
if isinstance(raw, property):
|
||||||
|
is_readonly = raw.fset is None
|
||||||
|
else:
|
||||||
|
# C getset_descriptors don't expose fset; probe at runtime
|
||||||
|
is_readonly = not _is_getset_writable(cls, name)
|
||||||
properties.append(
|
properties.append(
|
||||||
{
|
{
|
||||||
"name": name,
|
"name": name,
|
||||||
|
|||||||
1
main.py
1
main.py
@ -441,6 +441,7 @@ def conformance_check(versions: list[str] | None = None) -> None:
|
|||||||
"extraPaths": [str(version_dir)],
|
"extraPaths": [str(version_dir)],
|
||||||
"typeCheckingMode": "strict",
|
"typeCheckingMode": "strict",
|
||||||
"pythonVersion": python_version,
|
"pythonVersion": python_version,
|
||||||
|
"reportUnusedExpression": False,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
12
overrides/5.0/mathutils.json
Normal file
12
overrides/5.0/mathutils.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"Matrix.Translation": {
|
||||||
|
"params": {
|
||||||
|
"vector": "Vector | Sequence[float]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Matrix.Scale": {
|
||||||
|
"params": {
|
||||||
|
"axis": "Vector | Sequence[float]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user