mirror of
https://github.com/blender/blender
synced 2026-09-29 04:37:17 +03:00
PyAPI: step support for mathutils slicing
Extend slice get/set on Vector, Color, Euler, Quaternion, Matrix and MatrixAccess to support extended slices with arbitrary step (`obj[::2]`, `obj[::-1]`, etc.), matching Python list semantics, with the exception that resizing isn't supported. This also add support for MatrixAccess slice assignment (mat.row[i:j] = ..., mat.col[i:j] = ...) previously raised TypeError. Tests extended with checks that steps work as intended. Ref !158183
This commit is contained in:
parent
4a741dfa2e
commit
141af3d117
7 changed files with 492 additions and 329 deletions
|
|
@ -235,6 +235,21 @@ void _BaseMathObject_RaiseNotFrozenExc(const BaseMathObject *self);
|
|||
PyObject *value,
|
||||
const char *error_prefix);
|
||||
|
||||
/**
|
||||
* Returns true when a slice does *not* address every element of an `array_num`.
|
||||
*/
|
||||
[[nodiscard]] inline bool mathutils_slice_is_subset(Py_ssize_t start,
|
||||
Py_ssize_t step,
|
||||
Py_ssize_t slice_length,
|
||||
Py_ssize_t array_num)
|
||||
{
|
||||
return !((slice_length == array_num) &&
|
||||
/* All forward `[:]`. */
|
||||
((start == 0 && step == 1) ||
|
||||
/* All reverse `[::-1]`. */
|
||||
(start == array_num - 1 && step == -1)));
|
||||
}
|
||||
|
||||
/**
|
||||
* helper function that returns a Python `__hash__`.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -570,63 +570,57 @@ static int Color_ass_item(ColorObject *self, Py_ssize_t i, PyObject *value)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/** Sequence slice accessor (get): `x = object[i:j]`. */
|
||||
static PyObject *Color_slice(ColorObject *self, int begin, int end)
|
||||
/** Sequence slice accessor (get): `x = object[i:j]` / `object[i:j:step]`. */
|
||||
static PyObject *Color_slice(ColorObject *self,
|
||||
Py_ssize_t start,
|
||||
Py_ssize_t step,
|
||||
Py_ssize_t slice_length)
|
||||
{
|
||||
PyObject *tuple;
|
||||
int count;
|
||||
|
||||
if (BaseMath_ReadCallback(self) == -1) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CLAMP(begin, 0, COLOR_SIZE);
|
||||
if (end < 0) {
|
||||
end = (COLOR_SIZE + 1) + end;
|
||||
PyObject *tuple = PyTuple_New(slice_length);
|
||||
Py_ssize_t index = start;
|
||||
for (Py_ssize_t i = 0; i < slice_length; i++, index += step) {
|
||||
BLI_assert(index >= 0 && index < COLOR_SIZE);
|
||||
PyTuple_SET_ITEM(tuple, i, PyFloat_FromDouble(self->col[index]));
|
||||
}
|
||||
CLAMP(end, 0, COLOR_SIZE);
|
||||
begin = std::min(begin, end);
|
||||
|
||||
tuple = PyTuple_New(end - begin);
|
||||
for (count = begin; count < end; count++) {
|
||||
PyTuple_SET_ITEM(tuple, count - begin, PyFloat_FromDouble(self->col[count]));
|
||||
}
|
||||
|
||||
return tuple;
|
||||
}
|
||||
|
||||
/** Sequence slice accessor (set): `object[i:j] = x`. */
|
||||
static int Color_ass_slice(ColorObject *self, int begin, int end, PyObject *seq)
|
||||
/**
|
||||
* Sequence slice accessor (set): `object[i:j] = x` / `object[i:j:step] = x`.
|
||||
* Length of `seq` must equal `slice_length`
|
||||
* (Python list semantics: extended slice assignment cannot resize).
|
||||
*/
|
||||
static int Color_ass_slice(
|
||||
ColorObject *self, Py_ssize_t start, Py_ssize_t step, Py_ssize_t slice_length, PyObject *seq)
|
||||
{
|
||||
int i, size;
|
||||
float col[COLOR_SIZE];
|
||||
|
||||
if (BaseMath_ReadCallback_ForWrite(self) == -1) {
|
||||
return -1;
|
||||
/* Subset writes merge into existing values, so sync the source first. */
|
||||
if (mathutils_slice_is_subset(start, step, slice_length, COLOR_SIZE)) {
|
||||
if (BaseMath_ReadCallback_ForWrite(self) == -1) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (BaseMath_Prepare_ForWrite(self) == -1) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
CLAMP(begin, 0, COLOR_SIZE);
|
||||
if (end < 0) {
|
||||
end = (COLOR_SIZE + 1) + end;
|
||||
}
|
||||
CLAMP(end, 0, COLOR_SIZE);
|
||||
begin = std::min(begin, end);
|
||||
|
||||
if ((size = mathutils_array_parse(col, 0, COLOR_SIZE, seq, "mathutils.Color[begin:end] = []")) ==
|
||||
-1)
|
||||
if (mathutils_array_parse(
|
||||
col, slice_length, slice_length, seq, "mathutils.Color[slice] = seq") == -1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (size != (end - begin)) {
|
||||
PyErr_SetString(PyExc_ValueError,
|
||||
"color[begin:end] = []: "
|
||||
"size mismatch in slice assignment");
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < size; i++) {
|
||||
self->col[begin + i] = col[i];
|
||||
Py_ssize_t index = start;
|
||||
for (Py_ssize_t i = 0; i < slice_length; i++, index += step) {
|
||||
BLI_assert(index >= 0 && index < COLOR_SIZE);
|
||||
self->col[index] = col[i];
|
||||
}
|
||||
|
||||
(void)BaseMath_WriteCallback(self);
|
||||
|
|
@ -648,21 +642,13 @@ static PyObject *Color_subscript(ColorObject *self, PyObject *item)
|
|||
return Color_item(self, i);
|
||||
}
|
||||
if (PySlice_Check(item)) {
|
||||
Py_ssize_t start, stop, step, slicelength;
|
||||
Py_ssize_t start, stop, step, slice_length;
|
||||
|
||||
if (PySlice_GetIndicesEx(item, COLOR_SIZE, &start, &stop, &step, &slicelength) < 0) {
|
||||
if (PySlice_GetIndicesEx(item, COLOR_SIZE, &start, &stop, &step, &slice_length) < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (slicelength <= 0) {
|
||||
return PyTuple_New(0);
|
||||
}
|
||||
if (step == 1) {
|
||||
return Color_slice(self, start, stop);
|
||||
}
|
||||
|
||||
PyErr_SetString(PyExc_IndexError, "slice steps not supported with color");
|
||||
return nullptr;
|
||||
return Color_slice(self, start, step, slice_length);
|
||||
}
|
||||
|
||||
PyErr_Format(
|
||||
|
|
@ -684,18 +670,13 @@ static int Color_ass_subscript(ColorObject *self, PyObject *item, PyObject *valu
|
|||
return Color_ass_item(self, i, value);
|
||||
}
|
||||
if (PySlice_Check(item)) {
|
||||
Py_ssize_t start, stop, step, slicelength;
|
||||
Py_ssize_t start, stop, step, slice_length;
|
||||
|
||||
if (PySlice_GetIndicesEx(item, COLOR_SIZE, &start, &stop, &step, &slicelength) < 0) {
|
||||
if (PySlice_GetIndicesEx(item, COLOR_SIZE, &start, &stop, &step, &slice_length) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (step == 1) {
|
||||
return Color_ass_slice(self, start, stop, value);
|
||||
}
|
||||
|
||||
PyErr_SetString(PyExc_IndexError, "slice steps not supported with color");
|
||||
return -1;
|
||||
return Color_ass_slice(self, start, step, slice_length, value);
|
||||
}
|
||||
|
||||
PyErr_Format(
|
||||
|
|
|
|||
|
|
@ -598,63 +598,57 @@ static int Euler_ass_item(EulerObject *self, Py_ssize_t i, PyObject *value)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/** Sequence slice accessor (get): `x = object[i:j]`. */
|
||||
static PyObject *Euler_slice(EulerObject *self, int begin, int end)
|
||||
/** Sequence slice accessor (get): `x = object[i:j]` / `object[i:j:step]`. */
|
||||
static PyObject *Euler_slice(EulerObject *self,
|
||||
Py_ssize_t start,
|
||||
Py_ssize_t step,
|
||||
Py_ssize_t slice_length)
|
||||
{
|
||||
PyObject *tuple;
|
||||
int count;
|
||||
|
||||
if (BaseMath_ReadCallback(self) == -1) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CLAMP(begin, 0, EULER_SIZE);
|
||||
if (end < 0) {
|
||||
end = (EULER_SIZE + 1) + end;
|
||||
PyObject *tuple = PyTuple_New(slice_length);
|
||||
Py_ssize_t index = start;
|
||||
for (Py_ssize_t i = 0; i < slice_length; i++, index += step) {
|
||||
BLI_assert(index >= 0 && index < EULER_SIZE);
|
||||
PyTuple_SET_ITEM(tuple, i, PyFloat_FromDouble(self->eul[index]));
|
||||
}
|
||||
CLAMP(end, 0, EULER_SIZE);
|
||||
begin = std::min(begin, end);
|
||||
|
||||
tuple = PyTuple_New(end - begin);
|
||||
for (count = begin; count < end; count++) {
|
||||
PyTuple_SET_ITEM(tuple, count - begin, PyFloat_FromDouble(self->eul[count]));
|
||||
}
|
||||
|
||||
return tuple;
|
||||
}
|
||||
|
||||
/** Sequence slice accessor (set): `object[i:j] = x`. */
|
||||
static int Euler_ass_slice(EulerObject *self, int begin, int end, PyObject *seq)
|
||||
/**
|
||||
* Sequence slice accessor (set): `object[i:j] = x` / `object[i:j:step] = x`.
|
||||
* Length of `seq` must equal `slice_length`
|
||||
* (Python list semantics: extended slice assignment cannot resize).
|
||||
*/
|
||||
static int Euler_ass_slice(
|
||||
EulerObject *self, Py_ssize_t start, Py_ssize_t step, Py_ssize_t slice_length, PyObject *seq)
|
||||
{
|
||||
int i, size;
|
||||
float eul[EULER_SIZE];
|
||||
|
||||
if (BaseMath_ReadCallback_ForWrite(self) == -1) {
|
||||
return -1;
|
||||
/* Subset writes merge into existing values, so sync the source first. */
|
||||
if (mathutils_slice_is_subset(start, step, slice_length, EULER_SIZE)) {
|
||||
if (BaseMath_ReadCallback_ForWrite(self) == -1) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (BaseMath_Prepare_ForWrite(self) == -1) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
CLAMP(begin, 0, EULER_SIZE);
|
||||
if (end < 0) {
|
||||
end = (EULER_SIZE + 1) + end;
|
||||
}
|
||||
CLAMP(end, 0, EULER_SIZE);
|
||||
begin = std::min(begin, end);
|
||||
|
||||
if ((size = mathutils_array_parse(eul, 0, EULER_SIZE, seq, "mathutils.Euler[begin:end] = []")) ==
|
||||
-1)
|
||||
if (mathutils_array_parse(
|
||||
eul, slice_length, slice_length, seq, "mathutils.Euler[slice] = seq") == -1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (size != (end - begin)) {
|
||||
PyErr_SetString(PyExc_ValueError,
|
||||
"euler[begin:end] = []: "
|
||||
"size mismatch in slice assignment");
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < size; i++) {
|
||||
self->eul[begin + i] = eul[i];
|
||||
Py_ssize_t index = start;
|
||||
for (Py_ssize_t i = 0; i < slice_length; i++, index += step) {
|
||||
BLI_assert(index >= 0 && index < EULER_SIZE);
|
||||
self->eul[index] = eul[i];
|
||||
}
|
||||
|
||||
(void)BaseMath_WriteCallback(self);
|
||||
|
|
@ -676,21 +670,13 @@ static PyObject *Euler_subscript(EulerObject *self, PyObject *item)
|
|||
return Euler_item(self, i);
|
||||
}
|
||||
if (PySlice_Check(item)) {
|
||||
Py_ssize_t start, stop, step, slicelength;
|
||||
Py_ssize_t start, stop, step, slice_length;
|
||||
|
||||
if (PySlice_GetIndicesEx(item, EULER_SIZE, &start, &stop, &step, &slicelength) < 0) {
|
||||
if (PySlice_GetIndicesEx(item, EULER_SIZE, &start, &stop, &step, &slice_length) < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (slicelength <= 0) {
|
||||
return PyTuple_New(0);
|
||||
}
|
||||
if (step == 1) {
|
||||
return Euler_slice(self, start, stop);
|
||||
}
|
||||
|
||||
PyErr_SetString(PyExc_IndexError, "slice steps not supported with eulers");
|
||||
return nullptr;
|
||||
return Euler_slice(self, start, step, slice_length);
|
||||
}
|
||||
|
||||
PyErr_Format(
|
||||
|
|
@ -712,18 +698,13 @@ static int Euler_ass_subscript(EulerObject *self, PyObject *item, PyObject *valu
|
|||
return Euler_ass_item(self, i, value);
|
||||
}
|
||||
if (PySlice_Check(item)) {
|
||||
Py_ssize_t start, stop, step, slicelength;
|
||||
Py_ssize_t start, stop, step, slice_length;
|
||||
|
||||
if (PySlice_GetIndicesEx(item, EULER_SIZE, &start, &stop, &step, &slicelength) < 0) {
|
||||
if (PySlice_GetIndicesEx(item, EULER_SIZE, &start, &stop, &step, &slice_length) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (step == 1) {
|
||||
return Euler_ass_slice(self, start, stop, value);
|
||||
}
|
||||
|
||||
PyErr_SetString(PyExc_IndexError, "slice steps not supported with euler");
|
||||
return -1;
|
||||
return Euler_ass_slice(self, start, step, slice_length, value);
|
||||
}
|
||||
|
||||
PyErr_Format(
|
||||
|
|
|
|||
|
|
@ -35,7 +35,11 @@ enum eMatrixAccess_t {
|
|||
static PyObject *Matrix_copy_notest(MatrixObject *self, const float *matrix);
|
||||
static PyObject *Matrix_copy(MatrixObject *self);
|
||||
static PyObject *Matrix_deepcopy(MatrixObject *self, PyObject *args);
|
||||
static int Matrix_ass_slice(MatrixObject *self, int begin, int end, PyObject *value);
|
||||
static int Matrix_ass_slice(MatrixObject *self,
|
||||
Py_ssize_t start,
|
||||
Py_ssize_t step,
|
||||
Py_ssize_t slice_length,
|
||||
PyObject *value);
|
||||
static PyObject *matrix__apply_to_copy(PyObject *(*matrix_func)(MatrixObject *),
|
||||
MatrixObject *self);
|
||||
static PyObject *MatrixAccess_CreatePyObject(MatrixObject *matrix, const eMatrixAccess_t type);
|
||||
|
|
@ -634,7 +638,8 @@ static PyObject *Matrix_vectorcall(PyObject *type,
|
|||
/* Sane row & col size, new matrix and assign as slice. */
|
||||
PyObject *matrix = Matrix_CreatePyObject(
|
||||
nullptr, col_num, row_num, reinterpret_cast<PyTypeObject *>(type));
|
||||
if (Matrix_ass_slice(reinterpret_cast<MatrixObject *>(matrix), 0, INT_MAX, arg) == 0) {
|
||||
if (Matrix_ass_slice(reinterpret_cast<MatrixObject *>(matrix), 0, 1, row_num, arg) == 0)
|
||||
{
|
||||
return matrix;
|
||||
}
|
||||
/* matrix ok, slice assignment not */
|
||||
|
|
@ -2680,88 +2685,98 @@ static int Matrix_ass_item_col(MatrixObject *self, int col, PyObject *value)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/** Sequence slice accessor (get): `x = object[i:j]`. */
|
||||
static PyObject *Matrix_slice(MatrixObject *self, int begin, int end)
|
||||
/**
|
||||
* Sequence slice accessor (get): `x = object[i:j]` / `object[i:j:step]`.
|
||||
* \return a tuple of `Vector` row wrappers.
|
||||
*/
|
||||
static PyObject *Matrix_slice(MatrixObject *self,
|
||||
Py_ssize_t start,
|
||||
Py_ssize_t step,
|
||||
Py_ssize_t slice_length)
|
||||
{
|
||||
|
||||
PyObject *tuple;
|
||||
int count;
|
||||
|
||||
if (BaseMath_ReadCallback(self) == -1) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CLAMP(begin, 0, self->row_num);
|
||||
CLAMP(end, 0, self->row_num);
|
||||
begin = std::min(begin, end);
|
||||
|
||||
tuple = PyTuple_New(end - begin);
|
||||
for (count = begin; count < end; count++) {
|
||||
PyObject *tuple = PyTuple_New(slice_length);
|
||||
Py_ssize_t index = start;
|
||||
for (Py_ssize_t i = 0; i < slice_length; i++, index += step) {
|
||||
BLI_assert(index >= 0 && index < self->row_num);
|
||||
PyTuple_SET_ITEM(tuple,
|
||||
count - begin,
|
||||
i,
|
||||
Vector_CreatePyObject_cb(
|
||||
(PyObject *)self, self->col_num, mathutils_matrix_row_cb_index, count));
|
||||
(PyObject *)self, self->col_num, mathutils_matrix_row_cb_index, index));
|
||||
}
|
||||
|
||||
return tuple;
|
||||
}
|
||||
|
||||
/** Sequence slice accessor (set): `object[i:j] = x`. */
|
||||
static int Matrix_ass_slice(MatrixObject *self, int begin, int end, PyObject *value)
|
||||
/**
|
||||
* Sequence slice accessor (set): `object[i:j] = x` / `object[i:j:step] = x`.
|
||||
* `value` must be a sequence of `slice_length` row-sequences, each with
|
||||
* `col_num` floats (Python list semantics: extended slice cannot resize).
|
||||
*/
|
||||
static int Matrix_ass_slice(MatrixObject *self,
|
||||
Py_ssize_t start,
|
||||
Py_ssize_t step,
|
||||
Py_ssize_t slice_length,
|
||||
PyObject *value)
|
||||
{
|
||||
PyObject *value_fast;
|
||||
/* Subset writes merge into existing values, so sync the source first. */
|
||||
if (mathutils_slice_is_subset(start, step, slice_length, self->row_num)) {
|
||||
if (BaseMath_ReadCallback_ForWrite(self) == -1) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (BaseMath_Prepare_ForWrite(self) == -1) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (BaseMath_ReadCallback_ForWrite(self) == -1) {
|
||||
PyObject *value_fast = PySequence_Fast(value, "matrix[slice] = value");
|
||||
if (value_fast == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
CLAMP(begin, 0, self->row_num);
|
||||
CLAMP(end, 0, self->row_num);
|
||||
begin = std::min(begin, end);
|
||||
|
||||
/* non list/tuple cases */
|
||||
if (!(value_fast = PySequence_Fast(value, "matrix[begin:end] = value"))) {
|
||||
/* PySequence_Fast sets the error */
|
||||
return -1;
|
||||
}
|
||||
|
||||
PyObject **value_fast_items = PySequence_Fast_ITEMS(value_fast);
|
||||
const int size = end - begin;
|
||||
int row, col;
|
||||
float mat[MATRIX_MAX_DIM * MATRIX_MAX_DIM];
|
||||
float vec[4];
|
||||
|
||||
if (PySequence_Fast_GET_SIZE(value_fast) != size) {
|
||||
const Py_ssize_t value_num = PySequence_Fast_GET_SIZE(value_fast);
|
||||
if (value_num != slice_length) {
|
||||
Py_DECREF(value_fast);
|
||||
PyErr_SetString(PyExc_ValueError,
|
||||
"matrix[begin:end] = []: "
|
||||
"size mismatch in slice assignment");
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"matrix[slice] = value: sequence size is %zd, expected %zd",
|
||||
value_num,
|
||||
slice_length);
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(mat, self->matrix, self->col_num * self->row_num * sizeof(float));
|
||||
/* Stage parsed rows so a mid-sequence parse failure cannot leave the matrix
|
||||
* half-written: only commit after every row has parsed successfully. */
|
||||
BLI_assert(slice_length <= MATRIX_MAX_DIM);
|
||||
BLI_assert(self->col_num <= MATRIX_MAX_DIM);
|
||||
float staging[MATRIX_MAX_DIM][MATRIX_MAX_DIM];
|
||||
PyObject **value_fast_items = PySequence_Fast_ITEMS(value_fast);
|
||||
|
||||
/* parse sub items */
|
||||
for (row = begin; row < end; row++) {
|
||||
/* parse each sub sequence */
|
||||
PyObject *item = value_fast_items[row - begin];
|
||||
|
||||
if (mathutils_array_parse(
|
||||
vec, self->col_num, self->col_num, item, "matrix[begin:end] = value assignment") == -1)
|
||||
for (Py_ssize_t i = 0; i < slice_length; i++) {
|
||||
if (mathutils_array_parse(staging[i],
|
||||
self->col_num,
|
||||
self->col_num,
|
||||
value_fast_items[i],
|
||||
"matrix[slice] = value") == -1)
|
||||
{
|
||||
Py_DECREF(value_fast);
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (col = 0; col < self->col_num; col++) {
|
||||
mat[col * self->row_num + row] = vec[col];
|
||||
}
|
||||
}
|
||||
|
||||
Py_DECREF(value_fast);
|
||||
|
||||
/* Parsed well - now set in matrix. */
|
||||
memcpy(self->matrix, mat, self->col_num * self->row_num * sizeof(float));
|
||||
/* Commit. */
|
||||
Py_ssize_t index = start;
|
||||
for (Py_ssize_t i = 0; i < slice_length; i++, index += step) {
|
||||
BLI_assert(index >= 0 && index < self->row_num);
|
||||
for (int col = 0; col < self->col_num; col++) {
|
||||
MATRIX_ITEM(self, index, col) = staging[i][col];
|
||||
}
|
||||
}
|
||||
|
||||
(void)BaseMath_WriteCallback(self);
|
||||
return 0;
|
||||
|
|
@ -2782,21 +2797,13 @@ static PyObject *Matrix_subscript(MatrixObject *self, PyObject *item)
|
|||
return Matrix_item_row(self, i);
|
||||
}
|
||||
if (PySlice_Check(item)) {
|
||||
Py_ssize_t start, stop, step, slicelength;
|
||||
Py_ssize_t start, stop, step, slice_length;
|
||||
|
||||
if (PySlice_GetIndicesEx(item, self->row_num, &start, &stop, &step, &slicelength) < 0) {
|
||||
if (PySlice_GetIndicesEx(item, self->row_num, &start, &stop, &step, &slice_length) < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (slicelength <= 0) {
|
||||
return PyTuple_New(0);
|
||||
}
|
||||
if (step == 1) {
|
||||
return Matrix_slice(self, start, stop);
|
||||
}
|
||||
|
||||
PyErr_SetString(PyExc_IndexError, "slice steps not supported with matrices");
|
||||
return nullptr;
|
||||
return Matrix_slice(self, start, step, slice_length);
|
||||
}
|
||||
|
||||
PyErr_Format(
|
||||
|
|
@ -2818,18 +2825,13 @@ static int Matrix_ass_subscript(MatrixObject *self, PyObject *item, PyObject *va
|
|||
return Matrix_ass_item_row(self, i, value);
|
||||
}
|
||||
if (PySlice_Check(item)) {
|
||||
Py_ssize_t start, stop, step, slicelength;
|
||||
Py_ssize_t start, stop, step, slice_length;
|
||||
|
||||
if (PySlice_GetIndicesEx(item, self->row_num, &start, &stop, &step, &slicelength) < 0) {
|
||||
if (PySlice_GetIndicesEx(item, self->row_num, &start, &stop, &step, &slice_length) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (step == 1) {
|
||||
return Matrix_ass_slice(self, start, stop, value);
|
||||
}
|
||||
|
||||
PyErr_SetString(PyExc_IndexError, "slice steps not supported with matrices");
|
||||
return -1;
|
||||
return Matrix_ass_slice(self, start, step, slice_length, value);
|
||||
}
|
||||
|
||||
PyErr_Format(
|
||||
|
|
@ -3974,12 +3976,15 @@ static Py_ssize_t MatrixAccess_len(MatrixAccessObject *self)
|
|||
return (self->type == MAT_ACCESS_ROW) ? self->matrix_user->row_num : self->matrix_user->col_num;
|
||||
}
|
||||
|
||||
static PyObject *MatrixAccess_slice(MatrixAccessObject *self, Py_ssize_t begin, Py_ssize_t end)
|
||||
/**
|
||||
* Slice get: `mat.row[i:j]` / `mat.row[i:j:step]` (or `.col`).
|
||||
* \return a tuple of `Vector` row/column wrappers.
|
||||
*/
|
||||
static PyObject *MatrixAccess_slice(MatrixAccessObject *self,
|
||||
Py_ssize_t start,
|
||||
Py_ssize_t step,
|
||||
Py_ssize_t slice_length)
|
||||
{
|
||||
PyObject *tuple;
|
||||
Py_ssize_t count;
|
||||
|
||||
/* row/col access */
|
||||
MatrixObject *matrix_user = self->matrix_user;
|
||||
int matrix_access_len;
|
||||
PyObject *(*Matrix_item_new)(MatrixObject *, Py_ssize_t);
|
||||
|
|
@ -3993,21 +3998,111 @@ static PyObject *MatrixAccess_slice(MatrixAccessObject *self, Py_ssize_t begin,
|
|||
Matrix_item_new = Matrix_item_col;
|
||||
}
|
||||
|
||||
CLAMP(begin, 0, matrix_access_len);
|
||||
if (end < 0) {
|
||||
end = (matrix_access_len + 1) + end;
|
||||
PyObject *tuple = PyTuple_New(slice_length);
|
||||
Py_ssize_t index = start;
|
||||
for (Py_ssize_t i = 0; i < slice_length; i++, index += step) {
|
||||
BLI_assert(index >= 0 && index < matrix_access_len);
|
||||
PyTuple_SET_ITEM(tuple, i, Matrix_item_new(matrix_user, index));
|
||||
}
|
||||
CLAMP(end, 0, matrix_access_len);
|
||||
begin = std::min(begin, end);
|
||||
|
||||
tuple = PyTuple_New(end - begin);
|
||||
for (count = begin; count < end; count++) {
|
||||
PyTuple_SET_ITEM(tuple, count - begin, Matrix_item_new(matrix_user, count));
|
||||
}
|
||||
|
||||
return tuple;
|
||||
}
|
||||
|
||||
/**
|
||||
* Slice set: `mat.row[i:j] = (vec, ...)` / `mat.row[i:j:step] = (...)` (or `.col`).
|
||||
* Length of `value` must equal `slice_length` and each item must be a sequence
|
||||
* of `axis_size` floats (Python list semantics: extended slice cannot resize).
|
||||
*/
|
||||
static int MatrixAccess_ass_slice(MatrixAccessObject *self,
|
||||
Py_ssize_t start,
|
||||
Py_ssize_t step,
|
||||
Py_ssize_t slice_length,
|
||||
PyObject *value)
|
||||
{
|
||||
MatrixObject *matrix_user = self->matrix_user;
|
||||
int matrix_access_len;
|
||||
int axis_size;
|
||||
|
||||
if (self->type == MAT_ACCESS_ROW) {
|
||||
matrix_access_len = matrix_user->row_num;
|
||||
axis_size = matrix_user->col_num;
|
||||
}
|
||||
else { /* MAT_ACCESS_COL */
|
||||
matrix_access_len = matrix_user->col_num;
|
||||
axis_size = matrix_user->row_num;
|
||||
}
|
||||
|
||||
/* Subset writes merge into existing values, so sync the source first. */
|
||||
if (mathutils_slice_is_subset(start, step, slice_length, matrix_access_len)) {
|
||||
if (BaseMath_ReadCallback_ForWrite(matrix_user) == -1) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (BaseMath_Prepare_ForWrite(matrix_user) == -1) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
PyObject *value_fast = PySequence_Fast(value, "matrix.row/col[slice] = value");
|
||||
if (value_fast == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const Py_ssize_t value_num = PySequence_Fast_GET_SIZE(value_fast);
|
||||
if (value_num != slice_length) {
|
||||
Py_DECREF(value_fast);
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"matrix.%s[slice] = value: sequence size is %zd, expected %zd",
|
||||
(self->type == MAT_ACCESS_ROW) ? "row" : "col",
|
||||
value_num,
|
||||
slice_length);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Stage parsed rows/cols so a mid-sequence parse failure cannot leave the
|
||||
* matrix half-written: only commit after every item has parsed successfully. */
|
||||
BLI_assert(slice_length <= MATRIX_MAX_DIM);
|
||||
BLI_assert(axis_size <= MATRIX_MAX_DIM);
|
||||
float staging[MATRIX_MAX_DIM][MATRIX_MAX_DIM];
|
||||
PyObject **value_fast_items = PySequence_Fast_ITEMS(value_fast);
|
||||
|
||||
for (Py_ssize_t i = 0; i < slice_length; i++) {
|
||||
if (mathutils_array_parse(staging[i],
|
||||
axis_size,
|
||||
axis_size,
|
||||
value_fast_items[i],
|
||||
"matrix.row/col[slice] = value") == -1)
|
||||
{
|
||||
Py_DECREF(value_fast);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
Py_DECREF(value_fast);
|
||||
|
||||
/* Commit. */
|
||||
Py_ssize_t index = start;
|
||||
if (self->type == MAT_ACCESS_ROW) {
|
||||
for (Py_ssize_t i = 0; i < slice_length; i++, index += step) {
|
||||
BLI_assert(index >= 0 && index < matrix_access_len);
|
||||
for (int col = 0; col < axis_size; col++) {
|
||||
MATRIX_ITEM(matrix_user, index, col) = staging[i][col];
|
||||
}
|
||||
}
|
||||
}
|
||||
else { /* MAT_ACCESS_COL */
|
||||
for (Py_ssize_t i = 0; i < slice_length; i++, index += step) {
|
||||
BLI_assert(index >= 0 && index < matrix_access_len);
|
||||
for (int row = 0; row < axis_size; row++) {
|
||||
MATRIX_ITEM(matrix_user, row, index) = staging[i][row];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(void)BaseMath_WriteCallback(matrix_user);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static PyObject *MatrixAccess_subscript(MatrixAccessObject *self, PyObject *item)
|
||||
{
|
||||
MatrixObject *matrix_user = self->matrix_user;
|
||||
|
|
@ -4031,22 +4126,15 @@ static PyObject *MatrixAccess_subscript(MatrixAccessObject *self, PyObject *item
|
|||
return Matrix_item_col(matrix_user, i);
|
||||
}
|
||||
if (PySlice_Check(item)) {
|
||||
Py_ssize_t start, stop, step, slicelength;
|
||||
Py_ssize_t start, stop, step, slice_length;
|
||||
|
||||
if (PySlice_GetIndicesEx(item, MatrixAccess_len(self), &start, &stop, &step, &slicelength) < 0)
|
||||
if (PySlice_GetIndicesEx(item, MatrixAccess_len(self), &start, &stop, &step, &slice_length) <
|
||||
0)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (slicelength <= 0) {
|
||||
return PyTuple_New(0);
|
||||
}
|
||||
if (step == 1) {
|
||||
return MatrixAccess_slice(self, start, stop);
|
||||
}
|
||||
|
||||
PyErr_SetString(PyExc_IndexError, "slice steps not supported with matrix accessors");
|
||||
return nullptr;
|
||||
return MatrixAccess_slice(self, start, step, slice_length);
|
||||
}
|
||||
|
||||
PyErr_Format(
|
||||
|
|
@ -4076,7 +4164,17 @@ static int MatrixAccess_ass_subscript(MatrixAccessObject *self, PyObject *item,
|
|||
}
|
||||
return Matrix_ass_item_col(matrix_user, i, value);
|
||||
}
|
||||
/* TODO: slice. */
|
||||
if (PySlice_Check(item)) {
|
||||
Py_ssize_t start, stop, step, slice_length;
|
||||
|
||||
if (PySlice_GetIndicesEx(item, MatrixAccess_len(self), &start, &stop, &step, &slice_length) <
|
||||
0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return MatrixAccess_ass_slice(self, start, step, slice_length, value);
|
||||
}
|
||||
|
||||
PyErr_Format(
|
||||
PyExc_TypeError, "matrix indices must be integers, not %.200s", Py_TYPE(item)->tp_name);
|
||||
|
|
@ -4088,7 +4186,7 @@ static PyObject *MatrixAccess_iter(MatrixAccessObject *self)
|
|||
/* Try get values from a collection. */
|
||||
PyObject *ret;
|
||||
PyObject *iter = nullptr;
|
||||
ret = MatrixAccess_slice(self, 0, MATRIX_MAX_DIM);
|
||||
ret = MatrixAccess_slice(self, 0, 1, MatrixAccess_len(self));
|
||||
|
||||
/* We know this is a tuple so no need to #PyIter_Check
|
||||
* otherwise it could be nullptr (unlikely) if conversion failed. */
|
||||
|
|
|
|||
|
|
@ -1084,64 +1084,60 @@ static int Quaternion_ass_item(QuaternionObject *self, Py_ssize_t i, PyObject *o
|
|||
return 0;
|
||||
}
|
||||
|
||||
/** Sequence slice accessor (get): `x = object[i:j]`. */
|
||||
static PyObject *Quaternion_slice(QuaternionObject *self, int begin, int end)
|
||||
/** Sequence slice accessor (get): `x = object[i:j]` / `object[i:j:step]`. */
|
||||
static PyObject *Quaternion_slice(QuaternionObject *self,
|
||||
Py_ssize_t start,
|
||||
Py_ssize_t step,
|
||||
Py_ssize_t slice_length)
|
||||
{
|
||||
PyObject *tuple;
|
||||
int count;
|
||||
|
||||
if (BaseMath_ReadCallback(self) == -1) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CLAMP(begin, 0, QUAT_SIZE);
|
||||
if (end < 0) {
|
||||
end = (QUAT_SIZE + 1) + end;
|
||||
PyObject *tuple = PyTuple_New(slice_length);
|
||||
Py_ssize_t index = start;
|
||||
for (Py_ssize_t i = 0; i < slice_length; i++, index += step) {
|
||||
BLI_assert(index >= 0 && index < QUAT_SIZE);
|
||||
PyTuple_SET_ITEM(tuple, i, PyFloat_FromDouble(self->quat[index]));
|
||||
}
|
||||
CLAMP(end, 0, QUAT_SIZE);
|
||||
begin = std::min(begin, end);
|
||||
|
||||
tuple = PyTuple_New(end - begin);
|
||||
for (count = begin; count < end; count++) {
|
||||
PyTuple_SET_ITEM(tuple, count - begin, PyFloat_FromDouble(self->quat[count]));
|
||||
}
|
||||
|
||||
return tuple;
|
||||
}
|
||||
|
||||
/** Sequence slice accessor (set): `object[i:j] = x`. */
|
||||
static int Quaternion_ass_slice(QuaternionObject *self, int begin, int end, PyObject *seq)
|
||||
/**
|
||||
* Sequence slice accessor (set): `object[i:j] = x` / `object[i:j:step] = x`.
|
||||
* Length of `seq` must equal `slice_length`
|
||||
* (Python list semantics: extended slice assignment cannot resize).
|
||||
*/
|
||||
static int Quaternion_ass_slice(QuaternionObject *self,
|
||||
Py_ssize_t start,
|
||||
Py_ssize_t step,
|
||||
Py_ssize_t slice_length,
|
||||
PyObject *seq)
|
||||
{
|
||||
int i, size;
|
||||
float quat[QUAT_SIZE];
|
||||
|
||||
if (BaseMath_ReadCallback_ForWrite(self) == -1) {
|
||||
return -1;
|
||||
/* Subset writes merge into existing values, so sync the source first. */
|
||||
if (mathutils_slice_is_subset(start, step, slice_length, QUAT_SIZE)) {
|
||||
if (BaseMath_ReadCallback_ForWrite(self) == -1) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (BaseMath_Prepare_ForWrite(self) == -1) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
CLAMP(begin, 0, QUAT_SIZE);
|
||||
if (end < 0) {
|
||||
end = (QUAT_SIZE + 1) + end;
|
||||
}
|
||||
CLAMP(end, 0, QUAT_SIZE);
|
||||
begin = std::min(begin, end);
|
||||
|
||||
if ((size = mathutils_array_parse(
|
||||
quat, 0, QUAT_SIZE, seq, "mathutils.Quaternion[begin:end] = []")) == -1)
|
||||
if (mathutils_array_parse(
|
||||
quat, slice_length, slice_length, seq, "mathutils.Quaternion[slice] = seq") == -1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (size != (end - begin)) {
|
||||
PyErr_SetString(PyExc_ValueError,
|
||||
"quaternion[begin:end] = []: "
|
||||
"size mismatch in slice assignment");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Parsed well, now set in vector. */
|
||||
for (i = 0; i < size; i++) {
|
||||
self->quat[begin + i] = quat[i];
|
||||
Py_ssize_t index = start;
|
||||
for (Py_ssize_t i = 0; i < slice_length; i++, index += step) {
|
||||
BLI_assert(index >= 0 && index < QUAT_SIZE);
|
||||
self->quat[index] = quat[i];
|
||||
}
|
||||
|
||||
(void)BaseMath_WriteCallback(self);
|
||||
|
|
@ -1163,21 +1159,13 @@ static PyObject *Quaternion_subscript(QuaternionObject *self, PyObject *item)
|
|||
return Quaternion_item(self, i);
|
||||
}
|
||||
if (PySlice_Check(item)) {
|
||||
Py_ssize_t start, stop, step, slicelength;
|
||||
Py_ssize_t start, stop, step, slice_length;
|
||||
|
||||
if (PySlice_GetIndicesEx(item, QUAT_SIZE, &start, &stop, &step, &slicelength) < 0) {
|
||||
if (PySlice_GetIndicesEx(item, QUAT_SIZE, &start, &stop, &step, &slice_length) < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (slicelength <= 0) {
|
||||
return PyTuple_New(0);
|
||||
}
|
||||
if (step == 1) {
|
||||
return Quaternion_slice(self, start, stop);
|
||||
}
|
||||
|
||||
PyErr_SetString(PyExc_IndexError, "slice steps not supported with quaternions");
|
||||
return nullptr;
|
||||
return Quaternion_slice(self, start, step, slice_length);
|
||||
}
|
||||
|
||||
PyErr_Format(
|
||||
|
|
@ -1199,18 +1187,13 @@ static int Quaternion_ass_subscript(QuaternionObject *self, PyObject *item, PyOb
|
|||
return Quaternion_ass_item(self, i, value);
|
||||
}
|
||||
if (PySlice_Check(item)) {
|
||||
Py_ssize_t start, stop, step, slicelength;
|
||||
Py_ssize_t start, stop, step, slice_length;
|
||||
|
||||
if (PySlice_GetIndicesEx(item, QUAT_SIZE, &start, &stop, &step, &slicelength) < 0) {
|
||||
if (PySlice_GetIndicesEx(item, QUAT_SIZE, &start, &stop, &step, &slice_length) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (step == 1) {
|
||||
return Quaternion_ass_slice(self, start, stop, value);
|
||||
}
|
||||
|
||||
PyErr_SetString(PyExc_IndexError, "slice steps not supported with quaternion");
|
||||
return -1;
|
||||
return Quaternion_ass_slice(self, start, step, slice_length, value);
|
||||
}
|
||||
|
||||
PyErr_Format(
|
||||
|
|
|
|||
|
|
@ -1901,48 +1901,49 @@ static int Vector_ass_item(VectorObject *self, Py_ssize_t i, PyObject *value)
|
|||
return vector_ass_item_internal(self, i, value, false);
|
||||
}
|
||||
|
||||
/** Sequence slice accessor (get): `x = object[i:j]`. */
|
||||
static PyObject *Vector_slice(VectorObject *self, int begin, int end)
|
||||
/** Sequence slice accessor (get): `x = object[i:j]` / `object[i:j:step]`. */
|
||||
static PyObject *Vector_slice(VectorObject *self,
|
||||
Py_ssize_t start,
|
||||
Py_ssize_t step,
|
||||
Py_ssize_t slice_length)
|
||||
{
|
||||
PyObject *tuple;
|
||||
int count;
|
||||
|
||||
if (BaseMath_ReadCallback(self) == -1) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CLAMP(begin, 0, self->vec_num);
|
||||
if (end < 0) {
|
||||
end = self->vec_num + end + 1;
|
||||
PyObject *tuple = PyTuple_New(slice_length);
|
||||
Py_ssize_t index = start;
|
||||
for (Py_ssize_t i = 0; i < slice_length; i++, index += step) {
|
||||
BLI_assert(index >= 0 && index < self->vec_num);
|
||||
PyTuple_SET_ITEM(tuple, i, PyFloat_FromDouble(self->vec[index]));
|
||||
}
|
||||
CLAMP(end, 0, self->vec_num);
|
||||
begin = std::min(begin, end);
|
||||
|
||||
tuple = PyTuple_New(end - begin);
|
||||
for (count = begin; count < end; count++) {
|
||||
PyTuple_SET_ITEM(tuple, count - begin, PyFloat_FromDouble(self->vec[count]));
|
||||
}
|
||||
|
||||
return tuple;
|
||||
}
|
||||
|
||||
/** Sequence slice accessor (set): `object[i:j] = x`. */
|
||||
static int Vector_ass_slice(VectorObject *self, int begin, int end, PyObject *seq)
|
||||
/**
|
||||
* Sequence slice accessor (set): `object[i:j] = x` / `object[i:j:step] = x`.
|
||||
* Length of `seq` must equal `slice_length`
|
||||
* (Python list semantics: extended slice assignment cannot resize).
|
||||
*/
|
||||
static int Vector_ass_slice(
|
||||
VectorObject *self, Py_ssize_t start, Py_ssize_t step, Py_ssize_t slice_length, PyObject *seq)
|
||||
{
|
||||
int vec_num = 0;
|
||||
float *vec = nullptr;
|
||||
|
||||
if (BaseMath_ReadCallback_ForWrite(self) == -1) {
|
||||
return -1;
|
||||
/* Subset writes merge into existing values, so sync the source first. */
|
||||
if (mathutils_slice_is_subset(start, step, slice_length, self->vec_num)) {
|
||||
if (BaseMath_ReadCallback_ForWrite(self) == -1) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (BaseMath_Prepare_ForWrite(self) == -1) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
CLAMP(begin, 0, self->vec_num);
|
||||
CLAMP(end, 0, self->vec_num);
|
||||
begin = std::min(begin, end);
|
||||
|
||||
vec_num = (end - begin);
|
||||
const int parsed_size = mathutils_array_parse_alloc(
|
||||
&vec, vec_num, seq, "vector[begin:end] = [...]");
|
||||
&vec, int(slice_length), seq, "vector[slice] = seq");
|
||||
if (parsed_size == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -1951,17 +1952,20 @@ static int Vector_ass_slice(VectorObject *self, int begin, int end, PyObject *se
|
|||
* Rely on explicit use of the `.resize()` method because (unlike lists),
|
||||
* these are more typically fixed size collections.
|
||||
* Resizing is more likely to be a mistake as it isn't a common operation. */
|
||||
if (parsed_size != vec_num) {
|
||||
if (parsed_size != slice_length) {
|
||||
PyMem_Free(vec);
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"vector[begin:end] = [...]: sequence size is %d, expected %d",
|
||||
"vector[slice] = seq: sequence size is %d, expected %d",
|
||||
parsed_size,
|
||||
vec_num);
|
||||
int(slice_length));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Parsed well - now set in vector. */
|
||||
memcpy(self->vec + begin, vec, vec_num * sizeof(float));
|
||||
Py_ssize_t index = start;
|
||||
for (Py_ssize_t i = 0; i < slice_length; i++, index += step) {
|
||||
BLI_assert(index >= 0 && index < self->vec_num);
|
||||
self->vec[index] = vec[i];
|
||||
}
|
||||
|
||||
PyMem_Free(vec);
|
||||
|
||||
|
|
@ -1987,21 +1991,13 @@ static PyObject *Vector_subscript(VectorObject *self, PyObject *item)
|
|||
return Vector_item(self, i);
|
||||
}
|
||||
if (PySlice_Check(item)) {
|
||||
Py_ssize_t start, stop, step, slicelength;
|
||||
Py_ssize_t start, stop, step, slice_length;
|
||||
|
||||
if (PySlice_GetIndicesEx(item, self->vec_num, &start, &stop, &step, &slicelength) < 0) {
|
||||
if (PySlice_GetIndicesEx(item, self->vec_num, &start, &stop, &step, &slice_length) < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (slicelength <= 0) {
|
||||
return PyTuple_New(0);
|
||||
}
|
||||
if (step == 1) {
|
||||
return Vector_slice(self, start, stop);
|
||||
}
|
||||
|
||||
PyErr_SetString(PyExc_IndexError, "slice steps not supported with vectors");
|
||||
return nullptr;
|
||||
return Vector_slice(self, start, step, slice_length);
|
||||
}
|
||||
|
||||
PyErr_Format(
|
||||
|
|
@ -2023,18 +2019,13 @@ static int Vector_ass_subscript(VectorObject *self, PyObject *item, PyObject *va
|
|||
return Vector_ass_item(self, i, value);
|
||||
}
|
||||
if (PySlice_Check(item)) {
|
||||
Py_ssize_t start, stop, step, slicelength;
|
||||
Py_ssize_t start, stop, step, slice_length;
|
||||
|
||||
if (PySlice_GetIndicesEx(item, self->vec_num, &start, &stop, &step, &slicelength) < 0) {
|
||||
if (PySlice_GetIndicesEx(item, self->vec_num, &start, &stop, &step, &slice_length) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (step == 1) {
|
||||
return Vector_ass_slice(self, start, stop, value);
|
||||
}
|
||||
|
||||
PyErr_SetString(PyExc_IndexError, "slice steps not supported with vectors");
|
||||
return -1;
|
||||
return Vector_ass_slice(self, start, step, slice_length, value);
|
||||
}
|
||||
|
||||
PyErr_Format(
|
||||
|
|
|
|||
|
|
@ -88,11 +88,17 @@ class GenericSliceMixIn:
|
|||
base = tuple(float(i + 1) for i in range(self.generic_len))
|
||||
obj = self.generic_make(base)
|
||||
self.assertIsInstance(obj[:], tuple)
|
||||
# Step 1.
|
||||
self.assertEqual(obj[:], base)
|
||||
self.assertEqual(obj[1:], base[1:])
|
||||
self.assertEqual(obj[:-1], base[:-1])
|
||||
self.assertEqual(obj[-1:], base[-1:])
|
||||
self.assertEqual(obj[1:1], ())
|
||||
# Stepped.
|
||||
self.assertEqual(obj[::2], base[::2])
|
||||
self.assertEqual(obj[::-1], base[::-1])
|
||||
self.assertEqual(obj[::-2], base[::-2])
|
||||
self.assertEqual(obj[1:1:-1], ())
|
||||
|
||||
def test_slice_set(self):
|
||||
base = tuple(float(i + 1) for i in range(self.generic_len))
|
||||
|
|
@ -101,11 +107,23 @@ class GenericSliceMixIn:
|
|||
new_full = tuple(10.0 * (i + 1) for i in range(self.generic_len))
|
||||
obj[:] = new_full
|
||||
self.assertEqual(tuple(obj), new_full)
|
||||
# Reverse full overwrite (fast path).
|
||||
obj[::-1] = base
|
||||
self.assertEqual(tuple(obj), base[::-1])
|
||||
# Partial step-1 (slow path).
|
||||
obj = self.generic_make(base)
|
||||
new_tail = tuple(50.0 * (i + 1) for i in range(self.generic_len - 1))
|
||||
obj[1:] = new_tail
|
||||
self.assertEqual(tuple(obj), (base[0],) + new_tail)
|
||||
# Stepped partial (slow path).
|
||||
obj = self.generic_make(base)
|
||||
every_other = base[::2]
|
||||
new_every_other = tuple(99.0 + i for i in range(len(every_other)))
|
||||
obj[::2] = new_every_other
|
||||
expected = list(base)
|
||||
for i, v in zip(range(0, self.generic_len, 2), new_every_other):
|
||||
expected[i] = v
|
||||
self.assertEqual(tuple(obj), tuple(expected))
|
||||
# Empty extended slice with empty seq is a no-op.
|
||||
obj = self.generic_make(base)
|
||||
obj[5:2:1] = ()
|
||||
|
|
@ -116,6 +134,8 @@ class GenericSliceMixIn:
|
|||
obj = self.generic_make(base)
|
||||
with self.assertRaises(ValueError):
|
||||
obj[:] = base[:-1]
|
||||
with self.assertRaises(ValueError):
|
||||
obj[::2] = base
|
||||
with self.assertRaises(ValueError):
|
||||
obj[5:2:1] = (1.0,)
|
||||
self.assertEqual(tuple(obj), base)
|
||||
|
|
@ -124,10 +144,22 @@ class GenericSliceMixIn:
|
|||
base = tuple(float(i + 1) for i in range(self.generic_len))
|
||||
obj = self.generic_make(base)
|
||||
obj.freeze()
|
||||
# Fast path (full overwrite, frozen check only).
|
||||
with self.assertRaises(TypeError):
|
||||
obj[:] = base
|
||||
# Slow path (partial, frozen check inside `ReadCallback_ForWrite`).
|
||||
with self.assertRaises(TypeError):
|
||||
obj[::2] = base[::2]
|
||||
self.assertEqual(tuple(obj), base)
|
||||
|
||||
def test_slice_set_self_aliased_reverse(self):
|
||||
# `obj[::-1] = obj` must reverse in place. Exercises the parse-through-seq
|
||||
# sync path that silently undoes the `is_subset=false` fast-path skip.
|
||||
base = tuple(float(i + 1) for i in range(self.generic_len))
|
||||
obj = self.generic_make(base)
|
||||
obj[::-1] = obj
|
||||
self.assertEqual(tuple(obj), base[::-1])
|
||||
|
||||
def test_slice_set_self_aliased_full(self):
|
||||
# `obj[:] = obj` writes original values back; effectively a no-op.
|
||||
base = tuple(float(i + 1) for i in range(self.generic_len))
|
||||
|
|
@ -154,6 +186,8 @@ class GenericSliceMixIn:
|
|||
original_id = id(obj)
|
||||
obj[:] = base
|
||||
self.assertEqual(id(obj), original_id)
|
||||
obj[::2] = base[::2]
|
||||
self.assertEqual(id(obj), original_id)
|
||||
|
||||
def test_slice_set_seq_longer_than_slice(self):
|
||||
# Assigning a sequence longer than the slice must raise ValueError,
|
||||
|
|
@ -494,11 +528,59 @@ class MatrixSliceMixIn:
|
|||
base = tuple(self._make_value(i + 1) for i in range(n))
|
||||
_mat, obj = self._make_obj(base)
|
||||
self.assertIsInstance(obj[:], tuple)
|
||||
# Step 1.
|
||||
self.assertEqual(obj[:], base)
|
||||
self.assertEqual(obj[1:], base[1:])
|
||||
self.assertEqual(obj[:-1], base[:-1])
|
||||
self.assertEqual(obj[-1:], base[-1:])
|
||||
self.assertEqual(obj[1:1], ())
|
||||
# Stepped.
|
||||
self.assertEqual(obj[::2], base[::2])
|
||||
self.assertEqual(obj[::-1], base[::-1])
|
||||
self.assertEqual(obj[::-2], base[::-2])
|
||||
self.assertEqual(obj[1:1:-1], ())
|
||||
|
||||
def test_slice_set(self):
|
||||
n = self.matrix_size
|
||||
base = tuple(self._make_value(i + 1) for i in range(n))
|
||||
# Forward full overwrite (fast path).
|
||||
_mat, obj = self._make_obj(base)
|
||||
new_full = tuple(self._make_value(10 * (i + 1)) for i in range(n))
|
||||
obj[:] = new_full
|
||||
self.assertEqual(tuple(obj), new_full)
|
||||
# Reverse full overwrite (fast path).
|
||||
obj[::-1] = base
|
||||
self.assertEqual(tuple(obj), base[::-1])
|
||||
# Partial step-1 (slow path).
|
||||
_mat, obj = self._make_obj(base)
|
||||
new_tail = tuple(self._make_value(50 * (i + 1)) for i in range(n - 1))
|
||||
obj[1:] = new_tail
|
||||
self.assertEqual(tuple(obj), (base[0],) + new_tail)
|
||||
# Stepped partial (slow path).
|
||||
_mat, obj = self._make_obj(base)
|
||||
every_other = base[::2]
|
||||
new_every_other = tuple(self._make_value(99 + i) for i in range(len(every_other)))
|
||||
obj[::2] = new_every_other
|
||||
expected = list(base)
|
||||
for i, v in zip(range(0, n, 2), new_every_other):
|
||||
expected[i] = v
|
||||
self.assertEqual(tuple(obj), tuple(expected))
|
||||
# Empty extended slice with empty seq is a no-op.
|
||||
_mat, obj = self._make_obj(base)
|
||||
obj[5:2:1] = ()
|
||||
self.assertEqual(tuple(obj), base)
|
||||
|
||||
def test_slice_set_length_mismatch(self):
|
||||
n = self.matrix_size
|
||||
base = tuple(self._make_value(i + 1) for i in range(n))
|
||||
_mat, obj = self._make_obj(base)
|
||||
with self.assertRaises(ValueError):
|
||||
obj[:] = base[:-1]
|
||||
with self.assertRaises(ValueError):
|
||||
obj[::2] = base
|
||||
with self.assertRaises(ValueError):
|
||||
obj[5:2:1] = (self._make_value(1),)
|
||||
self.assertEqual(tuple(obj), base)
|
||||
|
||||
def test_slice_set_frozen(self):
|
||||
n = self.matrix_size
|
||||
|
|
@ -506,10 +588,42 @@ class MatrixSliceMixIn:
|
|||
mat, obj = self._make_obj(base)
|
||||
# `MatrixAccess` has no `freeze()`; freezing the matrix is enough either way.
|
||||
mat.freeze()
|
||||
# Fast path (full overwrite, frozen check only).
|
||||
with self.assertRaises(TypeError):
|
||||
obj[:] = base
|
||||
# Slow path (partial, frozen check inside ReadCallback_ForWrite).
|
||||
with self.assertRaises(TypeError):
|
||||
obj[::2] = base[::2]
|
||||
self.assertEqual(tuple(obj), base)
|
||||
|
||||
def test_slice_set_self_aliased_reverse(self):
|
||||
# `obj[::-1] = obj` reverses rows/cols in place.
|
||||
# Atomicity comes from staging all parsed rows/cols before assigning.
|
||||
n = self.matrix_size
|
||||
base = tuple(self._make_value(i + 1) for i in range(n))
|
||||
_mat, obj = self._make_obj(base)
|
||||
obj[::-1] = obj
|
||||
self.assertEqual(tuple(obj), base[::-1])
|
||||
|
||||
def test_slice_set_self_aliased_full(self):
|
||||
# `obj[:] = obj` writes original values back; effectively a no-op.
|
||||
n = self.matrix_size
|
||||
base = tuple(self._make_value(i + 1) for i in range(n))
|
||||
_mat, obj = self._make_obj(base)
|
||||
obj[:] = obj
|
||||
self.assertEqual(tuple(obj), base)
|
||||
|
||||
def test_slice_set_identity_preserved(self):
|
||||
# Slice assignment must not replace the matrix or its `MatrixAccess` wrapper.
|
||||
n = self.matrix_size
|
||||
base = tuple(self._make_value(i + 1) for i in range(n))
|
||||
_mat, obj = self._make_obj(base)
|
||||
original_id = id(obj)
|
||||
obj[:] = base
|
||||
self.assertEqual(id(obj), original_id)
|
||||
obj[::2] = base[::2]
|
||||
self.assertEqual(id(obj), original_id)
|
||||
|
||||
|
||||
class Matrix3x3TestingSlice(MatrixSliceMixIn, unittest.TestCase):
|
||||
matrix_size = 3
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue