1
1

Compare commits

...

148 Commits

Author SHA1 Message Date
ef2aecf2db Merge branch 'master' into GPencil_Editing_Stage3 2015-12-13 20:33:35 +13:00
cf8980559f Revert previous eraser tweak - It made using the mouse to erase too difficult 2015-12-13 20:29:47 +13:00
7fd36af698 GP Sculpt: Cleanup old comments and some debug prints 2015-12-13 13:04:33 +13:00
a72bbd9214 GPencil EditMode Keymap: Asterisk/Star Key on Numpad can be used to toggle Isolate Layers 2015-12-13 12:53:18 +13:00
23656a54fd GPencil Eraser: Some tweaks to eraser strength
Damping factors make the eraser too hard to use in general cases, so leaving as-is.
2015-12-13 12:46:37 +13:00
4213acedca Fix: Header help text was not defined for Grease Pencil "Poly" drawmode 2015-12-13 12:25:51 +13:00
34241fe6bb GPencil UI: Added a few extra icons for decoration 2015-12-13 12:21:44 +13:00
35db71ddb7 GPencil EditMode Keymap: Alt-C now activates the "Convert" tool for Grease Pencil (instead of the object-mode one) 2015-12-13 12:02:03 +13:00
4bbc2cc1b1 Code Cleanup: Remove code for old GP eraser 2015-12-13 12:01:31 +13:00
ced987b5b4 GPencil Eraser: Eraser is now pressure sensitive
Instead of immediately erasing points, the eraser now first reduces the thickness
of the stroke points before culling the ones that have become overly thin (and
cannot be rendered properly anyway as a result). It is also pressure sensitive now,
and has a linear falloff effect (i.e. points further away from the center of the
eraser circle get affected less). This should make it easier to use to eraser to
make fine adjustments to your sketches.

Notes:
* The eraser has been rewritten to use a new algorithm. Specifically, it now takes
  advantage of the stroke point deletion methods developed for the Delete operator
  instead of it's own buggy split-and-delete methods (which were only removing
  a single point from each stroke each time).

* In tests so far, it seems to allow a wider range of control over what gets erased
  and what doesn't. Pressing hard (or using a mouse) should still just erase everything
  with relative ease still.

* The way that this behaves could still do with some tweaking. In particular, care
  is needed when trying to use the eraser to "lighten up" tips of thin bunches of
  strokes (e.g. eyebrows/moustaches), as it still tends to gobble these up too much.
  We could probably do with some tool settings to control the eraser strength.
2015-12-13 03:23:37 +13:00
d1a3223c76 Fix: Delete tagged GPencil stroke points now adjusts timing info
gp_stroke_delete_tagged_points() now adjusts timing data for stroke points
to ensure that all the timing info will be valid after creating the new
stroke segments (from splitting the original stroke).

Several other tools need to be modified to do this still (e.g. Copy and Duplicate)
2015-12-13 02:30:09 +13:00
c99dc5a1c1 GPencil: Code cleanup - Moved logic for removing selected/tagged points from strokes into a separate function 2015-12-13 00:45:19 +13:00
833268e2b8 Compile fix for changes from master 2015-12-12 17:52:52 +13:00
50fde02b42 Merge branch 'master' into GPencil_Editing_Stage3
Conflicts:
	source/blender/editors/transform/transform_manipulator.c
2015-12-12 17:43:00 +13:00
85bbf7e04f Code Cleanup: Move away from inlined math for the eraser tests 2015-12-12 12:26:53 +13:00
418b908309 Some code cleanup - Using proper types in more places 2015-12-12 01:27:04 +13:00
b3072b5617 Fix: Fixed incorrect tooltip
This tooltip was missed when was copying over the RNA stuff from my
earlier Pose Sculpting work (that much of these tools were based on)
2015-12-11 19:28:05 +13:00
fae7a8b97a GPencil Sculpt: Expose setting for toggling whether to invert the way the brush works
As in mesh sculptmode, there is now an Add/Subtract toggle to control whether the
brush applies the named effect or not  (e.g. Thickness = Thicker/Thinner, or
Pinch = Pinch/Inflate)
2015-12-11 19:25:50 +13:00
0175834112 Fix: Keep onion skinning toggles in sync
When toggling the per-layer onionskinning settings, the datablock-level
toggle (shown on the header) should also be updated. This only really
applies when there is/was a single layer with onionskinning.
2015-12-11 18:13:36 +13:00
1806db07e7 GPencil Eraser: Operates on all visible + editable layers instead of active only
The GPencil eraser now operates on all visible and editable layers, instead of
only operating on the active layer. There was really no reason why it needed to
only consider the active layer, as doing so only made it confusing for the user when
the strokes they were trying to erase wouldn't be affected. Besides, if you really
needed to restrict the eraser to working on a particular layer, you could just
lock all the others (which is now very easy with the new operators)
2015-12-11 17:29:48 +13:00
9afd9dc78e Fix: Save off GPencil eraser size earlier, to prevent it from being zeroed if something goes wrong (e.g. locked layers) in the meantime 2015-12-11 17:27:32 +13:00
ca8184c6cc Fix: Grease Pencil eraser would operate on strokes that were not valid for the current context
For example, if a GP datablock was shared between two different types of editors
(e.g. 3D and Sequencer Preview), even the non-valid (and invisible) strokes would
get affected.
2015-12-11 15:55:03 +13:00
5368505ef3 GPencil Layer Management: Lock/Unlock All + Isolate Layer
This commit introduces a few operators to make it easier to perform a few common
layer-manipulation operations. Some of these have been sorely needed for quite
a while now...

* Lock/Unlock All - Just as their names suggest, these operators will lock and unlock
  all layers in the GP datablock. This is a quick way to unlock all layers previously
  locked. These can be found in the new dropdown which replaces the old "Duplicate"
  below the +/- (for adding/removing layers); also featured in the dropdown are
  the "Duplicate Layers" operator, as well as the show/hide ones.

* Isolate Layer - This operator makes it easy to focus on just a single layer (e.g. the
  outlines for a particular character). The "star" button affects editability, while the
  "eye" below it toggles editability + visibility.

  If any layer is visible/unlocked, this operator will lock and/or hide all; otherwise,
  it will unlock/unhide all (to reverse the previous operation).
2015-12-10 19:11:19 +13:00
dda03a24b9 GPencil: On second thought, the status indicator doesn't need to be shown at all in the 3D View! 2015-12-07 18:47:21 +13:00
2167d521cc GPencil: Do not show "stroke editing" indicator when in "Only Render" mode in the 3D View 2015-12-07 18:46:45 +13:00
ca966644a8 3D View: Object name changes colour on frames with GP keyframes (if no other keyframes are present)
The colour used needs to be tweaked/changed (TH_TIME_GP_KEYFRAME would be ideal,
but since it's probably not initialised in many themes, we'll stick with
TH_CFRAME for now, even if it looks a bit rough)
2015-12-07 02:49:27 +13:00
1f93ec386b Fix: Stroke Placement options were not getting shown in the Sequencer 2015-12-07 02:28:46 +13:00
50111e83e7 GPencil UI: Spacing tweaks 2015-12-07 02:27:54 +13:00
2cdd34eb8c GPencil Drawing Usability: "Additive Drawing"
This commit adds a new option which will make it easier to use the Grease Pencil
drawing tools for animation. With the "Additive Drawing" option enabled in the
toolshelf (located alongside the "Continuous Drawing" toggle), the active frame's
strokes will be carried over/copied if you start drawing on an empty frame (i.e.
one without any keyframe already). This saves the effort of keeping a dopesshet
open, and to remember to duplicate the current frame before starting to draw the
next pose (or risk managing to draw the perfect pose, but without everything else).

Examples of cases where this comes in handy includes animating facial expressions
(when all outlines are on the same layer), or animating "growing" things (e.g. vines,
or concentric circles growing from a central point).

Note: Even without this option enabled, this is the default behaviour when using
the eraser on an "empty" frame. This makes it easier to do shots where you're just
changing parts of the facial expression, or if you're animating an "eraser" effect.

Note 2: The naming of this feature could do with some work. I struggled with several
alternative names for it, but this one seems the most useful about what it helps users
do. Suggestions welcome!
2015-12-07 02:21:29 +13:00
f501a4dd96 GPencil: Code Cleanup - Deduplicate code for adding a copy of the active frame (for editing) 2015-12-07 01:27:12 +13:00
80b1620cf3 GPencil: Moved Stroke Placement settings from GP datablock to tool settings
This commit moves the Stroke Placement settings from being a per-datablock
setting to being stored in tool settings instead. The main reason this was
done was to resolve a usability issue with the old way: in order to set this
setting, you had to first create a GP datablock to be able to see the settings!

Now, by having these as tool settings (as they should have been from the start),
they can be always visible (even when there isn't a datablock yet), meaning that
it will work as expected from the very first stroke.


Implementation Notes:
* There are separate Stroke Placement properties for the different editor types.
  e.g. 3D View, Sequencer Preview, Image Editor, and all other 2D Views

* I couldn't get the version patching code working (to port over placement settings
  from the GP datablock attached to the active scene), as this was causing crashes
  and other general weirdness when loading old files. The code to do this is there,
  but #if 0'ed out.
2015-12-06 03:46:15 +13:00
397f78f4be GPencil: Usability Tweaks to "Onion Skins" toggle on header
* The state of this toggle now reflects whether onion skinning is enabled on
  any layer, instead of just the active layer. This is handy if there's more than
  one GP datablock being shown in the scene, and you need to track down whether it's
  the current GP datablock that has a layer with this enabled

* Disabling the toggle now turns off onion skinning on ALL layers at the same time.
  This makes it much faster to turn off onion skinning if you've got it enabled
  on several layers and would now like to disable it (e.g. for a "clean" pass over
  the anim)

* Toggling this button still enables onion skinning for just the active layer,
  since that's the one you're most likely to want to see these for (i.e. the character
  outlines are on this layer, but some of the background elements are animated, and you
  don't really want to see those ghosted)
2015-12-04 02:28:57 +13:00
34d4c7c85f GPencil Dopesheet: Filtering option to only include GPencil layers related to the active scene
As requested by the Caminandes team, in the "Grease Pencil" mode of the dopesheet,
it is now possible to view only the Grease Pencil blocks+layers which come from the
active scene and/or the objects in that scene. This is useful when working with
multiple scenes, or when there are many other GPencil datablocks in the file for
various editors that aren't visible.

To use, enable the 'Active Only' toggle with the scene icon (located beside the
Summary toggle). By default, this is not enabled, so that backwards comptability
with old files is not affected (i.e. users are less likely to why they are no longer
able to see keyframes that would've previously been easily visible).
2015-12-03 21:59:24 +13:00
f4b8d2ef7a GPencil Dopesheet: Layers can now be filtered by name
This can be enabled using the "filter text" settings on the header (magnifying glass)
as in the other Dope Sheet modes.
2015-12-03 00:02:47 +13:00
aacd18a73d GPencil DopeSheet Mode: Code cleanup for anim filtering code in preparation for some new features
* Split out the code used to generate channels for a datablock out into its own method.
  This will be needed when we do some more useful filtering options

* Pass down dopesheet info
2015-12-02 19:31:33 +13:00
4f2a3437a3 Code cleanup: Minor formatting stuff 2015-12-02 18:55:14 +13:00
1b0f017a83 Merge branch 'master' into GPencil_Editing_Stage3 2015-11-25 23:43:55 +13:00
33b03e7c18 GP Sculpt: Attempted fix for bug with Grab brush where some points would randomly get left behind 2015-11-24 02:03:25 +13:00
59a9e4027a Fix: Initialise GP Sculpt settings when creating new scenes 2015-11-23 23:57:08 +13:00
6f6746fb01 Fix compile errors due to recent cleanups in master 2015-11-23 23:46:42 +13:00
e57fb9dae7 Merge branch 'master' into GPencil_Editing_Stage3
Conflicts:
	source/blender/makesrna/RNA_enum_types.h
	source/blender/makesrna/intern/rna_sculpt_paint.c
2015-11-23 23:15:57 +13:00
019a4ffcbe Merge branch 'master' into GPencil_Editing_Stage3
Conflicts:
	source/blender/blenloader/intern/versioning_270.c
2015-11-21 00:01:46 +13:00
3e178d58c0 Fix: Transforming GP Keyframes in DopeSheet was no longer possible when in EditMode
This was a consequence of the hack used to ensure that the jump keyframes operator
could correctly use GP keyframes when in the timeline and action editors.
2015-11-18 19:43:11 +13:00
fcc58a6f94 Fix: GPencil snap cursor to selected wasn't working
Min-max weren't getting initialised correctly, causing this to fail.
2015-11-16 23:56:14 +13:00
163e434b32 GP Editing: Snapping Tools (Shift S)
This commit introduces the ability to snap GP points to the cursor/grid,
as well as snapping the cursor to GP points. Currently this functionality
is only available in the 3D view (since the cursor/grid settings are only
available in that view), where they are most needed.

Later, some of these operators can be extended to other editors on an
"as-needed" basis, based on finding some way of dealing with the lack
of 2D cursor in those views.

Coded during my recent travels :)
2015-11-15 19:41:27 +13:00
f9b7ae71ff Fix small compile error noted by Sybren 2015-11-14 01:20:19 +13:00
6cd2bfa3ff GPencil: Proper fix for proportional editing not working 2015-10-31 13:25:59 +13:00
a8b143ec78 Stop-Gap fix: Proportional editing was broken in new files for GPencil
Looks like my earlier commit wasn't quite right. For now, just reverting these
so that I can use this build for Siggraph; will look into a better fix later.

(The problem seems to be that the gp flag is getting auto-set too late - needs
to be done in initTransInfo() for the prop edit to take hold)
2015-10-31 12:51:35 +13:00
7743a2d839 Fix: GP Sculpt was no longer making copies of the current frame when being used non-modally 2015-10-29 14:36:07 +13:00
035bd2736a Ack! Compile fix for typo
Thanks pepeland for the fix ;)
2015-10-29 11:27:09 +13:00
b615ce637f GP EditMode: Tweaked poll callback for selection ops to work when the active layer is empty 2015-10-24 01:58:11 +13:00
0fb97b6b59 GPencil Dopesheet Mode: Copy and Paste for Keyframes works again
For the first time since the 2.4x series, the copy and paste functionality for
Grease Pencil keyframes finally works again. Not only that, but it's now been
upgraded to include some of the new goodness supported by the 2.7x keyframe
copy and paste tools.
2015-10-24 01:47:32 +13:00
34993bf97d GP Editing: Transform Manipulators can now be used to transform GP points 2015-10-23 16:30:28 +13:00
f49c833d00 Code Cleanup: Removed the need to set the "gpencil_strokes" property on transform ops when editing GP Strokes
Now that we've got a dedicated mode for this, we can now safely check this from
the context settings and set the relevant flags internally instead.
2015-10-23 16:02:35 +13:00
dcc538c95a GP Edit Menus: Tweaks + Missing Items
* Added entries for shrink-fatten, sculpt mode, sculpt brush, and proportional editing
* Made the Convert and Move to Layer op entries into menus to save users a click
2015-10-23 15:14:46 +13:00
0da05bb4dc GP EditMode: Simplify the way that this is handled
Now, instead of actually altering the ob->mode flag, we just make the
object mode setter operator (and the header) delegate to the appropriate
GPencil things to do their thing if they wish first.

As a result, we don't have to worry about version patching or issues with
changing selected/active objects.
2015-10-23 13:22:26 +13:00
9ee297c61e Merge branch 'master' into GPencil_Editing_Stage3 2015-10-23 00:27:13 +13:00
a171fb4e3c GP EditMode: Some more fixes for editmode handling to resolve some annoying corner cases
* Edit Strokes mode should only be available if there is actually a GP datablock
  and it is in editmode
* If exiting strokes editmode with the active object hidden, the flag would not get
  cleared, and it would appear that we were still in editmode.
2015-10-23 00:21:12 +13:00
7686a0ae9c GP Edit: Provide more direct quick access to sculpt brush via D+E pie
Added direct buttons to quickly change the sculpt brush when bringing up the
D+E pie menu for GP Stroke Sculpting. These are only included for the most
useflu of the brushes. All others are currently still visible via the
dropdown on the left (of the pie), though I'm considering removing that
to make things less confusing.
2015-10-18 04:08:51 +13:00
488e5616dd Missed this old comment in previous commit 2015-10-18 03:54:45 +13:00
b9029d698c GP Edit: "Change Layer" operator - Integrated into the D+W Pie Menu
Create a "Change Stroke" operator for GP Layers which can be used to change
between layers without needing to have the layers list open. This can be
accessed via the D+W pie menu by clicking on the dropdown beside the active
layer's name. Also, added the ability to delete the active layer from the menu too.
2015-10-18 03:52:32 +13:00
affd1eacb5 GP Editing: "Move to Layer" operator (MKEY)
This commit adds an operator which can be used to move the selected strokes
to another layer (including to a new layer).

Currently this can only move entire strokes. Later we can check on doing
partial strokes too (just like copy/paste can handle now).
2015-10-18 03:29:23 +13:00
422344d40a Merge branch 'master' into GPencil_Editing_Stage3 2015-10-17 15:41:45 +13:00
8a84465eab Code reshuffle: GP Sculpt brush customdata init is needed for exec() too 2015-10-17 03:22:05 +13:00
5fb8875f9b GP Sculpt: More robust checks for empty stroke buffer 2015-10-17 03:16:53 +13:00
16ef4da5e6 GP Sculpt: Clone Brush - "Use Falloff" = "Smudge"
When the "Use Falloff" option for the Clone brush is enabled, moving the mouse
after pasting some cloned strokes will now allow you to distort the points of
the pasted strokes with non-uniform weights. This can be used for creating
"distortion" effects (similar to using the push brush on these strokes afterwards).
This could be useful as a quick way to get lots of variation to a field of items.
2015-10-17 03:06:13 +13:00
b1c162074d GP Sculpt: "Clone" Brush
Added a new brush which allows you to paste copies of whatever is on the clipboard
wherever you click. If after clicking you're not yet satisfied with where you clicked,
you can move the mouse to position the new strokes in an alternative place instead.

Future Todo's:
* Check whether this works in all views (especially 2d views)
* Fix version patching

* Remove/disable the UI options for brush size/strength, which currently have no use.
  Otherwise, we could experiment with allowing those to be used for a "smudging"
  effect, where the strength of the repositioning effect depends on the placement
  of the stroke points relative to the cursor.
2015-10-17 02:53:05 +13:00
ead5d92673 GP Editing: Expose the copy-paste buffer within the GPencil module, so more ops can use it 2015-10-16 14:31:00 +13:00
e04fd43d61 GP Sculpt: Adding some defines for some new brush types coming soon 2015-10-16 14:30:12 +13:00
a4720696f1 Code cleanups in preparation for next steps 2015-10-16 14:28:25 +13:00
5abaaf8311 Fix: Apply same fix from previous commit to the equivalent logic used for GP Sculpt editing 2015-10-11 03:07:22 +13:00
029a6c2383 Fix: The "transform strokes on unkeyed frame creates new frame" feature didn't work on the first frame
As a convenience feature, it is possible to jump to a frame without any existing
GP keyframe, and just use the transform tools to modify the stroke to create a new
keyframe on that frame. However, if the current frame was before the first GP keyframe,
the newly created keyframe would not work (if you scrub away), as it was getting
incorrectly added at the end of the list of keyframes, instead of in-order as expected.
2015-10-11 03:06:49 +13:00
b81f3246f8 Fix: When reloading files, stroke editmode was getting disabled
This commit should bring the logic here more into line with what's done for
the posemode toggle.

There's still some weirdness here when clicking on "Object Mode" vs "Edit Strokes"
(i.e. both will just always toggle!), and old files with GP EditMode won't get
the state synced with ob->mode yet.
2015-10-11 03:00:48 +13:00
7d9bbf1b78 Fixes for using tablet events in keymap
* Need to classify tablet events as being in the "mouse" category, or else attempts
  to add a new keymap item for this will fail by reverting to keyboard input

* Made event matching more restrictive - it will now trigger only on a tablet events
  for LEFTMOUSE events. The problem was that, even when hovering over the tablet
  and pressing a key, you'd still get an event with tablet data, which meant that
  that event would get wrongly caught and handled.
2015-10-11 01:42:11 +13:00
3d68c92f0a GP Sculpt: Tweak the keymaps again
* Added Ctrl-E-LMB and Shift-E-LMB to make it easier to start sculpt strokes
  to make it easier to use "invert" (and soon "smooth") with these brushes.

* Removed the Ctrl-E = Modal Sculpt keymapping, to avoid conflicts with the
  new additions. D+E+Sculpt still works though
2015-09-29 13:06:03 +13:00
d9af8e9b41 Merge branch 'master' into GPencil_Editing_Stage3
Conflicts:
	source/blender/blenloader/intern/versioning_270.c
2015-09-29 02:21:02 +13:00
763bc0c905 GP Sculpt: Experimenting to make sculpt operator non-modal
Sometimes the modal nature of this operator made it cumbersome to operate.
This commit tries to make this better by making changes to the operator + keymap
so that:

* EKEY + LMB-drag (just like for drawing) = Do a single sculpt stroke
  (i.e. it starts immediately, and ends as soon as the stroke ends)

* CTRL + EKEY = Enter Sculpt Mode (as before). Can do multiple strokes this way
  without holding down any keys.

* DKEY + EKEY -> Sculpt = Enter Sculpt Mode (as before)


I'm still on the fence about the Ctrl+EKEY one. Perhaps it's too much of a hassle,
and we just drop this one?
2015-09-29 02:15:40 +13:00
64c96f15fe GP Sculpt: Assorted tweaks to investigate problems with Twist brush
* Some of the problems stem from inaccuracies/noise introduced by the 2D -> 3D
  conversion that takes place. For some reason, some verts randomly get offsets
  when reprojecting everything.

* Twist brush strength isn't 1.0 degree anymore

* It still doesn't work well though :(
2015-09-29 01:52:59 +13:00
558e33ca45 GPencil: Initialise default values for before/after onionskin colours
Newly created layers will now get a pair of default values for the before and after
onion skinning colours. These are currently hardcoded to a green and blue colour
(respectively). Also, "use colours" toggle will be enabled by default, so that these
colours will be applied as soon as you show the onionskins.
2015-09-29 00:49:30 +13:00
24201d81de Merge branch 'master' into GPencil_Editing_Stage3 2015-09-22 01:09:19 +12:00
4eeffd5ea9 GP EditMode: Fix for crash loading files saved with stroke editmode active 2015-09-21 01:41:48 +12:00
1877525960 GP Sculpt: Dampen down the timer speed on the Thickness brush to be more manageable 2015-09-20 02:03:13 +12:00
50b16fee19 GP Sculpt: Pinch brush also uses the timer now 2015-09-20 01:59:08 +12:00
6b1b0f78c0 GPencil EditMode: Some fixes to allow mode switcher to still work when there's no active object 2015-09-20 01:58:03 +12:00
56bd24c1a9 GPencil EditMode: Manually setting the editmode setting also syncs ob->mode now
Since this is for the RNA version, we only check if the datablock is being used
by the scene or active object (whereas the operator checks whether we're operating
in a 3D view).
2015-09-20 00:25:22 +12:00
61b6eb606c GPencil EditMode: Only sync editmode with ob->mode in 3D view 2015-09-19 19:39:21 +12:00
eb22dcea61 Code Cleanup: Move the editmode toggle operator to another file 2015-09-19 19:38:48 +12:00
3b1330396e GPencil Stroke Editing: Stroke Editmode now behaves like other editing modes for objects
You can now enter stroke editmode from the usual mode dropdown in the 3D View
header. While this isn't strictly correct (data-wise, GPencil datablocks can
belong to stuff other than the active object), for the most common use case
of usage in the 3D view, this is appropriate.

Notes:
* This still needs more careful checking to ensure correctness/consistency of
  behaviour in all edge cases (e.g. gpencil datablock in a different editor,
  or user manually changed the mode toggle, etc.)
2015-09-19 18:54:19 +12:00
95cf2fbc94 GPencil Stroke EditMode: Replace 3D view menus when in stroke editmode
After playing around with the current workflow the other day, it became clear
that the current editing workflow could still do with some tweaks to be more
convenient.

* To make it less confusing/ambiguous what "mode" we're in when editing
  Grease Pencil strokes, and also to provide another way of seeing all
  the available operators, the Select and "Edit" menus in the 3D View
  header now show Grease Pencil specific lists of operators now.

* Also, added a quick-access button to the header to toggle onion skinning.
  Even with the Pie Menu and the Properties Panel, the existing ways of
  quickly toggling this aren't good enough.


TODO:
* The "Mode Selector" is now the next target to tweak. As Strokes EditMode is not
  a per-object setting, but rather, per GPencil datablock, this will be quite hairy
  to hack around.
2015-09-19 17:38:46 +12:00
15b793bd84 GPencil Keymap: Pen Eraser can now be used for erasing strokes
Using the keymap support added in the previous commit, we can now map the
pen's eraser to trigger erasing.

Pen drawing support is not enabled though, since users who use a tablet as
their primary pointing device will want to be able to continue using it
for things like selection, grabbing manipulators, and sculpting/texture painting.
2015-09-19 11:49:47 +12:00
5b02f2a0a2 Added support for tablet pen and eraser events to be added to keymaps
Added two new event types that will fire only when using a stylus or its eraser.
The use case for this is to allow artists to be able to set up their keymaps
so that they can just draw and erase directly using their tablet without holding
down any keys.
2015-09-19 11:43:45 +12:00
f7250246d7 Merge branch 'master' into GPencil_Editing_Stage3 2015-09-16 02:47:51 +12:00
f32f83a619 GP Sculpt: Clamp size/strength changes made using the scrollwheel
It would be possible to get weird out of bounds sizes/strength values,
and then wonder why the brush wasn't working at all.
2015-09-16 02:45:33 +12:00
3b569ac921 Tweak to make rendering different GPencil shots via the sequencer feasible again
This hack ensures that world backgrounds get rendered in OpenGL previews
when using scene strips in the sequencer. Without this, we cannot use
the sequencer to chain up different GPencil shots for a non-destructive
workflow (where you don't have to render out image sequences first).
2015-09-16 02:39:49 +12:00
54fd4ec960 Compile fix for changes from master 2015-09-01 12:16:00 +12:00
cea946112d Merge branch 'master' into GPencil_Editing_Stage3 2015-08-26 02:45:00 +12:00
f6135894fe GP Sculpt: Thickness brush now uses the timer too 2015-08-17 02:24:36 +12:00
b5b0968ba9 GP Sculpt: Added timer which can be used to "accumulate" the brush effects if held
* This is currently only used for the "twist" brush. It is useful for making points
  rotate around the pivot point without needing to make constant movements to trigger
  updates (whcih is undesirable here, as that would alter the transforms).

* Currently this uses a hardcoded "rate" (i.e. repeat frequency). This will be addressed
  alongside other issues, such as extending this to other appropriate brush types.
2015-08-17 02:18:37 +12:00
0ca69dbcdd GP Sculpt Twist Brush - Some debugging tweaks to figure out why we're getting some weird behaviour 2015-08-17 01:53:17 +12:00
9941da0702 GPencil Sculpt: Initial implementation of Twist brush
This doesn't entirely work as expected yet (it perhaps needs the timer),
so more work is still needed.
2015-08-16 22:53:03 +12:00
73ffcb0754 GPencil Sculpt: Fix compilation errors in previous commit 2015-08-16 22:52:16 +12:00
352863b13f Merge branch 'master' into GPencil_Editing_Stage3
Conflicts:
	source/blender/editors/space_view3d/view3d_draw.c
2015-08-16 18:21:31 +12:00
c78569a1e6 Code Cleanup: Extract out screenspace to 3D logic for GP Sculpt 2015-08-10 01:02:01 +12:00
6726379ce3 GPencil Tablet Support: Eraser now works during Continuous Drawing
When continuous drawing is enabled, the eraser-end of the pen can be used to
erase strokes (as an alternative to RMB).

The keymap system still doesn't let us add such tablet events to the keymap
as actual events though, so we cannot just have a D+Eraser = Erase keymapping.
2015-08-10 00:19:27 +12:00
b91ba21185 GPencil: Don't draw onion skins during animation playback
* When playing back animations, onion skinning is temporarily disabled now
  so that the poses can be seen more clearly.

  - I've supposedly used the check which disables this for animation scrubbing,
    (I've tried both and checked the logic for each), but they don't seem to be
    working as advertised. So, for now, onionskins are still visible during scrubbing.

* Onion skinning is also disabled for rendering playblasts and for file previews/
  sequencer renders.
2015-08-08 17:44:11 +12:00
6cef173140 Merge branch 'master' into GPencil_Editing_Stage3
Conflicts:
	release/scripts/startup/bl_ui/properties_grease_pencil_common.py
	source/blender/blenloader/intern/versioning_270.c
2015-08-07 02:53:32 +12:00
a84c9f527c Merge branch 'master' into GPencil_Editing_Stage3 2015-07-25 22:50:00 +12:00
dc1d95e404 GP Sculpt: Experimental pie menu for sculptmode (D+E) 2015-07-25 03:10:44 +12:00
f24f72afb9 GP Sculpt: Improved header info string
* Now it shows what the name of the active brush
* Also added the hotkeys for adjusting the brush size/strength
2015-07-25 02:49:26 +12:00
2c178440e3 GP Sculpt: Sculpting on a frame without an existing key will create a new one
Just like with the transform tools, if you start trying to sculpt strokes on a frame
where no gpframe/drawings exist, a new gpframe will now be created. This makes it
easier to use the sculpt tools for animating, as all you need to do is to start
animating a sketch is to jump to a frame and start sculpting.

The grab tool in particular works quite well for this sort of workflow. It appears
to be particularly handy when doing breakdowns and/or straight-ahead animation, as
well as for doing smaller tweaks.
2015-07-25 02:00:22 +12:00
146b731d7c Merge branch 'master' into GPencil_Editing_Stage3 2015-07-24 15:09:16 +12:00
d1a8a15ff6 Merge branch 'master' into GPencil_Editing_Stage3 2015-07-24 14:26:46 +12:00
3148f82e27 GP Sculpt: Code cleanup for randomise brush 2015-07-23 13:08:09 +12:00
db065e2ef8 GP Sculpt: Allow randomise to offset points to either side of the line 2015-07-23 12:50:45 +12:00
dcbe481cfd GP Sculpt: More WIP work to try and get this randomise brush working
So it turns out that the screenspace-to-3d math is a lot more convoluted now
than would be initially obvious. The brush is now behaving a lot better now
in that it somewhat does what is expected. The results aren't really what we
really want yet, so some more experimentation is still needed.
2015-07-23 01:33:21 +12:00
6d5cfd1810 GP Sculpt: Buggy attempt at building a "randomise" brush
This currently just sends the points off screen. We need another approach.
2015-07-21 00:12:50 +12:00
d4d3a77a2a GP Sculpt: Collapse Edit/Sculpt panels by default
Now tha we have many more editing tools/options here now, it's better to keep
both of these collapsed, so that users have a greater chance of discovering
the existence of either/or. Furthermore, since the editing tools have been
available for a few releases now, users should be more familiar with what's on
offer there now
2015-07-17 02:25:04 +12:00
7c2557c3da GP Sculpt: Version patching code to set sensible defaults for sculpt brushes 2015-07-15 17:52:48 +12:00
a5ed00f20a GP Sculpt: Shift-F affects brush strength, and Ctrl-F affects brush size
These hotkeys do not work when the brush is being used...
2015-07-15 03:03:26 +12:00
bce968044f GP Sculpt: Dampen Pinch brush strength
By trial and error, it seems that dividing the influence by 5 makes this tool
more manageable.
2015-07-15 02:57:26 +12:00
32aff3021f GP Sculpt: Pinch brush can now be inverted using Ctrl (i.e. "Inflate")
Just like with the mesh sculpting tools, holding Ctrl can now be used to
apply the inverse transform when using the pinch brush. So, instead of
drawing the points towards the brush, Inflate will push them away.
2015-07-15 02:51:34 +12:00
74638921a6 GP Sculpt: Initial implementation of "Pinch" Brush
This brush draws all the points inwards towards the midpoint of the brush.
I'm not sure if this brush is really that useful or not, so I'll leave it
as-is for now.

Watch out for how strong you set the brush to be; even with 0.3, you can already
get some really strong/rapidly changing results.
2015-07-15 02:42:49 +12:00
df0fdc4178 GP Sculpt: Reduce maximum size of brushes to make the UI sliders more controllable 2015-07-11 17:46:31 +12:00
78c92d7080 GP Sculpt: Fix for random crashes - forgot to adjust array size when adding new brush types 2015-07-11 16:54:55 +12:00
49e304c155 GPencil Sculpt: "Push" Brush
This is similar to the grab brush in that it translates points according to
the strength of the brush. The difference however is that it is applied instead
to whatever points are under the brush as it travels, instead of only those
that were within the brush region initially.

Notes:
* The effect of this brush is more like a "smear" effect
* I got a crash initially, but haven't been able to reproduce. So, unless this
  crops up again, I'll consider that a one-off for now.
2015-07-11 01:39:43 +12:00
63d0a78624 GPencil Sculpt: Added defines for a bunch of new brush types to be added 2015-07-11 01:24:53 +12:00
0307ebe63b GPencil Sculpt: Improved tooltip for Grab brush 2015-07-11 01:24:34 +12:00
32c7b72ff2 GPencil Smooth Brush: Added "Affect Pressure" option
Use the "Affect Pressure" option to smooth out the pressure values of strokes,
in addition to smoothing the coordinates.
2015-07-11 01:24:15 +12:00
61d9bcb26f Merge branch 'master' into GPencil_Editing_Stage3 2015-07-11 00:22:48 +12:00
571a46f32b GP Sculpt: "Grab" Brush
This brush can be used to move clusters of points around, without needing to
select them first. Like the "Grab" brush in Sculpt Mode, this brush only operates
on the brush circle at the start of the grab operation. The strength of the effect
applied to each point is determined at the start of the stroke based on factors
such as how far away from the center of the brush the point lies, and the strength
of the brush.

(Yay! Worked first time!)
2015-07-11 00:15:04 +12:00
718912f512 GPencil Sculpt: Redraw the view on mousemove even if none of the strokes was affected
The active viewport needs to get redrawn, or else the brush circle will not move
when "selection mask" is enabled and the user is trying to paint over non-selected
areas.
2015-07-07 22:49:33 +12:00
7e99bab425 GPencil Sculpt: MMB can now be used for viewport manipulations while the sculpt operator is active 2015-07-07 22:45:26 +12:00
7e333b863c CMake support 2015-07-07 21:40:59 +12:00
85d3d4c96a GPencil Sculpt: Code Cleanup
* Removed inlined math in favour of mathlib functions
* Simplified arguments to some functions/callbacks by reusing passsed-in coords
  (as vectors/arrays instead of as separate values)
2015-07-07 21:40:33 +12:00
ec4f82f9ca GP Smooth Brush: Affect endpoints by a reduced amount instead of not at all
Endpoints can't be handled normally, as they tend to easily "shrink",
reducing the length of the stroke. However, we still want some smoothing
to take place, as it looks odd without anything happening.
2015-07-07 17:52:36 +12:00
6a0547b934 Merge branch 'master' into GPencil_Editing_Stage3 2015-07-07 02:48:56 +12:00
d6e48b41ff Code cleanup
* Remove all mention of the complicated way of implementing smoothing for now
* Disable debug print
2015-07-07 02:46:31 +12:00
043db44904 GP Stroke Sculpting: Initial implementation of a "Smooth" Brush
This commit introduces a basic "smooth" brush tool for sculpting Grease Pencil
strokes (i.e. for getting rid of wobbles or kinks in the stroke). The current
implementation is still quite rough (with quite a few points noted about how it
could be better).

Usage Notes:
* It is highly recommended to have the "Use Falloff" option enabled. Doing so
  allows you to have quite a bit more subtlety to how well you can control the
  effects of this brush (i.e. this option can be used to prevent the brush from
  "shrinking" the strokes too much, if you use the edge of the brush)

* A radius of 25 px, and strength = 0.5 are also good starting points


Implementation Notes:
* This currently only affects the stroke coordintes. Whether pressure values should
  be handled by this brush (with/without an option to turn this on/off), or whether
  it should be handled as a separate brush remains to be seen.

* This isn't exactly the most efficient implementation. It also suffers from the
  fact that because its effects are applied from the first point in the stroke
  to the last (and in a single pass), you can get unwanted accumulated smoothing
  effects.

* Currently all points in the neighbourhood are given equal priority. We could
  investigate different weighting schemes here.

* This is currently hardcoded to consider 2 points before and 2 points after the
  current one. This could be improved by offering control over how many on either
  side to consider...
2015-07-07 02:43:38 +12:00
17f17a7013 GP Stroke Sculpting: Fix bug with falloff calculation
Falloff calculation was incorrect as it could produce invalid values if the
distance to a point exceeeded the radius of the brush, resulting in "large
negative values".
2015-07-07 02:34:21 +12:00
8ae7128991 Merge branch 'master' into GPencil_Editing_Stage3 2015-06-30 12:29:53 +12:00
2013b63d29 Merge branch 'master' into GPencil_Editing_Stage3 2015-06-20 22:46:47 +12:00
e3654f1da5 Initial stub callbacks for grab brush 2015-06-13 12:18:07 +12:00
dfb9df8578 GPencil Sculpt: Tweaked how strength values are interpreted for thickness brushes to be more controllable 2015-06-13 01:16:21 +12:00
44e171c7f0 GPencil Sculpt: Improve header infotip 2015-06-08 13:26:05 +12:00
3e67c06bd3 GPencil Sculpt: Draw a '+' cursor when in sculptmode 2015-06-08 13:20:07 +12:00
98d42b7236 GPencil: Minimum-working-version of new Stroke Sculpting functionality
Usage:
* In Stroke Edit Mode, EKEY will start the modal brush operator, using the active
  brush (set via the Toolbar)
* LMB-click-drag to sculpt, release to stop.
* RMB/ESC to stop editing
* Scrollwheel to increase/decrease brush radius
* Shift-Scrollwheel to increase/decrease strength
* Hold Ctrl to invert the brush action

Notes:
* Currently only "Thickness" brush is implemented. This adjusts the stroke thickness
  as you "paint" the effect onto the stroke. (Recommended Strength: 0.093)
* Strength and radius are not correctly initialised yet (to be fixed soon)
2015-06-08 13:15:32 +12:00
786da62b4b Code cleanup - Formatting 2015-06-01 02:11:29 +12:00
70f84fe4c8 GPencil Editing UI: Move editing toggle to the main panel
In preparation for a brush-based editing tool, moved the Stroke EditMode toggle
to the main toolshelf panel so that the editing panel can be collapsed when only
the brush panel is needed.

Also, improved the tooltip here.
2015-05-31 12:24:47 +12:00
54 changed files with 4327 additions and 641 deletions

View File

@@ -22,23 +22,34 @@
from bpy.types import Menu, UIList
def gpencil_stroke_placement_settings(context, layout, gpd):
def gpencil_stroke_placement_settings(context, layout):
if context.space_data.type == 'VIEW_3D':
propname = "gpencil_stroke_placement_view3d"
elif context.space_data.type == 'SEQUENCE_EDITOR':
propname = "gpencil_stroke_placement_sequencer_preview"
elif context.space_data.type == 'IMAGE_EDITOR':
propname = "gpencil_stroke_placement_image_edit"
else:
propname = "gpencil_stroke_placement_view2d"
ts = context.tool_settings
col = layout.column(align=True)
col.label(text="Stroke Placement:")
row = col.row(align=True)
row.prop_enum(gpd, "draw_mode", 'VIEW')
row.prop_enum(gpd, "draw_mode", 'CURSOR')
row.prop_enum(ts, propname, 'VIEW')
row.prop_enum(ts, propname, 'CURSOR')
if context.space_data.type == 'VIEW_3D':
row = col.row(align=True)
row.prop_enum(gpd, "draw_mode", 'SURFACE')
row.prop_enum(gpd, "draw_mode", 'STROKE')
row.prop_enum(ts, propname, 'SURFACE')
row.prop_enum(ts, propname, 'STROKE')
row = col.row(align=False)
row.active = gpd.draw_mode in {'SURFACE', 'STROKE'}
row.prop(gpd, "use_stroke_endpoints")
row.active = getattr(ts, propname) in {'SURFACE', 'STROKE'}
row.prop(ts, "use_gpencil_stroke_endpoints")
class GreasePencilDrawingToolsPanel:
@@ -56,15 +67,19 @@ class GreasePencilDrawingToolsPanel:
col.label(text="Draw:")
row = col.row(align=True)
row.operator("gpencil.draw", text="Draw").mode = 'DRAW'
row.operator("gpencil.draw", text="Erase").mode = 'ERASER'
row.operator("gpencil.draw", icon='GREASEPENCIL', text="Draw").mode = 'DRAW'
row.operator("gpencil.draw", icon='FORCE_CURVE', text="Erase").mode = 'ERASER' # XXX: Needs a dedicated icon
row = col.row(align=True)
row.operator("gpencil.draw", text="Line").mode = 'DRAW_STRAIGHT'
row.operator("gpencil.draw", text="Poly").mode = 'DRAW_POLY'
row.operator("gpencil.draw", icon='LINE_DATA', text="Line").mode = 'DRAW_STRAIGHT'
row.operator("gpencil.draw", icon='MESH_DATA', text="Poly").mode = 'DRAW_POLY'
row = col.row(align=True)
row.prop(context.tool_settings, "use_grease_pencil_sessions", text="Continuous Drawing")
sub = col.column(align=True)
sub.prop(context.tool_settings, "use_gpencil_additive_drawing", text="Additive Drawing")
sub.prop(context.tool_settings, "use_gpencil_continuous_drawing", text="Continuous Drawing")
col.separator()
col.separator()
if context.space_data.type in {'VIEW_3D', 'CLIP_EDITOR'}:
col.separator()
@@ -74,11 +89,20 @@ class GreasePencilDrawingToolsPanel:
row.prop(context.tool_settings, "grease_pencil_source", expand=True)
elif context.space_data.type == 'CLIP_EDITOR':
row.prop(context.space_data, "grease_pencil_source", expand=True)
col.separator()
col.separator()
gpencil_stroke_placement_settings(context, col)
gpd = context.gpencil_data
if gpd:
col.separator()
gpencil_stroke_placement_settings(context, col, gpd)
layout.separator()
layout.separator()
col = layout.column(align=True)
col.prop(gpd, "use_stroke_edit_mode", text="Enable Editing", icon='EDIT', toggle=True)
if context.space_data.type == 'VIEW_3D':
col.separator()
@@ -95,67 +119,103 @@ class GreasePencilStrokeEditPanel:
bl_label = "Edit Strokes"
bl_category = "Grease Pencil"
bl_region_type = 'TOOLS'
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
return (context.gpencil_data is not None)
if context.gpencil_data is None:
return False
gpd = context.gpencil_data
return bool(context.editable_gpencil_strokes) and bool(gpd.use_stroke_edit_mode)
@staticmethod
def draw(self, context):
layout = self.layout
gpd = context.gpencil_data
edit_ok = bool(context.editable_gpencil_strokes) and bool(gpd.use_stroke_edit_mode)
layout.label(text="Select:")
col = layout.column(align=True)
col.operator("gpencil.select_all", text="Select All")
col.operator("gpencil.select_border")
col.operator("gpencil.select_circle")
layout.separator()
col = layout.column(align=True)
col.prop(gpd, "use_stroke_edit_mode", text="Enable Editing", icon='EDIT', toggle=True)
col.operator("gpencil.select_linked")
col.operator("gpencil.select_more")
col.operator("gpencil.select_less")
col.separator()
layout.separator()
col.label(text="Select:")
subcol = col.column(align=True)
subcol.active = edit_ok
subcol.operator("gpencil.select_all", text="Select All")
subcol.operator("gpencil.select_border")
subcol.operator("gpencil.select_circle")
col.separator()
subcol = col.column(align=True)
subcol.active = edit_ok
subcol.operator("gpencil.select_linked")
subcol.operator("gpencil.select_more")
subcol.operator("gpencil.select_less")
col.separator()
col.label(text="Edit:")
row = col.row(align=True)
row.active = edit_ok
layout.label(text="Edit:")
row = layout.row(align=True)
row.operator("gpencil.copy", text="Copy")
row.operator("gpencil.paste", text="Paste")
subcol = col.column(align=True)
subcol.active = edit_ok
subcol.operator("gpencil.delete", text="Delete")
subcol.operator("gpencil.duplicate_move", text="Duplicate")
subcol.operator("transform.mirror", text="Mirror").gpencil_strokes = True
col = layout.column(align=True)
col.operator("gpencil.delete", text="Delete")
col.operator("gpencil.duplicate_move", text="Duplicate")
col.operator("transform.mirror", text="Mirror")
col.separator()
layout.separator()
subcol = col.column(align=True)
subcol.active = edit_ok
subcol.operator("transform.translate").gpencil_strokes = True # icon='MAN_TRANS'
subcol.operator("transform.rotate").gpencil_strokes = True # icon='MAN_ROT'
subcol.operator("transform.resize", text="Scale").gpencil_strokes = True # icon='MAN_SCALE'
col = layout.column(align=True)
col.operator("transform.translate") # icon='MAN_TRANS'
col.operator("transform.rotate") # icon='MAN_ROT'
col.operator("transform.resize", text="Scale") # icon='MAN_SCALE'
col.separator()
layout.separator()
subcol = col.column(align=True)
subcol.active = edit_ok
subcol.operator("transform.bend", text="Bend").gpencil_strokes = True
subcol.operator("transform.shear", text="Shear").gpencil_strokes = True
subcol.operator("transform.tosphere", text="To Sphere").gpencil_strokes = True
col = layout.column(align=True)
col.operator("transform.bend", text="Bend")
col.operator("transform.shear", text="Shear")
col.operator("transform.tosphere", text="To Sphere")
class GreasePencilStrokeSculptPanel:
# subclass must set
# bl_space_type = 'IMAGE_EDITOR'
bl_label = "Sculpt Strokes"
bl_category = "Grease Pencil"
bl_region_type = 'TOOLS'
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
if context.gpencil_data is None:
return False
gpd = context.gpencil_data
return bool(context.editable_gpencil_strokes) and bool(gpd.use_stroke_edit_mode)
@staticmethod
def draw(self, context):
layout = self.layout
settings = context.tool_settings.gpencil_sculpt
tool = settings.tool
brush = settings.brush
layout.column().prop(settings, "tool", expand=True)
col = layout.column()
col.prop(brush, "size", slider=True)
row = col.row(align=True)
row.prop(brush, "strength", slider=True)
row.prop(brush, "use_pressure_strength", text="")
col.prop(brush, "use_falloff")
layout.separator()
if settings.tool in {'THICKNESS', 'PINCH', 'TWIST'}:
layout.row().prop(brush, "direction", expand=True)
layout.separator()
layout.prop(settings, "use_select_mask")
if settings.tool == 'SMOOTH':
layout.prop(brush, "affect_pressure")
###############################
@@ -190,14 +250,14 @@ class GPENCIL_PIE_tool_palette(Menu):
if gpd:
if gpd.use_stroke_edit_mode and context.editable_gpencil_strokes:
# S - Exit Edit Mode
pie.prop(gpd, "use_stroke_edit_mode", text="Exit Edit Mode", icon='EDIT')
pie.operator("gpencil.editmode_toggle", text="Exit Edit Mode", icon='EDIT')
# N - Transforms
col = pie.column()
row = col.row(align=True)
row.operator("transform.translate", icon='MAN_TRANS').gpencil_strokes = True
row.operator("transform.rotate", icon='MAN_ROT').gpencil_strokes = True
row.operator("transform.resize", text="Scale", icon='MAN_SCALE').gpencil_strokes = True
row.operator("transform.translate", icon='MAN_TRANS')
row.operator("transform.rotate", icon='MAN_ROT')
row.operator("transform.resize", text="Scale", icon='MAN_SCALE')
row = col.row(align=True)
row.label("Proportional Edit:")
row.prop(context.tool_settings, "proportional_edit", text="", icon_only=True)
@@ -224,7 +284,7 @@ class GPENCIL_PIE_tool_palette(Menu):
pie.operator("wm.call_menu_pie", text="More...").name = "GPENCIL_PIE_tools_more"
else:
# Toggle Edit Mode
pie.prop(gpd, "use_stroke_edit_mode", text="Enable Stroke Editing", icon='EDIT')
pie.operator("gpencil.editmode_toggle", text="Enable Stroke Editing", icon='EDIT')
class GPENCIL_PIE_settings_palette(Menu):
@@ -261,11 +321,15 @@ class GPENCIL_PIE_settings_palette(Menu):
col.prop(gpl, "use_onion_skinning")
# N - Active Layer
# XXX: this should show an operator to change the active layer instead
col = pie.column()
col.label("Active Layer: ")
col.prop(gpl, "info", text="")
# col.prop(gpd, "layers")
row = col.row()
row.operator_context = 'EXEC_REGION_WIN'
row.operator_menu_enum("gpencil.layer_change", "layer", text="", icon='GREASEPENCIL')
row.prop(gpl, "info", text="")
row.operator("gpencil.layer_remove", text="", icon='X')
row = col.row()
row.prop(gpl, "lock")
row.prop(gpl, "hide")
@@ -294,17 +358,82 @@ class GPENCIL_PIE_tools_more(Menu):
col.operator("gpencil.select_more", icon='ZOOMIN')
col.operator("gpencil.select_less", icon='ZOOMOUT')
pie.operator("transform.mirror", icon='MOD_MIRROR').gpencil_strokes = True
pie.operator("transform.bend", icon='MOD_SIMPLEDEFORM').gpencil_strokes = True
pie.operator("transform.shear", icon='MOD_TRIANGULATE').gpencil_strokes = True
pie.operator("transform.tosphere", icon='MOD_MULTIRES').gpencil_strokes = True
pie.operator("transform.mirror", icon='MOD_MIRROR')
pie.operator("transform.bend", icon='MOD_SIMPLEDEFORM')
pie.operator("transform.shear", icon='MOD_TRIANGULATE')
pie.operator("transform.tosphere", icon='MOD_MULTIRES')
pie.operator("gpencil.convert", icon='OUTLINER_OB_CURVE', text="Convert...")
pie.operator("wm.call_menu_pie", text="Back to Main Palette...").name = "GPENCIL_PIE_tool_palette"
class GPENCIL_PIE_sculpt(Menu):
"""A pie menu for accessing Grease Pencil stroke sculpting settings"""
bl_label = "Grease Pencil Sculpt"
@classmethod
def poll(cls, context):
gpd = context.gpencil_data
return bool(gpd and gpd.use_stroke_edit_mode and context.editable_gpencil_strokes)
def draw(self, context):
layout = self.layout
pie = layout.menu_pie()
settings = context.tool_settings.gpencil_sculpt
brush = settings.brush
# W - Launch Sculpt Mode
col = pie.column()
#col.label("Tool:")
col.prop(settings, "tool", text="")
col.operator("gpencil.brush_paint", text="Sculpt", icon='SCULPTMODE_HLT')
# E - Common Settings
col = pie.column(align=True)
col.prop(brush, "size", slider=True)
row = col.row(align=True)
row.prop(brush, "strength", slider=True)
# row.prop(brush, "use_pressure_strength", text="", icon_only=True)
col.prop(brush, "use_falloff")
# S - Change Brush Type Shortcuts
row = pie.row()
row.prop_enum(settings, "tool", value='GRAB')
row.prop_enum(settings, "tool", value='PUSH')
row.prop_enum(settings, "tool", value='CLONE')
# N - Change Brush Type Shortcuts
row = pie.row()
row.prop_enum(settings, "tool", value='SMOOTH')
row.prop_enum(settings, "tool", value='THICKNESS')
row.prop_enum(settings, "tool", value='RANDOMISE')
###############################
class GPENCIL_MT_snap(Menu):
bl_label = "Snap"
def draw(self, context):
layout = self.layout
layout.operator("gpencil.snap_to_grid", text="Selection to Grid")
layout.operator("gpencil.snap_to_cursor", text="Selection to Cursor").use_offset = False
layout.operator("gpencil.snap_to_cursor", text="Selection to Cursor (Offset)").use_offset = True
layout.separator()
layout.operator("gpencil.snap_cursor_to_selected", text="Cursor to Selected")
layout.operator("view3d.snap_cursor_to_center", text="Cursor to Center")
layout.operator("view3d.snap_cursor_to_grid", text="Cursor to Grid")
###############################
class GPENCIL_UL_layer(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
# assert(isinstance(item, bpy.types.GPencilLayer)
@@ -328,6 +457,25 @@ class GPENCIL_UL_layer(UIList):
layout.label(text="", icon_value=icon)
class GPENCIL_MT_layer_specials(Menu):
bl_label = "Layer"
def draw(self, context):
layout = self.layout
layout.operator("gpencil.layer_duplicate", icon='COPY_ID') # XXX: needs a dedicated icon
layout.separator()
layout.operator("gpencil.reveal", icon='RESTRICT_VIEW_OFF', text="Show All")
layout.operator("gpencil.hide", icon='RESTRICT_VIEW_ON', text="Hide Others").unselected = True
layout.separator()
layout.operator("gpencil.lock_all", icon='LOCKED', text="Lock All")
layout.operator("gpencil.unlock_all", icon='UNLOCKED', text="UnLock All")
class GreasePencilDataPanel:
# subclass must set
# bl_space_type = 'IMAGE_EDITOR'
@@ -379,7 +527,7 @@ class GreasePencilDataPanel:
gpl = context.active_gpencil_layer
if gpl:
sub.operator("gpencil.layer_duplicate", icon='COPY_ID', text="") # XXX: needs a dedicated icon
sub.menu("GPENCIL_MT_layer_specials", icon='DOWNARROW_HLT', text="")
if len(gpd.layers) > 1:
col.separator()
@@ -388,6 +536,12 @@ class GreasePencilDataPanel:
sub.operator("gpencil.layer_move", icon='TRIA_UP', text="").type = 'UP'
sub.operator("gpencil.layer_move", icon='TRIA_DOWN', text="").type = 'DOWN'
col.separator()
sub = col.column(align=True)
sub.operator("gpencil.layer_isolate", icon='SOLO_OFF', text="").affect_visibility = False
sub.operator("gpencil.layer_isolate", icon='RESTRICT_VIEW_OFF', text="").affect_visibility = True
if gpl:
self.draw_layer(layout, gpl)
@@ -492,4 +646,4 @@ class GreasePencilToolsPanel:
layout.separator()
layout.separator()
gpencil_stroke_placement_settings(context, layout, gpd)
gpencil_stroke_placement_settings(context, layout)

View File

@@ -24,6 +24,7 @@ from bpy.app.translations import pgettext_iface as iface_
from bl_ui.properties_grease_pencil_common import (
GreasePencilDrawingToolsPanel,
GreasePencilStrokeEditPanel,
GreasePencilStrokeSculptPanel,
GreasePencilDataPanel
)
@@ -1134,6 +1135,10 @@ class CLIP_PT_tools_grease_pencil_draw(GreasePencilDrawingToolsPanel, Panel):
class CLIP_PT_tools_grease_pencil_edit(GreasePencilStrokeEditPanel, Panel):
bl_space_type = 'CLIP_EDITOR'
# Grease Pencil stroke sculpting tools
class CLIP_PT_tools_grease_pencil_sculpt(GreasePencilStrokeSculptPanel, Panel):
bl_space_type = 'CLIP_EDITOR'
class CLIP_MT_view(Menu):
bl_label = "View"

View File

@@ -138,6 +138,19 @@ class DOPESHEET_HT_header(Header):
# 'genericFiltersOnly' limits the options to only the relevant 'generic' subset of
# filters which will work here and are useful (especially for character animation)
dopesheet_filter(layout, context, genericFiltersOnly=True)
elif st.mode == 'GPENCIL':
row = layout.row(align=True)
row.prop(st.dopesheet, "show_gpencil_3d_only", text="Active Only")
if st.dopesheet.show_gpencil_3d_only:
row = layout.row(align=True)
row.prop(st.dopesheet, "show_only_selected", text="")
row.prop(st.dopesheet, "show_hidden", text="")
row = layout.row(align=True)
row.prop(st.dopesheet, "use_filter_text", text="")
if st.dopesheet.use_filter_text:
row.prop(st.dopesheet, "filter_text", text="")
row = layout.row(align=True)
row.prop(toolsettings, "use_proportional_action",

View File

@@ -28,6 +28,7 @@ from bl_ui.properties_paint_common import (
from bl_ui.properties_grease_pencil_common import (
GreasePencilDrawingToolsPanel,
GreasePencilStrokeEditPanel,
GreasePencilStrokeSculptPanel,
GreasePencilDataPanel,
)
from bpy.app.translations import pgettext_iface as iface_
@@ -1192,5 +1193,10 @@ class IMAGE_PT_tools_grease_pencil_edit(GreasePencilStrokeEditPanel, Panel):
bl_space_type = 'IMAGE_EDITOR'
# Grease Pencil stroke sculpting tools
class IMAGE_PT_tools_grease_pencil_sculpt(GreasePencilStrokeSculptPanel, Panel):
bl_space_type = 'IMAGE_EDITOR'
if __name__ == "__main__": # only for live edit.
bpy.utils.register_module(__name__)

View File

@@ -24,6 +24,7 @@ from bpy.app.translations import pgettext_iface as iface_
from bl_ui.properties_grease_pencil_common import (
GreasePencilDrawingToolsPanel,
GreasePencilStrokeEditPanel,
GreasePencilStrokeSculptPanel,
GreasePencilDataPanel,
GreasePencilToolsPanel,
)
@@ -488,6 +489,11 @@ class NODE_PT_tools_grease_pencil_edit(GreasePencilStrokeEditPanel, Panel):
bl_space_type = 'NODE_EDITOR'
bl_region_type = 'TOOLS'
# Grease Pencil stroke sculpting tools
class NODE_PT_tools_grease_pencil_sculpt(GreasePencilStrokeSculptPanel, Panel):
bl_space_type = 'NODE_EDITOR'
bl_region_type = 'TOOLS'
# -----------------------------

View File

@@ -118,6 +118,14 @@ class VIEW3D_HT_header(Header):
row.operator("pose.paste", text="", icon='PASTEDOWN').flipped = False
row.operator("pose.paste", text="", icon='PASTEFLIPDOWN').flipped = True
# GPencil
if context.gpencil_data and context.gpencil_data.use_stroke_edit_mode:
row = layout.row(align=True)
row.operator("gpencil.copy", text="", icon='COPYDOWN')
row.operator("gpencil.paste", text="", icon='PASTEDOWN')
layout.prop(context.gpencil_data, "use_onion_skinning", text="Onion Skins", icon='PARTICLE_PATH') # XXX: icon
class VIEW3D_MT_editor_menus(Menu):
bl_space_type = 'VIEW3D_MT_editor_menus'
@@ -131,11 +139,14 @@ class VIEW3D_MT_editor_menus(Menu):
obj = context.active_object
mode_string = context.mode
edit_object = context.edit_object
gp_edit = context.gpencil_data and context.gpencil_data.use_stroke_edit_mode
layout.menu("VIEW3D_MT_view")
# Select Menu
if mode_string in {'PAINT_WEIGHT', 'PAINT_VERTEX', 'PAINT_TEXTURE'}:
if gp_edit:
layout.menu("VIEW3D_MT_select_gpencil")
elif mode_string in {'PAINT_WEIGHT', 'PAINT_VERTEX', 'PAINT_TEXTURE'}:
mesh = obj.data
if mesh.use_paint_mask:
layout.menu("VIEW3D_MT_select_paint_mask")
@@ -144,7 +155,9 @@ class VIEW3D_MT_editor_menus(Menu):
elif mode_string != 'SCULPT':
layout.menu("VIEW3D_MT_select_%s" % mode_string.lower())
if mode_string == 'OBJECT':
if gp_edit:
pass
elif mode_string == 'OBJECT':
layout.menu("INFO_MT_add", text="Add")
elif mode_string == 'EDIT_MESH':
layout.menu("INFO_MT_mesh_add", text="Add")
@@ -157,7 +170,9 @@ class VIEW3D_MT_editor_menus(Menu):
elif mode_string == 'EDIT_ARMATURE':
layout.menu("INFO_MT_edit_armature_add", text="Add")
if edit_object:
if gp_edit:
layout.menu("VIEW3D_MT_edit_gpencil")
elif edit_object:
layout.menu("VIEW3D_MT_edit_%s" % edit_object.type.lower())
elif obj:
if mode_string != 'PAINT_TEXTURE':
@@ -883,6 +898,27 @@ class VIEW3D_MT_select_edit_armature(Menu):
layout.operator("object.select_pattern", text="Select Pattern...")
class VIEW3D_MT_select_gpencil(Menu):
bl_label = "Select"
def draw(self, context):
layout = self.layout
layout.operator("gpencil.select_border")
layout.operator("gpencil.select_circle")
layout.separator()
layout.operator("gpencil.select_all", text="(De)select All").action = 'TOGGLE'
layout.operator("gpencil.select_all", text="Inverse").action = 'INVERT'
layout.operator("gpencil.select_linked", text="Linked")
layout.separator()
layout.operator("gpencil.select_more")
layout.operator("gpencil.select_less")
class VIEW3D_MT_select_paint_mask(Menu):
bl_label = "Select"
@@ -2822,6 +2858,81 @@ class VIEW3D_MT_edit_armature_delete(Menu):
layout.operator("armature.dissolve", text="Dissolve")
# ********** GPencil Stroke Edit menu **********
class VIEW3D_MT_edit_gpencil(Menu):
bl_label = "GPencil"
def draw(self, context):
toolsettings = context.tool_settings
layout = self.layout
layout.operator("ed.undo")
layout.operator("ed.redo")
layout.operator("ed.undo_history")
layout.separator()
layout.operator("gpencil.brush_paint", text="Sculpt Strokes").wait_for_input = True
layout.prop_menu_enum(toolsettings.gpencil_sculpt, "tool", text="Sculpt Brush")
layout.separator()
layout.menu("VIEW3D_MT_edit_gpencil_transform")
layout.operator("transform.mirror", text="Mirror")
layout.menu("GPENCIL_MT_snap")
layout.separator()
layout.menu("VIEW3D_MT_object_animation") # NOTE: provides keyingset access...
layout.separator()
layout.menu("VIEW3D_MT_edit_gpencil_delete")
layout.operator("gpencil.duplicate_move", text="Duplicate")
layout.separator()
layout.operator("gpencil.copy", text="Copy")
layout.operator("gpencil.paste", text="Paste")
layout.separator()
layout.prop_menu_enum(toolsettings, "proportional_edit")
layout.prop_menu_enum(toolsettings, "proportional_edit_falloff")
layout.separator()
layout.operator("gpencil.reveal")
layout.operator("gpencil.hide", text="Show Active Layer Only").unselected = True
layout.operator("gpencil.hide", text="Hide Active Layer").unselected = False
layout.separator()
layout.operator_menu_enum("gpencil.move_to_layer", "layer", text="Move to Layer")
layout.operator_menu_enum("gpencil.convert", "type", text="Convert to Geometry...")
class VIEW3D_MT_edit_gpencil_transform(Menu):
bl_label = "Transform"
def draw(self, context):
layout = self.layout
layout.operator("transform.translate")
layout.operator("transform.rotate")
layout.operator("transform.resize", text="Scale")
layout.separator()
layout.operator("transform.bend", text="Bend")
layout.operator("transform.shear", text="Shear")
layout.operator("transform.tosphere", text="To Sphere")
layout.operator("transform.transform", text="Shrink Fatten").mode = 'GPENCIL_SHRINKFATTEN'
# ********** Panel **********

View File

@@ -21,7 +21,8 @@ import bpy
from bpy.types import Menu, Panel, UIList
from bl_ui.properties_grease_pencil_common import (
GreasePencilDrawingToolsPanel,
GreasePencilStrokeEditPanel
GreasePencilStrokeEditPanel,
GreasePencilStrokeSculptPanel
)
from bl_ui.properties_paint_common import (
UnifiedPaintPanel,
@@ -1875,6 +1876,11 @@ class VIEW3D_PT_tools_grease_pencil_edit(GreasePencilStrokeEditPanel, Panel):
bl_space_type = 'VIEW_3D'
# Grease Pencil stroke sculpting tools
class VIEW3D_PT_tools_grease_pencil_sculpt(GreasePencilStrokeSculptPanel, Panel):
bl_space_type = 'VIEW_3D'
# Note: moved here so that it's always in last position in 'Tools' panels!
class VIEW3D_PT_tools_history(View3DPanel, Panel):
bl_category = "Tools"

View File

@@ -47,6 +47,7 @@ void BKE_gpencil_free(struct bGPdata *gpd);
void gpencil_stroke_sync_selection(struct bGPDstroke *gps);
struct bGPDframe *gpencil_frame_addnew(struct bGPDlayer *gpl, int cframe);
struct bGPDframe *gpencil_frame_addcopy(struct bGPDlayer *gpl, int cframe);
struct bGPDlayer *gpencil_layer_addnew(struct bGPdata *gpd, const char *name, int setactive);
struct bGPdata *gpencil_data_addnew(const char name[]);
@@ -56,9 +57,24 @@ struct bGPdata *gpencil_data_duplicate(struct bGPdata *gpd, bool internal_copy);
void gpencil_frame_delete_laststroke(struct bGPDlayer *gpl, struct bGPDframe *gpf);
/* How gpencil_layer_getframe() should behave when there
* is no existing GP-Frame on the frame requested.
*/
typedef enum eGP_GetFrame_Mode {
/* Use the preceeding gp-frame (i.e. don't add anything) */
GP_GETFRAME_USE_PREV = 0,
/* Add a new empty/blank frame */
GP_GETFRAME_ADD_NEW = 1,
/* Make a copy of the active frame */
GP_GETFRAME_ADD_COPY = 2
} eGP_GetFrame_Mode;
struct bGPDframe *gpencil_layer_getframe(struct bGPDlayer *gpl, int cframe, eGP_GetFrame_Mode addnew);
struct bGPDframe *BKE_gpencil_layer_find_frame(struct bGPDlayer *gpl, int cframe);
struct bGPDframe *gpencil_layer_getframe(struct bGPDlayer *gpl, int cframe, short addnew);
bool gpencil_layer_delframe(struct bGPDlayer *gpl, struct bGPDframe *gpf);
struct bGPDlayer *gpencil_layer_getactive(struct bGPdata *gpd);
void gpencil_layer_setactive(struct bGPdata *gpd, struct bGPDlayer *active);
void gpencil_layer_delete(struct bGPdata *gpd, struct bGPDlayer *gpl);

View File

@@ -132,7 +132,7 @@ bGPDframe *gpencil_frame_addnew(bGPDlayer *gpl, int cframe)
bGPDframe *gpf = NULL, *gf = NULL;
short state = 0;
/* error checking (neg frame only if they are not allowed in Blender!) */
/* error checking */
if (gpl == NULL)
return NULL;
@@ -178,6 +178,61 @@ bGPDframe *gpencil_frame_addnew(bGPDlayer *gpl, int cframe)
return gpf;
}
/* add a copy of the active gp-frame to the given layer */
bGPDframe *gpencil_frame_addcopy(bGPDlayer *gpl, int cframe)
{
bGPDframe *new_frame, *gpf;
bool found = false;
/* Error checking/handling */
if (gpl == NULL) {
/* no layer */
return NULL;
}
else if (gpl->actframe == NULL) {
/* no active frame, so just create a new one from scratch */
return gpencil_frame_addnew(gpl, cframe);
}
/* Create a copy of the frame */
new_frame = gpencil_frame_duplicate(gpl->actframe);
/* Find frame to insert it before */
for (gpf = gpl->frames.first; gpf; gpf = gpf->next) {
if (gpf->framenum > cframe) {
/* Add it here */
BLI_insertlinkbefore(&gpl->frames, gpf, new_frame);
found = true;
break;
}
else if (gpf->framenum == cframe) {
/* This only happens when we're editing with framelock on...
* - Delete the new frame and don't do anything else here...
*/
free_gpencil_strokes(new_frame);
MEM_freeN(new_frame);
new_frame = NULL;
found = true;
break;
}
}
if (found == false) {
/* Add new frame to the end */
BLI_addtail(&gpl->frames, new_frame);
}
/* Ensure that frame is set up correctly, and return it */
if (new_frame) {
new_frame->framenum = cframe;
gpl->actframe = new_frame;
}
return new_frame;
}
/* add a new gp-layer and make it the active layer */
bGPDlayer *gpencil_layer_addnew(bGPdata *gpd, const char *name, int setactive)
{
@@ -197,6 +252,13 @@ bGPDlayer *gpencil_layer_addnew(bGPdata *gpd, const char *name, int setactive)
copy_v4_v4(gpl->color, U.gpencil_new_layer_col);
gpl->thickness = 3;
/* onion-skinning settings */
gpl->flag |= (GP_LAYER_GHOST_PREVCOL | GP_LAYER_GHOST_NEXTCOL);
ARRAY_SET_ITEMS(gpl->gcolor_prev, 0.145098f, 0.419608f, 0.137255f); /* green */
ARRAY_SET_ITEMS(gpl->gcolor_next, 0.125490f, 0.082353f, 0.529412f); /* blue */
/* auto-name */
BLI_strncpy(gpl->info, name, sizeof(gpl->info));
BLI_uniquename(&gpd->layers, gpl, DATA_("GP_Layer"), '.', offsetof(bGPDlayer, info), sizeof(gpl->info));
@@ -387,7 +449,7 @@ bGPDframe *BKE_gpencil_layer_find_frame(bGPDlayer *gpl, int cframe)
* - 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)
*/
bGPDframe *gpencil_layer_getframe(bGPDlayer *gpl, int cframe, short addnew)
bGPDframe *gpencil_layer_getframe(bGPDlayer *gpl, int cframe, eGP_GetFrame_Mode addnew)
{
bGPDframe *gpf = NULL;
short found = 0;
@@ -425,6 +487,8 @@ bGPDframe *gpencil_layer_getframe(bGPDlayer *gpl, int cframe, short addnew)
if (addnew) {
if ((found) && (gpf->framenum == cframe))
gpl->actframe = gpf;
else if (addnew == GP_GETFRAME_ADD_COPY)
gpl->actframe = gpencil_frame_addcopy(gpl, cframe);
else
gpl->actframe = gpencil_frame_addnew(gpl, cframe);
}
@@ -445,6 +509,8 @@ bGPDframe *gpencil_layer_getframe(bGPDlayer *gpl, int cframe, short addnew)
if (addnew) {
if ((found) && (gpf->framenum == cframe))
gpl->actframe = gpf;
else if (addnew == GP_GETFRAME_ADD_COPY)
gpl->actframe = gpencil_frame_addcopy(gpl, cframe);
else
gpl->actframe = gpencil_frame_addnew(gpl, cframe);
}

View File

@@ -750,6 +750,53 @@ void BKE_scene_init(Scene *sce)
copy_v2_fl2(sce->safe_areas.action_center, 15.0f / 100.0f, 5.0f / 100.0f);
sce->preview = NULL;
/* GP Sculpt brushes */
{
GP_BrushEdit_Settings *gset = &sce->toolsettings->gp_sculpt;
GP_EditBrush_Data *gp_brush;
gp_brush = &gset->brush[GP_EDITBRUSH_TYPE_SMOOTH];
gp_brush->size = 25;
gp_brush->strength = 0.3f;
gp_brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF | GP_EDITBRUSH_FLAG_SMOOTH_PRESSURE;
gp_brush = &gset->brush[GP_EDITBRUSH_TYPE_THICKNESS];
gp_brush->size = 25;
gp_brush->strength = 0.5f;
gp_brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
gp_brush = &gset->brush[GP_EDITBRUSH_TYPE_GRAB];
gp_brush->size = 50;
gp_brush->strength = 0.3f;
gp_brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
gp_brush = &gset->brush[GP_EDITBRUSH_TYPE_PUSH];
gp_brush->size = 25;
gp_brush->strength = 0.3f;
gp_brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
gp_brush = &gset->brush[GP_EDITBRUSH_TYPE_TWIST];
gp_brush->size = 50;
gp_brush->strength = 0.3f; // XXX?
gp_brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
gp_brush = &gset->brush[GP_EDITBRUSH_TYPE_PINCH];
gp_brush->size = 50;
gp_brush->strength = 0.5f; // XXX?
gp_brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
gp_brush = &gset->brush[GP_EDITBRUSH_TYPE_RANDOMISE];
gp_brush->size = 25;
gp_brush->strength = 0.5f;
gp_brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
}
/* GP Stroke Placement */
sce->toolsettings->gpencil_v3d_align = GP_PROJECT_VIEWSPACE;
sce->toolsettings->gpencil_v2d_align = GP_PROJECT_VIEWSPACE;
sce->toolsettings->gpencil_seq_align = GP_PROJECT_VIEWSPACE;
sce->toolsettings->gpencil_ima_align = GP_PROJECT_VIEWSPACE;
}
Scene *BKE_scene_add(Main *bmain, const char *name)

View File

@@ -5822,6 +5822,7 @@ static void direct_link_scene(FileData *fd, Scene *sce)
sce->toolsettings->particle.paintcursor = NULL;
sce->toolsettings->particle.scene = NULL;
sce->toolsettings->particle.object = NULL;
sce->toolsettings->gp_sculpt.paintcursor = NULL;
/* in rare cases this is needed, see [#33806] */
if (sce->toolsettings->vpaint) {

View File

@@ -38,6 +38,7 @@
#include "DNA_camera_types.h"
#include "DNA_cloth_types.h"
#include "DNA_constraint_types.h"
#include "DNA_gpencil_types.h"
#include "DNA_sdna_types.h"
#include "DNA_sequence_types.h"
#include "DNA_space_types.h"
@@ -935,5 +936,81 @@ void blo_do_versions_270(FileData *fd, Library *UNUSED(lib), Main *main)
}
}
{
Scene *scene;
for (scene = main->scene.first; scene; scene = scene->id.next) {
ToolSettings *ts = scene->toolsettings;
if (ts->gp_sculpt.brush[0].size == 0) {
GP_BrushEdit_Settings *gset = &ts->gp_sculpt;
GP_EditBrush_Data *brush;
brush = &gset->brush[GP_EDITBRUSH_TYPE_SMOOTH];
brush->size = 25;
brush->strength = 0.3f;
brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF | GP_EDITBRUSH_FLAG_SMOOTH_PRESSURE;
brush = &gset->brush[GP_EDITBRUSH_TYPE_THICKNESS];
brush->size = 25;
brush->strength = 0.5f;
brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
brush = &gset->brush[GP_EDITBRUSH_TYPE_GRAB];
brush->size = 50;
brush->strength = 0.3f;
brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
brush = &gset->brush[GP_EDITBRUSH_TYPE_PUSH];
brush->size = 25;
brush->strength = 0.3f;
brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
brush = &gset->brush[GP_EDITBRUSH_TYPE_TWIST];
brush->size = 50;
brush->strength = 0.3f; // XXX?
brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
brush = &gset->brush[GP_EDITBRUSH_TYPE_PINCH];
brush->size = 50;
brush->strength = 0.5f; // XXX?
brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
brush = &gset->brush[GP_EDITBRUSH_TYPE_RANDOMISE];
brush->size = 25;
brush->strength = 0.5f;
brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
brush = &gset->brush[GP_EDITBRUSH_TYPE_CLONE];
brush->size = 50;
brush->strength = 1.0f;
}
if (!DNA_struct_elem_find(fd->filesdna, "ToolSettings", "char", "gpencil_v3d_align")) {
#if 0 /* XXX: Cannot do this, as we get random crashes... */
if (scene->gpd) {
bGPdata *gpd = scene->gpd;
/* Copy over the settings stored in the GP datablock linked to the scene, for minimal disruption */
ts->gpencil_v3d_align = 0;
if (gpd->flag & GP_DATA_VIEWALIGN) ts->gpencil_v3d_align |= GP_PROJECT_VIEWSPACE;
if (gpd->flag & GP_DATA_DEPTH_VIEW) ts->gpencil_v3d_align |= GP_PROJECT_DEPTH_VIEW;
if (gpd->flag & GP_DATA_DEPTH_STROKE) ts->gpencil_v3d_align |= GP_PROJECT_DEPTH_STROKE;
if (gpd->flag & GP_DATA_DEPTH_STROKE_ENDPOINTS)
ts->gpencil_v3d_align |= GP_PROJECT_DEPTH_STROKE_ENDPOINTS;
}
else {
/* Default to cursor for all standard 3D views */
ts->gpencil_v3d_align = GP_PROJECT_VIEWSPACE;
}
#endif
ts->gpencil_v3d_align = GP_PROJECT_VIEWSPACE;
ts->gpencil_v2d_align = GP_PROJECT_VIEWSPACE;
ts->gpencil_seq_align = GP_PROJECT_VIEWSPACE;
ts->gpencil_ima_align = GP_PROJECT_VIEWSPACE;
}
}
}
}

View File

@@ -91,6 +91,51 @@ void BLO_update_defaults_startup_blend(Main *bmain)
sculpt->flags |= SCULPT_DYNTOPO_COLLAPSE;
sculpt->detail_size = 12;
}
if (ts->gp_sculpt.brush[0].size == 0) {
GP_BrushEdit_Settings *gset = &ts->gp_sculpt;
GP_EditBrush_Data *brush;
brush = &gset->brush[GP_EDITBRUSH_TYPE_SMOOTH];
brush->size = 25;
brush->strength = 0.3f;
brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF | GP_EDITBRUSH_FLAG_SMOOTH_PRESSURE;
brush = &gset->brush[GP_EDITBRUSH_TYPE_THICKNESS];
brush->size = 25;
brush->strength = 0.5f;
brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
brush = &gset->brush[GP_EDITBRUSH_TYPE_GRAB];
brush->size = 50;
brush->strength = 0.3f;
brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
brush = &gset->brush[GP_EDITBRUSH_TYPE_PUSH];
brush->size = 25;
brush->strength = 0.3f;
brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
brush = &gset->brush[GP_EDITBRUSH_TYPE_TWIST];
brush->size = 50;
brush->strength = 0.3f; // XXX?
brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
brush = &gset->brush[GP_EDITBRUSH_TYPE_PINCH];
brush->size = 50;
brush->strength = 0.5f; // XXX?
brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
brush = &gset->brush[GP_EDITBRUSH_TYPE_RANDOMISE];
brush->size = 25;
brush->strength = 0.5f;
brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
}
ts->gpencil_v3d_align = GP_PROJECT_VIEWSPACE;
ts->gpencil_v2d_align = GP_PROJECT_VIEWSPACE;
ts->gpencil_seq_align = GP_PROJECT_VIEWSPACE;
ts->gpencil_ima_align = GP_PROJECT_VIEWSPACE;
}
scene->gm.lodflag |= SCE_LOD_USE_HYST;

View File

@@ -403,7 +403,6 @@ static bool find_prev_next_keyframes(struct bContext *C, int *nextfra, int *prev
{
Scene *scene = CTX_data_scene(C);
Object *ob = CTX_data_active_object(C);
bGPdata *gpd = CTX_data_gpencil_data(C);
Mask *mask = CTX_data_edit_mask(C);
bDopeSheet ads = {NULL};
DLRBT_Tree keys;
@@ -425,11 +424,12 @@ static bool find_prev_next_keyframes(struct bContext *C, int *nextfra, int *prev
/* populate tree with keyframe nodes */
scene_to_keylist(&ads, scene, &keys, NULL);
gpencil_to_keylist(&ads, scene->gpd, &keys);
if (ob)
if (ob) {
ob_to_keylist(&ads, ob, &keys, NULL);
gpencil_to_keylist(&ads, gpd, &keys);
gpencil_to_keylist(&ads, ob->gpd, &keys);
}
if (mask) {
MaskLayer *masklay = BKE_mask_layer_active(mask);

View File

@@ -1505,7 +1505,7 @@ static size_t animdata_filter_shapekey(bAnimContext *ac, ListBase *anim_data, Ke
}
/* Helper for Grease Pencil - layers within a datablock */
static size_t animdata_filter_gpencil_data(ListBase *anim_data, bGPdata *gpd, int filter_mode)
static size_t animdata_filter_gpencil_layers_data(ListBase *anim_data, bDopeSheet *ads, bGPdata *gpd, int filter_mode)
{
bGPDlayer *gpl;
size_t items = 0;
@@ -1518,6 +1518,13 @@ static size_t animdata_filter_gpencil_data(ListBase *anim_data, bGPdata *gpd, in
if (!(filter_mode & ANIMFILTER_FOREDIT) || EDITABLE_GPL(gpl)) {
/* active... */
if (!(filter_mode & ANIMFILTER_ACTIVE) || (gpl->flag & GP_LAYER_ACTIVE)) {
/* skip layer if the name doesn't match the filter string */
if ((ads) && (ads->filterflag & ADS_FILTER_BY_FCU_NAME)) {
if (BLI_strcasestr(gpl->info, ads->searchstr) == NULL)
continue;
}
/* add to list */
ANIMCHANNEL_NEW_CHANNEL(gpl, ANIMTYPE_GPLAYER, gpd);
}
@@ -1528,54 +1535,121 @@ static size_t animdata_filter_gpencil_data(ListBase *anim_data, bGPdata *gpd, in
return items;
}
/* Grab all Grease Pencil datablocks in file */
// TODO: should this be amalgamated with the dopesheet filtering code?
static size_t animdata_filter_gpencil(ListBase *anim_data, void *UNUSED(data), int filter_mode)
/* Helper for Grease Pencil - Grease Pencil datablock - GP Frames */
static size_t animdata_filter_gpencil_data(ListBase *anim_data, bDopeSheet *ads, bGPdata *gpd, int filter_mode)
{
bGPdata *gpd;
size_t items = 0;
/* for now, grab grease pencil datablocks directly from main */
// XXX: this is not good...
for (gpd = G.main->gpencil.first; gpd; gpd = gpd->id.next) {
/* When asked from "AnimData" blocks (i.e. the top-level containers for normal animation),
* for convenience, this will return GP Datablocks instead. This may cause issues down
* the track, but for now, this will do...
*/
if (filter_mode & ANIMFILTER_ANIMDATA) {
/* just add GPD as a channel - this will add everything needed */
ANIMCHANNEL_NEW_CHANNEL(gpd, ANIMTYPE_GPDATABLOCK, NULL);
}
else {
ListBase tmp_data = {NULL, NULL};
size_t tmp_items = 0;
/* only show if gpd is used by something... */
if (ID_REAL_USERS(gpd) < 1)
continue;
/* When asked from "AnimData" blocks (i.e. the top-level containers for normal animation),
* for convenience, this will return GP Datablocks instead. This may cause issues down
* the track, but for now, this will do...
*/
if (filter_mode & ANIMFILTER_ANIMDATA) {
/* just add GPD as a channel - this will add everything needed */
ANIMCHANNEL_NEW_CHANNEL(gpd, ANIMTYPE_GPDATABLOCK, NULL);
/* add gpencil animation channels */
BEGIN_ANIMFILTER_SUBCHANNELS(EXPANDED_GPD(gpd))
{
tmp_items += animdata_filter_gpencil_layers_data(&tmp_data, ads, gpd, filter_mode);
}
else {
/* add gpencil animation channels */
BEGIN_ANIMFILTER_SUBCHANNELS(EXPANDED_GPD(gpd))
{
tmp_items += animdata_filter_gpencil_data(&tmp_data, gpd, filter_mode);
END_ANIMFILTER_SUBCHANNELS;
/* did we find anything? */
if (tmp_items) {
/* include data-expand widget first */
if (filter_mode & ANIMFILTER_LIST_CHANNELS) {
/* add gpd as channel too (if for drawing, and it has layers) */
ANIMCHANNEL_NEW_CHANNEL(gpd, ANIMTYPE_GPDATABLOCK, NULL);
}
END_ANIMFILTER_SUBCHANNELS;
/* did we find anything? */
if (tmp_items) {
/* include data-expand widget first */
if (filter_mode & ANIMFILTER_LIST_CHANNELS) {
/* add gpd as channel too (if for drawing, and it has layers) */
ANIMCHANNEL_NEW_CHANNEL(gpd, ANIMTYPE_GPDATABLOCK, NULL);
/* now add the list of collected channels */
BLI_movelisttolist(anim_data, &tmp_data);
BLI_assert(BLI_listbase_is_empty(&tmp_data));
items += tmp_items;
}
}
return items;
}
/* Grab all Grease Pencil datablocks in file */
// TODO: should this be amalgamated with the dopesheet filtering code?
static size_t animdata_filter_gpencil(bAnimContext *ac, ListBase *anim_data, void *UNUSED(data), int filter_mode)
{
bDopeSheet *ads = ac->ads;
size_t items = 0;
if (ads->filterflag & ADS_FILTER_GP_3DONLY) {
Scene *scene = (Scene *)ads->source;
Base *base;
/* Active scene's GPencil block first - No parent item needed... */
if (scene->gpd) {
items += animdata_filter_gpencil_data(anim_data, ads, scene->gpd, filter_mode);
}
/* Objects in the scene */
for (base = scene->base.first; base; base = base->next) {
/* Only consider this object if it has got some GP data (saving on all the other tests) */
if (base->object && base->object->gpd) {
Object *ob = base->object;
/* firstly, check if object can be included, by the following factors:
* - if only visible, must check for layer and also viewport visibility
* --> while tools may demand only visible, user setting takes priority
* as user option controls whether sets of channels get included while
* tool-flag takes into account collapsed/open channels too
* - if only selected, must check if object is selected
* - there must be animation data to edit (this is done recursively as we
* try to add the channels)
*/
if ((filter_mode & ANIMFILTER_DATA_VISIBLE) && !(ads->filterflag & ADS_FILTER_INCL_HIDDEN)) {
/* layer visibility - we check both object and base, since these may not be in sync yet */
if ((scene->lay & (ob->lay | base->lay)) == 0) continue;
/* outliner restrict-flag */
if (ob->restrictflag & OB_RESTRICT_VIEW) continue;
}
/* now add the list of collected channels */
BLI_movelisttolist(anim_data, &tmp_data);
BLI_assert(BLI_listbase_is_empty(&tmp_data));
items += tmp_items;
/* check selection and object type filters */
if ( (ads->filterflag & ADS_FILTER_ONLYSEL) && !((base->flag & SELECT) /*|| (base == scene->basact)*/) ) {
/* only selected should be shown */
continue;
}
/* check if object belongs to the filtering group if option to filter
* objects by the grouped status is on
* - used to ease the process of doing multiple-character choreographies
*/
if (ads->filterflag & ADS_FILTER_ONLYOBGROUP) {
if (BKE_group_object_exists(ads->filter_grp, ob) == 0)
continue;
}
/* finally, include this object's grease pencil datablock */
/* XXX: Should we store these under expanders per item? */
items += animdata_filter_gpencil_data(anim_data, ads, ob->gpd, filter_mode);
}
}
}
else {
bGPdata *gpd;
/* Grab all Grease Pencil datablocks directly from main, but only those that seem to be useful somewhere */
for (gpd = G.main->gpencil.first; gpd; gpd = gpd->id.next) {
/* only show if gpd is used by something... */
if (ID_REAL_USERS(gpd) < 1)
continue;
/* add GP frames from this datablock */
items += animdata_filter_gpencil_data(anim_data, ads, gpd, filter_mode);
}
}
/* return the number of items added to the list */
return items;
@@ -2880,7 +2954,7 @@ size_t ANIM_animdata_filter(bAnimContext *ac, ListBase *anim_data, eAnimFilter_F
case ANIMCONT_GPENCIL:
{
if (animdata_filter_dopesheet_summary(ac, anim_data, filter_mode, &items))
items = animdata_filter_gpencil(anim_data, data, filter_mode);
items = animdata_filter_gpencil(ac, anim_data, data, filter_mode);
break;
}
case ANIMCONT_MASK:

View File

@@ -929,7 +929,7 @@ short paste_animedit_keys(bAnimContext *ac, ListBase *anim_data,
return -1;
}
/* mathods of offset */
/* methods of offset */
switch (offset_mode) {
case KEYFRAME_PASTE_OFFSET_CFRA_START:
offset = (float)(CFRA - animcopy_firstframe);

View File

@@ -40,6 +40,7 @@ set(INC_SYS
set(SRC
drawgpencil.c
editaction_gpencil.c
gpencil_brush.c
gpencil_convert.c
gpencil_data.c
gpencil_edit.c

View File

@@ -1166,6 +1166,7 @@ static void gp_draw_data_all(Scene *scene, bGPdata *gpd, int offsx, int offsy, i
/* draw grease-pencil sketches to specified 2d-view that uses ibuf corrections */
void ED_gpencil_draw_2dimage(const bContext *C)
{
wmWindowManager *wm = CTX_wm_manager(C);
ScrArea *sa = CTX_wm_area(C);
ARegion *ar = CTX_wm_region(C);
Scene *scene = CTX_data_scene(C);
@@ -1218,6 +1219,13 @@ void ED_gpencil_draw_2dimage(const bContext *C)
break;
}
if (ED_screen_animation_playing(wm)) {
/* don't show onionskins during animation playback/scrub (i.e. it obscures the poses)
* OpenGL Renders (i.e. final output), or depth buffer (i.e. not real strokes)
*/
dflag |= GP_DRAWDATA_NO_ONIONS;
}
/* draw it! */
gp_draw_data_all(scene, gpd, offsx, offsy, sizex, sizey, CFRA, dflag, sa->spacetype);
@@ -1228,6 +1236,7 @@ void ED_gpencil_draw_2dimage(const bContext *C)
* second time with onlyv2d=0 for screen-aligned strokes */
void ED_gpencil_draw_view2d(const bContext *C, bool onlyv2d)
{
wmWindowManager *wm = CTX_wm_manager(C);
ScrArea *sa = CTX_wm_area(C);
ARegion *ar = CTX_wm_region(C);
Scene *scene = CTX_data_scene(C);
@@ -1246,6 +1255,8 @@ void ED_gpencil_draw_view2d(const bContext *C, bool onlyv2d)
/* draw it! */
if (onlyv2d) dflag |= (GP_DRAWDATA_ONLYV2D | GP_DRAWDATA_NOSTATUS);
if (ED_screen_animation_playing(wm)) dflag |= GP_DRAWDATA_NO_ONIONS;
gp_draw_data_all(scene, gpd, 0, 0, ar->winx, ar->winy, CFRA, dflag, sa->spacetype);
/* draw status text (if in screen/pixel-space) */
@@ -1257,7 +1268,7 @@ void ED_gpencil_draw_view2d(const bContext *C, bool onlyv2d)
/* draw grease-pencil sketches to specified 3d-view assuming that matrices are already set correctly
* Note: this gets called twice - first time with only3d=1 to draw 3d-strokes,
* second time with only3d=0 for screen-aligned strokes */
void ED_gpencil_draw_view3d(Scene *scene, View3D *v3d, ARegion *ar, bool only3d)
void ED_gpencil_draw_view3d(wmWindowManager *wm, Scene *scene, View3D *v3d, ARegion *ar, bool only3d)
{
bGPdata *gpd;
int dflag = 0;
@@ -1300,13 +1311,15 @@ void ED_gpencil_draw_view3d(Scene *scene, View3D *v3d, ARegion *ar, bool only3d)
dflag |= GP_DRAWDATA_NOSTATUS;
}
if ((wm == NULL) || ED_screen_animation_playing(wm)) {
/* don't show onionskins during animation playback/scrub (i.e. it obscures the poses)
* OpenGL Renders (i.e. final output), or depth buffer (i.e. not real strokes)
*/
dflag |= GP_DRAWDATA_NO_ONIONS;
}
/* draw it! */
gp_draw_data_all(scene, gpd, offsx, offsy, winx, winy, CFRA, dflag, v3d->spacetype);
/* draw status text (if in screen/pixel-space) */
if (only3d == false) {
gp_draw_status_text(gpd, ar);
}
}
void ED_gpencil_draw_ex(Scene *scene, bGPdata *gpd, int winx, int winy, const int cfra, const char spacetype)

View File

@@ -44,11 +44,15 @@
#include "BKE_fcurve.h"
#include "BKE_gpencil.h"
#include "BKE_report.h"
#include "ED_anim_api.h"
#include "ED_gpencil.h"
#include "ED_keyframes_edit.h"
#include "ED_markers.h"
#include "WM_api.h"
/* ***************************************** */
/* NOTE ABOUT THIS FILE:
* This file contains code for editing Grease Pencil data in the Action Editor
@@ -268,7 +272,7 @@ void ED_gplayer_frames_keytype_set(bGPDlayer *gpl, short type)
}
}
#if 0 // XXX disabled until grease pencil code stabilises again
/* -------------------------------------- */
/* Copy and Paste Tools */
/* - The copy/paste buffer currently stores a set of GP_Layers, with temporary
@@ -280,118 +284,155 @@ void ED_gplayer_frames_keytype_set(bGPDlayer *gpl, short type)
*/
/* globals for copy/paste data (like for other copy/paste buffers) */
ListBase gpcopybuf = {NULL, NULL};
static int gpcopy_firstframe = 999999999;
ListBase gp_anim_copybuf = {NULL, NULL};
static int gp_anim_copy_firstframe = 999999999;
static int gp_anim_copy_lastframe = -999999999;
static int gp_anim_copy_cfra = 0;
/* This function frees any MEM_calloc'ed copy/paste buffer data */
void free_gpcopybuf()
void ED_gpencil_anim_copybuf_free(void)
{
free_gpencil_layers(&gpcopybuf);
free_gpencil_layers(&gp_anim_copybuf);
BLI_listbase_clear(&gp_anim_copybuf);
BLI_listbase_clear(&gpcopybuf);
gpcopy_firstframe = 999999999;
gp_anim_copy_firstframe = 999999999;
gp_anim_copy_lastframe = -999999999;
gp_anim_copy_cfra = 0;
}
/* This function adds data to the copy/paste buffer, freeing existing data first
* Only the selected GP-layers get their selected keyframes copied.
*
* Returns whether the copy operation was successful or not
*/
void copy_gpdata()
bool ED_gpencil_anim_copybuf_copy(bAnimContext *ac)
{
ListBase act_data = {NULL, NULL};
bActListElem *ale;
ListBase anim_data = {NULL, NULL};
bAnimListElem *ale;
int filter;
void *data;
short datatype;
Scene *scene = ac->scene;
/* clear buffer first */
free_gpcopybuf();
/* get data */
data = get_action_context(&datatype);
if (data == NULL) return;
if (datatype != ACTCONT_GPENCIL) return;
ED_gpencil_anim_copybuf_free();
/* filter data */
filter = (ACTFILTER_VISIBLE | ACTFILTER_SEL);
actdata_filter(&act_data, filter, data, datatype);
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_NODUPLIS);
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
/* assume that each of these is an ipo-block */
for (ale = act_data.first; ale; ale = ale->next) {
bGPDlayer *gpls, *gpln;
bGPDframe *gpf, *gpfn;
/* get new layer to put into buffer */
gpls = (bGPDlayer *)ale->data;
gpln = MEM_callocN(sizeof(bGPDlayer), "GPCopyPasteLayer");
BLI_listbase_clear(&gpln->frames);
BLI_strncpy(gpln->info, gpls->info, sizeof(gpln->info));
BLI_addtail(&gpcopybuf, gpln);
/* assume that each of these is a GP layer */
for (ale = anim_data.first; ale; ale = ale->next) {
ListBase copied_frames = {NULL, NULL};
bGPDlayer *gpl = (bGPDlayer *)ale->data;
bGPDframe *gpf;
/* loop over frames, and copy only selected frames */
for (gpf = gpls->frames.first; gpf; gpf = gpf->next) {
for (gpf = gpl->frames.first; gpf; gpf = gpf->next) {
/* if frame is selected, make duplicate it and its strokes */
if (gpf->flag & GP_FRAME_SELECT) {
/* add frame to buffer */
gpfn = gpencil_frame_duplicate(gpf);
BLI_addtail(&gpln->frames, gpfn);
/* make a copy of this frame */
bGPDframe *new_frame = gpencil_frame_duplicate(gpf);
BLI_addtail(&copied_frames, new_frame);
/* check if this is the earliest frame encountered so far */
if (gpf->framenum < gpcopy_firstframe)
gpcopy_firstframe = gpf->framenum;
/* extend extents for keyframes encountered */
if (gpf->framenum < gp_anim_copy_firstframe)
gp_anim_copy_firstframe = gpf->framenum;
if (gpf->framenum > gp_anim_copy_lastframe)
gp_anim_copy_lastframe = gpf->framenum;
}
}
/* create a new layer in buffer if there were keyframes here */
if (BLI_listbase_is_empty(&copied_frames) == false) {
bGPDlayer *new_layer = MEM_callocN(sizeof(bGPDlayer), "GPCopyPasteLayer");
BLI_addtail(&gp_anim_copybuf, new_layer);
/* move over copied frames */
BLI_movelisttolist(&new_layer->frames, &copied_frames);
BLI_assert(copied_frames.first == NULL);
/* make a copy of the layer's name - for name-based matching later... */
BLI_strncpy(new_layer->info, gpl->info, sizeof(new_layer->info));
}
}
/* check if anything ended up in the buffer */
if (ELEM(NULL, gpcopybuf.first, gpcopybuf.last))
error("Nothing copied to buffer");
/* in case 'relative' paste method is used */
gp_anim_copy_cfra = CFRA;
/* free temp memory */
BLI_freelistN(&act_data);
/* clean up */
ANIM_animdata_freelist(&anim_data);
/* check if anything ended up in the buffer */
if (ELEM(NULL, gp_anim_copybuf.first, gp_anim_copybuf.last)) {
BKE_report(ac->reports, RPT_ERROR, "No keyframes copied to keyframes copy/paste buffer");
return false;
}
/* report success */
return true;
}
void paste_gpdata(Scene *scene)
/* Pastes keyframes from buffer, and reports success */
bool ED_gpencil_anim_copybuf_paste(bAnimContext *ac, const short offset_mode)
{
ListBase act_data = {NULL, NULL};
bActListElem *ale;
ListBase anim_data = {NULL, NULL};
bAnimListElem *ale;
int filter;
void *data;
short datatype;
const int offset = (CFRA - gpcopy_firstframe);
short no_name = 0;
Scene *scene = ac->scene;
bool no_name = false;
int offset = 0;
/* check if buffer is empty */
if (ELEM(NULL, gpcopybuf.first, gpcopybuf.last)) {
error("No data in buffer to paste");
return;
if (BLI_listbase_is_empty(&gp_anim_copybuf)) {
BKE_report(ac->reports, RPT_ERROR, "No data in buffer to paste");
return false;
}
/* check if single channel in buffer (disregard names if so) */
if (gpcopybuf.first == gpcopybuf.last)
no_name = 1;
/* get data */
data = get_action_context(&datatype);
if (data == NULL) return;
if (datatype != ACTCONT_GPENCIL) return;
/* check if single channel in buffer (disregard names if so) */
if (gp_anim_copybuf.first == gp_anim_copybuf.last) {
no_name = true;
}
/* methods of offset (eKeyPasteOffset) */
switch (offset_mode) {
case KEYFRAME_PASTE_OFFSET_CFRA_START:
offset = (CFRA - gp_anim_copy_firstframe);
break;
case KEYFRAME_PASTE_OFFSET_CFRA_END:
offset = (CFRA - gp_anim_copy_lastframe);
break;
case KEYFRAME_PASTE_OFFSET_CFRA_RELATIVE:
offset = (CFRA - gp_anim_copy_cfra);
break;
case KEYFRAME_PASTE_OFFSET_NONE:
offset = 0;
break;
}
/* filter data */
filter = (ACTFILTER_VISIBLE | ACTFILTER_SEL | ACTFILTER_FOREDIT);
actdata_filter(&act_data, filter, data, datatype);
// TODO: try doing it with selection, then without selection imits
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_SEL | ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS);
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
/* from selected channels */
for (ale = act_data.first; ale; ale = ale->next) {
for (ale = anim_data.first; ale; ale = ale->next) {
bGPDlayer *gpld = (bGPDlayer *)ale->data;
bGPDlayer *gpls = NULL;
bGPDframe *gpfs, *gpf;
/* find suitable layer from buffer to use to paste from */
for (gpls = gpcopybuf.first; gpls; gpls = gpls->next) {
for (gpls = gp_anim_copybuf.first; gpls; gpls = gpls->next) {
/* check if layer name matches */
if ((no_name) || STREQ(gpls->info, gpld->info))
if ((no_name) || STREQ(gpls->info, gpld->info)) {
break;
}
}
/* this situation might occur! */
@@ -407,58 +448,21 @@ void paste_gpdata(Scene *scene)
gpf = gpencil_layer_getframe(gpld, gpfs->framenum, 1);
if (gpf) {
bGPDstroke *gps, *gpsn;
ScrArea *sa;
/* get area that gp-data comes from */
//sa = gpencil_data_findowner((bGPdata *)ale->owner);
sa = NULL;
/* this should be the right frame... as it may be a pre-existing frame,
/* This should be the right frame... as it may be a pre-existing frame,
* must make sure that only compatible stroke types get copied over
* - we cannot just add a duplicate frame, as that would cause errors
* - need to check for compatible types to minimize memory usage (copying 'junk' over)
* - We cannot just add a duplicate frame, as that would cause errors
* - For now, we don't check if the types will be compatible since we
* don't have enough info to do so. Instead, we simply just paste,
* af it works, it will show up.
*/
for (gps = gpfs->strokes.first; gps; gps = gps->next) {
short stroke_ok;
/* make a copy of stroke, then of its points array */
gpsn = MEM_dupallocN(gps);
gpsn->points = MEM_dupallocN(gps->points);
/* if there's an area, check that it supports this type of stroke */
if (sa) {
stroke_ok = 0;
/* check if spacetype supports this type of stroke
* - NOTE: must sync this with gp_paint_initstroke() in gpencil.c
*/
switch (sa->spacetype) {
case SPACE_VIEW3D: /* 3D-View: either screen-aligned or 3d-space */
if ((gps->flag == 0) || (gps->flag & GP_STROKE_3DSPACE))
stroke_ok = 1;
break;
case SPACE_NODE: /* Nodes Editor: either screen-aligned or view-aligned */
case SPACE_IMAGE: /* Image Editor: either screen-aligned or view\image-aligned */
case SPACE_CLIP: /* Image Editor: either screen-aligned or view\image-aligned */
if ((gps->flag == 0) || (gps->flag & GP_STROKE_2DSPACE))
stroke_ok = 1;
break;
case SPACE_SEQ: /* Sequence Editor: either screen-aligned or view-aligned */
if ((gps->flag == 0) || (gps->flag & GP_STROKE_2DIMAGE))
stroke_ok = 1;
break;
}
}
else
stroke_ok = 1;
/* if stroke is ok, we make a copy of this stroke and add to frame */
if (stroke_ok) {
/* make a copy of stroke, then of its points array */
gpsn = MEM_dupallocN(gps);
gpsn->points = MEM_dupallocN(gps->points);
/* append stroke to frame */
BLI_addtail(&gpf->strokes, gpsn);
}
/* append stroke to frame */
BLI_addtail(&gpf->strokes, gpsn);
}
/* if no strokes (i.e. new frame) added, free gpf */
@@ -471,13 +475,10 @@ void paste_gpdata(Scene *scene)
}
}
/* free temp memory */
BLI_freelistN(&act_data);
/* undo and redraw stuff */
BIF_undo_push("Paste Grease Pencil Frames");
/* clean up */
ANIM_animdata_freelist(&anim_data);
return true;
}
#endif /* XXX disabled until Grease Pencil code stabilises again... */
/* -------------------------------------- */
/* Snap Tools */

File diff suppressed because it is too large Load Diff

View File

@@ -77,6 +77,7 @@
#include "RNA_access.h"
#include "RNA_define.h"
#include "UI_resources.h"
#include "UI_view2d.h"
#include "ED_gpencil.h"
@@ -106,9 +107,9 @@ enum {
/* RNA enum define */
static EnumPropertyItem prop_gpencil_convertmodes[] = {
{GP_STROKECONVERT_PATH, "PATH", 0, "Path", ""},
{GP_STROKECONVERT_CURVE, "CURVE", 0, "Bezier Curve", ""},
{GP_STROKECONVERT_POLY, "POLY", 0, "Polygon Curve", ""},
{GP_STROKECONVERT_PATH, "PATH", ICON_CURVE_PATH, "Path", "Animation path"},
{GP_STROKECONVERT_CURVE, "CURVE", ICON_CURVE_BEZCURVE, "Bezier Curve", "Smooth Bezier curve"},
{GP_STROKECONVERT_POLY, "POLY", ICON_MESH_DATA, "Polygon Curve", "Bezier curve with straight-line segments (vector handles)"},
{0, NULL, 0, NULL, NULL}
};

View File

@@ -59,12 +59,14 @@
#include "BKE_screen.h"
#include "UI_interface.h"
#include "UI_resources.h"
#include "WM_api.h"
#include "WM_types.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "RNA_enum_types.h"
#include "ED_gpencil.h"
@@ -444,4 +446,220 @@ void GPENCIL_OT_reveal(wmOperatorType *ot)
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* ***************** Lock/Unlock All Layers ************************ */
static int gp_lock_all_exec(bContext *C, wmOperator *UNUSED(op))
{
bGPdata *gpd = ED_gpencil_data_get_active(C);
bGPDlayer *gpl;
/* sanity checks */
if (gpd == NULL)
return OPERATOR_CANCELLED;
/* make all layers non-editable */
for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
gpl->flag |= GP_LAYER_LOCKED;
}
/* notifiers */
WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
return OPERATOR_FINISHED;
}
void GPENCIL_OT_lock_all(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Lock All Layers";
ot->idname = "GPENCIL_OT_lock_all";
ot->description = "Lock all Grease Pencil layers to prevent them from being accidentally modified";
/* callbacks */
ot->exec = gp_lock_all_exec;
ot->poll = gp_reveal_poll; /* XXX: could use dedicated poll later */
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* -------------------------- */
static int gp_unlock_all_exec(bContext *C, wmOperator *UNUSED(op))
{
bGPdata *gpd = ED_gpencil_data_get_active(C);
bGPDlayer *gpl;
/* sanity checks */
if (gpd == NULL)
return OPERATOR_CANCELLED;
/* make all layers editable again */
for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
gpl->flag &= ~GP_LAYER_LOCKED;
}
/* notifiers */
WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
return OPERATOR_FINISHED;
}
void GPENCIL_OT_unlock_all(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Unlock All Layers";
ot->idname = "GPENCIL_OT_unlock_all";
ot->description = "unlock all Grease Pencil layers so that they can be edited";
/* callbacks */
ot->exec = gp_unlock_all_exec;
ot->poll = gp_reveal_poll; /* XXX: could use dedicated poll later */
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* ********************** Isolate Layer **************************** */
static int gp_isolate_layer_exec(bContext *C, wmOperator *op)
{
bGPdata *gpd = ED_gpencil_data_get_active(C);
bGPDlayer *layer = gpencil_layer_getactive(gpd);
bGPDlayer *gpl;
int flags = GP_LAYER_LOCKED;
bool isolate = false;
if (RNA_boolean_get(op->ptr, "affect_visibility"))
flags |= GP_LAYER_HIDE;
if (ELEM(NULL, gpd, layer)) {
BKE_report(op->reports, RPT_ERROR, "No active layer to isolate");
return OPERATOR_CANCELLED;
}
/* Test whether to isolate or clear all flags */
for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
/* Skip if this is the active layer */
if (gpl == layer)
continue;
/* If the flags aren't set, that means that the layer is
* not alone, so we have some layers to isolate still
*/
if ((gpl->flag & flags) == 0) {
isolate = true;
break;
}
}
/* Set/Clear flags as appropriate */
/* TODO: Include onionskinning on this list? */
if (isolate) {
/* Set flags on all "other" layers */
for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
if (gpl == layer)
continue;
else
gpl->flag |= flags;
}
}
else {
/* Clear flags - Restore everything else */
for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
gpl->flag &= ~flags;
}
}
/* notifiers */
WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
return OPERATOR_FINISHED;
}
void GPENCIL_OT_layer_isolate(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Isolate Layer";
ot->idname = "GPENCIL_OT_layer_isolate";
ot->description = "Toggle whether the active layer is the only one that can be edited and/or visible";
/* callbacks */
ot->exec = gp_isolate_layer_exec;
ot->poll = gp_active_layer_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
RNA_def_boolean(ot->srna, "affect_visibility", false, "Affect Visibility",
"In addition to toggling the editability, also affect the visibility");
}
/* ********************** Change Layer ***************************** */
static int gp_layer_change_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(evt))
{
uiPopupMenu *pup;
uiLayout *layout;
/* call the menu, which will call this operator again, hence the canceled */
pup = UI_popup_menu_begin(C, op->type->name, ICON_NONE);
layout = UI_popup_menu_layout(pup);
uiItemsEnumO(layout, "GPENCIL_OT_layer_change", "layer");
UI_popup_menu_end(C, pup);
return OPERATOR_INTERFACE;
}
static int gp_layer_change_exec(bContext *C, wmOperator *op)
{
bGPdata *gpd = CTX_data_gpencil_data(C);
bGPDlayer *gpl = NULL;
int layer_num = RNA_enum_get(op->ptr, "layer");
/* Get layer or create new one */
if (layer_num == -1) {
/* Create layer */
gpl = gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true);
}
else {
/* Try to get layer */
gpl = BLI_findlink(&gpd->layers, layer_num);
if (gpl == NULL) {
BKE_reportf(op->reports, RPT_ERROR, "Cannot change to non-existent layer (index = %d)", layer_num);
return OPERATOR_CANCELLED;
}
}
/* Set active layer */
gpencil_layer_setactive(gpd, gpl);
/* updates */
WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
return OPERATOR_FINISHED;
}
void GPENCIL_OT_layer_change(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Change Layer";
ot->idname = "GPENCIL_OT_layer_change";
ot->description = "Change active Grease Pencil layer";
/* callbacks */
ot->invoke = gp_layer_change_invoke;
ot->exec = gp_layer_change_exec;
ot->poll = gp_active_layer_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* gp layer to use (dynamic enum) */
ot->prop = RNA_def_enum(ot->srna, "layer", DummyRNA_DEFAULT_items, 0, "Grease Pencil Layer", "");
RNA_def_enum_funcs(ot->prop, ED_gpencil_layers_with_new_enum_itemf);
}
/* ************************************************ */

View File

@@ -44,6 +44,7 @@
#include "BLT_translation.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
@@ -58,20 +59,62 @@
#include "BKE_screen.h"
#include "UI_interface.h"
#include "UI_resources.h"
#include "WM_api.h"
#include "WM_types.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "RNA_enum_types.h"
#include "UI_view2d.h"
#include "ED_gpencil.h"
#include "ED_object.h"
#include "ED_view3d.h"
#include "gpencil_intern.h"
/* ************************************************ */
/* Stroke Edit Mode Management */
static int gpencil_editmode_toggle_poll(bContext *C)
{
return ED_gpencil_data_get_active(C) != NULL;
}
static int gpencil_editmode_toggle_exec(bContext *C, wmOperator *op)
{
bGPdata *gpd = ED_gpencil_data_get_active(C);
if (gpd == NULL)
return OPERATOR_CANCELLED;
/* Just toggle editmode flag... */
gpd->flag ^= GP_DATA_STROKE_EDITMODE;
WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | ND_GPENCIL_EDITMODE, NULL);
WM_event_add_notifier(C, NC_SCENE | ND_MODE, NULL);
return OPERATOR_FINISHED;
}
void GPENCIL_OT_editmode_toggle(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Strokes Edit Mode Toggle";
ot->idname = "GPENCIL_OT_editmode_toggle";
ot->description = "Enter/Exit edit mode for Grease Pencil strokes";
/* callbacks */
ot->exec = gpencil_editmode_toggle_exec;
ot->poll = gpencil_editmode_toggle_poll;
/* flags */
ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER;
}
/* ************************************************ */
/* Stroke Editing Operators */
@@ -233,7 +276,8 @@ void GPENCIL_OT_duplicate(wmOperatorType *ot)
*/
/* list of bGPDstroke instances */
static ListBase gp_strokes_copypastebuf = {NULL, NULL};
/* NOTE: is exposed within the editors/gpencil module so that other tools can use it too */
ListBase gp_strokes_copypastebuf = {NULL, NULL};
/* Free copy/paste buffer data */
void ED_gpencil_strokes_copybuf_free(void)
@@ -339,7 +383,7 @@ static int gp_strokes_paste_exec(bContext *C, wmOperator *op)
BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data");
return OPERATOR_CANCELLED;
}
else if (gp_strokes_copypastebuf.first == NULL) {
else if (BLI_listbase_is_empty(&gp_strokes_copypastebuf)) {
BKE_report(op->reports, RPT_ERROR, "No strokes to paste, select and copy some points before trying again");
return OPERATOR_CANCELLED;
}
@@ -434,6 +478,110 @@ void GPENCIL_OT_paste(wmOperatorType *ot)
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* ******************* Move To Layer ****************************** */
static int gp_move_to_layer_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(evt))
{
uiPopupMenu *pup;
uiLayout *layout;
/* call the menu, which will call this operator again, hence the canceled */
pup = UI_popup_menu_begin(C, op->type->name, ICON_NONE);
layout = UI_popup_menu_layout(pup);
uiItemsEnumO(layout, "GPENCIL_OT_move_to_layer", "layer");
UI_popup_menu_end(C, pup);
return OPERATOR_INTERFACE;
}
// FIXME: allow moving partial strokes
static int gp_move_to_layer_exec(bContext *C, wmOperator *op)
{
bGPdata *gpd = CTX_data_gpencil_data(C);
bGPDlayer *target_layer = NULL;
ListBase strokes = {NULL, NULL};
int layer_num = RNA_enum_get(op->ptr, "layer");
/* Get layer or create new one */
if (layer_num == -1) {
/* Create layer */
target_layer = gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true);
}
else {
/* Try to get layer */
target_layer = BLI_findlink(&gpd->layers, layer_num);
if (target_layer == NULL) {
BKE_reportf(op->reports, RPT_ERROR, "There is no layer number %d", layer_num);
return OPERATOR_CANCELLED;
}
}
/* Extract all strokes to move to this layer
* NOTE: We need to do this in a two-pass system to avoid conflicts with strokes
* getting repeatedly moved
*/
CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
{
bGPDframe *gpf = gpl->actframe;
bGPDstroke *gps, *gpsn;
/* skip if no frame with strokes, or if this is the layer we're moving strokes to */
if ((gpl == target_layer) || (gpf == NULL))
continue;
/* make copies of selected strokes, and deselect these once we're done */
for (gps = gpf->strokes.first; gps; gps = gpsn) {
gpsn = gps->next;
/* skip strokes that are invalid for current view */
if (ED_gpencil_stroke_can_use(C, gps) == false)
continue;
/* TODO: Don't just move entire strokes - instead, only copy the selected portions... */
if (gps->flag & GP_STROKE_SELECT) {
BLI_remlink(&gpf->strokes, gps);
BLI_addtail(&strokes, gps);
}
}
}
CTX_DATA_END;
/* Paste them all in one go */
if (strokes.first) {
Scene *scene = CTX_data_scene(C);
bGPDframe *gpf = gpencil_layer_getframe(target_layer, CFRA, true);
BLI_movelisttolist(&gpf->strokes, &strokes);
BLI_assert((strokes.first == strokes.last) && (strokes.first == NULL));
}
/* updates */
WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
return OPERATOR_FINISHED;
}
void GPENCIL_OT_move_to_layer(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Move Strokes to Layer";
ot->idname = "GPENCIL_OT_move_to_layer";
ot->description = "Move selected strokes to another layer"; // XXX: allow moving individual points too?
/* callbacks */
ot->invoke = gp_move_to_layer_invoke;
ot->exec = gp_move_to_layer_exec;
ot->poll = gp_stroke_edit_poll; // XXX?
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* gp layer to use (dynamic enum) */
ot->prop = RNA_def_enum(ot->srna, "layer", DummyRNA_DEFAULT_items, 0, "Grease Pencil Layer", "");
RNA_def_enum_funcs(ot->prop, ED_gpencil_layers_with_new_enum_itemf);
}
/* ******************* Delete Active Frame ************************ */
static int gp_actframe_delete_poll(bContext *C)
@@ -497,6 +645,7 @@ typedef enum eGP_DeleteMode {
GP_DELETEOP_FRAME = 2,
} eGP_DeleteMode;
/* ----------------------------------- */
/* Delete selected strokes */
static int gp_delete_selected_strokes(bContext *C)
@@ -540,6 +689,8 @@ static int gp_delete_selected_strokes(bContext *C)
}
}
/* ----------------------------------- */
/* Delete selected points but keep the stroke */
static int gp_dissolve_selected_points(bContext *C)
{
@@ -621,6 +772,124 @@ static int gp_dissolve_selected_points(bContext *C)
}
}
/* ----------------------------------- */
/* Temp data for storing information about an "island" of points
* that should be kept when splitting up a stroke. Used in:
* gp_stroke_delete_tagged_points()
*/
typedef struct tGPDeleteIsland {
int start_idx;
int end_idx;
} tGPDeleteIsland;
/* Split the given stroke into several new strokes, partitioning
* it based on whether the stroke points have a particular flag
* is set (e.g. "GP_SPOINT_SELECT" in most cases, but not always)
*
* The algorithm used here is as follows:
* 1) We firstly identify the number of "islands" of non-tagged points
* which will all end up being in new strokes.
* - In the most extreme case (i.e. every other vert is a 1-vert island),
* we have at most n / 2 islands
* - Once we start having larger islands than that, the number required
* becomes much less
* 2) Each island gets converted to a new stroke
*/
void gp_stroke_delete_tagged_points(bGPDframe *gpf, bGPDstroke *gps, bGPDstroke *next_stroke, int tag_flags)
{
tGPDeleteIsland *islands = MEM_callocN(sizeof(tGPDeleteIsland) * (gps->totpoints + 1) / 2, "gp_point_islands");
bool in_island = false;
int num_islands = 0;
bGPDspoint *pt;
int i;
/* First Pass: Identify start/end of islands */
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
if (pt->flag & tag_flags) {
/* selected - stop accumulating to island */
in_island = false;
}
else {
/* unselected - start of a new island? */
int idx;
if (in_island) {
/* extend existing island */
idx = num_islands - 1;
islands[idx].end_idx = i;
}
else {
/* start of new island */
in_island = true;
num_islands++;
idx = num_islands - 1;
islands[idx].start_idx = islands[idx].end_idx = i;
}
}
}
/* Watch out for special case where No islands = All points selected = Delete Stroke only */
if (num_islands) {
/* there are islands, so create a series of new strokes, adding them before the "next" stroke */
int idx;
/* Create each new stroke... */
for (idx = 0; idx < num_islands; idx++) {
tGPDeleteIsland *island = &islands[idx];
bGPDstroke *new_stroke = MEM_dupallocN(gps);
/* Compute new buffer size (+ 1 needed as the endpoint index is "inclusive") */
new_stroke->totpoints = island->end_idx - island->start_idx + 1;
new_stroke->points = MEM_callocN(sizeof(bGPDspoint) * new_stroke->totpoints, "gp delete stroke fragment");
/* Copy over the relevant points */
memcpy(new_stroke->points, gps->points + island->start_idx, sizeof(bGPDspoint) * new_stroke->totpoints);
/* Each island corresponds to a new stroke. We must adjust the
* timings of these new strokes:
*
* Each point's timing data is a delta from stroke's inittime, so as we erase some points from
* the start of the stroke, we have to offset this inittime and all remaing points' delta values.
* This way we get a new stroke with exactly the same timing as if user had started drawing from
* the first non-removed point...
*/
{
bGPDspoint *pts;
float delta = gps->points[island->start_idx].time;
int j;
new_stroke->inittime += (double)delta;
pts = new_stroke->points;
for (j = 0; j < new_stroke->totpoints; j++, pts++) {
pts->time -= delta;
}
}
/* Add new stroke to the frame */
if (next_stroke) {
BLI_insertlinkbefore(&gpf->strokes, next_stroke, new_stroke);
}
else {
BLI_addtail(&gpf->strokes, new_stroke);
}
}
}
/* free islands */
MEM_freeN(islands);
/* Delete the old stroke */
MEM_freeN(gps->points);
BLI_freelinkN(&gpf->strokes, gps);
}
/* Split selected strokes into segments, splitting on selected points */
static int gp_delete_selected_points(bContext *C)
{
@@ -644,89 +913,11 @@ static int gp_delete_selected_points(bContext *C)
if (gps->flag & GP_STROKE_SELECT) {
bGPDspoint *pt;
int i;
/* deselect old stroke, since it will be used as template for the new strokes */
gps->flag &= ~GP_STROKE_SELECT;
/* The algorithm used here is as follows:
* 1) We firstly identify the number of "islands" of non-selected points
* which will all end up being in new strokes.
* - In the most extreme case (i.e. every other vert is a 1-vert island),
* we have at most n / 2 islands
* - Once we start having larger islands than that, the number required
* becomes much less
* 2) Each island gets converted to a new stroke
*/
typedef struct tGPDeleteIsland {
int start_idx;
int end_idx;
} tGPDeleteIsland;
tGPDeleteIsland *islands = MEM_callocN(sizeof(tGPDeleteIsland) * (gps->totpoints + 1) / 2, "gp_point_islands");
bool in_island = false;
int num_islands = 0;
/* First Pass: Identify start/end of islands */
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
if (pt->flag & GP_SPOINT_SELECT) {
/* selected - stop accumulating to island */
in_island = false;
}
else {
/* unselected - start of a new island? */
int idx;
if (in_island) {
/* extend existing island */
idx = num_islands - 1;
islands[idx].end_idx = i;
}
else {
/* start of new island */
in_island = true;
num_islands++;
idx = num_islands - 1;
islands[idx].start_idx = islands[idx].end_idx = i;
}
}
}
/* Watch out for special case where No islands = All points selected = Delete Stroke only */
if (num_islands) {
/* there are islands, so create a series of new strokes, adding them before the "next" stroke */
int idx;
/* deselect old stroke, since it will be used as template for the new strokes */
gps->flag &= ~GP_STROKE_SELECT;
/* create each new stroke... */
for (idx = 0; idx < num_islands; idx++) {
tGPDeleteIsland *island = &islands[idx];
bGPDstroke *new_stroke = MEM_dupallocN(gps);
/* compute new buffer size (+ 1 needed as the endpoint index is "inclusive") */
new_stroke->totpoints = island->end_idx - island->start_idx + 1;
new_stroke->points = MEM_callocN(sizeof(bGPDspoint) * new_stroke->totpoints, "gp delete stroke fragment");
/* copy over the relevant points */
memcpy(new_stroke->points, gps->points + island->start_idx, sizeof(bGPDspoint) * new_stroke->totpoints);
/* add new stroke to the frame */
if (gpsn) {
BLI_insertlinkbefore(&gpf->strokes, gpsn, new_stroke);
}
else {
BLI_addtail(&gpf->strokes, new_stroke);
}
}
}
/* free islands */
MEM_freeN(islands);
/* Delete the old stroke */
MEM_freeN(gps->points);
BLI_freelinkN(&gpf->strokes, gps);
/* delete unwanted points by splitting stroke into several smaller ones */
gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_SELECT);
changed = true;
}
@@ -743,6 +934,7 @@ static int gp_delete_selected_points(bContext *C)
}
}
/* ----------------------------------- */
static int gp_delete_exec(bContext *C, wmOperator *op)
{
@@ -812,4 +1004,190 @@ void GPENCIL_OT_dissolve(wmOperatorType *ot)
ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER;
}
/* ****************** Snapping - Strokes <-> Cursor ************************ */
/* Poll callback for snap operators */
/* NOTE: For now, we only allow these in the 3D view, as other editors do not
* define a cursor or gridstep which can be used
*/
static int gp_snap_poll(bContext *C)
{
bGPdata *gpd = CTX_data_gpencil_data(C);
ScrArea *sa = CTX_wm_area(C);
return (gpd != NULL) && ((sa != NULL) && (sa->spacetype == SPACE_VIEW3D));
}
/* --------------------------------- */
static int gp_snap_to_grid(bContext *C, wmOperator *op)
{
RegionView3D *rv3d = CTX_wm_region_data(C);
float gridf = rv3d->gridview;
CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
{
bGPDspoint *pt;
int i;
// TOOD: if entire stroke is selected, offset entire stroke by same amount?
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
/* only if point is selected.. */
if (pt->flag & GP_SPOINT_SELECT) {
pt->x = gridf * floorf(0.5f + pt->x / gridf);
pt->y = gridf * floorf(0.5f + pt->y / gridf);
pt->z = gridf * floorf(0.5f + pt->z / gridf);
}
}
}
CTX_DATA_END;
WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
return OPERATOR_FINISHED;
}
void GPENCIL_OT_snap_to_grid(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Snap Selection to Grid";
ot->idname = "GPENCIL_OT_snap_to_grid";
ot->description = "Snap selected points to the nearest grid points";
/* callbacks */
ot->exec = gp_snap_to_grid;
ot->poll = gp_snap_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* ------------------------------- */
static int gp_snap_to_cursor(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
View3D *v3d = CTX_wm_view3d(C);
const bool use_offset = RNA_boolean_get(op->ptr, "use_offset");
const float *cursor_global = ED_view3d_cursor3d_get(scene, v3d);
CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
{
bGPDspoint *pt;
int i;
/* only continue if this stroke is selected (editable doesn't guarantee this)... */
if ((gps->flag & GP_STROKE_SELECT) == 0)
continue;
if (use_offset) {
float offset[3];
/* compute offset from first point of stroke to cursor */
/* TODO: Allow using midpoint instead? */
sub_v3_v3v3(offset, cursor_global, &gps->points->x);
/* apply offset to all points in the stroke */
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
add_v3_v3(&pt->x, offset);
}
}
else {
/* affect each selected point */
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
if (pt->flag & GP_SPOINT_SELECT) {
copy_v3_v3(&pt->x, cursor_global);
}
}
}
}
CTX_DATA_END;
WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
return OPERATOR_FINISHED;
}
void GPENCIL_OT_snap_to_cursor(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Snap Selection to Cursor";
ot->idname = "GPENCIL_OT_snap_to_cursor";
ot->description = "Snap selected points/strokes to the cursor";
/* callbacks */
ot->exec = gp_snap_to_cursor;
ot->poll = gp_snap_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
ot->prop = RNA_def_boolean(ot->srna, "use_offset", true, "With Offset",
"Offset the entire stroke instead of selected points only");
}
/* ------------------------------- */
static int gp_snap_cursor_to_sel(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
View3D *v3d = CTX_wm_view3d(C);
float *cursor = ED_view3d_cursor3d_get(scene, v3d);
float centroid[3] = {0.0f};
float min[3], max[3];
size_t count = 0;
INIT_MINMAX(min, max);
/* calculate midpoints from selected points */
CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
{
bGPDspoint *pt;
int i;
/* only continue if this stroke is selected (editable doesn't guarantee this)... */
if ((gps->flag & GP_STROKE_SELECT) == 0)
continue;
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
if (pt->flag & GP_SPOINT_SELECT) {
add_v3_v3(centroid, &pt->x);
minmax_v3v3_v3(min, max, &pt->x);
count++;
}
}
}
CTX_DATA_END;
if (v3d->around == V3D_AROUND_CENTER_MEAN) {
mul_v3_fl(centroid, 1.0f / (float)count);
copy_v3_v3(cursor, centroid);
}
else {
mid_v3_v3v3(cursor, min, max);
}
WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
return OPERATOR_FINISHED;
}
void GPENCIL_OT_snap_cursor_to_selected(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Snap Cursor to Selected Points";
ot->idname = "GPENCIL_OT_snap_cursor_to_selected";
ot->description = "Snap cursor to center of selected points";
/* callbacks */
ot->exec = gp_snap_cursor_to_sel;
ot->poll = gp_snap_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* ************************************************ */

View File

@@ -44,6 +44,10 @@ struct ARegion;
struct View2D;
struct wmOperatorType;
struct PointerRNA;
struct PropertyRNA;
struct EnumPropertyItem;
/* ***************************************************** */
/* Internal API */
@@ -96,12 +100,37 @@ void gp_point_conversion_init(struct bContext *C, GP_SpaceConversion *r_gsc);
void gp_point_to_xy(GP_SpaceConversion *settings, struct bGPDstroke *gps, struct bGPDspoint *pt,
int *r_x, int *r_y);
/**
* Convert a screenspace point to a 3D Grease Pencil coordinate.
*
* For use with editing tools where it is easier to perform the operations in 2D,
* and then later convert the transformed points back to 3D.
*
* \param screeN_co The screenspace 2D coordinates to convert to
* \param[out] r_out The resulting 3D coordinates of the input point
*/
bool gp_point_xy_to_3d(GP_SpaceConversion *gsc, struct Scene *scene, const float screen_co[2], float r_out[3]);
/* Poll Callbacks ------------------------------------ */
/* gpencil_utils.c */
int gp_add_poll(struct bContext *C);
int gp_active_layer_poll(struct bContext *C);
/* Copy/Paste Buffer --------------------------------- */
/* gpencil_edit.c */
extern ListBase gp_strokes_copypastebuf;
/* Stroke Editing ------------------------------------ */
void gp_stroke_delete_tagged_points(bGPDframe *gpf, bGPDstroke *gps, bGPDstroke *next_stroke, int tag_flags);
/* Layers Enums -------------------------------------- */
struct EnumPropertyItem *ED_gpencil_layers_enum_itemf(struct bContext *C, struct PointerRNA *ptr, struct PropertyRNA *prop, bool *r_free);
struct EnumPropertyItem *ED_gpencil_layers_with_new_enum_itemf(struct bContext *C, struct PointerRNA *ptr, struct PropertyRNA *prop, bool *r_free);
/* ***************************************************** */
/* Operator Defines */
@@ -119,6 +148,8 @@ typedef enum eGPencil_PaintModes {
/* stroke editing ----- */
void GPENCIL_OT_editmode_toggle(struct wmOperatorType *ot);
void GPENCIL_OT_select(struct wmOperatorType *ot);
void GPENCIL_OT_select_all(struct wmOperatorType *ot);
void GPENCIL_OT_select_circle(struct wmOperatorType *ot);
@@ -135,6 +166,19 @@ void GPENCIL_OT_dissolve(struct wmOperatorType *ot);
void GPENCIL_OT_copy(struct wmOperatorType *ot);
void GPENCIL_OT_paste(struct wmOperatorType *ot);
void GPENCIL_OT_move_to_layer(struct wmOperatorType *ot);
void GPENCIL_OT_layer_change(struct wmOperatorType *ot);
void GPENCIL_OT_snap_to_grid(struct wmOperatorType *ot);
void GPENCIL_OT_snap_to_cursor(struct wmOperatorType *ot);
void GPENCIL_OT_snap_cursor_to_selected(struct wmOperatorType *ot);
void GPENCIL_OT_snap_cursor_to_center(struct wmOperatorType *ot);
/* stroke sculpting -- */
void GPENCIL_OT_brush_paint(struct wmOperatorType *ot);
/* buttons editing --- */
void GPENCIL_OT_data_add(struct wmOperatorType *ot);
@@ -148,6 +192,11 @@ void GPENCIL_OT_layer_duplicate(struct wmOperatorType *ot);
void GPENCIL_OT_hide(struct wmOperatorType *ot);
void GPENCIL_OT_reveal(struct wmOperatorType *ot);
void GPENCIL_OT_lock_all(struct wmOperatorType *ot);
void GPENCIL_OT_unlock_all(struct wmOperatorType *ot);
void GPENCIL_OT_layer_isolate(struct wmOperatorType *ot);
void GPENCIL_OT_active_frame_delete(struct wmOperatorType *ot);
void GPENCIL_OT_convert(struct wmOperatorType *ot);

View File

@@ -79,11 +79,25 @@ static void ed_keymap_gpencil_general(wmKeyConfig *keyconf)
RNA_enum_set(kmi->ptr, "mode", GP_PAINTMODE_ERASER);
RNA_boolean_set(kmi->ptr, "wait_for_input", false);
/* Tablet Mappings for Drawing ------------------ */
/* For now, only support direct drawing using the eraser, as most users using a tablet
* may still want to use that as their primary pointing device!
*/
#if 0
kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_draw", TABLET_STYLUS, KM_PRESS, 0, 0);
RNA_enum_set(kmi->ptr, "mode", GP_PAINTMODE_DRAW);
RNA_boolean_set(kmi->ptr, "wait_for_input", false);
#endif
kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_draw", TABLET_ERASER, KM_PRESS, 0, 0);
RNA_enum_set(kmi->ptr, "mode", GP_PAINTMODE_ERASER);
RNA_boolean_set(kmi->ptr, "wait_for_input", false);
/* Viewport Tools ------------------------------- */
/* Enter EditMode */
kmi = WM_keymap_add_item(keymap, "WM_OT_context_toggle", TABKEY, KM_PRESS, 0, DKEY);
RNA_string_set(kmi->ptr, "data_path", "gpencil_data.use_stroke_edit_mode");
WM_keymap_add_item(keymap, "GPENCIL_OT_editmode_toggle", TABKEY, KM_PRESS, 0, DKEY);
/* Pie Menu - For standard tools */
WM_keymap_add_menu_pie(keymap, "GPENCIL_PIE_tool_palette", QKEY, KM_PRESS, 0, DKEY);
@@ -111,8 +125,10 @@ static void ed_keymap_gpencil_editing(wmKeyConfig *keyconf)
/* ----------------------------------------------- */
/* Exit EditMode */
kmi = WM_keymap_add_item(keymap, "WM_OT_context_toggle", TABKEY, KM_PRESS, 0, 0);
RNA_string_set(kmi->ptr, "data_path", "gpencil_data.use_stroke_edit_mode");
WM_keymap_add_item(keymap, "GPENCIL_OT_editmode_toggle", TABKEY, KM_PRESS, 0, 0);
/* Pie Menu - For settings/tools easy access */
WM_keymap_add_menu_pie(keymap, "GPENCIL_PIE_sculpt", EKEY, KM_PRESS, 0, DKEY);
/* Brush Settings */
/* NOTE: We cannot expose these in the standard keymap, as they will interfere with regular hotkeys
@@ -185,6 +201,14 @@ static void ed_keymap_gpencil_editing(wmKeyConfig *keyconf)
WM_keymap_add_item(keymap, "GPENCIL_OT_copy", CKEY, KM_PRESS, KM_OSKEY, 0);
WM_keymap_add_item(keymap, "GPENCIL_OT_paste", VKEY, KM_PRESS, KM_OSKEY, 0);
#endif
/* snap */
WM_keymap_add_menu(keymap, "GPENCIL_MT_snap", SKEY, KM_PRESS, KM_SHIFT, 0);
/* convert to geometry */
WM_keymap_add_item(keymap, "GPENCIL_OT_convert", CKEY, KM_PRESS, KM_ALT, 0);
/* Show/Hide */
/* NOTE: These are available only in EditMode now, since they clash with general-purpose hotkeys */
@@ -196,35 +220,62 @@ static void ed_keymap_gpencil_editing(wmKeyConfig *keyconf)
kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_hide", HKEY, KM_PRESS, KM_SHIFT, 0);
RNA_boolean_set(kmi->ptr, "unselected", true);
/* Isolate Layer */
WM_keymap_add_item(keymap, "GPENCIL_OT_layer_isolate", PADASTERKEY, KM_PRESS, 0, 0);
/* Move to Layer */
WM_keymap_add_item(keymap, "GPENCIL_OT_move_to_layer", MKEY, KM_PRESS, 0, 0);
/* Brush-Based Editing:
* EKEY + LMB = Single stroke, draw immediately
* + Other Modifiers (Ctrl/Shift) = Invert, Smooth, etc.
*
* For the modal version, use D+E -> Sculpt
*/
kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_brush_paint", LEFTMOUSE, KM_PRESS, 0, EKEY);
RNA_boolean_set(kmi->ptr, "wait_for_input", false);
kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_brush_paint", LEFTMOUSE, KM_PRESS, KM_CTRL, EKEY);
RNA_boolean_set(kmi->ptr, "wait_for_input", false);
/*RNA_boolean_set(kmi->ptr, "use_invert", true);*/
kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_brush_paint", LEFTMOUSE, KM_PRESS, KM_SHIFT, EKEY);
RNA_boolean_set(kmi->ptr, "wait_for_input", false);
/*RNA_boolean_set(kmi->ptr, "use_smooth", true);*/
/* Shift-FKEY = Sculpt Strength */
kmi = WM_keymap_add_item(keymap, "WM_OT_radial_control", FKEY, KM_PRESS, KM_SHIFT, 0);
RNA_string_set(kmi->ptr, "data_path_primary", "tool_settings.gpencil_sculpt.brush.strength");
/* Ctrl-FKEY = Sculpt Brush Size */
kmi = WM_keymap_add_item(keymap, "WM_OT_radial_control", FKEY, KM_PRESS, KM_CTRL, 0);
RNA_string_set(kmi->ptr, "data_path_primary", "tool_settings.gpencil_sculpt.brush.size");
/* Transform Tools */
kmi = WM_keymap_add_item(keymap, "TRANSFORM_OT_translate", GKEY, KM_PRESS, 0, 0);
RNA_boolean_set(kmi->ptr, "gpencil_strokes", true);
kmi = WM_keymap_add_item(keymap, "TRANSFORM_OT_translate", EVT_TWEAK_S, KM_ANY, 0, 0);
RNA_boolean_set(kmi->ptr, "gpencil_strokes", true);
kmi = WM_keymap_add_item(keymap, "TRANSFORM_OT_rotate", RKEY, KM_PRESS, 0, 0);
RNA_boolean_set(kmi->ptr, "gpencil_strokes", true);
kmi = WM_keymap_add_item(keymap, "TRANSFORM_OT_resize", SKEY, KM_PRESS, 0, 0);
RNA_boolean_set(kmi->ptr, "gpencil_strokes", true);
kmi = WM_keymap_add_item(keymap, "TRANSFORM_OT_mirror", MKEY, KM_PRESS, KM_CTRL, 0);
RNA_boolean_set(kmi->ptr, "gpencil_strokes", true);
kmi = WM_keymap_add_item(keymap, "TRANSFORM_OT_bend", WKEY, KM_PRESS, KM_SHIFT, 0);
RNA_boolean_set(kmi->ptr, "gpencil_strokes", true);
WM_keymap_add_item(keymap, "TRANSFORM_OT_tosphere", SKEY, KM_PRESS, KM_ALT | KM_SHIFT, 0);
RNA_boolean_set(kmi->ptr, "gpencil_strokes", true);
WM_keymap_add_item(keymap, "TRANSFORM_OT_shear", SKEY, KM_PRESS, KM_ALT | KM_CTRL | KM_SHIFT, 0);
RNA_boolean_set(kmi->ptr, "gpencil_strokes", true);
kmi = WM_keymap_add_item(keymap, "TRANSFORM_OT_transform", SKEY, KM_PRESS, KM_ALT, 0);
RNA_enum_set(kmi->ptr, "mode", TFM_GPENCIL_SHRINKFATTEN);
RNA_boolean_set(kmi->ptr, "gpencil_strokes", true);
/* Proportional Editing */
ED_keymap_proportional_cycle(keyconf, keymap);
@@ -249,6 +300,8 @@ void ED_operatortypes_gpencil(void)
/* Editing (Strokes) ------------ */
WM_operatortype_append(GPENCIL_OT_editmode_toggle);
WM_operatortype_append(GPENCIL_OT_select);
WM_operatortype_append(GPENCIL_OT_select_all);
WM_operatortype_append(GPENCIL_OT_select_circle);
@@ -265,6 +318,15 @@ void ED_operatortypes_gpencil(void)
WM_operatortype_append(GPENCIL_OT_copy);
WM_operatortype_append(GPENCIL_OT_paste);
WM_operatortype_append(GPENCIL_OT_move_to_layer);
WM_operatortype_append(GPENCIL_OT_layer_change);
WM_operatortype_append(GPENCIL_OT_snap_to_grid);
WM_operatortype_append(GPENCIL_OT_snap_to_cursor);
WM_operatortype_append(GPENCIL_OT_snap_cursor_to_selected);
WM_operatortype_append(GPENCIL_OT_brush_paint);
/* Editing (Buttons) ------------ */
WM_operatortype_append(GPENCIL_OT_data_add);
@@ -277,6 +339,9 @@ void ED_operatortypes_gpencil(void)
WM_operatortype_append(GPENCIL_OT_hide);
WM_operatortype_append(GPENCIL_OT_reveal);
WM_operatortype_append(GPENCIL_OT_lock_all);
WM_operatortype_append(GPENCIL_OT_unlock_all);
WM_operatortype_append(GPENCIL_OT_layer_isolate);
WM_operatortype_append(GPENCIL_OT_active_frame_delete);
@@ -290,12 +355,14 @@ void ED_operatormacros_gpencil(void)
wmOperatorType *ot;
wmOperatorTypeMacro *otmacro;
/* Duplicate + Move = Interactively place newly duplicated strokes */
ot = WM_operatortype_append_macro("GPENCIL_OT_duplicate_move", "Duplicate Strokes",
"Make copies of the selected Grease Pencil strokes and move them",
OPTYPE_UNDO | OPTYPE_REGISTER);
WM_operatortype_macro_define(ot, "GPENCIL_OT_duplicate");
otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
RNA_boolean_set(otmacro->ptr, "gpencil_strokes", true);
}
/* ****************************************** */

View File

@@ -77,7 +77,33 @@
/* ******************************************* */
/* 'Globals' and Defines */
/* Temporary 'Stroke' Operation data */
/* values for tGPsdata->status */
typedef enum eGPencil_PaintStatus {
GP_STATUS_IDLING = 0, /* stroke isn't in progress yet */
GP_STATUS_PAINTING, /* a stroke is in progress */
GP_STATUS_ERROR, /* something wasn't correctly set up */
GP_STATUS_DONE /* painting done */
} eGPencil_PaintStatus;
/* Return flags for adding points to stroke buffer */
typedef enum eGP_StrokeAdd_Result {
GP_STROKEADD_INVALID = -2, /* error occurred - insufficient info to do so */
GP_STROKEADD_OVERFLOW = -1, /* error occurred - cannot fit any more points */
GP_STROKEADD_NORMAL, /* point was successfully added */
GP_STROKEADD_FULL /* cannot add any more points to buffer */
} eGP_StrokeAdd_Result;
/* Runtime flags */
typedef enum eGPencil_PaintFlags {
GP_PAINTFLAG_FIRSTRUN = (1 << 0), /* operator just started */
GP_PAINTFLAG_STROKEADDED = (1 << 1),
GP_PAINTFLAG_V3D_ERASER_DEPTH = (1 << 2)
} eGPencil_PaintFlags;
/* Temporary 'Stroke' Operation data
* "p" = op->customdata
*/
typedef struct tGPsdata {
Scene *scene; /* current scene from context */
@@ -95,8 +121,13 @@ typedef struct tGPsdata {
bGPDlayer *gpl; /* layer we're working on */
bGPDframe *gpf; /* frame we're working on */
short status; /* current status of painting */
short paintmode; /* mode for painting */
char *align_flag; /* projection-mode flags (toolsettings - eGPencil_Placement_Flags) */
eGPencil_PaintStatus status; /* current status of painting */
eGPencil_PaintModes paintmode; /* mode for painting */
eGPencil_PaintFlags flags; /* flags that can get set during runtime (eGPencil_PaintFlags) */
short radius; /* radius of influence for eraser */
int mval[2]; /* current mouse-position */
int mvalo[2]; /* previous recorded mouse-position */
@@ -104,9 +135,6 @@ typedef struct tGPsdata {
float pressure; /* current stylus pressure */
float opressure; /* previous stylus pressure */
short radius; /* radius of influence for eraser */
short flags; /* flags that can get set during runtime */
/* These need to be doubles, as (at least under unix) they are in seconds since epoch,
* float (and its 7 digits precision) is definitively not enough here!
* double, with its 15 digits precision, ensures us millisecond precision for a few centuries at least.
@@ -124,29 +152,6 @@ typedef struct tGPsdata {
void *erasercursor; /* radial cursor data for drawing eraser */
} tGPsdata;
/* values for tGPsdata->status */
enum {
GP_STATUS_IDLING = 0, /* stroke isn't in progress yet */
GP_STATUS_PAINTING, /* a stroke is in progress */
GP_STATUS_ERROR, /* something wasn't correctly set up */
GP_STATUS_DONE /* painting done */
};
/* Return flags for adding points to stroke buffer */
enum {
GP_STROKEADD_INVALID = -2, /* error occurred - insufficient info to do so */
GP_STROKEADD_OVERFLOW = -1, /* error occurred - cannot fit any more points */
GP_STROKEADD_NORMAL, /* point was successfully added */
GP_STROKEADD_FULL /* cannot add any more points to buffer */
};
/* Runtime flags */
enum {
GP_PAINTFLAG_FIRSTRUN = (1 << 0), /* operator just started */
GP_PAINTFLAG_STROKEADDED = (1 << 1),
GP_PAINTFLAG_V3D_ERASER_DEPTH = (1 << 2)
};
/* ------ */
/* maximum sizes of gp-session buffer */
@@ -204,7 +209,7 @@ static int gpencil_draw_poll(bContext *C)
static bool gpencil_project_check(tGPsdata *p)
{
bGPdata *gpd = p->gpd;
return ((gpd->sbuffer_sflag & GP_STROKE_3DSPACE) && (p->gpd->flag & (GP_DATA_DEPTH_VIEW | GP_DATA_DEPTH_STROKE)));
return ((gpd->sbuffer_sflag & GP_STROKE_3DSPACE) && (*p->align_flag & (GP_PROJECT_DEPTH_VIEW | GP_PROJECT_DEPTH_STROKE)));
}
/* ******************************************* */
@@ -736,112 +741,6 @@ static void gp_stroke_newfrombuffer(tGPsdata *p)
/* --- 'Eraser' for 'Paint' Tool ------ */
/* eraser tool - remove segment from stroke/split stroke (after lasso inside) */
static short gp_stroke_eraser_splitdel(bGPDframe *gpf, bGPDstroke *gps, int i)
{
bGPDspoint *pt_tmp = gps->points;
bGPDstroke *gsn = NULL;
/* if stroke only had two points, get rid of stroke */
if (gps->totpoints == 2) {
/* free stroke points, then stroke */
MEM_freeN(pt_tmp);
BLI_freelinkN(&gpf->strokes, gps);
/* nothing left in stroke, so stop */
return 1;
}
/* if last segment, just remove segment from the stroke */
else if (i == gps->totpoints - 2) {
/* allocate new points array, and assign most of the old stroke there */
gps->totpoints--;
gps->points = MEM_mallocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points");
memcpy(gps->points, pt_tmp, sizeof(bGPDspoint) * gps->totpoints);
/* free temp buffer */
MEM_freeN(pt_tmp);
/* nothing left in stroke, so stop */
return 1;
}
/* if first segment, just remove segment from the stroke */
else if (i == 0) {
/* allocate new points array, and assign most of the old stroke there */
gps->totpoints--;
gps->points = MEM_mallocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points");
memcpy(gps->points, pt_tmp + 1, sizeof(bGPDspoint) * gps->totpoints);
/* We must adjust timings!
* Each point's timing data is a delta from stroke's inittime, so as we erase the first
* point of the stroke, we have to offset this inittime and all remaining points' delta values.
* This way we get a new stroke with exactly the same timing as if user had started drawing from
* the second point...
*/
{
bGPDspoint *pts;
float delta = pt_tmp[1].time;
int j;
gps->inittime += (double)delta;
pts = gps->points;
for (j = 0; j < gps->totpoints; j++, pts++) {
pts->time -= delta;
}
}
/* free temp buffer */
MEM_freeN(pt_tmp);
/* no break here, as there might still be stuff to remove in this stroke */
return 0;
}
/* segment occurs in 'middle' of stroke, so split */
else {
/* duplicate stroke, and assign 'later' data to that stroke */
gsn = MEM_dupallocN(gps);
gsn->prev = gsn->next = NULL;
BLI_insertlinkafter(&gpf->strokes, gps, gsn);
gsn->totpoints = gps->totpoints - i;
gsn->points = MEM_mallocN(sizeof(bGPDspoint) * gsn->totpoints, "gp_stroke_points");
memcpy(gsn->points, pt_tmp + i, sizeof(bGPDspoint) * gsn->totpoints);
/* We must adjust timings of this new stroke!
* Each point's timing data is a delta from stroke's inittime, so as we erase the first
* point of the stroke, we have to offset this inittime and all remaing points' delta values.
* This way we get a new stroke with exactly the same timing as if user had started drawing from
* the second point...
*/
{
bGPDspoint *pts;
float delta = pt_tmp[i].time;
int j;
gsn->inittime += (double)delta;
pts = gsn->points;
for (j = 0; j < gsn->totpoints; j++, pts++) {
pts->time -= delta;
}
}
/* adjust existing stroke */
gps->totpoints = i;
gps->points = MEM_mallocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points");
memcpy(gps->points, pt_tmp, sizeof(bGPDspoint) * gps->totpoints);
/* free temp buffer */
MEM_freeN(pt_tmp);
/* nothing left in stroke, so stop */
return 1;
}
}
/* which which point is infront (result should only be used for comparison) */
static float view3d_point_depth(const RegionView3D *rv3d, const float co[3])
{
@@ -853,6 +752,7 @@ static float view3d_point_depth(const RegionView3D *rv3d, const float co[3])
}
}
/* only erase stroke points that are visible */
static bool gp_stroke_eraser_is_occluded(tGPsdata *p, const bGPDspoint *pt, const int x, const int y)
{
if ((p->sa->spacetype == SPACE_VIEW3D) &&
@@ -874,15 +774,33 @@ static bool gp_stroke_eraser_is_occluded(tGPsdata *p, const bGPDspoint *pt, cons
return false;
}
/* apply a falloff effect to brush strength, based on distance */
static float gp_stroke_eraser_calc_influence(tGPsdata *p, const int mval[2], const int radius, const int co[2])
{
/* Linear Falloff... */
float distance = (float)len_v2v2_int(mval, co);
float fac;
CLAMP(distance, 0.0f, (float)radius);
fac = 1.0f - (distance / (float)radius);
/* Control this further using pen pressure */
fac *= p->pressure;
/* Return influence factor computed here */
return fac;
}
/* eraser tool - evaluation per stroke */
/* TODO: this could really do with some optimization (KD-Tree/BVH?) */
static void gp_stroke_eraser_dostroke(tGPsdata *p,
bGPDlayer *gpl, bGPDframe *gpf, bGPDstroke *gps,
const int mval[2], const int mvalo[2],
short rad, const rcti *rect, bGPDframe *gpf, bGPDstroke *gps)
const int radius, const rcti *rect)
{
bGPDspoint *pt1, *pt2;
int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
int pc1[2] = {0};
int pc2[2] = {0};
int i;
if (gps->totpoints == 0) {
@@ -892,56 +810,101 @@ static void gp_stroke_eraser_dostroke(tGPsdata *p,
BLI_freelinkN(&gpf->strokes, gps);
}
else if (gps->totpoints == 1) {
gp_point_to_xy(&p->gsc, gps, gps->points, &x0, &y0);
gp_point_to_xy(&p->gsc, gps, gps->points, &pc1[0], &pc1[1]);
/* do boundbox check first */
if ((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(rect, x0, y0)) {
if ((!ELEM(V2D_IS_CLIPPED, pc1[0], pc1[1])) && BLI_rcti_isect_pt(rect, pc1[0], pc1[1])) {
/* only check if point is inside */
if (((x0 - mval[0]) * (x0 - mval[0]) + (y0 - mval[1]) * (y0 - mval[1])) <= rad * rad) {
if (len_v2v2_int(mval, pc1) <= radius) {
/* free stroke */
// XXX: pressure sensitive eraser should apply here too?
MEM_freeN(gps->points);
BLI_freelinkN(&gpf->strokes, gps);
}
}
}
else {
/* loop over the points in the stroke, checking for intersections
* - an intersection will require the stroke to be split
/* Pressure threshold at which stroke should be culled: Calculated as pressure value
* below which we would have invisible strokes
*/
const float cull_thresh = (gpl->thickness) ? 1.0f / ((float)gpl->thickness) : 1.0f;
/* Amount to decrease the pressure of each point with each stroke */
// TODO: Fetch from toolsettings, or compute based on thickness instead?
const float strength = 0.1f;
/* Perform culling? */
bool do_cull = false;
/* Clear Tags
*
* Note: It's better this way, as we are sure that
* we don't miss anything, though things will be
* slightly slower as a result
*/
for (i = 0; i < gps->totpoints; i++) {
bGPDspoint *pt = &gps->points[i];
pt->flag &= ~GP_SPOINT_TAG;
}
/* First Pass: Loop over the points in the stroke
* 1) Thin out parts of the stroke under the brush
* 2) Tag "too thin" parts for removal (in second pass)
*/
for (i = 0; (i + 1) < gps->totpoints; i++) {
/* get points to work with */
pt1 = gps->points + i;
pt2 = gps->points + i + 1;
gp_point_to_xy(&p->gsc, gps, pt1, &x0, &y0);
gp_point_to_xy(&p->gsc, gps, pt2, &x1, &y1);
gp_point_to_xy(&p->gsc, gps, pt1, &pc1[0], &pc1[1]);
gp_point_to_xy(&p->gsc, gps, pt2, &pc2[0], &pc2[1]);
/* check that point segment of the boundbox of the eraser stroke */
if (((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(rect, x0, y0)) ||
((!ELEM(V2D_IS_CLIPPED, x1, y1)) && BLI_rcti_isect_pt(rect, x1, y1)))
/* Check that point segment of the boundbox of the eraser stroke */
if (((!ELEM(V2D_IS_CLIPPED, pc1[0], pc1[1])) && BLI_rcti_isect_pt(rect, pc1[0], pc1[1])) ||
((!ELEM(V2D_IS_CLIPPED, pc2[0], pc2[1])) && BLI_rcti_isect_pt(rect, pc2[0], pc2[1])))
{
/* check if point segment of stroke had anything to do with
/* Check if point segment of stroke had anything to do with
* eraser region (either within stroke painted, or on its lines)
* - this assumes that linewidth is irrelevant
*/
if (gp_stroke_inside_circle(mval, mvalo, rad, x0, y0, x1, y1)) {
if ((gp_stroke_eraser_is_occluded(p, pt1, x0, y0) == false) ||
(gp_stroke_eraser_is_occluded(p, pt2, x1, y1) == false))
if (gp_stroke_inside_circle(mval, mvalo, radius, pc1[0], pc1[1], pc2[0], pc2[1])) {
if ((gp_stroke_eraser_is_occluded(p, pt1, pc1[0], pc1[1]) == false) ||
(gp_stroke_eraser_is_occluded(p, pt2, pc2[0], pc2[1]) == false))
{
/* if function returns true, break this loop (as no more point to check) */
if (gp_stroke_eraser_splitdel(gpf, gps, i))
break;
/* Point is affected: */
/* 1) Adjust thickness
* - Influence of eraser falls off with distance from the middle of the eraser
* - Second point gets less influence, as it might get hit again in the next segment
*/
pt1->pressure -= gp_stroke_eraser_calc_influence(p, mval, radius, pc1) * strength;
pt2->pressure -= gp_stroke_eraser_calc_influence(p, mval, radius, pc2) * strength / 2.0f;
/* 2) Tag any point with overly low influence for removal in the next pass */
if (pt1->pressure < cull_thresh) {
pt1->flag |= GP_SPOINT_TAG;
do_cull = true;
}
if (pt2->pressure < cull_thresh) {
pt2->flag |= GP_SPOINT_TAG;
do_cull = true;
}
}
}
}
}
/* Second Pass: Remove any points that are tagged */
if (do_cull) {
gp_stroke_delete_tagged_points(gpf, gps, gps->next, GP_SPOINT_TAG);
}
}
}
/* erase strokes which fall under the eraser strokes */
static void gp_stroke_doeraser(tGPsdata *p)
{
bGPDframe *gpf = p->gpf;
bGPDlayer *gpl;
bGPDstroke *gps, *gpn;
rcti rect;
@@ -960,10 +923,32 @@ static void gp_stroke_doeraser(tGPsdata *p)
}
}
/* loop over strokes, checking segments for intersections */
for (gps = gpf->strokes.first; gps; gps = gpn) {
gpn = gps->next;
gp_stroke_eraser_dostroke(p, p->mval, p->mvalo, p->radius, &rect, gpf, gps);
/* loop over all layers too, since while it's easy to restrict editing to
* only a subset of layers, it is harder to perform the same erase operation
* on multiple layers...
*/
for (gpl = p->gpd->layers.first; gpl; gpl = gpl->next) {
bGPDframe *gpf = gpl->actframe;
/* only affect layer if it's editable (and visible) */
if (gpl->flag & (GP_LAYER_HIDE | GP_LAYER_LOCKED)) {
continue;
}
else if (gpf == NULL) {
continue;
}
/* loop over strokes, checking segments for intersections */
for (gps = gpf->strokes.first; gps; gps = gpn) {
gpn = gps->next;
/* Not all strokes in the datablock may be valid in the current editor/context
* (e.g. 2D space strokes in the 3D view, if the same datablock is shared)
*/
if (ED_gpencil_stroke_can_use_direct(p->sa, gps)) {
gp_stroke_eraser_dostroke(p, gpl, gpf, gps, p->mval, p->mvalo, p->radius, &rect);
}
}
}
}
@@ -1001,6 +986,7 @@ static bool gp_session_initdata(bContext *C, tGPsdata *p)
bGPdata **gpd_ptr = NULL;
ScrArea *curarea = CTX_wm_area(C);
ARegion *ar = CTX_wm_region(C);
ToolSettings *ts = CTX_data_tool_settings(C);
/* make sure the active view (at the starting time) is a 3d-view */
if (curarea == NULL) {
@@ -1030,6 +1016,7 @@ static bool gp_session_initdata(bContext *C, tGPsdata *p)
/* CAUTION: If this is the "toolbar", then this will change on the first stroke */
p->sa = curarea;
p->ar = ar;
p->align_flag = &ts->gpencil_v3d_align;
if (ar->regiondata == NULL) {
p->status = GP_STATUS_ERROR;
@@ -1047,6 +1034,7 @@ static bool gp_session_initdata(bContext *C, tGPsdata *p)
p->sa = curarea;
p->ar = ar;
p->v2d = &ar->v2d;
p->align_flag = &ts->gpencil_v2d_align;
break;
}
case SPACE_SEQ:
@@ -1057,6 +1045,7 @@ static bool gp_session_initdata(bContext *C, tGPsdata *p)
p->sa = curarea;
p->ar = ar;
p->v2d = &ar->v2d;
p->align_flag = &ts->gpencil_seq_align;
/* check that gpencil data is allowed to be drawn */
if (sseq->mainb == SEQ_DRAW_SEQUENCE) {
@@ -1075,6 +1064,7 @@ static bool gp_session_initdata(bContext *C, tGPsdata *p)
p->sa = curarea;
p->ar = ar;
p->v2d = &ar->v2d;
p->align_flag = &ts->gpencil_ima_align;
break;
}
case SPACE_CLIP:
@@ -1091,6 +1081,7 @@ static bool gp_session_initdata(bContext *C, tGPsdata *p)
p->sa = curarea;
p->ar = ar;
p->v2d = &ar->v2d;
p->align_flag = &ts->gpencil_v2d_align;
invert_m4_m4(p->imat, sc->unistabmat);
@@ -1167,6 +1158,12 @@ static tGPsdata *gp_session_initpaint(bContext *C)
gp_session_initdata(C, p);
/* radius for eraser circle is defined in userprefs now */
/* NOTE: we do this here, so that if we exit immediately,
* erase size won't get lost
*/
p->radius = U.gp_eraser;
/* return context data for running paint operator */
return p;
}
@@ -1194,8 +1191,10 @@ static void gp_session_cleanup(tGPsdata *p)
}
/* init new stroke */
static void gp_paint_initstroke(tGPsdata *p, short paintmode)
static void gp_paint_initstroke(tGPsdata *p, eGPencil_PaintModes paintmode)
{
Scene *scene = p->scene;
/* get active layer (or add a new one if non-existent) */
p->gpl = gpencil_layer_getactive(p->gpd);
if (p->gpl == NULL) {
@@ -1212,15 +1211,61 @@ static void gp_paint_initstroke(tGPsdata *p, short paintmode)
}
/* get active frame (add a new one if not matching frame) */
p->gpf = gpencil_layer_getframe(p->gpl, p->scene->r.cfra, 1);
if (p->gpf == NULL) {
p->status = GP_STATUS_ERROR;
if (G.debug & G_DEBUG)
printf("Error: No frame created (gpencil_paint_init)\n");
return;
if (paintmode == GP_PAINTMODE_ERASER) {
/* Eraser mode:
* 1) Add new frames to all frames that we might touch,
* 2) Ensure that p->gpf refers to the frame used for the active layer
* (to avoid problems with other tools which expect it to exist)
*/
bGPDlayer *gpl;
for (gpl = p->gpd->layers.first; gpl; gpl = gpl->next) {
/* Skip if layer not editable */
if (gpl->flag & (GP_LAYER_HIDE | GP_LAYER_LOCKED))
continue;
/* Add a new frame if needed (and based off the active frame,
* as we need some existing strokes to erase)
*/
gpl->actframe = gpencil_layer_getframe(gpl, CFRA, GP_GETFRAME_ADD_COPY);
/* XXX: we omit GP_FRAME_PAINT here for now,
* as it is only really useful for doing
* paintbuffer drawing
*/
}
/* Ensure this gets set... */
p->gpf = p->gpl->actframe;
if (p->gpf == NULL) {
p->status = GP_STATUS_ERROR;
//if (G.debug & G_DEBUG)
printf("Error: No frame created (gpencil_paint_init)\n");
return;
}
}
else {
/* Drawing Modes - Add a new frame if needed on the active layer */
ToolSettings *ts = p->scene->toolsettings;
short add_frame_mode;
if (ts->gpencil_flags & GP_TOOL_FLAG_RETAIN_LAST)
add_frame_mode = GP_GETFRAME_ADD_COPY;
else
add_frame_mode = GP_GETFRAME_ADD_NEW;
p->gpf = gpencil_layer_getframe(p->gpl, CFRA, add_frame_mode);
if (p->gpf == NULL) {
p->status = GP_STATUS_ERROR;
if (G.debug & G_DEBUG)
printf("Error: No frame created (gpencil_paint_init)\n");
return;
}
else {
p->gpf->flag |= GP_FRAME_PAINT;
}
}
else
p->gpf->flag |= GP_FRAME_PAINT;
/* set 'eraser' for this stroke if using eraser */
p->paintmode = paintmode;
@@ -1251,7 +1296,7 @@ static void gp_paint_initstroke(tGPsdata *p, short paintmode)
/* when drawing in the camera view, in 2D space, set the subrect */
p->subrect = NULL;
if (!(p->gpd->flag & GP_DATA_VIEWALIGN)) {
if ((*p->align_flag & GP_PROJECT_VIEWSPACE) == 0) {
if (p->sa->spacetype == SPACE_VIEW3D) {
View3D *v3d = p->sa->spacedata.first;
RegionView3D *rv3d = p->ar->regiondata;
@@ -1279,7 +1324,7 @@ static void gp_paint_initstroke(tGPsdata *p, short paintmode)
/* check if points will need to be made in view-aligned space */
if (p->gpd->flag & GP_DATA_VIEWALIGN) {
if (*p->align_flag & GP_PROJECT_VIEWSPACE) {
switch (p->sa->spacetype) {
case SPACE_VIEW3D:
{
@@ -1308,7 +1353,7 @@ static void gp_paint_initstroke(tGPsdata *p, short paintmode)
if (ELEM(NULL, sima, sima->image)) {
/* make strokes be drawn in screen space */
p->gpd->sbuffer_sflag &= ~GP_STROKE_2DSPACE;
p->gpd->flag &= ~GP_DATA_VIEWALIGN;
*(p->align_flag) &= ~GP_PROJECT_VIEWSPACE;
}
else {
p->gpd->sbuffer_sflag |= GP_STROKE_2DSPACE;
@@ -1417,6 +1462,17 @@ static void gpencil_draw_toggle_eraser_cursor(bContext *C, tGPsdata *p, short en
}
}
/* Check if tablet eraser is being used (when processing events) */
static bool gpencil_is_tablet_eraser_active(const wmEvent *event)
{
if (event->tablet_data) {
const wmTabletData *wmtab = event->tablet_data;
return (wmtab->Active == EVT_TABLET_ERASER);
}
return false;
}
/* ------------------------------- */
@@ -1467,7 +1523,7 @@ static void gpencil_draw_cancel(bContext *C, wmOperator *op)
static int gpencil_draw_init(bContext *C, wmOperator *op)
{
tGPsdata *p;
int paintmode = RNA_enum_get(op->ptr, "mode");
eGPencil_PaintModes paintmode = RNA_enum_get(op->ptr, "mode");
/* check context */
p = op->customdata = gp_session_initpaint(C);
@@ -1484,9 +1540,6 @@ static int gpencil_draw_init(bContext *C, wmOperator *op)
return 0;
}
/* radius for eraser circle is defined in userprefs now */
p->radius = U.gp_eraser;
/* everything is now setup ok */
return 1;
}
@@ -1529,6 +1582,10 @@ static void gpencil_draw_status_indicators(tGPsdata *p)
ED_area_headerprint(p->sa, IFACE_("Grease Pencil Freehand Session: Hold and drag LMB to draw | "
"ESC/Enter to end"));
break;
case GP_PAINTMODE_DRAW_POLY:
ED_area_headerprint(p->sa, IFACE_("Grease Pencil Poly Session: LMB click to place next stroke vertex | "
"ESC/Enter to end"));
break;
default: /* unhandled future cases */
ED_area_headerprint(p->sa, IFACE_("Grease Pencil Session: ESC/Enter to end"));
@@ -1622,9 +1679,6 @@ static void gpencil_draw_apply_event(wmOperator *op, const wmEvent *event)
tablet = (wmtab->Active != EVT_TABLET_NONE);
p->pressure = wmtab->Pressure;
/* if (wmtab->Active == EVT_TABLET_ERASER) */
/* TODO... this should get caught by the keymaps which call drawing in the first place */
}
else
p->pressure = 1.0f;
@@ -1819,7 +1873,6 @@ static tGPsdata *gpencil_stroke_begin(bContext *C, wmOperator *op)
/* we may need to set up paint env again if we're resuming */
/* XXX: watch it with the paintmode! in future,
* it'd be nice to allow changing paint-mode when in sketching-sessions */
/* XXX: with tablet events, we may event want to check for eraser here, for nicer tablet support */
if (gp_session_initdata(C, p))
gp_paint_initstroke(p, p->paintmode);
@@ -2008,14 +2061,14 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event)
/* Switch paintmode (temporarily if need be) based on which button was used
* NOTE: This is to make it more convenient to erase strokes when using drawing sessions
*/
if (event->type == LEFTMOUSE) {
/* restore drawmode to default */
p->paintmode = RNA_enum_get(op->ptr, "mode");
}
else if (event->type == RIGHTMOUSE) {
if ((event->type == RIGHTMOUSE) || gpencil_is_tablet_eraser_active(event)) {
/* turn on eraser */
p->paintmode = GP_PAINTMODE_ERASER;
}
else if (event->type == LEFTMOUSE) {
/* restore drawmode to default */
p->paintmode = RNA_enum_get(op->ptr, "mode");
}
gpencil_draw_toggle_eraser_cursor(C, p, p->paintmode == GP_PAINTMODE_ERASER);

View File

@@ -67,10 +67,15 @@
static int gpencil_select_poll(bContext *C)
{
bGPdata *gpd = ED_gpencil_data_get_active(C);
bGPDlayer *gpl = gpencil_layer_getactive(gpd);
/* only if there's an active layer with an active frame */
return (gpl && gpl->actframe);
/* we just need some visible strokes, and to be in editmode */
if ((gpd) && (gpd->flag & GP_DATA_STROKE_EDITMODE)) {
/* TODO: include a check for visible strokes? */
if (gpd->layers.first)
return true;
}
return false;
}
/* ********************************************** */

View File

@@ -51,7 +51,9 @@
#include "RNA_access.h"
#include "RNA_define.h"
#include "RNA_enum_types.h"
#include "UI_resources.h"
#include "UI_view2d.h"
#include "ED_gpencil.h"
@@ -76,8 +78,6 @@ bGPdata **ED_gpencil_data_get_pointers_direct(ID *screen_id, Scene *scene, ScrAr
switch (sa->spacetype) {
case SPACE_VIEW3D: /* 3D-View */
case SPACE_TIME: /* Timeline - XXX: this is a hack to get it to show GP keyframes for 3D view */
case SPACE_ACTION: /* DepeSheet - XXX: this is a hack to get the keyframe jump operator to take GP Keyframes into account */
{
BLI_assert(scene && ELEM(scene->toolsettings->gpencil_src,
GP_TOOL_SOURCE_SCENE, GP_TOOL_SOURCE_OBJECT));
@@ -211,6 +211,45 @@ bGPdata *ED_gpencil_data_get_active_v3d(Scene *scene, View3D *v3d)
return gpd ? gpd : scene->gpd;
}
/* ******************************************************** */
/* Keyframe Indicator Checks */
/* Check whether there's an active GP keyframe on the current frame */
bool ED_gpencil_has_keyframe_v3d(Scene *scene, Object *ob, int cfra)
{
/* just check both for now... */
// XXX: this could get confusing (e.g. if only on the object, but other places don't show this)
if (scene->gpd) {
bGPDlayer *gpl = gpencil_layer_getactive(scene->gpd);
if (gpl) {
if (gpl->actframe) {
// XXX: assumes that frame has been fetched already
return (gpl->actframe->framenum == cfra);
}
else {
/* XXX: disabled as could be too much of a penalty */
/* return BKE_gpencil_layer_find_frame(gpl, cfra); */
}
}
}
if (ob && ob->gpd) {
bGPDlayer *gpl = gpencil_layer_getactive(ob->gpd);
if (gpl) {
if (gpl->actframe) {
// XXX: assumes that frame has been fetched already
return (gpl->actframe->framenum == cfra);
}
else {
/* XXX: disabled as could be too much of a penalty */
/* return BKE_gpencil_layer_find_frame(gpl, cfra); */
}
}
}
return false;
}
/* ******************************************************** */
/* Poll Callbacks */
@@ -230,6 +269,92 @@ int gp_active_layer_poll(bContext *C)
return (gpl != NULL);
}
/* ******************************************************** */
/* Dynamic Enums of GP Layers */
/* NOTE: These include an option to create a new layer and use that... */
/* Just existing layers */
EnumPropertyItem *ED_gpencil_layers_enum_itemf(bContext *C, PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free)
{
bGPdata *gpd = CTX_data_gpencil_data(C);
bGPDlayer *gpl;
EnumPropertyItem *item = NULL, item_tmp = {0};
int totitem = 0;
int i = 0;
if (ELEM(NULL, C, gpd)) {
return DummyRNA_DEFAULT_items;
}
/* Existing layers */
for (gpl = gpd->layers.first; gpl; gpl = gpl->next, i++) {
item_tmp.identifier = gpl->info;
item_tmp.name = gpl->info;
item_tmp.value = i;
if (gpl->flag & GP_LAYER_ACTIVE)
item_tmp.icon = ICON_GREASEPENCIL;
else
item_tmp.icon = ICON_NONE;
RNA_enum_item_add(&item, &totitem, &item_tmp);
}
RNA_enum_item_end(&item, &totitem);
*r_free = true;
return item;
}
/* Existing + Option to add/use new layer */
EnumPropertyItem *ED_gpencil_layers_with_new_enum_itemf(bContext *C, PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free)
{
bGPdata *gpd = CTX_data_gpencil_data(C);
bGPDlayer *gpl;
EnumPropertyItem *item = NULL, item_tmp = {0};
int totitem = 0;
int i = 0;
if (ELEM(NULL, C, gpd)) {
return DummyRNA_DEFAULT_items;
}
/* Create new layer */
/* TODO: have some way of specifying that we don't want this? */
{
/* active Keying Set */
item_tmp.identifier = "__CREATE__";
item_tmp.name = "New Layer";
item_tmp.value = -1;
item_tmp.icon = ICON_ZOOMIN;
RNA_enum_item_add(&item, &totitem, &item_tmp);
/* separator */
RNA_enum_item_add_separator(&item, &totitem);
}
/* Existing layers */
for (gpl = gpd->layers.first, i = 0; gpl; gpl = gpl->next, i++) {
item_tmp.identifier = gpl->info;
item_tmp.name = gpl->info;
item_tmp.value = i;
if (gpl->flag & GP_LAYER_ACTIVE)
item_tmp.icon = ICON_GREASEPENCIL;
else
item_tmp.icon = ICON_NONE;
RNA_enum_item_add(&item, &totitem, &item_tmp);
}
RNA_enum_item_end(&item, &totitem);
*r_free = true;
return item;
}
/* ******************************************************** */
/* Brush Tool Core */
@@ -372,4 +497,37 @@ void gp_point_to_xy(GP_SpaceConversion *gsc, bGPDstroke *gps, bGPDspoint *pt,
}
}
/* Project screenspace coordinates to 3D-space
* NOTE: We include this as a utility function, since the standard method
* involves quite a few steps, which are invariably always the same
* for all GPencil operations. So, it's nicer to just centralise these.
* WARNING: Assumes that it is getting called in a 3D view only
*/
bool gp_point_xy_to_3d(GP_SpaceConversion *gsc, Scene *scene, const float screen_co[2], float r_out[3])
{
View3D *v3d = gsc->sa->spacedata.first;
RegionView3D *rv3d = gsc->ar->regiondata;
float *rvec = ED_view3d_cursor3d_get(scene, v3d);
float ref[3] = {rvec[0], rvec[1], rvec[2]};
float zfac = ED_view3d_calc_zfac(rv3d, rvec, NULL);
float mval_f[2], mval_prj[2];
float dvec[3];
copy_v2_v2(mval_f, screen_co);
if (ED_view3d_project_float_global(gsc->ar, ref, mval_prj, V3D_PROJ_TEST_NOP) == V3D_PROJ_RET_OK) {
sub_v2_v2v2(mval_f, mval_prj, mval_f);
ED_view3d_win_to_delta(gsc->ar, mval_f, dvec, zfac);
sub_v3_v3v3(r_out, rvec, dvec);
return true;
}
else {
zero_v3(r_out);
return false;
}
}
/* ******************************************************** */

View File

@@ -41,7 +41,9 @@ struct bGPdata;
struct bGPDlayer;
struct bGPDframe;
struct bGPDstroke;
struct bAnimContext;
struct PointerRNA;
struct wmWindowManager;
struct wmKeyConfig;
@@ -77,6 +79,8 @@ struct bGPdata *ED_gpencil_data_get_active_direct(struct ID *screen_id, struct S
/* 3D View */
struct bGPdata *ED_gpencil_data_get_active_v3d(struct Scene *scene, struct View3D *v3d);
bool ED_gpencil_has_keyframe_v3d(struct Scene *scene, struct Object *ob, int cfra);
/* ----------- Stroke Editing Utilities ---------------- */
bool ED_gpencil_stroke_can_use_direct(const struct ScrArea *sa, const struct bGPDstroke *gps);
@@ -100,7 +104,7 @@ void ED_gpencil_strokes_copybuf_free(void);
void ED_gpencil_draw_2dimage(const struct bContext *C);
void ED_gpencil_draw_view2d(const struct bContext *C, bool onlyv2d);
void ED_gpencil_draw_view3d(struct Scene *scene, struct View3D *v3d, struct ARegion *ar, bool only3d);
void ED_gpencil_draw_view3d(struct wmWindowManager *wm, struct Scene *scene, struct View3D *v3d, struct ARegion *ar, bool only3d);
void ED_gpencil_draw_ex(struct Scene *scene, struct bGPdata *gpd, int winx, int winy,
const int cfra, const char spacetype);
@@ -122,11 +126,11 @@ void ED_gplayer_frames_keytype_set(struct bGPDlayer *gpl, short type);
void ED_gplayer_snap_frames(struct bGPDlayer *gpl, struct Scene *scene, short mode);
#if 0
void free_gpcopybuf(void);
void copy_gpdata(void);
void paste_gpdata(void);
void ED_gpencil_anim_copybuf_free(void);
bool ED_gpencil_anim_copybuf_copy(struct bAnimContext *ac);
bool ED_gpencil_anim_copybuf_paste(struct bAnimContext *ac, const short copy_mode);
#if 0
void mirror_gplayer_frames(struct bGPDlayer *gpl, short mode);
#endif

View File

@@ -46,6 +46,7 @@
#include "DNA_armature_types.h"
#include "DNA_curve_types.h"
#include "DNA_gpencil_types.h"
#include "DNA_group_types.h"
#include "DNA_material_types.h"
#include "DNA_meta_types.h"
@@ -1509,6 +1510,7 @@ static EnumPropertyItem *object_mode_set_itemsf(bContext *C, PointerRNA *UNUSED(
EnumPropertyItem *input = rna_enum_object_mode_items;
EnumPropertyItem *item = NULL;
Object *ob;
bGPdata *gpd;
int totitem = 0;
if (!C) /* needed for docs */
@@ -1536,6 +1538,14 @@ static EnumPropertyItem *object_mode_set_itemsf(bContext *C, PointerRNA *UNUSED(
/* We need at least this one! */
RNA_enum_items_add_value(&item, &totitem, input, OB_MODE_OBJECT);
}
/* On top of all the rest, GPencil Stroke Edit Mode
* is available if there's a valid gp datablock...
*/
gpd = CTX_data_gpencil_data(C);
if (gpd) {
RNA_enum_items_add_value(&item, &totitem, rna_enum_object_mode_items, OB_MODE_GPENCIL);
}
RNA_enum_item_end(&item, &totitem);
@@ -1560,6 +1570,8 @@ static const char *object_mode_op_string(int mode)
return "PARTICLE_OT_particle_edit_toggle";
if (mode == OB_MODE_POSE)
return "OBJECT_OT_posemode_toggle";
if (mode == OB_MODE_GPENCIL)
return "GPENCIL_OT_editmode_toggle";
return NULL;
}
@@ -1571,6 +1583,8 @@ static bool object_mode_compat_test(Object *ob, ObjectMode mode)
if (ob) {
if (mode == OB_MODE_OBJECT)
return true;
else if (mode == OB_MODE_GPENCIL)
return true; /* XXX: assume this is the case for now... */
switch (ob->type) {
case OB_MESH:
@@ -1625,13 +1639,45 @@ bool ED_object_mode_compat_set(bContext *C, Object *ob, int mode, ReportList *re
return ok;
}
static int object_mode_set_poll(bContext *C)
{
/* Since Grease Pencil editmode is also handled here,
* we have a special exception for allowing this operator
* to still work in that case when there's no active object
* so that users can exit editmode this way as per normal.
*/
if (ED_operator_object_active_editable(C))
return true;
else
return (CTX_data_gpencil_data(C) != NULL);
}
static int object_mode_set_exec(bContext *C, wmOperator *op)
{
Object *ob = CTX_data_active_object(C);
bGPdata *gpd = CTX_data_gpencil_data(C);
ObjectMode mode = RNA_enum_get(op->ptr, "mode");
ObjectMode restore_mode = (ob) ? ob->mode : OB_MODE_OBJECT;
const bool toggle = RNA_boolean_get(op->ptr, "toggle");
if (gpd) {
/* GP Mode is not bound to a specific object. Therefore,
* we don't want it to be actually saved on any objects,
* as weirdness can happen if you select other objects,
* or load old files.
*
* Instead, we use the following 2 rules to ensure that
* the mode selector works as expected:
* 1) If there's no object, we want to enter editmode.
* (i.e. with no object, we're in object mode)
* 2) Otherwise, exit stroke editmode, so that we can
* enter another mode...
*/
if (!ob || (gpd->flag & GP_DATA_STROKE_EDITMODE)) {
WM_operator_name_call(C, "GPENCIL_OT_editmode_toggle", WM_OP_EXEC_REGION_WIN, NULL);
}
}
if (!ob || !object_mode_compat_test(ob, mode))
return OPERATOR_PASS_THROUGH;
@@ -1675,7 +1721,7 @@ void OBJECT_OT_mode_set(wmOperatorType *ot)
/* api callbacks */
ot->exec = object_mode_set_exec;
ot->poll = ED_operator_object_active_editable;
ot->poll = object_mode_set_poll; //ED_operator_object_active_editable;
/* flags */
ot->flag = 0; /* no register/undo here, leave it to operators being called */

View File

@@ -2204,7 +2204,6 @@ static int keyframe_jump_exec(bContext *C, wmOperator *op)
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
Object *ob = CTX_data_active_object(C);
bGPdata *gpd = CTX_data_gpencil_data(C);
bDopeSheet ads = {NULL};
DLRBT_Tree keys;
ActKeyColumn *ak;
@@ -2229,11 +2228,12 @@ static int keyframe_jump_exec(bContext *C, wmOperator *op)
/* populate tree with keyframe nodes */
scene_to_keylist(&ads, scene, &keys, NULL);
gpencil_to_keylist(&ads, scene->gpd, &keys);
if (ob)
if (ob) {
ob_to_keylist(&ads, ob, &keys, NULL);
gpencil_to_keylist(&ads, gpd, &keys);
gpencil_to_keylist(&ads, ob->gpd, &keys);
}
{
Mask *mask = CTX_data_edit_mask(C);

View File

@@ -549,9 +549,10 @@ static int actkeys_copy_exec(bContext *C, wmOperator *op)
/* copy keyframes */
if (ac.datatype == ANIMCONT_GPENCIL) {
/* FIXME... */
BKE_report(op->reports, RPT_ERROR, "Keyframe pasting is not available for grease pencil mode");
return OPERATOR_CANCELLED;
if (ED_gpencil_anim_copybuf_copy(&ac) == false) {
/* Nothing got copied - An error about this should be been logged already */
return OPERATOR_CANCELLED;
}
}
else if (ac.datatype == ANIMCONT_MASK) {
/* FIXME... */
@@ -599,7 +600,13 @@ static int actkeys_paste_exec(bContext *C, wmOperator *op)
ac.reports = op->reports;
/* paste keyframes */
if (ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK)) {
if (ac.datatype == ANIMCONT_GPENCIL) {
if (ED_gpencil_anim_copybuf_paste(&ac, offset_mode) == false) {
/* An error occurred - Reports should have been fired already */
return OPERATOR_CANCELLED;
}
}
else if (ac.datatype == ANIMCONT_MASK) {
/* FIXME... */
BKE_report(op->reports, RPT_ERROR, "Keyframe pasting is not available for grease pencil or mask mode");
return OPERATOR_CANCELLED;

View File

@@ -349,14 +349,16 @@ static void time_draw_keyframes(const bContext *C, ARegion *ar)
{
Scene *scene = CTX_data_scene(C);
Object *ob = CTX_data_active_object(C);
bGPdata *gpd = CTX_data_gpencil_data(C);
View2D *v2d = &ar->v2d;
bool onlysel = ((scene->flag & SCE_KEYS_NO_SELONLY) == 0);
/* draw grease pencil keyframes (if available) */
if (gpd) {
UI_ThemeColor(TH_TIME_GP_KEYFRAME);
time_draw_idblock_keyframes(v2d, (ID *)gpd, onlysel);
/* draw grease pencil keyframes (if available) */
UI_ThemeColor(TH_TIME_GP_KEYFRAME);
if (scene->gpd) {
time_draw_idblock_keyframes(v2d, (ID *)scene->gpd, onlysel);
}
if (ob && ob->gpd) {
time_draw_idblock_keyframes(v2d, (ID *)ob->gpd, onlysel);
}
/* draw scene keyframes first

View File

@@ -941,6 +941,8 @@ static void draw_selected_name(Scene *scene, Object *ob, rcti *rect)
/* color depends on whether there is a keyframe */
if (id_frame_has_keyframe((ID *)ob, /* BKE_scene_frame_get(scene) */ (float)cfra, ANIMFILTER_KEYS_LOCAL))
UI_ThemeColor(TH_VERTEX_SELECT);
else if (ED_gpencil_has_keyframe_v3d(scene, ob, cfra))
UI_ThemeColor(TH_CFRAME); // XXX
else
UI_ThemeColor(TH_TEXT_HI);
}
@@ -2349,7 +2351,7 @@ void ED_view3d_draw_depth_gpencil(Scene *scene, ARegion *ar, View3D *v3d)
glEnable(GL_DEPTH_TEST);
if (v3d->flag2 & V3D_SHOW_GPENCIL) {
ED_gpencil_draw_view3d(scene, v3d, ar, true);
ED_gpencil_draw_view3d(NULL, scene, v3d, ar, true);
}
v3d->zbuf = zbuf;
@@ -2853,9 +2855,11 @@ static void view3d_draw_objects(
/* must be before xray draw which clears the depth buffer */
if (v3d->flag2 & V3D_SHOW_GPENCIL) {
wmWindowManager *wm = (C != NULL) ? CTX_wm_manager(C) : NULL;
/* must be before xray draw which clears the depth buffer */
if (v3d->zbuf) glDisable(GL_DEPTH_TEST);
ED_gpencil_draw_view3d(scene, v3d, ar, true);
ED_gpencil_draw_view3d(wm, scene, v3d, ar, true);
if (v3d->zbuf) glEnable(GL_DEPTH_TEST);
}
@@ -3266,7 +3270,7 @@ void ED_view3d_draw_offscreen(
if (v3d->flag2 & V3D_SHOW_GPENCIL) {
/* draw grease-pencil stuff - needed to get paint-buffer shown too (since it's 2D) */
ED_gpencil_draw_view3d(scene, v3d, ar, false);
ED_gpencil_draw_view3d(NULL, scene, v3d, ar, false);
}
/* freeing the images again here could be done after the operator runs, leaving for now */
@@ -3481,6 +3485,9 @@ ImBuf *ED_view3d_draw_offscreen_imbuf_simple(
if (use_solid_tex)
v3d.flag2 |= V3D_SOLID_TEX;
if (draw_background)
v3d.flag3 |= V3D_SHOW_WORLD;
rv3d.persp = RV3D_CAMOB;
@@ -3949,6 +3956,7 @@ static void view3d_main_region_draw_info(const bContext *C, Scene *scene,
ARegion *ar, View3D *v3d,
const char *grid_unit, bool render_border)
{
wmWindowManager *wm = CTX_wm_manager(C);
RegionView3D *rv3d = ar->regiondata;
rcti rect;
@@ -3972,7 +3980,7 @@ static void view3d_main_region_draw_info(const bContext *C, Scene *scene,
if (v3d->flag2 & V3D_SHOW_GPENCIL) {
/* draw grease-pencil stuff - needed to get paint-buffer shown too (since it's 2D) */
ED_gpencil_draw_view3d(scene, v3d, ar, false);
ED_gpencil_draw_view3d(wm, scene, v3d, ar, false);
}
if ((v3d->flag2 & V3D_RENDER_OVERRIDE) == 0) {
@@ -3999,8 +4007,6 @@ static void view3d_main_region_draw_info(const bContext *C, Scene *scene,
}
if ((v3d->flag2 & V3D_RENDER_OVERRIDE) == 0) {
wmWindowManager *wm = CTX_wm_manager(C);
if ((U.uiflag & USER_SHOW_FPS) && ED_screen_animation_no_scrub(wm)) {
ED_scene_draw_fps(scene, &rect);
}

View File

@@ -34,6 +34,7 @@
#include "DNA_scene_types.h"
#include "DNA_object_types.h"
#include "DNA_gpencil_types.h"
#include "BLI_utildefines.h"
@@ -287,6 +288,7 @@ void uiTemplateHeader3D(uiLayout *layout, struct bContext *C)
PointerRNA v3dptr, toolsptr, sceneptr;
Object *ob = OBACT;
Object *obedit = CTX_data_edit_object(C);
bGPdata *gpd = CTX_data_gpencil_data(C);
uiBlock *block;
uiLayout *row;
bool is_paint = false;
@@ -303,7 +305,10 @@ void uiTemplateHeader3D(uiLayout *layout, struct bContext *C)
UI_block_emboss_set(block, UI_EMBOSS);
/* mode */
if (ob) {
if ((gpd) && (gpd->flag & GP_DATA_STROKE_EDITMODE)) {
modeselect = OB_MODE_GPENCIL;
}
else if (ob) {
modeselect = ob->mode;
is_paint = ELEM(ob->mode, OB_MODE_SCULPT, OB_MODE_VERTEX_PAINT, OB_MODE_WEIGHT_PAINT, OB_MODE_TEXTURE_PAINT);
}

View File

@@ -7767,45 +7767,7 @@ static void createTransGPencil(bContext *C, TransInfo *t)
*/
// XXX: should this be allowed when framelock is enabled?
if (gpf->framenum != cfra) {
bGPDframe *new_frame = gpencil_frame_duplicate(gpf);
bGPDframe *gf;
bool found = false;
/* Find frame to insert it before */
for (gf = gpf->next; gf; gf = gf->next) {
if (gf->framenum > cfra) {
/* Add it here */
BLI_insertlinkbefore(&gpl->frames, gf, new_frame);
found = true;
break;
}
else if (gf->framenum == cfra) {
/* This only happens when we're editing with framelock on...
* - Delete the new frame and don't do anything else here...
*/
//printf("GP Frame convert to TransData - Copy aborted for frame %d -> %d\n", gpf->framenum, gf->framenum);
free_gpencil_strokes(new_frame);
MEM_freeN(new_frame);
new_frame = NULL;
found = true;
break;
}
}
if (found == false) {
/* Add new frame to the end */
BLI_addtail(&gpl->frames, new_frame);
}
/* Edit the new frame instead, if it did get created + added */
if (new_frame) {
// TODO: tag this one as being "newly created" so that we can remove it if the edit is cancelled
new_frame->framenum = cfra;
gpf = new_frame;
}
gpf = gpencil_frame_addcopy(gpl, cfra);
}
/* Loop over strokes, adding TransData for points as needed... */
@@ -7930,7 +7892,8 @@ void createTransData(bContext *C, TransInfo *t)
}
}
else if (t->options & CTX_GPENCIL_STROKES) {
t->flag |= T_POINTS; // XXX...
t->options |= CTX_GPENCIL_STROKES;
t->flag |= T_POINTS;
createTransGPencil(C, t);
if (t->data && (t->flag & T_PROP_EDIT)) {

View File

@@ -39,6 +39,7 @@
#include "DNA_anim_types.h"
#include "DNA_armature_types.h"
#include "DNA_brush_types.h"
#include "DNA_gpencil_types.h"
#include "DNA_lattice_types.h"
#include "DNA_screen_types.h"
#include "DNA_sequence_types.h"
@@ -1095,6 +1096,7 @@ void initTransInfo(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve
ScrArea *sa = CTX_wm_area(C);
Object *obedit = CTX_data_edit_object(C);
Object *ob = CTX_data_active_object(C);
bGPdata *gpd = CTX_data_gpencil_data(C);
PropertyRNA *prop;
t->scene = sce;
@@ -1164,6 +1166,11 @@ void initTransInfo(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve
t->remove_on_cancel = true;
}
}
/* GPencil editing context */
if ((gpd) && (gpd->flag & GP_DATA_STROKE_EDITMODE)) {
t->options |= CTX_GPENCIL_STROKES;
}
/* Assign the space type, some exceptions for running in different mode */
if (sa == NULL) {

View File

@@ -37,12 +37,14 @@
#include "DNA_armature_types.h"
#include "DNA_curve_types.h"
#include "DNA_gpencil_types.h"
#include "DNA_lattice_types.h"
#include "DNA_meta_types.h"
#include "DNA_screen_types.h"
#include "DNA_scene_types.h"
#include "DNA_view3d_types.h"
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BLI_utildefines.h"
@@ -272,6 +274,8 @@ static int calc_manipulator_stats(const bContext *C)
RegionView3D *rv3d = ar->regiondata;
Base *base;
Object *ob = OBACT;
bGPdata *gpd = CTX_data_gpencil_data(C);
const bool is_gp_edit = ((gpd) && (gpd->flag & GP_DATA_STROKE_EDITMODE));
int a, totsel = 0;
/* transform widget matrix */
@@ -282,8 +286,32 @@ static int calc_manipulator_stats(const bContext *C)
/* transform widget centroid/center */
INIT_MINMAX(scene->twmin, scene->twmax);
zero_v3(scene->twcent);
if (obedit) {
if (is_gp_edit) {
CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
{
/* we're only interested in selected points here... */
if (gps->flag & GP_STROKE_SELECT) {
bGPDspoint *pt;
int i;
/* Change selection status of all points, then make the stroke match */
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
if (pt->flag & GP_SPOINT_SELECT) {
calc_tw_center(scene, &pt->x);
totsel++;
}
}
}
}
CTX_DATA_END;
/* selection center */
if (totsel) {
mul_v3_fl(scene->twcent, 1.0f / (float)totsel); /* centroid! */
}
}
else if (obedit) {
ob = obedit;
if ((ob->lay & v3d->lay) == 0) return 0;
@@ -546,7 +574,7 @@ static int calc_manipulator_stats(const bContext *C)
}
/* global, local or normal orientation? */
if (ob && totsel) {
if (ob && totsel && !is_gp_edit) {
switch (v3d->twmode) {
@@ -1595,9 +1623,12 @@ void BIF_draw_manipulator(const bContext *C)
case V3D_AROUND_CENTER_BOUNDS:
case V3D_AROUND_ACTIVE:
{
Object *ob;
bGPdata *gpd = CTX_data_gpencil_data(C);
Object *ob = OBACT;
if (((v3d->around == V3D_AROUND_ACTIVE) && (scene->obedit == NULL)) &&
((ob = OBACT) && !(ob->mode & OB_MODE_POSE)))
((gpd == NULL) || !(gpd->flag & GP_DATA_STROKE_EDITMODE)) &&
(!(ob->mode & OB_MODE_POSE)))
{
copy_v3_v3(rv3d->twmat[3], ob->obmat[3]);
}

View File

@@ -596,6 +596,9 @@ typedef enum eDopeSheet_FilterFlag {
ADS_FILTER_BY_FCU_NAME = (1 << 27), /* for F-Curves, filter by the displayed name (i.e. to isolate all Location curves only) */
ADS_FILTER_ONLY_ERRORS = (1 << 28), /* show only F-Curves which are disabled/have errors - for debugging drivers */
/* GPencil Mode */
ADS_FILTER_GP_3DONLY = (1 << 29), /* GP Mode - Only show datablocks used in the scene */
/* combination filters (some only used at runtime) */
ADS_FILTER_NOOBDATA = (ADS_FILTER_NOCAM | ADS_FILTER_NOMAT | ADS_FILTER_NOLAM | ADS_FILTER_NOCUR | ADS_FILTER_NOPART | ADS_FILTER_NOARM | ADS_FILTER_NOSPK | ADS_FILTER_NOMODIFIERS)
} eDopeSheet_FilterFlag;

View File

@@ -51,7 +51,10 @@ typedef struct bGPDspoint {
/* bGPDspoint->flag */
typedef enum eGPDspoint_Flag {
/* stroke point is selected (for editing) */
GP_SPOINT_SELECT = (1 << 0)
GP_SPOINT_SELECT = (1 << 0),
/* stroke point is tagged (for some editing operation) */
GP_SPOINT_TAG = (1 << 1),
} eGPSPoint_Flag;
/* Grease-Pencil Annotations - 'Stroke'
@@ -190,6 +193,7 @@ typedef enum eGPdata_Flag {
/* is the block overriding all clicks? */
/* GP_DATA_EDITPAINT = (1 << 3), */
/* ------------------------------------------------ DEPRECATED */
/* new strokes are added in viewport space */
GP_DATA_VIEWALIGN = (1 << 4),
@@ -198,9 +202,13 @@ typedef enum eGPdata_Flag {
GP_DATA_DEPTH_STROKE = (1 << 6),
GP_DATA_DEPTH_STROKE_ENDPOINTS = (1 << 7),
/* ------------------------------------------------ DEPRECATED */
/* Stroke Editing Mode - Toggle to enable alternative keymap for easier editing of stroke points */
GP_DATA_STROKE_EDITMODE = (1 << 8)
GP_DATA_STROKE_EDITMODE = (1 << 8),
/* Convenience/cache flag to make it easier to quickly toggle onion skinning on/off */
GP_DATA_SHOW_ONIONSKINS = (1 << 9)
} eGPdata_Flag;
#endif /* __DNA_GPENCIL_TYPES_H__ */

View File

@@ -675,6 +675,7 @@ typedef enum ObjectMode {
OB_MODE_TEXTURE_PAINT = 1 << 4,
OB_MODE_PARTICLE_EDIT = 1 << 5,
OB_MODE_POSE = 1 << 6,
OB_MODE_GPENCIL = 1 << 7, /* NOTE: Just a dummy to make the UI nicer */
} ObjectMode;
/* any mode where the brush system is used */

View File

@@ -911,6 +911,7 @@ typedef enum StereoViews {
STEREO_MONO_ID = 3,
} StereoViews;
/* *************************************************************** */
/* Markers */
typedef struct TimeMarker {
@@ -1041,6 +1042,7 @@ typedef struct Sculpt {
typedef struct UvSculpt {
Paint paint;
} UvSculpt;
/* ------------------------------------------- */
/* Vertex Paint */
@@ -1066,6 +1068,65 @@ enum {
VP_ONLYVGROUP = (1 << 7) /* weight paint only */
};
/* ------------------------------------------- */
/* GPencil Stroke Sculpting */
/* Brush types */
typedef enum eGP_EditBrush_Types {
GP_EDITBRUSH_TYPE_SMOOTH = 0,
GP_EDITBRUSH_TYPE_THICKNESS = 1,
GP_EDITBRUSH_TYPE_GRAB = 2,
GP_EDITBRUSH_TYPE_PUSH = 3,
GP_EDITBRUSH_TYPE_TWIST = 4,
GP_EDITBRUSH_TYPE_PINCH = 5,
GP_EDITBRUSH_TYPE_RANDOMISE = 6,
GP_EDITBRUSH_TYPE_SUBDIVIDE = 7,
GP_EDITBRUSH_TYPE_SIMPLIFY = 8,
GP_EDITBRUSH_TYPE_CLONE = 9,
/* !!! Update GP_EditBrush_Data brush[###]; below !!! */
TOT_GP_EDITBRUSH_TYPES
} eGP_EditBrush_Types;
/* Settings for a GPencil Stroke Sculpting Brush */
typedef struct GP_EditBrush_Data {
short size; /* radius of brush */
short flag; /* eGP_EditBrush_Flag */
float strength; /* strength of effect */
} GP_EditBrush_Data;
/* GP_EditBrush_Data.flag */
typedef enum eGP_EditBrush_Flag {
/* invert the effect of the brush */
GP_EDITBRUSH_FLAG_INVERT = (1 << 0),
/* adjust strength using pen pressure */
GP_EDITBRUSH_FLAG_USE_PRESSURE = (1 << 1),
/* strength of brush falls off with distance from cursor */
GP_EDITBRUSH_FLAG_USE_FALLOFF = (1 << 2),
/* smooth brush affects pressure values as well */
GP_EDITBRUSH_FLAG_SMOOTH_PRESSURE = (1 << 3)
} eGP_EditBrush_Flag;
/* GPencil Stroke Sculpting Settings */
typedef struct GP_BrushEdit_Settings {
GP_EditBrush_Data brush[10]; /* TOT_GP_EDITBRUSH_TYPES */
void *paintcursor; /* runtime */
int brushtype; /* eGP_EditBrush_Types */
int flag; /* eGP_BrushEdit_SettingsFlag */
} GP_BrushEdit_Settings;
/* GP_BrushEdit_Settings.flag */
typedef enum eGP_BrushEdit_SettingsFlag {
/* only affect selected points */
GP_BRUSHEDIT_FLAG_SELECT_MASK = (1 << 0)
} eGP_BrushEdit_SettingsFlag;
/* *************************************************************** */
/* Transform Orientations */
@@ -1172,6 +1233,10 @@ typedef enum {
UNIFIED_PAINT_BRUSH_ALPHA_PRESSURE = (1 << 4)
} UnifiedPaintSettingsFlags;
/* *************************************************************** */
/* Stats */
/* Stats for Meshes */
typedef struct MeshStatVis {
char type;
char _pad1[2];
@@ -1228,7 +1293,13 @@ typedef struct ToolSettings {
char gpencil_flags; /* flags/options for how the tool works */
char gpencil_src; /* for main 3D view Grease Pencil, where data comes from */
char pad[4];
char gpencil_v3d_align; /* stroke placement settings: 3D View */
char gpencil_v2d_align; /* : General 2D Editor */
char gpencil_seq_align; /* : Sequencer Preview */
char gpencil_ima_align; /* : Image Editor */
/* Grease Pencil Sculpt */
struct GP_BrushEdit_Settings gp_sculpt;
/* Image Paint (8 byttse aligned please!) */
struct ImagePaintSettings imapaint;
@@ -1917,7 +1988,12 @@ typedef enum ImagePaintMode {
#define EDGE_MODE_TAG_FREESTYLE 5
/* toolsettings->gpencil_flags */
#define GP_TOOL_FLAG_PAINTSESSIONS_ON (1<<0)
typedef enum eGPencil_Flags {
/* "Continuous Drawing" - The drawing operator enters a mode where multiple strokes can be drawn */
GP_TOOL_FLAG_PAINTSESSIONS_ON = (1 << 0),
/* When creating new frames, the last frame gets used as the basis for the new one */
GP_TOOL_FLAG_RETAIN_LAST = (1 << 1),
} eGPencil_Flags;
/* toolsettings->gpencil_src */
typedef enum eGPencil_Source_3D {
@@ -1925,6 +2001,22 @@ typedef enum eGPencil_Source_3D {
GP_TOOL_SOURCE_OBJECT = 1
} eGPencil_Source_3d;
/* toolsettings->gpencil_*_align - Stroke Placement mode flags */
typedef enum eGPencil_Placement_Flags {
/* New strokes are added in viewport/data space (i.e. not screen space) */
GP_PROJECT_VIEWSPACE = (1 << 0),
/* Viewport space, but relative to render canvas (Sequencer Preview Only) */
GP_PROJECT_CANVAS = (1 << 1),
/* Project into the screen's Z values */
GP_PROJECT_DEPTH_VIEW = (1 << 2),
GP_PROJECT_DEPTH_STROKE = (1 << 3),
/* "Use Endpoints" */
GP_PROJECT_DEPTH_STROKE_ENDPOINTS = (1 << 4),
} eGPencil_Placement_Flags;
/* toolsettings->particle flag */
#define PE_KEEP_LENGTHS 1
#define PE_LOCK_FIRST 2

View File

@@ -259,6 +259,8 @@ extern StructRNA RNA_GPencilFrame;
extern StructRNA RNA_GPencilLayer;
extern StructRNA RNA_GPencilStroke;
extern StructRNA RNA_GPencilStrokePoint;
extern StructRNA RNA_GPencilSculptSettings;
extern StructRNA RNA_GPencilSculptBrush;
extern StructRNA RNA_GameBooleanProperty;
extern StructRNA RNA_GameFloatProperty;
extern StructRNA RNA_GameIntProperty;

View File

@@ -111,6 +111,8 @@ extern EnumPropertyItem rna_enum_brush_sculpt_tool_items[];
extern EnumPropertyItem rna_enum_brush_vertex_tool_items[];
extern EnumPropertyItem rna_enum_brush_image_tool_items[];
extern EnumPropertyItem rna_enum_gpencil_sculpt_brush_items[];
extern EnumPropertyItem rna_enum_symmetrize_direction_items[];
extern EnumPropertyItem rna_enum_texture_type_items[];

View File

@@ -495,6 +495,14 @@ static void rna_def_dopesheet(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Display Grease Pencil", "Include visualization of Grease Pencil related animation data and frames");
RNA_def_property_ui_icon(prop, ICON_GREASEPENCIL, 0);
RNA_def_property_update(prop, NC_ANIMATION | ND_ANIMCHAN | NA_EDITED, NULL);
/* GPencil Mode Settings */
prop = RNA_def_property(srna, "show_gpencil_3d_only", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "filterflag", ADS_FILTER_GP_3DONLY);
RNA_def_property_ui_text(prop, "Active Scene Only",
"Only show Grease Pencil datablocks used as part of the active scene");
RNA_def_property_ui_icon(prop, ICON_SCENE_DATA, 0);
RNA_def_property_update(prop, NC_ANIMATION | ND_ANIMCHAN | NA_EDITED, NULL);
}
static void rna_def_action_group(BlenderRNA *brna)

View File

@@ -50,11 +50,46 @@
#include "BKE_gpencil.h"
#include "DNA_object_types.h"
static void rna_GPencil_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *UNUSED(ptr))
{
WM_main_add_notifier(NC_GPENCIL | NA_EDITED, NULL);
}
static void rna_GPencil_editmode_update(Main *UNUSED(bmain), Scene *scene, PointerRNA *ptr)
{
/* Notify all places where GPencil data lives that the editing state is different */
WM_main_add_notifier(NC_GPENCIL | NA_EDITED, NULL);
WM_main_add_notifier(NC_SCENE | ND_MODE, NULL);
}
static void rna_GPencil_onion_skinning_update(Main *bmain, Scene *scene, PointerRNA *ptr)
{
bGPdata *gpd = (bGPdata *)ptr->id.data;
bGPDlayer *gpl;
bool enabled = false;
/* Ensure that the datablock's onionskinning toggle flag
* stays in sync with the status of the actual layers
*/
for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
if (gpl->flag & GP_LAYER_ONIONSKIN) {
enabled = true;
}
}
if (enabled)
gpd->flag |= GP_DATA_SHOW_ONIONSKINS;
else
gpd->flag &= ~GP_DATA_SHOW_ONIONSKINS;
/* Now do standard updates... */
rna_GPencil_update(bmain, scene, ptr);
}
static char *rna_GPencilLayer_path(PointerRNA *ptr)
{
bGPDlayer *gpl = (bGPDlayer *)ptr->data;
@@ -201,6 +236,30 @@ static void rna_GPencilLayer_info_set(PointerRNA *ptr, const char *value)
BLI_uniquename(&gpd->layers, gpl, DATA_("GP_Layer"), '.', offsetof(bGPDlayer, info), sizeof(gpl->info));
}
static void rna_GPencil_use_onion_skinning_set(PointerRNA *ptr, const int value)
{
bGPdata *gpd = ptr->id.data;
bGPDlayer *gpl;
/* set new value */
if (value) {
/* enable on active layer (it's the one that's most likely to be of interest right now) */
gpl = gpencil_layer_getactive(gpd);
if (gpl) {
gpl->flag |= GP_LAYER_ONIONSKIN;
}
gpd->flag |= GP_DATA_SHOW_ONIONSKINS;
}
else {
/* disable on all layers - allowa quickly turning them all off, without having to check */
for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
gpl->flag &= ~GP_LAYER_ONIONSKIN;
}
gpd->flag &= ~GP_DATA_SHOW_ONIONSKINS;
}
}
static bGPDstroke *rna_GPencil_stroke_point_find_stroke(const bGPdata *gpd, const bGPDspoint *pt, bGPDlayer **r_gpl, bGPDframe **r_gpf)
{
@@ -701,7 +760,7 @@ static void rna_def_gpencil_layer(BlenderRNA *brna)
prop = RNA_def_property(srna, "use_onion_skinning", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_LAYER_ONIONSKIN);
RNA_def_property_ui_text(prop, "Onion Skinning", "Ghost frames on either side of frame");
RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_update");
RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_onion_skinning_update");
prop = RNA_def_property(srna, "ghost_before_range", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, NULL, "gstep");
@@ -849,14 +908,6 @@ static void rna_def_gpencil_data(BlenderRNA *brna)
PropertyRNA *prop;
FunctionRNA *func;
static EnumPropertyItem draw_mode_items[] = {
{GP_DATA_VIEWALIGN, "CURSOR", 0, "Cursor", "Draw stroke at the 3D cursor"},
{0, "VIEW", 0, "View", "Stick stroke to the view "}, /* weird, GP_DATA_VIEWALIGN is inverted */
{GP_DATA_VIEWALIGN | GP_DATA_DEPTH_VIEW, "SURFACE", 0, "Surface", "Stick stroke to surfaces"},
{GP_DATA_VIEWALIGN | GP_DATA_DEPTH_STROKE, "STROKE", 0, "Stroke", "Stick stroke to other strokes"},
{0, NULL, 0, NULL, NULL}
};
srna = RNA_def_struct(brna, "GreasePencil", "ID");
RNA_def_struct_sdna(srna, "bGPdata");
RNA_def_struct_ui_text(srna, "Grease Pencil", "Freehand annotation sketchbook");
@@ -873,21 +924,17 @@ static void rna_def_gpencil_data(BlenderRNA *brna)
rna_def_animdata_common(srna);
/* Flags */
prop = RNA_def_property(srna, "draw_mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_bitflag_sdna(prop, NULL, "flag");
RNA_def_property_enum_items(prop, draw_mode_items);
RNA_def_property_ui_text(prop, "Draw Mode", "");
RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, NULL);
prop = RNA_def_property(srna, "use_stroke_endpoints", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_DATA_DEPTH_STROKE_ENDPOINTS);
RNA_def_property_ui_text(prop, "Only Endpoints", "Only use the first and last parts of the stroke for snapping");
RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, NULL);
prop = RNA_def_property(srna, "use_stroke_edit_mode", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_DATA_STROKE_EDITMODE);
RNA_def_property_ui_text(prop, "Stroke Edit Mode", "Enable alternative keymap to make editing stroke points easier");
RNA_def_property_update(prop, NC_GPENCIL | ND_DATA | ND_GPENCIL_EDITMODE, "rna_GPencil_update");
RNA_def_property_ui_text(prop, "Stroke Edit Mode", "Edit Grease Pencil strokes instead of viewport data");
RNA_def_property_update(prop, NC_GPENCIL | ND_DATA | ND_GPENCIL_EDITMODE, "rna_GPencil_editmode_update");
prop = RNA_def_property(srna, "use_onion_skinning", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_DATA_SHOW_ONIONSKINS);
RNA_def_property_boolean_funcs(prop, NULL, "rna_GPencil_use_onion_skinning_set");
RNA_def_property_ui_text(prop, "Onion Skins",
"Show ghosts of the frames before and after the current frame, toggle to enable on active layer or disable all");
RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, NULL);
/* API Functions */
func = RNA_def_function(srna, "clear", "rna_GPencil_clear");

View File

@@ -69,6 +69,7 @@ EnumPropertyItem rna_enum_object_mode_items[] = {
{OB_MODE_WEIGHT_PAINT, "WEIGHT_PAINT", ICON_WPAINT_HLT, "Weight Paint", ""},
{OB_MODE_TEXTURE_PAINT, "TEXTURE_PAINT", ICON_TPAINT_HLT, "Texture Paint", ""},
{OB_MODE_PARTICLE_EDIT, "PARTICLE_EDIT", ICON_PARTICLEMODE, "Particle Edit", ""},
{OB_MODE_GPENCIL, "GPENCIL_EDIT", ICON_GREASEPENCIL, "Edit Strokes", "Edit Grease Pencil Strokes"},
{0, NULL, 0, NULL, NULL}
};

View File

@@ -2057,11 +2057,19 @@ static void rna_def_tool_settings(BlenderRNA *brna)
"unless the active object already has Grease Pencil data (i.e. for old files)"},
{GP_TOOL_SOURCE_OBJECT, "OBJECT", 0, "Object",
"Grease Pencil data-blocks attached to the active object are used "
"(required using pre 2.73 add-ons, e.g. BSurfaces)"},
"(required when using pre 2.73 add-ons, e.g. BSurfaces)"},
{0, NULL, 0, NULL, NULL}
};
static EnumPropertyItem gpencil_stroke_placement_items[] = {
{GP_PROJECT_VIEWSPACE, "CURSOR", 0, "Cursor", "Draw stroke at the 3D cursor"},
{0, "VIEW", 0, "View", "Stick stroke to the view "}, /* weird, GP_PROJECT_VIEWALIGN is inverted */
{GP_PROJECT_VIEWSPACE | GP_PROJECT_DEPTH_VIEW, "SURFACE", 0, "Surface", "Stick stroke to surfaces"},
{GP_PROJECT_VIEWSPACE | GP_PROJECT_DEPTH_STROKE, "STROKE", 0, "Stroke", "Stick stroke to other strokes"},
{0, NULL, 0, NULL, NULL}
};
srna = RNA_def_struct(brna, "ToolSettings", NULL);
RNA_def_struct_path_func(srna, "rna_ToolSettings_path");
RNA_def_struct_ui_text(srna, "Tool Settings", "");
@@ -2269,12 +2277,19 @@ static void rna_def_tool_settings(BlenderRNA *brna)
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); /* header redraw */
/* Grease Pencil */
prop = RNA_def_property(srna, "use_grease_pencil_sessions", PROP_BOOLEAN, PROP_NONE);
prop = RNA_def_property(srna, "use_gpencil_continuous_drawing", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "gpencil_flags", GP_TOOL_FLAG_PAINTSESSIONS_ON);
RNA_def_property_ui_text(prop, "Use Sketching Sessions",
RNA_def_property_ui_text(prop, "Use Continuous Drawing",
"Allow drawing multiple strokes at a time with Grease Pencil");
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); /* xxx: need toolbar to be redrawn... */
prop = RNA_def_property(srna, "use_gpencil_additive_drawing", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "gpencil_flags", GP_TOOL_FLAG_RETAIN_LAST);
RNA_def_property_ui_text(prop, "Use Additive Drawing",
"When creating new frames, the strokes from the previous/active frame "
"are included as the basis for the new one");
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL);
prop = RNA_def_property(srna, "grease_pencil_source", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_bitflag_sdna(prop, NULL, "gpencil_src");
RNA_def_property_enum_items(prop, gpencil_source_3d_items);
@@ -2282,6 +2297,45 @@ static void rna_def_tool_settings(BlenderRNA *brna)
"Datablock where active Grease Pencil data is found from");
RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, NULL);
prop = RNA_def_property(srna, "gpencil_sculpt", PROP_POINTER, PROP_NONE);
RNA_def_property_pointer_sdna(prop, NULL, "gp_sculpt");
RNA_def_property_struct_type(prop, "GPencilSculptSettings");
RNA_def_property_ui_text(prop, "Grease Pencil Sculpt", "");
/* Grease Pencil - 3D View Stroke Placement */
prop = RNA_def_property(srna, "gpencil_stroke_placement_view3d", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_bitflag_sdna(prop, NULL, "gpencil_v3d_align");
RNA_def_property_enum_items(prop, gpencil_stroke_placement_items);
RNA_def_property_ui_text(prop, "Stroke Placement (3D View)", "");
RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, NULL);
prop = RNA_def_property(srna, "use_gpencil_stroke_endpoints", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "gpencil_v3d_align", GP_PROJECT_DEPTH_STROKE_ENDPOINTS);
RNA_def_property_ui_text(prop, "Only Endpoints", "Only use the first and last parts of the stroke for snapping");
RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, NULL);
/* Grease Pencil - 2D Views Stroke Placement */
prop = RNA_def_property(srna, "gpencil_stroke_placement_view2d", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_bitflag_sdna(prop, NULL, "gpencil_v2d_align");
RNA_def_property_enum_items(prop, gpencil_stroke_placement_items);
RNA_def_property_ui_text(prop, "Stroke Placement (2D View)", "");
RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, NULL);
/* Grease Pencil - Sequencer Preview Stroke Placement */
prop = RNA_def_property(srna, "gpencil_stroke_placement_sequencer_preview", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_bitflag_sdna(prop, NULL, "gpencil_seq_align");
RNA_def_property_enum_items(prop, gpencil_stroke_placement_items);
RNA_def_property_ui_text(prop, "Stroke Placement (Sequencer Preview)", "");
RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, NULL);
/* Grease Pencil - Image Editor Stroke Placement */
prop = RNA_def_property(srna, "gpencil_stroke_placement_image_editor", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_bitflag_sdna(prop, NULL, "gpencil_ima_align");
RNA_def_property_enum_items(prop, gpencil_stroke_placement_items);
RNA_def_property_ui_text(prop, "Stroke Placement (Image Editor)", "");
RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, NULL);
/* Auto Keying */
prop = RNA_def_property(srna, "use_keyframe_insert_auto", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "autokey_mode", AUTOKEY_ON);

View File

@@ -60,6 +60,20 @@ static EnumPropertyItem particle_edit_hair_brush_items[] = {
{0, NULL, 0, NULL, NULL}
};
EnumPropertyItem rna_enum_gpencil_sculpt_brush_items[] = {
{GP_EDITBRUSH_TYPE_SMOOTH, "SMOOTH", 0, "Smooth", "Smooth stroke points"},
{GP_EDITBRUSH_TYPE_THICKNESS, "THICKNESS", 0, "Thickness", "Adjust thickness of strokes"},
{GP_EDITBRUSH_TYPE_GRAB, "GRAB", 0, "Grab", "Translate the set of points initially within the brush circle"},
{GP_EDITBRUSH_TYPE_PUSH, "PUSH", 0, "Push", "Move points out of the way, as if combing them"},
{GP_EDITBRUSH_TYPE_TWIST, "TWIST", 0, "Twist", "Rotate points around the midpoint of the brush"},
{GP_EDITBRUSH_TYPE_PINCH, "PINCH", 0, "Pinch", "Pull points towards the midpoint of the brush"},
{GP_EDITBRUSH_TYPE_RANDOMISE, "RANDOMISE", 0, "Randomise", "Introduce jitter/randomness into strokes"},
//{GP_EDITBRUSH_TYPE_SUBDIVIDE, "SUBDIVIDE", 0, "Subdivide", "Increase point density for higher resolution strokes when zoomed in"},
//{GP_EDITBRUSH_TYPE_SIMPLIFY, "SIMPLIFY", 0, "Simplify", "Reduce density of stroke points"},
{GP_EDITBRUSH_TYPE_CLONE, "CLONE", 0, "Clone", "Paste copies of the strokes stored on the clipboard"},
{0, NULL, 0, NULL, NULL}
};
EnumPropertyItem rna_enum_symmetrize_direction_items[] = {
{BMO_SYMMETRIZE_NEGATIVE_X, "NEGATIVE_X", 0, "-X to +X", ""},
{BMO_SYMMETRIZE_POSITIVE_X, "POSITIVE_X", 0, "+X to -X", ""},
@@ -359,6 +373,30 @@ static int rna_ImaPaint_detect_data(ImagePaintSettings *imapaint)
{
return imapaint->missing_data == 0;
}
static PointerRNA rna_GPencilSculptSettings_brush_get(PointerRNA *ptr)
{
GP_BrushEdit_Settings *gset = (GP_BrushEdit_Settings *)ptr->data;
GP_EditBrush_Data *brush = NULL;
if ((gset->brushtype >= 0) && (gset->brushtype < TOT_GP_EDITBRUSH_TYPES))
brush = &gset->brush[gset->brushtype];
return rna_pointer_inherit_refine(ptr, &RNA_GPencilSculptBrush, brush);
}
static char *rna_GPencilSculptSettings_path(PointerRNA *UNUSED(ptr))
{
return BLI_strdup("tool_settings.gpencil_sculpt");
}
static char *rna_GPencilSculptBrush_path(PointerRNA *UNUSED(ptr))
{
return BLI_strdup("tool_settings.gpencil_sculpt.brush");
}
#else
static void rna_def_paint_curve(BlenderRNA *brna)
@@ -945,6 +983,72 @@ static void rna_def_particle_edit(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Curve", "");
}
static void rna_def_gpencil_sculpt(BlenderRNA *brna)
{
static EnumPropertyItem prop_direction_items[]= {
{0, "ADD", 0, "Add", "Add effect of brush"},
{GP_EDITBRUSH_FLAG_INVERT, "SUBTRACT", 0, "Subtract", "Subtract effect of brush"},
{0, NULL, 0, NULL, NULL}};
StructRNA *srna;
PropertyRNA *prop;
/* == Settings == */
srna = RNA_def_struct(brna, "GPencilSculptSettings", NULL);
RNA_def_struct_sdna(srna, "GP_BrushEdit_Settings");
RNA_def_struct_path_func(srna, "rna_GPencilSculptSettings_path");
RNA_def_struct_ui_text(srna, "GPencil Sculpt Settings", "Properties for Grease Pencil stroke sculpting tool");
prop = RNA_def_property(srna, "tool", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "brushtype");
RNA_def_property_enum_items(prop, rna_enum_gpencil_sculpt_brush_items);
RNA_def_property_ui_text(prop, "Tool", "");
prop = RNA_def_property(srna, "brush", PROP_POINTER, PROP_NONE);
RNA_def_property_struct_type(prop, "GPencilSculptBrush");
RNA_def_property_pointer_funcs(prop, "rna_GPencilSculptSettings_brush_get", NULL, NULL, NULL);
RNA_def_property_ui_text(prop, "Brush", "");
prop = RNA_def_property(srna, "use_select_mask", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_BRUSHEDIT_FLAG_SELECT_MASK);
RNA_def_property_ui_text(prop, "Selection Mask", "Only sculpt selected stroke points");
RNA_def_property_ui_icon(prop, ICON_VERTEXSEL, 0); // FIXME: this needs a custom icon
/* brush */
srna = RNA_def_struct(brna, "GPencilSculptBrush", NULL);
RNA_def_struct_sdna(srna, "GP_EditBrush_Data");
RNA_def_struct_path_func(srna, "rna_GPencilSculptBrush_path");
RNA_def_struct_ui_text(srna, "GPencil Sculpt Brush", "Stroke editing brush");
prop = RNA_def_property(srna, "size", PROP_INT, PROP_PIXEL);
RNA_def_property_range(prop, 1, MAX_BRUSH_PIXEL_RADIUS);
RNA_def_property_ui_range(prop, 1, 100, 10, 3); // XXX: too big
RNA_def_property_ui_text(prop, "Radius", "Radius of the brush in pixels");
prop = RNA_def_property(srna, "strength", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_range(prop, 0.001, 1.0);
RNA_def_property_ui_text(prop, "Strength", "Brush strength");
prop = RNA_def_property(srna, "use_pressure_strength", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_EDITBRUSH_FLAG_USE_PRESSURE);
RNA_def_property_ui_icon(prop, ICON_STYLUS_PRESSURE, 0);
RNA_def_property_ui_text(prop, "Strength Pressure", "Enable tablet pressure sensitivity for strength");
prop = RNA_def_property(srna, "use_falloff", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_EDITBRUSH_FLAG_USE_FALLOFF);
RNA_def_property_ui_text(prop, "Use Falloff", "Strength of brush decays with distance from cursor");
prop = RNA_def_property(srna, "affect_pressure", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_EDITBRUSH_FLAG_SMOOTH_PRESSURE);
RNA_def_property_ui_text(prop, "Affect Pressure", "Affect pressure values as well when smoothing strokes");
prop = RNA_def_property(srna, "direction", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_bitflag_sdna(prop, NULL, "flag");
RNA_def_property_enum_items(prop, prop_direction_items);
RNA_def_property_ui_text(prop, "Direction", "");
}
void RNA_def_sculpt_paint(BlenderRNA *brna)
{
/* *** Non-Animated *** */
@@ -956,6 +1060,7 @@ void RNA_def_sculpt_paint(BlenderRNA *brna)
rna_def_vertex_paint(brna);
rna_def_image_paint(brna);
rna_def_particle_edit(brna);
rna_def_gpencil_sculpt(brna);
RNA_define_animate_sdna(true);
}

View File

@@ -86,6 +86,9 @@ static EnumPropertyItem event_mouse_type_items[] = {
{ACTIONMOUSE, "ACTIONMOUSE", 0, "Action", ""},
{SELECTMOUSE, "SELECTMOUSE", 0, "Select", ""},
{0, "", 0, NULL, NULL},
{TABLET_STYLUS, "PEN", 0, "Pen", ""},
{TABLET_ERASER, "ERASER", 0, "Eraser", ""},
{0, "", 0, NULL, NULL},
{MOUSEMOVE, "MOUSEMOVE", 0, "Move", ""},
{MOUSEPAN, "TRACKPADPAN", 0, "Mouse/Trackpad Pan", ""},
{MOUSEZOOM, "TRACKPADZOOM", 0, "Mouse/Trackpad Zoom", ""},
@@ -180,6 +183,9 @@ EnumPropertyItem rna_enum_event_type_items[] = {
{ACTIONMOUSE, "ACTIONMOUSE", 0, "Action Mouse", "MBA"},
{SELECTMOUSE, "SELECTMOUSE", 0, "Select Mouse", "MBS"},
{0, "", 0, NULL, NULL},
{TABLET_STYLUS, "PEN", 0, "Pen", ""},
{TABLET_ERASER, "ERASER", 0, "Eraser", ""},
{0, "", 0, NULL, NULL},
{MOUSEMOVE, "MOUSEMOVE", 0, "Mouse Move", "MsMov"},
{INBETWEEN_MOUSEMOVE, "INBETWEEN_MOUSEMOVE", 0, "In-between Move", "MsSubMov"},
{MOUSEPAN, "TRACKPADPAN", 0, "Mouse/Trackpad Pan", "MsPan"},

View File

@@ -1538,8 +1538,24 @@ static int wm_eventmatch(wmEvent *winevent, wmKeyMapItem *kmi)
if (ISKEYBOARD(winevent->type) && (winevent->ascii || winevent->utf8_buf[0])) return 1;
}
if (kmitype != KM_ANY)
if (winevent->type != kmitype) return 0;
if (kmitype != KM_ANY) {
if (ELEM(kmitype, TABLET_STYLUS, TABLET_ERASER)) {
const wmTabletData *wmtab = winevent->tablet_data;
if (wmtab == NULL)
return 0;
else if (winevent->type != LEFTMOUSE) /* tablet events can occur on hover + keypress */
return 0;
else if ((kmitype == TABLET_STYLUS) && (wmtab->Active != EVT_TABLET_STYLUS))
return 0;
else if ((kmitype == TABLET_ERASER) && (wmtab->Active != EVT_TABLET_ERASER))
return 0;
}
else {
if (winevent->type != kmitype)
return 0;
}
}
if (kmi->val != KM_ANY)
if (winevent->val != kmi->val) return 0;

View File

@@ -513,6 +513,7 @@ void WM_exit_ext(bContext *C, const bool do_python)
free_anim_copybuf();
free_anim_drivers_copybuf();
free_fmodifiers_copybuf();
ED_gpencil_anim_copybuf_free();
ED_gpencil_strokes_copybuf_free();
ED_clipboard_posebuf_free();
BKE_node_clipboard_clear();

View File

@@ -202,6 +202,9 @@ int WM_keymap_map_type_get(wmKeyMapItem *kmi)
if (kmi->type == KM_TEXTINPUT) {
return KMI_TYPE_TEXTINPUT;
}
if (ELEM(kmi->type, TABLET_STYLUS, TABLET_ERASER)) {
return KMI_TYPE_MOUSE;
}
return KMI_TYPE_KEYBOARD;
}

View File

@@ -92,6 +92,10 @@ enum {
WM_IME_COMPOSITE_EVENT = 0x0015,
/* IME event, GHOST_kEventImeCompositionEnd in ghost */
WM_IME_COMPOSITE_END = 0x0016,
/* Tablet/Pen Specific Events */
TABLET_STYLUS = 0x001a,
TABLET_ERASER = 0x001b,
/* *** Start of keyboard codes. *** */