Grease Pencil: Code design issue with operators

I have been working on the “GSoC editing grease pencil strokes using curves” project with Antonio. But we hit a structural code design issue that we both don’t really know how to solve. We thought it would be a good idea to share the problem with other developers and see if we can get some help.

So here is the problem: the new “mode” (in the code it’s not a mode per se. It’s a boolean flag attached to the grease pencil object) that we are trying to build, allows the user to convert strokes to bezier curves and vice versa. We call it “curve edit mode”. One important aspect of the conversion from stroke to curve is that, ideally, the user has control over how good the curve fits the stroke. We call this parameter the error_threshold. A conversion from stroke to curve should happen automatically when:

  • the user is in Edit Mode, selects one or more stroke(s), and enters curve edit mode.
  • the user is in curve edit mode and selects a stroke that was not yet converted.

In the two cases above, the user should have control over the error_threshold to adjust the fit as needed.
We think it would be best to have the conversion (from stroke to curve) be capsulated inside an operator (say GPENCIL_OT_convert_stroke_to_curve). This way, the error_threshold value can be added as a parameter and changed in the redo panel. Now our issue is that we don’t know how to achieve this in the Blender operator architecture. We need to call an operator conditionally from another one (select operator), such that the redo panel contains the parameters from both the first and second one (namely the error_threshold).

If the current operator system does not allow for what is described above, then we need to think of some other way of achieving this goal. Maybe there are some tricks here that we just don’t know about.

We already tried the following (which ended up not working): Simply put the error_threshold parameter in the select operator and call the BKE functions for the conversion directly. The issue with that is there is no good way of not showing the parameter in the redo panel, when it is not needed (e.g. the user selects a stroke that already was converted to a curve). We can only decide when we need the conversion after we find which stroke to select (which gets done in the execute-part of the select operator). So we can’t check for this in the poll function. Either way, we think its not a good idea to have the select operators handle these two different functionalities.

It’s important to note that we cannot convert all strokes to Bezie rmode because there are strokes that are no logic to be edited as bezier curve, for example the new spray brush type that generate strokes like a paint spray.

This doesn’t sound like it should be an operator setting, but a mode setting like for example the Auto Merge Threshold in mesh edit mode.

It’s not clear to me that converting strokes to curves should be an operator at all. If the code needs to perform some kind of automatic conversion or update on selection that’s fine. But why not just call a regular C function rather than an operator for that?

And shouldn’t this also work when selecting strokes through the Python API? To me this seems more like some kind of cached data that is automatically updated in response to selection changes.

Yes, this is indeed similar. But I think it’s missing something that we need, which is the possibility to change the parameter (in case of auto merge the distance threshold) and immediately see the result. This wouldn’t make much sense in the case of auto merge, but it’s crucial for our case. Basically there is no good “default” value for our operation, so the user will have to choose a value more often than not. It would be very cumbersome to: pick a threshold value in an option dropdown, do the operation, not liking the result, have to undo the operation, pick a different value, and so on…
Here is a demo video on what I am trying to say. (Hopefully this clears things up.)

I tried to explain why this is an issue in the last paragraph.

No, I don’t think so. The Python API should expose functions that can convert a stroke to a curve. Really this behavior is more of a quality-of-life feature for the user, imho.

I assume curve editing will be a tool or a mode, so the option for this can be in the tool settings bar? To me it seems that from a user point of view, this is something you set before using the curve editing tool, not after you select.

The number button can be directly in the bar for quick access, it doesn’t have to be in a popover.

Its more of a mode, not a tool. I think we will just have it as a parameter for now. But I would like to come back to this at some point because of what I wrote before:

And this is not solved with that approach. At least not from what I understand.

I’m suggesting to put it in the tool settings bar, which is always visible in the default setup. And changing the value should refresh the cache immediately.

Doing this as an property for selection operators is really the wrong approach, we should not do it that way.

I understand, but there is an issue here and I should’ve explained this earlier. When we fit the curve to the stroke, we actually overwrite the previous stroke data immediately by sampling points on the curve. This is done because we didn’t want to change anything in the drawing engine etc. So in order for this approach to work, we would have to have a copy of the previous stroke data and a flag that indicates, that the curve has not been changed yet (if it was, a refitting would be pointless). There was the idea of using bGPDstroke_runtime to allocate a copy.

It seems like a usability problem if merely selecting data will actually modify it? I don’t think we do this anywhere else.

I think the argument that this is not done elsewhere is not a very valid argument here. The 3D and 2D workflow is totally different and in grease pencil we have lots of things that are only done in grease pencil. Of course, we must maintain a general philosophy in Blender, but at the same time we must achieve an efficient workflow. If we don’t have an efficient workflow, everything else is useless.

Actually, the idea of ​​doing a conversion by clicking on a stroke is very similar to when you extrude on a vertex of a mesh. This extrude performs the creation of a vertex and then executes the transform operator. In this case it would be, for example, as a selection operator and automatically run a smooth operator where you can adjust the level.

We could do the conversion as a separate operator with the redo panel, but then the user would have to first select the Bezier conversion tool, do the conversion and adjust the error level, and then go back to the normal selection tool for handle the curve handles. If you have to do this 500 times in each drawing session, it is logical to try to do everything in one process.

We have to think that in a 3D workflow, the user usually stays in the same mode for a long time (Edition, Sculpt, etc.), but in the 2D workflow, the change between modes is continuous and with this new edition, the change between “Normal” edition and “Bezier” edition will be continuous, so every click we can save is important.

Currently the conversion operator is only used to support the Redo panel, but all the functionality is actually built into BKE functions. In any case, if it is impossible to do so, we will try to separate it.

2 Likes

I don’t think this is really a 2D vs. 3D issue. I’m fine with clicking on a stroke both selecting and resampling it, but then it should be a curve editing tool that does that, not the regular selection tool or operator? And then you can add whatever properties you need for that curve editing tool.