Initial Grease Pencil 3.0 stage #106848

Merged
Falk David merged 224 commits from filedescriptor/blender:grease-pencil-v3 into main 2023-05-30 11:14:22 +02:00
50 changed files with 891 additions and 454 deletions
Showing only changes of commit 17f723245b - Show all commits

View File

@ -126,8 +126,8 @@ class GHOST_ContextVK : public GHOST_Context {
void *image, void *framebuffer, void *render_pass, void *extent, uint32_t *fb_id);
/**
* Sets the swap interval for swapBuffers.
* \param interval The swap interval to use.
* Sets the swap interval for `swapBuffers`.
* \param interval: The swap interval to use.
* \return A boolean success indicator.
*/
GHOST_TSuccess setSwapInterval(int /* interval */)
@ -137,7 +137,7 @@ class GHOST_ContextVK : public GHOST_Context {
/**
* Gets the current swap interval for swapBuffers.
* \param intervalOut Variable to store the swap interval if it can be read.
* \param intervalOut: Variable to store the swap interval if it can be read.
* \return Whether the swap interval can be read.
*/
GHOST_TSuccess getSwapInterval(int &)

View File

@ -376,7 +376,7 @@ bool BKE_fcurve_calc_range(const struct FCurve *fcu,
/**
* Calculate the x and y extents of F-Curve's data.
* \param frame_range Only calculate the bounds of the FCurve in the given range.
* \param frame_range: Only calculate the bounds of the FCurve in the given range.
* Does the full range if NULL.
* \return true if the bounds have been found.
*/

View File

@ -344,7 +344,7 @@ typedef enum eGP_GetFrame_Mode {
/**
* Get the appropriate gp-frame from a given layer
* - this sets the layer's actframe var (if allowed to)
* - this sets the layer's `actframe` var (if allowed to)
* - extension beyond range (if first gp-frame is after all frame in interest and cannot add)
*
* \param gpl: Grease pencil layer
@ -523,7 +523,7 @@ struct Material *BKE_gpencil_object_material_new(struct Main *bmain,
int *r_index);
/**
* Get material index (0-based like mat_nr not actcol).
* Get material index (0-based like mat_nr not #Object::actcol).
* \param ob: Grease pencil object
* \param ma: Material
* \return Index of the material

View File

@ -185,7 +185,7 @@ typedef struct Main {
ListBase particles;
ListBase palettes;
ListBase paintcurves;
ListBase wm; /* Singleton (exception). */
ListBase wm; /* Singleton (exception). */
ListBase gpencils; /* Legacy Grease Pencil. */
ListBase movieclips;
ListBase masks;

View File

@ -43,7 +43,7 @@ float3 poly_center_calc(Span<float3> vert_positions, Span<MLoop> poly_loops);
/** Calculate the surface area of the polygon described by the indexed vertices. */
float poly_area_calc(Span<float3> vert_positions, Span<MLoop> poly_loops);
/** Calculate the angles at each of the polygons's corners. */
/** Calculate the angles at each of the polygons corners. */
void poly_angles_calc(Span<float3> vert_positions,
Span<MLoop> poly_loops,
MutableSpan<float> angles);

View File

@ -45,7 +45,7 @@ void BKE_mesh_remap_item_define_invalid(MeshPairRemap *map, int index);
* https://blenderartists.org/t/619105
*
* We could also use similar topology mappings inside a same mesh
* (cf. Campbell's 'select face islands from similar topology' wip work).
* (cf. Campbell's 'select face islands from similar topology' WIP work).
* Also, users will have to check, whether we can get rid of some modes here,
* not sure all will be useful!
*/

View File

@ -497,6 +497,7 @@ set(SRC
nla_private.h
particle_private.h
tracking_private.h
intern/CCGSubSurf.h
intern/CCGSubSurf_inline.h
intern/CCGSubSurf_intern.h
@ -508,6 +509,7 @@ set(SRC
intern/multires_unsubdivide.h
intern/ocean_intern.h
intern/pbvh_intern.hh
intern/pbvh_pixels_copy.hh
intern/pbvh_uv_islands.hh
intern/subdiv_converter.h
intern/subdiv_inline.h

View File

@ -1141,11 +1141,12 @@ static void collection_gobject_hash_ensure_fix(Collection *collection)
/**
* Update the collections object hash, removing `ob_old`, inserting `cob->ob` as the new key.
*
* \note This function is called fron foreach_id callback, and a difference of Object pointers is
* only expected in case ID remapping is happening. This code is the only are in Blender allowed to
* (temporarily) leave the CollectionObject list in an inconsistent/invalid state (with NULL object
* pointers, or duplicates of CollectionObjects). If such invalid cases are encountered, it will
* tag the collection objects list as dirty.
* \note This function is called from #IDTypeInfo::foreach_id callback,
* and a difference of Object pointers is only expected in case ID remapping is happening.
* This code is the only are in Blender allowed to (temporarily) leave the #CollectionObject list
* in an inconsistent/invalid state (with NULL object pointers, or duplicates of
* #CollectionObjects). If such invalid cases are encountered,
* it will tag the collection objects list as dirty.
*
* \param ob_old: The existing key to `cob` in the hash, not removed when NULL.
* \param cob: The `cob->ob` is to be used as the new key,
@ -1162,7 +1163,7 @@ static void collection_gobject_hash_update_object(Collection *collection,
if (ob_old) {
CollectionObject *cob_old = BLI_ghash_popkey(collection->runtime.gobject_hash, ob_old, NULL);
if (cob_old != cob) {
/* Old object alredy removed from the ghash. */
/* Old object already removed from the #GHash. */
collection->runtime.tag |= COLLECTION_TAG_COLLECTION_OBJECT_DIRTY;
}
}
@ -1173,28 +1174,28 @@ static void collection_gobject_hash_update_object(Collection *collection,
*cob_p = cob;
}
else {
/* Duplicate CollectionObject entries. */
/* Duplicate #CollectionObject entries. */
collection->runtime.tag |= COLLECTION_TAG_COLLECTION_OBJECT_DIRTY;
}
}
else {
/* CollectionObject with NULL objetc pointer. */
/* #CollectionObject with NULL object pointer. */
collection->runtime.tag |= COLLECTION_TAG_COLLECTION_OBJECT_DIRTY;
}
}
/**
* Validate the integrity of the collection's CollectionObject list, and of its mapping.
* Validate the integrity of the collection's #CollectionObject list, and of its mapping.
*
* Simple test is very fast, as it only checks that the 'dirty' tag for collection's objects is not
* set.
*
* The extensive check is expensive. This should not be done from within loops over collections
* items, or from low-level operations that can be assumed safe (like adding or removing an object
* from a colleciton). It ensures that:
* - There is a `gobject_hash` mapping.
* - There is no NULL-object CollectionObject items.
* - there is no duplicate CollectionObject items (two or more referencing the same Object).
* from a collection). It ensures that:
* - There is a `gobject_hash` mapping.
* - There is no NULL-object #CollectionObject items.
* - there is no duplicate #CollectionObject items (two or more referencing the same Object).
*/
static void collection_gobject_assert_internal_consistency(Collection *collection,
const bool do_extensive_check)
@ -1205,16 +1206,16 @@ static void collection_gobject_assert_internal_consistency(Collection *collectio
}
if (collection->runtime.gobject_hash == NULL) {
/* NOTE: If the ghash does not exist yet, it's creation will assert on errors, so in theory the
* second loop below could be skipped. */
/* NOTE: If the `ghash` does not exist yet, it's creation will assert on errors,
* so in theory the second loop below could be skipped. */
collection_gobject_hash_create(collection);
}
GHash *gobject_hash = collection->runtime.gobject_hash;
UNUSED_VARS_NDEBUG(gobject_hash);
LISTBASE_FOREACH (CollectionObject *, cob, &collection->gobject) {
BLI_assert(cob->ob != NULL);
/* If there are more than one CollectionObject for the same object, at most one of them will
* pass this test. */
/* If there are more than one #CollectionObject for the same object,
* at most one of them will pass this test. */
BLI_assert(BLI_ghash_lookup(gobject_hash, cob->ob) == cob);
}
}

View File

@ -556,9 +556,12 @@ int BKE_fcurve_bezt_binarysearch_index(const BezTriple array[],
/* ...................................... */
/* Get the first and last index to the bezt array that satisfies the given parameters.
* \param selected_keys_only Only accept indices of bezt that are selected. Is a subset of
* frame_range. \param frame_range Only consider keyframes in that frame interval. Can be NULL.
/**
* Get the first and last index to the bezt array that satisfies the given parameters.
*
* \param selected_keys_only: Only accept indices of bezt that are selected.
* Is a subset of frame_range.
* \param frame_range: Only consider keyframes in that frame interval. Can be NULL.
*/
static bool get_bounding_bezt_indices(const FCurve *fcu,
const bool selected_keys_only,

View File

@ -422,7 +422,7 @@ TEST(BKE_fcurve, BKE_fcurve_calc_bounds)
/* All keys. */
success = BKE_fcurve_calc_bounds(
fcu, false /* sel only */, false /* include handles */, NULL /* frame range */, &bounds);
fcu, false /* select only */, false /* include handles */, NULL /* frame range */, &bounds);
EXPECT_TRUE(success) << "A non-empty FCurve should have bounds.";
EXPECT_FLOAT_EQ(fcu->bezt[0].vec[1][0], bounds.xmin);
EXPECT_FLOAT_EQ(fcu->bezt[4].vec[1][0], bounds.xmax);
@ -431,7 +431,7 @@ TEST(BKE_fcurve, BKE_fcurve_calc_bounds)
/* Only selected. */
success = BKE_fcurve_calc_bounds(
fcu, true /* sel only */, false /* include handles */, NULL /* frame range */, &bounds);
fcu, true /* select only */, false /* include handles */, NULL /* frame range */, &bounds);
EXPECT_FALSE(success)
<< "Using selected keyframes only should not find bounds if nothing is selected.";
@ -439,7 +439,7 @@ TEST(BKE_fcurve, BKE_fcurve_calc_bounds)
fcu->bezt[3].f2 |= SELECT;
success = BKE_fcurve_calc_bounds(
fcu, true /* sel only */, false /* include handles */, NULL /* frame range */, &bounds);
fcu, true /* select only */, false /* include handles */, NULL /* frame range */, &bounds);
EXPECT_TRUE(success) << "Selected keys should have been found.";
EXPECT_FLOAT_EQ(fcu->bezt[1].vec[1][0], bounds.xmin);
EXPECT_FLOAT_EQ(fcu->bezt[3].vec[1][0], bounds.xmax);
@ -448,7 +448,7 @@ TEST(BKE_fcurve, BKE_fcurve_calc_bounds)
/* Including handles. */
success = BKE_fcurve_calc_bounds(
fcu, false /* sel only */, true /* include handles */, NULL /* frame range */, &bounds);
fcu, false /* select only */, true /* include handles */, NULL /* frame range */, &bounds);
EXPECT_TRUE(success) << "A non-empty FCurve should have bounds including handles.";
EXPECT_FLOAT_EQ(fcu->bezt[0].vec[0][0], bounds.xmin);
EXPECT_FLOAT_EQ(fcu->bezt[4].vec[2][0], bounds.xmax);
@ -461,13 +461,13 @@ TEST(BKE_fcurve, BKE_fcurve_calc_bounds)
range[0] = 25;
range[1] = 30;
success = BKE_fcurve_calc_bounds(
fcu, false /* sel only */, false /* include handles */, range /* frame range */, &bounds);
fcu, false /* select only */, false /* include handles */, range /* frame range */, &bounds);
EXPECT_FALSE(success) << "A frame range outside the range of keyframes should not find bounds.";
range[0] = 0;
range[1] = 18.2f;
success = BKE_fcurve_calc_bounds(
fcu, false /* sel only */, false /* include handles */, range /* frame range */, &bounds);
fcu, false /* select only */, false /* include handles */, range /* frame range */, &bounds);
EXPECT_TRUE(success) << "A frame range within the range of keyframes should find bounds.";
EXPECT_FLOAT_EQ(fcu->bezt[0].vec[1][0], bounds.xmin);
EXPECT_FLOAT_EQ(fcu->bezt[3].vec[1][0], bounds.xmax);
@ -476,7 +476,7 @@ TEST(BKE_fcurve, BKE_fcurve_calc_bounds)
/* Range and handles. */
success = BKE_fcurve_calc_bounds(
fcu, false /* sel only */, true /* include handles */, range /* frame range */, &bounds);
fcu, false /* select only */, true /* include handles */, range /* frame range */, &bounds);
EXPECT_TRUE(success)
<< "A frame range within the range of keyframes should find bounds with handles.";
EXPECT_FLOAT_EQ(fcu->bezt[0].vec[0][0], bounds.xmin);
@ -488,7 +488,7 @@ TEST(BKE_fcurve, BKE_fcurve_calc_bounds)
range[0] = 8.0f;
range[1] = 18.2f;
success = BKE_fcurve_calc_bounds(
fcu, true /* sel only */, true /* include handles */, range /* frame range */, &bounds);
fcu, true /* select only */, true /* include handles */, range /* frame range */, &bounds);
EXPECT_TRUE(success)
<< "A frame range within the range of keyframes should find bounds of selected keyframes.";
EXPECT_FLOAT_EQ(fcu->bezt[3].vec[0][0], bounds.xmin);
@ -502,7 +502,7 @@ TEST(BKE_fcurve, BKE_fcurve_calc_bounds)
fcurve_store_samples(fcu, NULL, sample_start, sample_end, fcurve_samplingcb_evalcurve);
success = BKE_fcurve_calc_bounds(
fcu, false /* sel only */, false /* include handles */, NULL /* frame range */, &bounds);
fcu, false /* select only */, false /* include handles */, NULL /* frame range */, &bounds);
EXPECT_TRUE(success) << "FCurve samples should have a range.";
EXPECT_FLOAT_EQ(sample_start, bounds.xmin);
@ -513,7 +513,7 @@ TEST(BKE_fcurve, BKE_fcurve_calc_bounds)
range[0] = 8.0f;
range[1] = 20.0f;
success = BKE_fcurve_calc_bounds(
fcu, false /* sel only */, false /* include handles */, range /* frame range */, &bounds);
fcu, false /* select only */, false /* include handles */, range /* frame range */, &bounds);
EXPECT_TRUE(success) << "FCurve samples should have a range.";
EXPECT_FLOAT_EQ(range[0], bounds.xmin);
@ -524,7 +524,7 @@ TEST(BKE_fcurve, BKE_fcurve_calc_bounds)
range[0] = 20.1f;
range[1] = 30.0f;
success = BKE_fcurve_calc_bounds(
fcu, false /* sel only */, false /* include handles */, range /* frame range */, &bounds);
fcu, false /* select only */, false /* include handles */, range /* frame range */, &bounds);
EXPECT_FALSE(success)
<< "A frame range outside the range of keyframe samples should not have bounds.";

View File

@ -368,7 +368,7 @@ static void gpencil_convert_spline(Main *bmain,
if ((nu->flagu & CU_NURB_CYCLIC) == 0) {
segments--;
}
/* Get all interpolated curve points of Beziert */
/* Get all interpolated curve points of Bezier. */
for (int s = 0; s < segments; s++) {
int inext = (s + 1) % nu->pntsu;
BezTriple *prevbezt = &nu->bezt[s];
@ -915,7 +915,7 @@ static float *gpencil_stroke_points_from_editcurve_adaptive_resolu(
bool is_cyclic,
int *r_points_len)
{
/* One stride contains: x, y, z, pressure, strength, Vr, Vg, Vb, Vmix_factor */
/* One stride contains: `x, y, z, pressure, strength, Vr, Vg, Vb, Vmix_factor`. */
const uint stride = sizeof(float[9]);
const uint cpt_last = curve_point_array_len - 1;
const uint num_segments = (is_cyclic) ? curve_point_array_len : curve_point_array_len - 1;

View File

@ -191,7 +191,7 @@ void BKE_gpencil_blend_read_data(BlendDataReader *reader, bGPdata *gpd)
BLO_read_data_address(reader, &gpd->adt);
BKE_animdata_blend_read_data(reader, gpd->adt);
/* Ensure full objectmode for linked grease pencil. */
/* Ensure full object-mode for linked grease pencil. */
if (ID_IS_LINKED(gpd)) {
gpd->flag &= ~GP_DATA_STROKE_PAINTMODE;
gpd->flag &= ~GP_DATA_STROKE_EDITMODE;
@ -1364,9 +1364,9 @@ bGPDframe *BKE_gpencil_layer_frame_get(bGPDlayer *gpl, int cframe, eGP_GetFrame_
gpl->actframe = gpl->frames.first;
}
else {
/* unresolved errogenous situation! */
/* Unresolved erogenous situation! */
CLOG_STR_ERROR(&LOG, "cannot find appropriate gp-frame");
/* gpl->actframe should still be NULL */
/* `gpl->actframe` should still be NULL. */
}
}
}
@ -2324,7 +2324,7 @@ bool BKE_gpencil_from_image(
pt->strength = 1.0f - color[3];
}
/* Selet Alpha points. */
/* Select Alpha points. */
if (pt->strength < 0.03f) {
gps->flag |= GP_STROKE_SELECT;
pt->flag |= GP_SPOINT_SELECT;

View File

@ -309,7 +309,7 @@ template<typename T> struct AngleCartesianBase {
math::sqrt((T(1) - a.cos_) / T(2))};
/* Recover sign only for sine. Cosine of half angle is given to be positive or 0 since the
* angle stored in #AngleCartesianBase is in the range [-pi..pi]. */
/* TODO(fclem): Could use copysign here. */
/* TODO(fclem): Could use `copysign` here. */
if (a.sin_ < T(0)) {
result.sin_ = -result.sin_;
}

View File

@ -1307,7 +1307,7 @@ void blo_do_versions_pre250(FileData *fd, Library *lib, Main *bmain)
/* Cannot call stuff now (pointers!), done in #setup_app_data. */
ob->id.recalc |= ID_RECALC_ALL;
/* new generic xray option */
/* New generic X-ray option. */
arm = blo_do_versions_newlibadr(fd, lib, ob->data);
enum { ARM_DRAWXRAY = (1 << 1) };
if (arm->flag & ARM_DRAWXRAY) {

View File

@ -378,6 +378,8 @@ typedef struct BMesh {
* This allows save invalidation of a #BMesh when it's freed,
* so the Python object will report it as having been removed,
* instead of crashing on invalid memory access.
*
* Doesn't hold a #PyObject reference, cleared when the last object is de-referenced.
*/
void *py_handle;
} BMesh;

View File

@ -1169,9 +1169,9 @@ static void bm_face_loop_table_build(BMesh &bm,
pin_offsets[i] = ldata.layers[pin_layers[i]].offset;
}
Array<bool> need_vert_sel(vert_sel_layers.size());
Array<bool> need_edge_sel(edge_sel_layers.size());
Array<bool> need_pin(pin_layers.size());
Array<bool> need_vert_sel(vert_sel_layers.size(), false);
Array<bool> need_edge_sel(edge_sel_layers.size(), false);
Array<bool> need_pin(pin_layers.size(), false);
char hflag = 0;
BMIter iter;
int face_i = 0;

View File

@ -8,7 +8,7 @@ void node_composite_luminance_matte(vec4 color,
out float matte)
{
float luminance = get_luminance(color.rgb, luminance_coefficients);
float alpha = clamp(0.0, 1.0, (luminance - low) / (high - low));
float alpha = clamp((luminance - low) / (high - low), 0.0, 1.0);
matte = min(alpha, color.a);
result = color * matte;
}

View File

@ -1243,7 +1243,7 @@ static void sculpt_draw_cb(DRWSculptCallbackData *scd,
DRWShadingGroup *shgrp = scd->shading_groups[index];
if (geom != nullptr && shgrp != nullptr) {
if (SCULPT_DEBUG_BUFFERS) {
/* Color each buffers in different colors. Only work in solid/Xray mode. */
/* Color each buffers in different colors. Only work in solid/X-ray mode. */
shgrp = DRW_shgroup_create_sub(shgrp);
DRW_shgroup_uniform_vec3(
shgrp, "materialDiffuseColor", SCULPT_DEBUG_COLOR(scd->debug_node_nr++), 1);

View File

@ -1715,6 +1715,10 @@ static int gpencil_strokes_paste_exec(bContext *C, wmOperator *op)
*/
for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) {
/* Active frame is copied later, so don't need duplicate the stroke here. */
if (gpl->actframe == gpf) {
continue;
}
if (gpf->flag & GP_FRAME_SELECT) {
if (gpf) {
/* Create new stroke */

View File

@ -1411,7 +1411,8 @@ static float gpencil_sculpt_rotation_eval_get(tGP_BrushEditData *gso,
const GP_SpaceConversion *gsc = &gso->gsc;
bGPDstroke *gps_orig = (gps_eval->runtime.gps_orig) ? gps_eval->runtime.gps_orig : gps_eval;
bGPDspoint *pt_orig = &gps_orig->points[pt_eval->runtime.idx_orig];
bGPDspoint *pt_orig = (pt_eval->runtime.pt_orig) ? &gps_orig->points[pt_eval->runtime.idx_orig] :
pt_eval;
bGPDspoint *pt_prev_eval = NULL;
bGPDspoint *pt_orig_prev = NULL;
if (idx_eval != 0) {

View File

@ -213,10 +213,10 @@ void paintface_reveal(bContext *C, Object *ob, const bool select)
}
/**
* Join all edges of each poly in the AtomicDisjointSet. This can be used to find out which polys
* Join all edges of each poly in the #AtomicDisjointSet. This can be used to find out which polys
* are connected to each other.
* \param islands Is expected to be of length mesh->totedge.
* \param skip_seams Polys separated by a seam will be treated as not connected.
* \param islands: Is expected to be of length `mesh->totedge`.
* \param skip_seams: Polys separated by a seam will be treated as not connected.
*/
static void build_poly_connections(blender::AtomicDisjointSet &islands,
Mesh &mesh,

View File

@ -174,7 +174,7 @@ static void get_nearest_fcurve_verts_list(bAnimContext *ac, const int mval[2], L
ANIMFILTER_NODUPLIS | ANIMFILTER_FCURVESONLY);
if (U.animation_flag &
USER_ANIM_ONLY_SHOW_SELECTED_CURVE_KEYS) { /* FIXME: this should really be check for by the
filtering code... */
* filtering code. */
filter |= ANIMFILTER_SEL;
}
mapping_flag |= ANIM_get_normalization_flags(ac);

View File

@ -675,7 +675,7 @@ int view3d_opengl_select_ex(ViewContext *vc,
GPU_depth_test(GPU_DEPTH_LESS_EQUAL);
}
/* If in xray mode, we select the wires in priority. */
/* If in X-ray mode, we select the wires in priority. */
if (XRAY_ACTIVE(v3d) && use_nearest) {
/* We need to call "GPU_select_*" API's inside DRW_draw_select_loop
* because the OpenGL context created & destroyed inside this function. */

View File

@ -3200,7 +3200,7 @@ static eSnapMode transform_snap_context_project_view3d_mixed_impl(SnapObjectCont
Depsgraph *depsgraph,
const ARegion *region,
const View3D *v3d,
const eSnapMode snap_to_flag,
eSnapMode snap_to_flag,
const SnapObjectParams *params,
const float init_co[3],
const float mval[2],
@ -3235,7 +3235,16 @@ static eSnapMode transform_snap_context_project_view3d_mixed_impl(SnapObjectCont
const RegionView3D *rv3d = static_cast<RegionView3D *>(region->regiondata);
bool use_occlusion_test = params->use_occlusion_test && !XRAY_ENABLED(v3d);
if (snap_to_flag & (SCE_SNAP_MODE_FACE_RAYCAST | SCE_SNAP_MODE_FACE_NEAREST)) {
if (params->use_occlusion_test && XRAY_ENABLED(v3d)) {
/* Remove Snap to Face with Occlusion Test as they are not visible in wireframe mode. */
snap_to_flag &= ~(SCE_SNAP_MODE_FACE_RAYCAST | SCE_SNAP_MODE_FACE_NEAREST);
}
else if (prev_co == nullptr || init_co == nullptr) {
/* No location to work with #SCE_SNAP_MODE_FACE_NEAREST. */
snap_to_flag &= ~SCE_SNAP_MODE_FACE_NEAREST;
}
}
/* NOTE: if both face ray-cast and face nearest are enabled, first find result of nearest, then
* override with ray-cast. */
@ -3261,7 +3270,7 @@ static eSnapMode transform_snap_context_project_view3d_mixed_impl(SnapObjectCont
}
}
if (snap_to_flag & SCE_SNAP_MODE_FACE_RAYCAST || use_occlusion_test) {
if (snap_to_flag & SCE_SNAP_MODE_FACE_RAYCAST) {
float ray_start[3], ray_normal[3];
if (!ED_view3d_win_to_ray_clipped_ex(
depsgraph, region, v3d, mval, nullptr, ray_normal, ray_start, true)) {

View File

@ -90,6 +90,12 @@ void VKBuffer::read(void *data) const
memcpy(data, mapped_memory_, size_in_bytes_);
}
void *VKBuffer::mapped_memory_get() const
{
BLI_assert_msg(is_mapped(), "Cannot access a non-mapped buffer.");
return mapped_memory_;
}
bool VKBuffer::is_mapped() const
{
return mapped_memory_ != nullptr;

View File

@ -49,6 +49,13 @@ class VKBuffer {
return vk_buffer_;
}
/**
* Get the reference to the mapped memory.
*
* Can only be called when the buffer is (still) mapped.
*/
void *mapped_memory_get() const;
private:
/** Check if this buffer is mapped. */
bool is_mapped() const;

View File

@ -11,20 +11,28 @@ namespace blender::gpu {
VKPixelBuffer::VKPixelBuffer(int64_t size) : PixelBuffer(size)
{
VKContext &context = *VKContext::get();
buffer_.create(context,
size,
GPU_USAGE_STATIC,
static_cast<VkBufferUsageFlagBits>(VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
VK_BUFFER_USAGE_TRANSFER_DST_BIT));
}
void *VKPixelBuffer::map()
{
return nullptr;
/* Vulkan buffers are always mapped between allocation and freeing. */
return buffer_.mapped_memory_get();
}
void VKPixelBuffer::unmap()
{
/* Vulkan buffers are always mapped between allocation and freeing. */
}
int64_t VKPixelBuffer::get_native_handle()
{
return -1;
return int64_t(buffer_.vk_handle());
}
uint VKPixelBuffer::get_size()

View File

@ -9,9 +9,13 @@
#include "gpu_texture_private.hh"
#include "vk_buffer.hh"
namespace blender::gpu {
class VKPixelBuffer : public PixelBuffer {
VKBuffer buffer_;
public:
VKPixelBuffer(int64_t size);
void *map() override;

View File

@ -18,6 +18,7 @@ set(INC_SYS
${JPEG_INCLUDE_DIR}
${PNG_INCLUDE_DIRS}
${ZLIB_INCLUDE_DIRS}
${OPENIMAGEIO_INCLUDE_DIRS}
)
set(SRC
@ -29,6 +30,7 @@ set(SRC
intern/divers.c
intern/filetype.c
intern/filter.c
intern/format_psd.cc
intern/imageprocess.c
intern/indexer.c
intern/iris.c

View File

@ -678,7 +678,7 @@ void IMB_sampleImageAtLocation(
* \attention defined in readimage.c
*/
struct ImBuf *IMB_loadifffile(
int file, const char *filepath, int flags, char colorspace[IM_MAX_SPACE], const char *descr);
int file, int flags, char colorspace[IM_MAX_SPACE], const char *descr);
/**
* \attention defined in scaling.c

View File

@ -318,9 +318,6 @@ extern const char *imb_ext_image[];
extern const char *imb_ext_movie[];
extern const char *imb_ext_audio[];
/** Image formats that can only be loaded via filepath. */
extern const char *imb_ext_image_filepath_only[];
/* -------------------------------------------------------------------- */
/** \name Imbuf Color Management Flag
*

View File

@ -8,6 +8,10 @@
#include "IMB_imbuf.h"
#ifdef __cplusplus
extern "C" {
#endif
/* -------------------------------------------------------------------- */
/** \name Generic File Type
* \{ */
@ -256,3 +260,20 @@ struct ImBuf *imb_load_filepath_thumbnail_webp(const char *filepath,
bool imb_savewebp(struct ImBuf *ibuf, const char *name, int flags);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Format: PSD (#IMB_FTYPE_PSD)
* \{ */
bool imb_is_a_psd(const unsigned char *buf, size_t size);
struct ImBuf *imb_load_psd(const uchar *mem,
size_t size,
int flags,
char colorspace[IM_MAX_SPACE]);
/** \} */
#ifdef __cplusplus
};
#endif

View File

@ -184,9 +184,9 @@ const ImFileType IMB_FILE_TYPES[] = {
{
.init = NULL,
.exit = NULL,
.is_a = imb_is_a_photoshop,
.load = NULL,
.load_filepath = imb_load_photoshop,
.is_a = imb_is_a_psd,
.load = imb_load_psd,
.load_filepath = NULL,
.load_filepath_thumbnail = NULL,
.save = NULL,
.flag = IM_FTYPE_FLOAT,

View File

@ -0,0 +1,31 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "oiio/openimageio_support.hh"
#include "IMB_filetype.h"
#include "IMB_imbuf_types.h"
OIIO_NAMESPACE_USING
using namespace blender::imbuf;
extern "C" {
bool imb_is_a_psd(const uchar *mem, size_t size)
{
return imb_oiio_check(mem, size, "psd");
}
ImBuf *imb_load_psd(const uchar *mem, size_t size, int flags, char colorspace[IM_MAX_SPACE])
{
ImageSpec config, spec;
config.attribute("oiio:UnassociatedAlpha", 1);
ReadContext ctx{mem, size, "psd", IMB_FTYPE_PSD, flags};
/* PSD should obey color space information embedded in the file. */
ctx.use_embedded_colorspace = true;
return imb_oiio_read(ctx, config, colorspace, spec);
}
}

View File

@ -18,8 +18,10 @@ set(INC_SYS
set(SRC
openimageio_api.h
openimageio_support.hh
openimageio_api.cpp
openimageio_support.cc
)
set(LIB

View File

@ -5,278 +5,16 @@
* \ingroup openimageio
*/
#include <set>
#if defined(WIN32)
# include "utfconv.h"
# define _USE_MATH_DEFINES
#endif
/* NOTE: Keep first, #BLI_path_util conflicts with OIIO's format. */
#include "openimageio_api.h"
#include <OpenImageIO/imageio.h>
#include <memory>
#include "MEM_guardedalloc.h"
#include "BLI_blenlib.h"
#include "IMB_allocimbuf.h"
#include "IMB_colormanagement.h"
#include "IMB_colormanagement_intern.h"
#include "IMB_imbuf.h"
#include "IMB_imbuf_types.h"
OIIO_NAMESPACE_USING
using std::string;
using std::unique_ptr;
using uchar = uchar;
template<class T, class Q>
static void fill_all_channels(T *pixels, int width, int height, int components, Q alpha)
{
if (components == 2) {
for (int i = width * height - 1; i >= 0; i--) {
pixels[i * 4 + 3] = pixels[i * 2 + 1];
pixels[i * 4 + 2] = pixels[i * 2 + 0];
pixels[i * 4 + 1] = pixels[i * 2 + 0];
pixels[i * 4 + 0] = pixels[i * 2 + 0];
}
}
else if (components == 3) {
for (int i = width * height - 1; i >= 0; i--) {
pixels[i * 4 + 3] = alpha;
pixels[i * 4 + 2] = pixels[i * 3 + 2];
pixels[i * 4 + 1] = pixels[i * 3 + 1];
pixels[i * 4 + 0] = pixels[i * 3 + 0];
}
}
else if (components == 1) {
for (int i = width * height - 1; i >= 0; i--) {
pixels[i * 4 + 3] = alpha;
pixels[i * 4 + 2] = pixels[i];
pixels[i * 4 + 1] = pixels[i];
pixels[i * 4 + 0] = pixels[i];
}
}
}
static ImBuf *imb_oiio_load_image(
ImageInput *in, int width, int height, int components, int flags, bool is_alpha)
{
ImBuf *ibuf;
int scanlinesize = width * components * sizeof(uchar);
/* allocate the memory for the image */
ibuf = IMB_allocImBuf(width, height, is_alpha ? 32 : 24, flags | IB_rect);
try {
if (!in->read_image(0,
0,
0,
components,
TypeDesc::UINT8,
(uchar *)ibuf->rect + (height - 1) * scanlinesize,
AutoStride,
-scanlinesize,
AutoStride)) {
std::cerr << __func__ << ": ImageInput::read_image() failed:" << std::endl
<< in->geterror() << std::endl;
if (ibuf) {
IMB_freeImBuf(ibuf);
}
return nullptr;
}
}
catch (const std::exception &exc) {
std::cerr << exc.what() << std::endl;
if (ibuf) {
IMB_freeImBuf(ibuf);
}
return nullptr;
}
/* ImBuf always needs 4 channels */
fill_all_channels((uchar *)ibuf->rect, width, height, components, 0xFF);
return ibuf;
}
static ImBuf *imb_oiio_load_image_float(
ImageInput *in, int width, int height, int components, int flags, bool is_alpha)
{
ImBuf *ibuf;
int scanlinesize = width * components * sizeof(float);
/* allocate the memory for the image */
ibuf = IMB_allocImBuf(width, height, is_alpha ? 32 : 24, flags | IB_rectfloat);
try {
if (!in->read_image(0,
0,
0,
components,
TypeDesc::FLOAT,
(uchar *)ibuf->rect_float + (height - 1) * scanlinesize,
AutoStride,
-scanlinesize,
AutoStride)) {
std::cerr << __func__ << ": ImageInput::read_image() failed:" << std::endl
<< in->geterror() << std::endl;
if (ibuf) {
IMB_freeImBuf(ibuf);
}
return nullptr;
}
}
catch (const std::exception &exc) {
std::cerr << exc.what() << std::endl;
if (ibuf) {
IMB_freeImBuf(ibuf);
}
return nullptr;
}
/* ImBuf always needs 4 channels */
fill_all_channels((float *)ibuf->rect_float, width, height, components, 1.0f);
/* NOTE: Photoshop 16 bit files never has alpha with it,
* so no need to handle associated/unassociated alpha. */
return ibuf;
}
extern "C" {
bool imb_is_a_photoshop(const uchar *mem, size_t size)
{
const uchar magic[4] = {'8', 'B', 'P', 'S'};
if (size < sizeof(magic)) {
return false;
}
return memcmp(magic, mem, sizeof(magic)) == 0;
}
int imb_save_photoshop(struct ImBuf *ibuf, const char * /*name*/, int flags)
{
if (flags & IB_mem) {
std::cerr << __func__ << ": Photoshop PSD-save: Create PSD in memory"
<< " currently not supported" << std::endl;
imb_addencodedbufferImBuf(ibuf);
ibuf->encodedsize = 0;
return 0;
}
return 0;
}
struct ImBuf *imb_load_photoshop(const char *filename, int flags, char colorspace[IM_MAX_SPACE])
{
struct ImBuf *ibuf = nullptr;
int width, height, components;
bool is_float, is_alpha, is_half;
int basesize;
char file_colorspace[IM_MAX_SPACE];
const bool is_colorspace_manually_set = (colorspace[0] != '\0');
/* load image from file through OIIO */
if (IMB_ispic_type_matches(filename, IMB_FTYPE_PSD) == 0) {
return nullptr;
}
colorspace_set_default_role(colorspace, IM_MAX_SPACE, COLOR_ROLE_DEFAULT_BYTE);
unique_ptr<ImageInput> in(ImageInput::create(filename));
if (!in) {
std::cerr << __func__ << ": ImageInput::create() failed:" << std::endl
<< OIIO_NAMESPACE::geterror() << std::endl;
return nullptr;
}
ImageSpec spec, config;
config.attribute("oiio:UnassociatedAlpha", int(1));
if (!in->open(filename, spec, config)) {
std::cerr << __func__ << ": ImageInput::open() failed:" << std::endl
<< in->geterror() << std::endl;
return nullptr;
}
if (!is_colorspace_manually_set) {
string ics = spec.get_string_attribute("oiio:ColorSpace");
BLI_strncpy(file_colorspace, ics.c_str(), IM_MAX_SPACE);
/* Only use color-spaces exist. */
if (colormanage_colorspace_get_named(file_colorspace)) {
strcpy(colorspace, file_colorspace);
}
else {
std::cerr << __func__ << ": The embed colorspace (\"" << file_colorspace
<< "\") not supported in existent OCIO configuration file. Fallback "
<< "to system default colorspace (\"" << colorspace << "\")." << std::endl;
}
}
width = spec.width;
height = spec.height;
components = spec.nchannels;
is_alpha = spec.alpha_channel != -1;
basesize = spec.format.basesize();
is_float = basesize > 1;
is_half = spec.format == TypeDesc::HALF;
/* we only handle certain number of components */
if (!(components >= 1 && components <= 4)) {
if (in) {
in->close();
}
return nullptr;
}
if (is_float) {
ibuf = imb_oiio_load_image_float(in.get(), width, height, components, flags, is_alpha);
}
else {
ibuf = imb_oiio_load_image(in.get(), width, height, components, flags, is_alpha);
}
if (in) {
in->close();
}
if (!ibuf) {
return nullptr;
}
/* ImBuf always needs 4 channels */
ibuf->ftype = IMB_FTYPE_PSD;
ibuf->channels = 4;
ibuf->planes = (3 + (is_alpha ? 1 : 0)) * 4 << basesize;
ibuf->flags |= (is_float && is_half) ? IB_halffloat : 0;
try {
return ibuf;
}
catch (const std::exception &exc) {
std::cerr << exc.what() << std::endl;
if (ibuf) {
IMB_freeImBuf(ibuf);
}
return nullptr;
}
}
int OIIO_getVersionHex(void)
{
return openimageio_version();
}
} /* export "C" */
} /* extern "C" */

View File

@ -7,20 +7,10 @@
#pragma once
#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
struct ImBuf;
bool imb_is_a_photoshop(const unsigned char *mem, size_t size);
int imb_save_photoshop(struct ImBuf *ibuf, const char *name, int flags);
struct ImBuf *imb_load_photoshop(const char *name, int flags, char *colorspace);
int OIIO_getVersionHex(void);
#ifdef __cplusplus

View File

@ -0,0 +1,398 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "openimageio_support.hh"
#include "BLI_blenlib.h"
#include "BKE_idprop.h"
#include "DNA_ID.h" /* ID property definitions. */
#include "IMB_allocimbuf.h"
#include "IMB_colormanagement.h"
#include "IMB_metadata.h"
OIIO_NAMESPACE_USING
using std::string;
using std::unique_ptr;
namespace blender::imbuf {
/* An OIIO IOProxy used during file packing to write into an in-memory #ImBuf buffer. */
class ImBufMemWriter : public Filesystem::IOProxy {
public:
ImBufMemWriter(ImBuf *ibuf) : IOProxy("", Write), ibuf_(ibuf)
{
}
const char *proxytype() const override
{
return "ImBufMemWriter";
}
size_t write(const void *buf, size_t size) override
{
size = pwrite(buf, size, m_pos);
m_pos += size;
return size;
}
size_t pwrite(const void *buf, size_t size, int64_t offset) override
{
/* If buffer is too small increase it. */
size_t end = offset + size;
while (end > ibuf_->encodedbuffersize) {
if (!imb_enlargeencodedbufferImBuf(ibuf_)) {
/* Out of memory. */
return 0;
}
}
memcpy(ibuf_->encodedbuffer + offset, buf, size);
if (end > ibuf_->encodedsize) {
ibuf_->encodedsize = end;
}
return size;
}
size_t size() const override
{
return ibuf_->encodedsize;
}
private:
ImBuf *ibuf_;
};
/* Utility to in-place expand an n-component pixel buffer into a 4-component buffer. */
template<typename T>
static void fill_all_channels(T *pixels, int width, int height, int components, T alpha)
{
const int64_t pixel_count = int64_t(width) * height;
if (components == 3) {
for (int64_t i = 0; i < pixel_count; i++) {
pixels[i * 4 + 3] = alpha;
}
}
else if (components == 1) {
for (int64_t i = 0; i < pixel_count; i++) {
pixels[i * 4 + 3] = alpha;
pixels[i * 4 + 2] = pixels[i * 4 + 0];
pixels[i * 4 + 1] = pixels[i * 4 + 0];
}
}
else if (components == 2) {
for (int64_t i = 0; i < pixel_count; i++) {
pixels[i * 4 + 3] = pixels[i * 4 + 1];
pixels[i * 4 + 2] = pixels[i * 4 + 0];
pixels[i * 4 + 1] = pixels[i * 4 + 0];
}
}
}
template<typename T>
static ImBuf *load_pixels(
ImageInput *in, int width, int height, int channels, int flags, bool use_all_planes)
{
/* Allocate the ImBuf for the image. */
constexpr bool is_float = sizeof(T) > 1;
const uint format_flag = is_float ? IB_rectfloat : IB_rect;
const uint ibuf_flags = (flags & IB_test) ? 0 : format_flag;
const int planes = use_all_planes ? 32 : 8 * channels;
ImBuf *ibuf = IMB_allocImBuf(width, height, planes, ibuf_flags);
if (!ibuf) {
return nullptr;
}
/* No need to load actual pixel data during the test phase. */
if (flags & IB_test) {
return ibuf;
}
/* Calculate an appropriate stride to read n-channels directly into
* the ImBuf 4-channel layout. */
const stride_t ibuf_xstride = sizeof(T) * 4;
const stride_t ibuf_ystride = ibuf_xstride * width;
const TypeDesc format = is_float ? TypeDesc::FLOAT : TypeDesc::UINT8;
uchar *rect = is_float ? reinterpret_cast<uchar *>(ibuf->rect_float) :
reinterpret_cast<uchar *>(ibuf->rect);
void *ibuf_data = rect + ((stride_t(height) - 1) * ibuf_ystride);
bool ok = in->read_image(
0, 0, 0, channels, format, ibuf_data, ibuf_xstride, -ibuf_ystride, AutoStride);
if (!ok) {
fprintf(stderr, "ImageInput::read_image() failed: %s\n", in->geterror().c_str());
IMB_freeImBuf(ibuf);
return nullptr;
}
/* ImBuf always needs 4 channels */
const T alpha_fill = is_float ? 1.0f : 0xFF;
fill_all_channels<T>(reinterpret_cast<T *>(rect), width, height, channels, alpha_fill);
return ibuf;
}
static void set_colorspace_name(char colorspace[IM_MAX_SPACE],
const ReadContext &ctx,
const ImageSpec &spec,
bool is_float)
{
const bool is_colorspace_set = (colorspace[0] != '\0');
if (is_colorspace_set) {
return;
}
/* Use a default role unless otherwise specified. */
if (ctx.use_colorspace_role >= 0) {
colorspace_set_default_role(colorspace, IM_MAX_SPACE, ctx.use_colorspace_role);
}
else if (is_float) {
colorspace_set_default_role(colorspace, IM_MAX_SPACE, COLOR_ROLE_DEFAULT_FLOAT);
}
else {
colorspace_set_default_role(colorspace, IM_MAX_SPACE, COLOR_ROLE_DEFAULT_BYTE);
}
/* Override if necessary. */
if (ctx.use_embedded_colorspace) {
string ics = spec.get_string_attribute("oiio:ColorSpace");
char file_colorspace[IM_MAX_SPACE];
BLI_strncpy(file_colorspace, ics.c_str(), IM_MAX_SPACE);
/* Only use color-spaces that exist. */
if (colormanage_colorspace_get_named(file_colorspace)) {
BLI_strncpy(colorspace, file_colorspace, IM_MAX_SPACE);
}
}
}
/**
* Get an #ImBuf filled in with pixel data and associated metadata using the provided ImageInput.
*/
static ImBuf *get_oiio_ibuf(ImageInput *in, const ReadContext &ctx, char colorspace[IM_MAX_SPACE])
{
const ImageSpec &spec = in->spec();
const int width = spec.width;
const int height = spec.height;
const int channels = spec.nchannels;
const bool has_alpha = spec.alpha_channel != -1;
const bool is_float = spec.format.basesize() > 1;
if (channels < 1 || channels > 4) {
return nullptr;
}
const bool use_all_planes = has_alpha || ctx.use_all_planes;
ImBuf *ibuf = nullptr;
if (is_float) {
ibuf = load_pixels<float>(in, width, height, channels, ctx.flags, use_all_planes);
ibuf->channels = 4;
}
else {
ibuf = load_pixels<uchar>(in, width, height, channels, ctx.flags, use_all_planes);
}
/* Fill in common ibuf properties. */
if (ibuf) {
ibuf->ftype = ctx.file_type;
ibuf->flags |= (spec.format == TypeDesc::HALF) ? IB_halffloat : 0;
set_colorspace_name(colorspace, ctx, spec, is_float);
float x_res = spec.get_float_attribute("XResolution", 0.0f);
float y_res = spec.get_float_attribute("YResolution", 0.0f);
if (x_res > 0.0f && y_res > 0.0f) {
double scale = 1.0;
auto unit = spec.get_string_attribute("ResolutionUnit", "");
if (unit == "in" || unit == "inch") {
scale = 100.0 / 2.54;
}
else if (unit == "cm") {
scale = 100.0;
}
ibuf->ppm[0] = scale * x_res;
ibuf->ppm[1] = scale * y_res;
}
/* Transfer metadata to the ibuf if necessary. */
if (ctx.flags & IB_metadata) {
IMB_metadata_ensure(&ibuf->metadata);
ibuf->flags |= (spec.extra_attribs.empty()) ? 0 : IB_metadata;
for (const auto &attrib : spec.extra_attribs) {
IMB_metadata_set_field(ibuf->metadata, attrib.name().c_str(), attrib.get_string().c_str());
}
}
}
return ibuf;
}
/**
* Returns an ImageInput for the precise `format` requested using the provided IOMemReader.
* If successful, the ImageInput will be opened and ready for operations. Null will be returned if
* the format was not found or if the open call fails.
*/
static unique_ptr<ImageInput> get_oiio_reader(const char *format,
const ImageSpec &config,
Filesystem::IOMemReader &mem_reader,
ImageSpec &r_newspec)
{
/* Attempt to create a reader based on the passed in format. */
unique_ptr<ImageInput> in = ImageInput::create(format);
if (!in) {
return nullptr;
}
/* Open the reader using the ioproxy. */
in->set_ioproxy(&mem_reader);
bool ok = in->open("", r_newspec, config);
if (!ok) {
in.reset();
}
return in;
}
bool imb_oiio_check(const uchar *mem, size_t mem_size, const char *file_format)
{
ImageSpec config, spec;
/* This memory proxy must remain alive for the full duration of the read. */
Filesystem::IOMemReader mem_reader(cspan<uchar>(mem, mem_size));
unique_ptr<ImageInput> in = get_oiio_reader(file_format, config, mem_reader, spec);
return in ? true : false;
}
ImBuf *imb_oiio_read(const ReadContext &ctx,
const ImageSpec &config,
char colorspace[IM_MAX_SPACE],
ImageSpec &r_newspec)
{
/* This memory proxy must remain alive for the full duration of the read. */
Filesystem::IOMemReader mem_reader(cspan<uchar>(ctx.mem_start, ctx.mem_size));
unique_ptr<ImageInput> in = get_oiio_reader(ctx.file_format, config, mem_reader, r_newspec);
if (!in) {
return nullptr;
}
return get_oiio_ibuf(in.get(), ctx, colorspace);
}
bool imb_oiio_write(const WriteContext &ctx, const char *filepath, const ImageSpec &file_spec)
{
unique_ptr<ImageOutput> out = ImageOutput::create(ctx.file_format);
if (!out) {
return false;
}
auto write_op = [&out, &ctx]() {
return out->write_image(
ctx.mem_format, ctx.mem_start, ctx.mem_xstride, -ctx.mem_ystride, AutoStride);
};
bool ok = false;
if (ctx.flags & IB_mem) {
/* This memory proxy must remain alive for the full duration of the write. */
ImBufMemWriter writer(ctx.ibuf);
imb_addencodedbufferImBuf(ctx.ibuf);
out->set_ioproxy(&writer);
out->open("", file_spec);
ok = write_op();
}
else {
out->open(filepath, file_spec);
ok = write_op();
}
out->close();
return ok;
}
WriteContext imb_create_write_context(const char *file_format,
ImBuf *ibuf,
int flags,
bool prefer_float)
{
WriteContext ctx{};
ctx.file_format = file_format;
ctx.ibuf = ibuf;
ctx.flags = flags;
const int width = ibuf->x;
const int height = ibuf->y;
const bool use_float = prefer_float && (ibuf->rect_float != nullptr);
if (use_float) {
const int mem_channels = ibuf->channels ? ibuf->channels : 4;
ctx.mem_xstride = sizeof(float) * mem_channels;
ctx.mem_ystride = width * ctx.mem_xstride;
ctx.mem_format = TypeDesc::FLOAT;
ctx.mem_start = reinterpret_cast<uchar *>(ibuf->rect_float);
}
else {
const int mem_channels = 4;
ctx.mem_xstride = sizeof(uchar) * mem_channels;
ctx.mem_ystride = width * ctx.mem_xstride;
ctx.mem_format = TypeDesc::UINT8;
ctx.mem_start = reinterpret_cast<uchar *>(ibuf->rect);
}
/* We always write using a negative y-stride so ensure we start at the end. */
ctx.mem_start = ctx.mem_start + ((stride_t(height) - 1) * ctx.mem_ystride);
return ctx;
}
ImageSpec imb_create_write_spec(const WriteContext &ctx, int file_channels, TypeDesc data_format)
{
const int width = ctx.ibuf->x;
const int height = ctx.ibuf->y;
ImageSpec file_spec(width, height, file_channels, data_format);
/* Populate the spec with all common attributes.
*
* Care must be taken with the metadata:
* - It should be processed first, before the "Resolution" metadata below, to
* ensure the proper values end up in the ImageSpec
* - It needs to filter format-specific metadata that may no longer apply to
* the current format being written (e.g. metadata for tiff being written to a png)
*/
if (ctx.ibuf->metadata) {
for (IDProperty *prop = static_cast<IDProperty *>(ctx.ibuf->metadata->data.group.first); prop;
prop = prop->next) {
if (prop->type == IDP_STRING) {
/* If this property has a prefixed name (oiio:, tiff:, etc.) and it belongs to
* oiio or a different format, then skip. */
if (char *colon = strchr(prop->name, ':')) {
std::string prefix(prop->name, colon);
Strutil::to_lower(prefix);
if (prefix == "oiio" ||
(!STREQ(prefix.c_str(), ctx.file_format) && OIIO::is_imageio_format_name(prefix))) {
/* Skip this attribute. */
continue;
}
}
file_spec.attribute(prop->name, IDP_String(prop));
}
}
}
if (ctx.ibuf->ppm[0] > 0.0 && ctx.ibuf->ppm[1] > 0.0) {
/* More OIIO formats support inch than meter. */
file_spec.attribute("ResolutionUnit", "in");
file_spec.attribute("XResolution", float(ctx.ibuf->ppm[0] * 0.0254));
file_spec.attribute("YResolution", float(ctx.ibuf->ppm[1] * 0.0254));
}
return file_spec;
}
} // namespace blender::imbuf

View File

@ -0,0 +1,104 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <memory>
#include <OpenImageIO/filesystem.h>
#include <OpenImageIO/imageio.h>
#include "BLI_sys_types.h"
#include "IMB_imbuf.h"
#include "IMB_imbuf_types.h"
namespace blender::imbuf {
/**
* Parameters and settings used while reading image formats.
*/
struct ReadContext {
const uchar *mem_start;
const size_t mem_size;
const char *file_format;
const eImbFileType file_type;
const int flags;
/* Override the automatic color-role choice with the value specified here. */
int use_colorspace_role = -1;
/* Allocate and use all ImBuf image planes even if the image has fewer. */
bool use_all_planes = false;
/* Use the colorspace provided in the image metadata when available. */
bool use_embedded_colorspace = false;
};
/**
* Parameters and settings used while writing image formats.
*/
struct WriteContext {
const char *file_format;
ImBuf *ibuf;
OIIO::stride_t mem_xstride;
OIIO::stride_t mem_ystride;
OIIO::TypeDesc mem_format;
uchar *mem_start;
int flags;
};
/**
* Check to see if we can load and open the given file format.
*/
bool imb_oiio_check(const uchar *mem, size_t mem_size, const char *file_format);
/**
* The primary method for reading data into an #ImBuf.
*
* During the `IB_test` phase of loading, the `colorspace` parameter will be populated
* with the appropriate colorspace name.
*
* Upon return, the `r_newspec` parameter will contain image format information
* which can be inspected afterwards if necessary.
*/
ImBuf *imb_oiio_read(const ReadContext &ctx,
const OIIO::ImageSpec &config,
char colorspace[IM_MAX_SPACE],
OIIO::ImageSpec &r_newspec);
/**
* The primary method for writing data from an #ImBuf to either a physical or in-memory
* destination.
*
* The `file_spec` parameter will typically come from #imb_create_write_spec.
*/
bool imb_oiio_write(const WriteContext &ctx,
const char *filepath,
const OIIO::ImageSpec &file_spec);
/**
* Create a #WriteContext based on the provided #ImBuf and format information.
*
* If the provided #ImBuf contains both byte and float buffers, the `prefer_float`
* flag controls which buffer to use. By default, if a float buffer exists it will
* be used.
*/
WriteContext imb_create_write_context(const char *file_format,
ImBuf *ibuf,
int flags,
bool prefer_float = true);
/**
* Returns an ImageSpec filled in with all common attributes associated with the #ImBuf
* provided as part of the #WriteContext.
*
* This includes optional metadata that has been attached to the #ImBuf and which should be
* written to the new file as necessary.
*/
OIIO::ImageSpec imb_create_write_spec(const WriteContext &ctx,
int file_channels,
OIIO::TypeDesc data_format);
} // namespace blender::imbuf

View File

@ -441,7 +441,7 @@ static void openexr_header_metadata(Header *header, struct ImBuf *ibuf)
IDProperty *prop;
for (prop = (IDProperty *)ibuf->metadata->data.group.first; prop; prop = prop->next) {
if (prop->type == IDP_STRING) {
if (prop->type == IDP_STRING && !STREQ(prop->name, "compression")) {
header->insert(prop->name, StringAttribute(IDP_String(prop)));
}
}

View File

@ -114,44 +114,7 @@ ImBuf *IMB_ibImageFromMemory(
return NULL;
}
static ImBuf *IMB_ibImageFromFile(const char *filepath,
int flags,
char colorspace[IM_MAX_SPACE],
const char *descr)
{
ImBuf *ibuf;
const ImFileType *type;
char effective_colorspace[IM_MAX_SPACE] = "";
if (colorspace) {
BLI_strncpy(effective_colorspace, colorspace, sizeof(effective_colorspace));
}
for (type = IMB_FILE_TYPES; type < IMB_FILE_TYPES_LAST; type++) {
if (type->load_filepath) {
ibuf = type->load_filepath(filepath, flags, effective_colorspace);
if (ibuf) {
imb_handle_alpha(ibuf, flags, colorspace, effective_colorspace);
return ibuf;
}
}
}
if ((flags & IB_test) == 0) {
fprintf(stderr, "%s: unknown fileformat (%s)\n", __func__, descr);
}
return NULL;
}
static bool imb_is_filepath_format(const char *filepath)
{
/* return true if this is one of the formats that can't be loaded from memory */
return BLI_path_extension_check_array(filepath, imb_ext_image_filepath_only);
}
ImBuf *IMB_loadifffile(
int file, const char *filepath, int flags, char colorspace[IM_MAX_SPACE], const char *descr)
ImBuf *IMB_loadifffile(int file, int flags, char colorspace[IM_MAX_SPACE], const char *descr)
{
ImBuf *ibuf;
uchar *mem;
@ -161,10 +124,6 @@ ImBuf *IMB_loadifffile(
return NULL;
}
if (imb_is_filepath_format(filepath)) {
return IMB_ibImageFromFile(filepath, flags, colorspace, descr);
}
size = BLI_file_descriptor_size(file);
imb_mmap_lock();
@ -198,7 +157,7 @@ ImBuf *IMB_loadiffname(const char *filepath, int flags, char colorspace[IM_MAX_S
return NULL;
}
ibuf = IMB_loadifffile(file, filepath, flags, colorspace, filepath);
ibuf = IMB_loadifffile(file, flags, colorspace, filepath);
if (ibuf) {
BLI_strncpy(ibuf->name, filepath, sizeof(ibuf->name));
@ -277,7 +236,7 @@ ImBuf *IMB_testiffname(const char *filepath, int flags)
return NULL;
}
ibuf = IMB_loadifffile(file, filepath, flags | IB_test | IB_multilayer, colorspace, filepath);
ibuf = IMB_loadifffile(file, flags | IB_test | IB_multilayer, colorspace, filepath);
if (ibuf) {
BLI_strncpy(ibuf->name, filepath, sizeof(ibuf->name));

View File

@ -67,13 +67,6 @@ const char *imb_ext_image[] = {
NULL,
};
const char *imb_ext_image_filepath_only[] = {
".psd",
".pdd",
".psb",
NULL,
};
const char *imb_ext_movie[] = {
".avi", ".flc", ".mov", ".movie", ".mp4", ".m4v", ".m2v", ".m2t", ".m2ts", ".mts",
".ts", ".mv", ".avs", ".wmv", ".ogv", ".ogg", ".r3d", ".dv", ".mpeg", ".mpg",

View File

@ -145,10 +145,10 @@ void build_material_map(const Main *bmain, std::map<std::string, Material *> *r_
* Returns an existing Blender material that corresponds to the USD material with the given path.
* Returns null if no such material exists.
*
* \param mat_map Map a material name to a Blender material. Note that the name key
* \param mat_map: Map a material name to a Blender material. Note that the name key
* might be the Blender material name modified to be a valid USD identifier,
* to match the material names in the imported USD.
* \param usd_path_to_mat_name Map a USD material path to the imported Blender material name.
* \param usd_path_to_mat_name: Map a USD material path to the imported Blender material name.
*
* The usd_path_to_mat_name is needed to determine the name of the Blender
* material imported from a USD path in the case when a unique name was generated

View File

@ -505,7 +505,7 @@ typedef struct bGPDlayer {
struct Object *parent;
/** Inverse matrix (only used if parented). */
float inverse[4][4];
/** String describing subobject info, MAX_ID_NAME-2. */
/** String describing sub-object info, `MAX_ID_NAME - 2`. */
char parsubstr[64];
short partype;
@ -574,7 +574,7 @@ typedef enum eGPDlayer_Flag {
GP_LAYER_SELECT = (1 << 5),
/* current frame for layer can't be changed */
GP_LAYER_FRAMELOCK = (1 << 6),
/* don't render xray (which is default) */
/* Don't render X-ray (which is default). */
GP_LAYER_NO_XRAY = (1 << 7),
/* "volumetric" strokes */
GP_LAYER_VOLUMETRIC = (1 << 10),
@ -852,7 +852,7 @@ typedef enum eGP_OnionModes {
GP_ONION_MODE_SELECTED = 2,
} eGP_OnionModes;
/* xray modes (Depth Ordering) */
/* X-ray modes (Depth Ordering). */
typedef enum eGP_DepthOrdering {
GP_XRAY_FRONT = 0,
GP_XRAY_3DSPACE = 1,

View File

@ -2296,10 +2296,6 @@ typedef enum eSnapMode {
SCE_SNAP_MODE_EDGE_PERPENDICULAR = (1 << 5),
SCE_SNAP_MODE_FACE_NEAREST = (1 << 8),
SCE_SNAP_MODE_GEOM = (SCE_SNAP_MODE_VERTEX | SCE_SNAP_MODE_EDGE | SCE_SNAP_MODE_FACE_RAYCAST |
SCE_SNAP_MODE_EDGE_PERPENDICULAR | SCE_SNAP_MODE_EDGE_MIDPOINT |
SCE_SNAP_MODE_FACE_NEAREST),
/** #ToolSettings.snap_node_mode */
SCE_SNAP_MODE_NODE_X = (1 << 0),
SCE_SNAP_MODE_NODE_Y = (1 << 1),
@ -2314,6 +2310,10 @@ typedef enum eSnapMode {
ENUM_OPERATORS(eSnapMode, SCE_SNAP_MODE_GRID)
#endif
#define SCE_SNAP_MODE_GEOM \
(SCE_SNAP_MODE_VERTEX | SCE_SNAP_MODE_EDGE | SCE_SNAP_MODE_FACE_RAYCAST | \
SCE_SNAP_MODE_EDGE_PERPENDICULAR | SCE_SNAP_MODE_EDGE_MIDPOINT | SCE_SNAP_MODE_FACE_NEAREST)
/** #SequencerToolSettings.snap_mode */
#define SEQ_SNAP_TO_STRIPS (1 << 0)
#define SEQ_SNAP_TO_CURRENT_FRAME (1 << 1)

View File

@ -218,6 +218,51 @@ static StructRNA *rna_FModifierType_refine(struct PointerRNA *ptr)
# include "DEG_depsgraph.h"
# include "DEG_depsgraph_build.h"
/**
* \warning this isn't efficient but it's unavoidable
* when only the #ID and the #DriverVar are known.
*/
static FCurve *rna_FCurve_find_driver_by_variable(ID *owner_id, DriverVar *dvar)
{
AnimData *adt = BKE_animdata_from_id(owner_id);
BLI_assert(adt != NULL);
LISTBASE_FOREACH (FCurve *, fcu, &adt->drivers) {
ChannelDriver *driver = fcu->driver;
if (driver == NULL) {
continue;
}
if (BLI_findindex(&driver->variables, dvar) != -1) {
return fcu;
}
}
return NULL;
}
/**
* \warning this isn't efficient but it's unavoidable
* when only the #ID and the #DriverTarget are known.
*/
static FCurve *rna_FCurve_find_driver_by_target(ID *owner_id, DriverTarget *dtar)
{
AnimData *adt = BKE_animdata_from_id(owner_id);
BLI_assert(adt != NULL);
LISTBASE_FOREACH (FCurve *, fcu, &adt->drivers) {
ChannelDriver *driver = fcu->driver;
if (driver == NULL) {
continue;
}
LISTBASE_FOREACH (DriverVar *, dvar, &driver->variables) {
/* NOTE: Use #MAX_DRIVER_TARGETS instead of `dvar->num_targets` because
* it's possible RNA holds a reference to a target that has been removed.
* In this case it's best to return the #FCurve it belongs to instead of nothing. */
if (ARRAY_HAS_ITEM(dtar, &dvar->targets[0], MAX_DRIVER_TARGETS)) {
return fcu;
}
}
}
return NULL;
}
static bool rna_ChannelDriver_is_simple_expression_get(PointerRNA *ptr)
{
ChannelDriver *driver = ptr->data;
@ -225,20 +270,27 @@ static bool rna_ChannelDriver_is_simple_expression_get(PointerRNA *ptr)
return BKE_driver_has_simple_expression(driver);
}
static void rna_ChannelDriver_update_data(Main *bmain, Scene *scene, PointerRNA *ptr)
static void rna_ChannelDriver_update_data_impl(Main *bmain,
Scene *scene,
ID *owner_id,
ChannelDriver *driver)
{
ID *id = ptr->owner_id;
ChannelDriver *driver = ptr->data;
driver->flag &= ~DRIVER_FLAG_INVALID;
/* TODO: this really needs an update guard... */
DEG_relations_tag_update(bmain);
DEG_id_tag_update(id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
DEG_id_tag_update(owner_id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
WM_main_add_notifier(NC_SCENE | ND_FRAME, scene);
}
static void rna_ChannelDriver_update_data(Main *bmain, Scene *scene, PointerRNA *ptr)
{
ID *id = ptr->owner_id;
ChannelDriver *driver = ptr->data;
rna_ChannelDriver_update_data_impl(bmain, scene, id, driver);
}
static void rna_ChannelDriver_update_expr(Main *bmain, Scene *scene, PointerRNA *ptr)
{
ChannelDriver *driver = ptr->data;
@ -252,33 +304,47 @@ static void rna_ChannelDriver_update_expr(Main *bmain, Scene *scene, PointerRNA
static void rna_DriverTarget_update_data(Main *bmain, Scene *scene, PointerRNA *ptr)
{
PointerRNA driverptr;
ChannelDriver *driver;
FCurve *fcu;
AnimData *adt = BKE_animdata_from_id(ptr->owner_id);
/* find the driver this belongs to and update it */
for (fcu = adt->drivers.first; fcu; fcu = fcu->next) {
driver = fcu->driver;
fcu->flag &= ~FCURVE_DISABLED;
if (driver) {
/* FIXME: need to be able to search targets for required one. */
// BLI_findindex(&driver->targets, ptr->data) != -1)
RNA_pointer_create(ptr->owner_id, &RNA_Driver, driver, &driverptr);
rna_ChannelDriver_update_data(bmain, scene, &driverptr);
}
DriverTarget *dtar = (DriverTarget *)ptr->data;
FCurve *fcu = rna_FCurve_find_driver_by_target(ptr->owner_id, dtar);
BLI_assert(fcu); /* This hints at an internal error, data may be corrupt. */
if (UNLIKELY(fcu == NULL)) {
return;
}
/* Find function ensures it's never NULL. */
ChannelDriver *driver = fcu->driver;
fcu->flag &= ~FCURVE_DISABLED;
rna_ChannelDriver_update_data_impl(bmain, scene, ptr->owner_id, driver);
}
static void rna_DriverTarget_update_name(Main *bmain, Scene *scene, PointerRNA *ptr)
static void rna_DriverVariable_update_name(Main *bmain, Scene *scene, PointerRNA *ptr)
{
ChannelDriver *driver = ptr->data;
rna_DriverTarget_update_data(bmain, scene, ptr);
DriverVar *dvar = (DriverVar *)ptr->data;
FCurve *fcu = rna_FCurve_find_driver_by_variable(ptr->owner_id, dvar);
BLI_assert(fcu); /* This hints at an internal error, data may be corrupt. */
if (UNLIKELY(fcu == NULL)) {
return;
}
/* Find function ensures it's never NULL. */
ChannelDriver *driver = fcu->driver;
fcu->flag &= ~FCURVE_DISABLED;
rna_ChannelDriver_update_data_impl(bmain, scene, ptr->owner_id, driver);
BKE_driver_invalidate_expression(driver, false, true);
}
static void rna_DriverVariable_update_data(Main *bmain, Scene *scene, PointerRNA *ptr)
{
DriverVar *dvar = (DriverVar *)ptr->data;
FCurve *fcu = rna_FCurve_find_driver_by_variable(ptr->owner_id, dvar);
BLI_assert(fcu); /* This hints at an internal error, data may be corrupt. */
if (UNLIKELY(fcu == NULL)) {
return;
}
/* Find function ensures it's never NULL. */
ChannelDriver *driver = fcu->driver;
fcu->flag &= ~FCURVE_DISABLED;
rna_ChannelDriver_update_data_impl(bmain, scene, ptr->owner_id, driver);
}
/* ----------- */
/* NOTE: this function exists only to avoid id reference-counting. */
@ -1951,14 +2017,14 @@ static void rna_def_drivervar(BlenderRNA *brna)
"Name",
"Name to use in scripted expressions/functions (no spaces or dots are allowed, "
"and must start with a letter)");
RNA_def_property_update(prop, 0, "rna_DriverTarget_update_name"); /* XXX */
RNA_def_property_update(prop, 0, "rna_DriverVariable_update_name");
/* Enums */
prop = RNA_def_property(srna, "type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, prop_type_items);
RNA_def_property_enum_funcs(prop, NULL, "rna_DriverVariable_type_set", NULL);
RNA_def_property_ui_text(prop, "Type", "Driver variable type");
RNA_def_property_update(prop, 0, "rna_ChannelDriver_update_data"); /* XXX */
RNA_def_property_update(prop, 0, "rna_DriverVariable_update_data");
/* Targets */
/* TODO: for nicer api, only expose the relevant props via subclassing,

View File

@ -989,7 +989,11 @@ static PyObject *bpy_bmesh_free(BPy_BMesh *self)
bm_dealloc_editmode_warn(self);
if ((self->flag & BPY_BMFLAG_IS_WRAPPED) == 0) {
if (self->flag & BPY_BMFLAG_IS_WRAPPED) {
/* Ensure further access doesn't return this invalid object, see: #105715. */
bm->py_handle = NULL;
}
else {
BM_mesh_free(bm);
}

View File

@ -480,7 +480,7 @@ static PyObject *M_imbuf_load(PyObject *UNUSED(self), PyObject *args, PyObject *
return NULL;
}
ImBuf *ibuf = IMB_loadifffile(file, filepath, IB_rect, NULL, filepath);
ImBuf *ibuf = IMB_loadifffile(file, IB_rect, NULL, filepath);
close(file);

View File

@ -247,6 +247,7 @@ static int count_items(PyObject *seq, int dim)
static int validate_array_length(PyObject *rvalue,
PointerRNA *ptr,
PropertyRNA *prop,
const bool prop_is_param_dyn_alloc,
int lvalue_dim,
int *r_totitem,
const char *error_prefix)
@ -266,26 +267,20 @@ static int validate_array_length(PyObject *rvalue,
return -1;
}
if ((RNA_property_flag(prop) & PROP_DYNAMIC) && lvalue_dim == 0) {
if (RNA_property_array_length(ptr, prop) != tot) {
#if 0
/* length is flexible */
if (!RNA_property_dynamic_array_set_length(ptr, prop, tot)) {
/* BLI_snprintf(error_str, error_str_size,
* "%s.%s: array length cannot be changed to %d",
* RNA_struct_identifier(ptr->type), RNA_property_identifier(prop), tot); */
const int tot_expected = RNA_property_array_length(ptr, prop);
if (tot_expected != tot) {
*r_totitem = tot;
if (!prop_is_param_dyn_alloc) {
PyErr_Format(PyExc_ValueError,
"%s %s.%s: array length cannot be changed to %d",
"%s %s.%s: array length cannot be changed to %d (expected %d)",
error_prefix,
RNA_struct_identifier(ptr->type),
RNA_property_identifier(prop),
tot);
tot,
tot_expected);
return -1;
}
#else
*r_totitem = tot;
return 0;
#endif
}
len = tot;
@ -340,6 +335,7 @@ static int validate_array_length(PyObject *rvalue,
static int validate_array(PyObject *rvalue,
PointerRNA *ptr,
PropertyRNA *prop,
const bool prop_is_param_dyn_alloc,
int lvalue_dim,
ItemTypeCheckFunc check_item_type,
const char *item_type_str,
@ -410,7 +406,8 @@ static int validate_array(PyObject *rvalue,
return -1;
}
return validate_array_length(rvalue, ptr, prop, lvalue_dim, r_totitem, error_prefix);
return validate_array_length(
rvalue, ptr, prop, prop_is_param_dyn_alloc, lvalue_dim, r_totitem, error_prefix);
}
}
@ -527,15 +524,26 @@ static int py_to_array(PyObject *seq,
char *data = NULL;
// totdim = RNA_property_array_dimension(ptr, prop, dim_size); /* UNUSED */
const int flag = RNA_property_flag(prop);
if (validate_array(seq, ptr, prop, 0, check_item_type, item_type_str, &totitem, error_prefix) ==
-1) {
/* Use #ParameterDynAlloc which defines it's own array length. */
const bool prop_is_param_dyn_alloc = param_data && (flag & PROP_DYNAMIC);
if (validate_array(seq,
ptr,
prop,
prop_is_param_dyn_alloc,
0,
check_item_type,
item_type_str,
&totitem,
error_prefix) == -1) {
return -1;
}
if (totitem) {
/* NOTE: this code is confusing. */
if (param_data && RNA_property_flag(prop) & PROP_DYNAMIC) {
if (prop_is_param_dyn_alloc) {
/* not freeing allocated mem, RNA_parameter_list_free() will do this */
ParameterDynAlloc *param_alloc = (ParameterDynAlloc *)param_data;
param_alloc->array_tot = (int)totitem;
@ -626,9 +634,16 @@ static int py_to_array_index(PyObject *py,
copy_value_single(py, ptr, prop, NULL, 0, &index, convert_item, rna_set_index);
}
else {
if (validate_array(
py, ptr, prop, lvalue_dim, check_item_type, item_type_str, &totitem, error_prefix) ==
-1) {
const bool prop_is_param_dyn_alloc = false;
if (validate_array(py,
ptr,
prop,
prop_is_param_dyn_alloc,
lvalue_dim,
check_item_type,
item_type_str,
&totitem,
error_prefix) == -1) {
return -1;
}

View File

@ -195,6 +195,76 @@ class TestPropArrayMultiDimensional(unittest.TestCase):
del id_type.temp
class TestPropArrayDynamicAssign(unittest.TestCase):
"""
Pixels are dynamic in the sense the size can change however the assignment does not define the size.
"""
dims = 12
def setUp(self):
self.image = bpy.data.images.new("", self.dims, self.dims)
def tearDown(self):
bpy.data.images.remove(self.image)
self.image = None
def test_assign_fixed_under_1px(self):
image = self.image
with self.assertRaises(ValueError):
image.pixels = [1.0, 1.0, 1.0, 1.0]
def test_assign_fixed_under_0px(self):
image = self.image
with self.assertRaises(ValueError):
image.pixels = []
def test_assign_fixed_over_by_1px(self):
image = self.image
with self.assertRaises(ValueError):
image.pixels = ([1.0, 1.0, 1.0, 1.0] * (self.dims * self.dims)) + [1.0]
def test_assign_fixed(self):
# Valid assignment, ensure it works as intended.
image = self.image
values = [1.0, 0.0, 1.0, 0.0] * (self.dims * self.dims)
image.pixels = values
self.assertEqual(tuple(values), tuple(image.pixels))
class TestPropArrayDynamicArg(unittest.TestCase):
"""
Index array, a dynamic array argument which defines it's own length.
"""
dims = 8
def setUp(self):
self.me = bpy.data.meshes.new("")
self.me.vertices.add(self.dims)
self.ob = bpy.data.objects.new("", self.me)
def tearDown(self):
bpy.data.objects.remove(self.ob)
bpy.data.meshes.remove(self.me)
self.me = None
self.ob = None
def test_param_dynamic(self):
ob = self.ob
vg = ob.vertex_groups.new(name="")
# Add none.
vg.add(index=(), weight=1.0, type='REPLACE')
for i in range(self.dims):
with self.assertRaises(RuntimeError):
vg.weight(i)
# Add all.
vg.add(index=range(self.dims), weight=1.0, type='REPLACE')
self.assertEqual(tuple([1.0] * self.dims), tuple([vg.weight(i) for i in range(self.dims)]))
if __name__ == '__main__':
import sys
sys.argv = [__file__] + (sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [])