1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
19 '''Based on Box_deform standalone addon - Author: Samuel Bernou'''
21 from .prefs
import get_addon_prefs
26 def location_to_region(worldcoords
):
27 from bpy_extras
import view3d_utils
28 return view3d_utils
.location_3d_to_region_2d(bpy
.context
.region
, bpy
.context
.space_data
.region_3d
, worldcoords
)
30 def region_to_location(viewcoords
, depthcoords
):
31 from bpy_extras
import view3d_utils
32 return view3d_utils
.region_2d_to_location_3d(bpy
.context
.region
, bpy
.context
.space_data
.region_3d
, viewcoords
, depthcoords
)
34 def store_cage(self
, vg_name
):
36 unique_id
= time
.strftime(r
'%y%m%d%H%M%S') # ex: 20210711111117
37 # name = f'gp_lattice_{unique_id}'
38 name
= f
'{self.gp_obj.name}_lat{unique_id}'
39 vg
= self
.gp_obj
.vertex_groups
.get(vg_name
)
42 for o
in self
.other_gp
:
43 vg
= o
.vertex_groups
.get(vg_name
)
48 self
.cage
.data
.name
= name
49 mod
= self
.gp_obj
.grease_pencil_modifiers
.get('tmp_lattice')
51 mod
.name
= name
#f'Lattice_{unique_id}'
52 mod
.vertex_group
= name
53 for o
in self
.other_gp
:
54 mod
= o
.grease_pencil_modifiers
.get('tmp_lattice')
57 mod
.vertex_group
= name
59 def assign_vg(obj
, vg_name
, delete
=False):
60 ## create vertex group
61 vg
= obj
.vertex_groups
.get(vg_name
)
63 # remove to start clean
64 obj
.vertex_groups
.remove(vg
)
68 vg
= obj
.vertex_groups
.new(name
=vg_name
)
69 bpy
.ops
.gpencil
.vertex_group_assign()
73 prefs
= get_addon_prefs()
74 lattice_interp
= prefs
.default_deform_type
79 from_obj
= bpy
.context
.mode
== 'OBJECT'
80 all_gps
= [o
for o
in bpy
.context
.selected_objects
if o
.type == 'GPENCIL']
81 other_gp
= [o
for o
in all_gps
if o
is not obj
]
84 initial_mode
= bpy
.context
.mode
87 if bpy
.context
.mode
== 'EDIT_GPENCIL':
89 if l
.lock
or l
.hide
or not l
.active_frame
:#or len(l.frames)
92 target_frames
= [f
for f
in l
.frames
if f
.select
]
94 target_frames
= [l
.active_frame
]
96 for f
in target_frames
:
103 coords
.append(obj
.matrix_world
@ p
.co
)
105 elif bpy
.context
.mode
== 'OBJECT': # object mode -> all points of all selected gp objects
107 for l
in gpo
.data
.layers
:# if l.hide:continue# only visible ? (might break things)
108 if not len(l
.frames
):
109 continue # skip frameless layer
110 for s
in l
.active_frame
.strokes
:
112 coords
.append(gpo
.matrix_world
@ p
.co
)
114 elif bpy
.context
.mode
== 'PAINT_GPENCIL':
115 # get last stroke points coordinated
116 if not gpl
.active
or not gpl
.active
.active_frame
:
117 return 'No frame to deform'
119 if not len(gpl
.active
.active_frame
.strokes
):
120 return 'No stroke found to deform'
123 if bpy
.context
.scene
.tool_settings
.use_gpencil_draw_onback
:
125 coords
= [obj
.matrix_world
@ p
.co
for p
in gpl
.active
.active_frame
.strokes
[paint_id
].points
]
131 ## maybe silent return instead (need special str code to manage errorless return)
132 return 'No points found!'
134 if bpy
.context
.mode
in ('EDIT_GPENCIL', 'PAINT_GPENCIL') and len(coords
) < 2:
135 # Dont block object mod
136 return 'Less than two point selected'
138 vg_name
= 'lattice_cage_deform_group'
140 if bpy
.context
.mode
== 'EDIT_GPENCIL':
141 vg
= assign_vg(obj
, vg_name
)
143 if bpy
.context
.mode
== 'PAINT_GPENCIL':
144 # points cannot be assign to API yet(ugly and slow workaround but only way)
145 # -> https://developer.blender.org/T56280 so, hop'in'ops !
147 # store selection and deselect all
149 for s
in gpl
.active
.active_frame
.strokes
:
151 plist
.append([p
, p
.select
])
155 ## foreach_set does not update
156 # gpl.active.active_frame.strokes[paint_id].points.foreach_set('select', [True]*len(gpl.active.active_frame.strokes[paint_id].points))
157 for p
in gpl
.active
.active_frame
.strokes
[paint_id
].points
:
161 bpy
.ops
.object.mode_set(mode
='EDIT_GPENCIL')
162 vg
= assign_vg(obj
, vg_name
)
169 ## View axis Mode ---
171 ## get view coordinate of all points
172 coords2D
= [location_to_region(co
) for co
in coords
]
174 # find centroid for depth (or more economic, use obj origin...)
175 centroid
= np
.mean(coords
, axis
=0)
177 # not a mean ! a mean of extreme ! centroid2d = np.mean(coords2D, axis=0)
178 all_x
, all_y
= np
.array(coords2D
)[:, 0], np
.array(coords2D
)[:, 1]
179 min_x
, min_y
= np
.min(all_x
), np
.min(all_y
)
180 max_x
, max_y
= np
.max(all_x
), np
.max(all_y
)
182 width
= (max_x
- min_x
)
183 height
= (max_y
- min_y
)
184 center_x
= min_x
+ (width
/2)
185 center_y
= min_y
+ (height
/2)
187 centroid2d
= (center_x
,center_y
)
188 center
= region_to_location(centroid2d
, centroid
)
189 # bpy.context.scene.cursor.location = center#Dbg
192 #corner Bottom-left to Bottom-right
193 x0
= region_to_location((min_x
, min_y
), centroid
)
194 x1
= region_to_location((max_x
, min_y
), centroid
)
195 x_worldsize
= (x0
- x1
).length
197 #corner Bottom-left to top-left
198 y0
= region_to_location((min_x
, min_y
), centroid
)
199 y1
= region_to_location((min_x
, max_y
), centroid
)
200 y_worldsize
= (y0
- y1
).length
204 lattice_name
= 'lattice_cage_deform'
206 cage
= bpy
.data
.objects
.get(lattice_name
)
208 bpy
.data
.objects
.remove(cage
)
210 lattice
= bpy
.data
.lattices
.get(lattice_name
)
212 bpy
.data
.lattices
.remove(lattice
)
214 # create lattice object
215 lattice
= bpy
.data
.lattices
.new(lattice_name
)
216 cage
= bpy
.data
.objects
.new(lattice_name
, lattice
)
217 cage
.show_in_front
= True
219 ## Master (root) collection
220 bpy
.context
.scene
.collection
.objects
.link(cage
)
222 # spawn cage and align it to view
224 r3d
= bpy
.context
.space_data
.region_3d
225 viewmat
= r3d
.view_matrix
227 cage
.matrix_world
= viewmat
.inverted()
228 cage
.scale
= (x_worldsize
, y_worldsize
, 1)
229 ## Z aligned in view direction (need minus X 90 degree to be aligned FRONT)
230 # cage.rotation_euler.x -= radians(90)
231 # cage.scale = (x_worldsize, 1, y_worldsize)
232 cage
.location
= center
238 lattice
.interpolation_type_u
= lattice_interp
#'KEY_LINEAR'-'KEY_BSPLINE'
239 lattice
.interpolation_type_v
= lattice_interp
240 lattice
.interpolation_type_w
= lattice_interp
242 mod
= obj
.grease_pencil_modifiers
.new('tmp_lattice', 'GP_LATTICE')
246 mods
.append( o
.grease_pencil_modifiers
.new('tmp_lattice', 'GP_LATTICE') )
248 # move to top if modifiers exists
249 for _
in range(len(obj
.grease_pencil_modifiers
)):
250 bpy
.ops
.object.gpencil_modifier_move_up(modifier
='tmp_lattice')
253 for _
in range(len(o
.grease_pencil_modifiers
)):
254 bpy
.ops
.object.gpencil_modifier_move_up({'object':o
}, modifier
='tmp_lattice')
261 if initial_mode
== 'PAINT_GPENCIL':
262 mod
.layer
= gpl
.active
.info
264 # note : if initial was Paint, changed to Edit
265 # so vertex attribution is valid even for paint
266 if bpy
.context
.mode
== 'EDIT_GPENCIL':
267 mod
.vertex_group
= vg
.name
269 # Go in object mode if not already
270 if bpy
.context
.mode
!= 'OBJECT':
271 bpy
.ops
.object.mode_set(mode
='OBJECT')
273 # Store name of deformed object in case of 'revive modal'
274 cage
.vertex_groups
.new(name
=obj
.name
)
277 cage
.vertex_groups
.new(name
=o
.name
)
279 ## select and make cage active
280 # cage.select_set(True)
281 bpy
.context
.view_layer
.objects
.active
= cage
282 obj
.select_set(False) # deselect GP object
283 bpy
.ops
.object.mode_set(mode
='EDIT') # go in lattice edit mode
284 bpy
.ops
.lattice
.select_all(action
='SELECT') # select all points
286 if prefs
.use_clic_drag
:
287 ## Eventually change tool mode to tweak for direct point editing (reset after before leaving)
288 bpy
.ops
.wm
.tool_set_by_id(name
="builtin.select") # Tweaktoolcode
292 def back_to_obj(obj
, gp_mode
, org_lattice_toolset
, context
):
293 if context
.mode
== 'EDIT_LATTICE' and org_lattice_toolset
: # Tweaktoolcode - restore the active tool used by lattice edit..
294 bpy
.ops
.wm
.tool_set_by_id(name
= org_lattice_toolset
) # Tweaktoolcode
296 # gp object active and selected
297 bpy
.ops
.object.mode_set(mode
='OBJECT')
299 bpy
.context
.view_layer
.objects
.active
= obj
302 def delete_cage(cage
):
304 bpy
.data
.objects
.remove(cage
)
305 bpy
.data
.lattices
.remove(lattice
)
307 def apply_cage(gp_obj
):
308 mod
= gp_obj
.grease_pencil_modifiers
.get('tmp_lattice')
311 if gp_obj
.data
.users
> 1:
313 multi_user
= old
.name
314 other_user
= [o
for o
in bpy
.data
.objects
if o
is not gp_obj
and o
.data
is old
]
315 gp_obj
.data
= gp_obj
.data
.copy()
317 # bpy.ops.object.gpencil_modifier_apply(apply_as='DATA', modifier=mod.name)
318 bpy
.ops
.object.gpencil_modifier_apply({'object': gp_obj
}, apply_as
='DATA', modifier
=mod
.name
)
321 for o
in other_user
: # relink
323 bpy
.data
.grease_pencils
.remove(old
)
324 gp_obj
.data
.name
= multi_user
327 print('tmp_lattice modifier not found to apply...')
329 def cancel_cage(self
):
331 mod
= self
.gp_obj
.grease_pencil_modifiers
.get('tmp_lattice')
333 self
.gp_obj
.grease_pencil_modifiers
.remove(mod
)
335 print(f
'tmp_lattice modifier not found to remove on {self.gp_obj.name}')
337 for ob
in self
.other_gp
:
338 mod
= ob
.grease_pencil_modifiers
.get('tmp_lattice')
340 ob
.grease_pencil_modifiers
.remove(mod
)
342 print(f
'tmp_lattice modifier not found to remove on {ob.name}')
344 delete_cage(self
.cage
)
347 class GP_OT_latticeGpDeform(bpy
.types
.Operator
):
348 """Create a lattice to use as quad corner transform"""
349 bl_idname
= "gp.latticedeform"
350 bl_label
= "Box Deform"
351 bl_description
= "Use lattice for free box transforms on grease pencil points (Ctrl+T)"
352 bl_options
= {"REGISTER", "UNDO"}
355 def poll(cls
, context
):
356 return context
.object is not None and context
.object.type in ('GPENCIL','LATTICE')
361 def modal(self
, context
, event
):
362 display_text
= f
"Deform Cage size: {self.lat.points_u}x{self.lat.points_v} (1-9 or ctrl + ←→↑↓) | \
363 mode (M) : {'Linear' if self.lat.interpolation_type_u == 'KEY_LINEAR' else 'Spline'} | \
364 valid:Spacebar/Enter, cancel:Del/Backspace/Tab/Ctrl+T"
365 context
.area
.header_text_set(display_text
)
369 if event
.type in {'Z'} and event
.value
== 'PRESS' and event
.ctrl
:
370 ## Disable (capture key)
371 return {"RUNNING_MODAL"}
372 ## Not found how possible to find modal start point in undo stack to
373 # print('ops list', context.window_manager.operators.keys())
374 # if context.window_manager.operators:#can be empty
375 # print('\nlast name', context.window_manager.operators[-1].name)
379 if event
.type in {'TWO', 'THREE', 'FOUR', 'FIVE', 'SIX', 'SEVEN', 'EIGHT', 'NINE', 'ZERO',} and event
.value
== 'PRESS':
380 self
.set_lattice_interp('KEY_BSPLINE')
381 if event
.type in {'DOWN_ARROW', "UP_ARROW", "RIGHT_ARROW", "LEFT_ARROW"} and event
.value
== 'PRESS' and event
.ctrl
:
382 self
.set_lattice_interp('KEY_BSPLINE')
383 if event
.type in {'ONE'} and event
.value
== 'PRESS':
384 self
.set_lattice_interp('KEY_LINEAR')
387 if event
.type in {'H'} and event
.value
== 'PRESS':
388 # self.report({'INFO'}, "Can't hide")
389 return {"RUNNING_MODAL"}
391 if event
.type in {'ONE'} and event
.value
== 'PRESS':# , 'NUMPAD_1'
392 self
.lat
.points_u
= self
.lat
.points_v
= 2
393 return {"RUNNING_MODAL"}
395 if event
.type in {'TWO'} and event
.value
== 'PRESS':# , 'NUMPAD_2'
396 self
.lat
.points_u
= self
.lat
.points_v
= 3
397 return {"RUNNING_MODAL"}
399 if event
.type in {'THREE'} and event
.value
== 'PRESS':# , 'NUMPAD_3'
400 self
.lat
.points_u
= self
.lat
.points_v
= 4
401 return {"RUNNING_MODAL"}
403 if event
.type in {'FOUR'} and event
.value
== 'PRESS':# , 'NUMPAD_4'
404 self
.lat
.points_u
= self
.lat
.points_v
= 5
405 return {"RUNNING_MODAL"}
407 if event
.type in {'FIVE'} and event
.value
== 'PRESS':# , 'NUMPAD_5'
408 self
.lat
.points_u
= self
.lat
.points_v
= 6
409 return {"RUNNING_MODAL"}
411 if event
.type in {'SIX'} and event
.value
== 'PRESS':# , 'NUMPAD_6'
412 self
.lat
.points_u
= self
.lat
.points_v
= 7
413 return {"RUNNING_MODAL"}
415 if event
.type in {'SEVEN'} and event
.value
== 'PRESS':# , 'NUMPAD_7'
416 self
.lat
.points_u
= self
.lat
.points_v
= 8
417 return {"RUNNING_MODAL"}
419 if event
.type in {'EIGHT'} and event
.value
== 'PRESS':# , 'NUMPAD_8'
420 self
.lat
.points_u
= self
.lat
.points_v
= 9
421 return {"RUNNING_MODAL"}
423 if event
.type in {'NINE'} and event
.value
== 'PRESS':# , 'NUMPAD_9'
424 self
.lat
.points_u
= self
.lat
.points_v
= 10
425 return {"RUNNING_MODAL"}
427 if event
.type in {'ZERO'} and event
.value
== 'PRESS':# , 'NUMPAD_0'
428 self
.lat
.points_u
= 2
429 self
.lat
.points_v
= 1
430 return {"RUNNING_MODAL"}
432 if event
.type in {'RIGHT_ARROW'} and event
.value
== 'PRESS' and event
.ctrl
:
433 if self
.lat
.points_u
< 20:
434 self
.lat
.points_u
+= 1
435 return {"RUNNING_MODAL"}
437 if event
.type in {'LEFT_ARROW'} and event
.value
== 'PRESS' and event
.ctrl
:
438 if self
.lat
.points_u
> 1:
439 self
.lat
.points_u
-= 1
440 return {"RUNNING_MODAL"}
442 if event
.type in {'UP_ARROW'} and event
.value
== 'PRESS' and event
.ctrl
:
443 if self
.lat
.points_v
< 20:
444 self
.lat
.points_v
+= 1
445 return {"RUNNING_MODAL"}
447 if event
.type in {'DOWN_ARROW'} and event
.value
== 'PRESS' and event
.ctrl
:
448 if self
.lat
.points_v
> 1:
449 self
.lat
.points_v
-= 1
450 return {"RUNNING_MODAL"}
454 if event
.type in {'M'} and event
.value
== 'PRESS':
455 self
.auto_interp
= False
456 interp
= 'KEY_BSPLINE' if self
.lat
.interpolation_type_u
== 'KEY_LINEAR' else 'KEY_LINEAR'
457 self
.set_lattice_interp(interp
)
458 return {"RUNNING_MODAL"}
461 if event
.type in {'RET', 'SPACE', 'NUMPAD_ENTER'}:
462 if event
.value
== 'PRESS':
463 context
.window_manager
.boxdeform_running
= False
464 self
.restore_prefs(context
)
465 back_to_obj(self
.gp_obj
, self
.gp_mode
, self
.org_lattice_toolset
, context
)
467 # Let the cage as is with a unique ID
468 store_cage(self
, 'lattice_cage_deform_group')
470 apply_cage(self
.gp_obj
) # must be in object mode
471 assign_vg(self
.gp_obj
, 'lattice_cage_deform_group', delete
=True)
472 for o
in self
.other_gp
:
474 assign_vg(o
, 'lattice_cage_deform_group', delete
=True)
475 delete_cage(self
.cage
)
477 # back to original mode
478 if self
.gp_mode
!= 'OBJECT':
479 bpy
.ops
.object.mode_set(mode
=self
.gp_mode
)
480 context
.area
.header_text_set(None)#reset header
485 # One Warning for Tab cancellation.
486 if event
.type == 'TAB' and event
.value
== 'PRESS':
487 self
.tab_press_ct
+= 1
488 if self
.tab_press_ct
< 2:
489 self
.report({'WARNING'}, "Pressing TAB again will Cancel")
490 return {"RUNNING_MODAL"}
492 if event
.type in {'T'} and event
.value
== 'PRESS' and event
.ctrl
:# Retyped same shortcut
496 if event
.type in {'DEL', 'BACK_SPACE'} or self
.tab_press_ct
>= 2:#'ESC',
500 return {'PASS_THROUGH'}
502 def set_lattice_interp(self
, interp
):
503 self
.lat
.interpolation_type_u
= self
.lat
.interpolation_type_v
= self
.lat
.interpolation_type_w
= interp
505 def cancel(self
, context
):
506 context
.window_manager
.boxdeform_running
= False
507 self
.restore_prefs(context
)
508 back_to_obj(self
.gp_obj
, self
.gp_mode
, self
.org_lattice_toolset
, context
)
510 assign_vg(self
.gp_obj
, 'lattice_cage_deform_group', delete
=True)
511 context
.area
.header_text_set(None)
512 if self
.gp_mode
!= 'OBJECT':
513 bpy
.ops
.object.mode_set(mode
=self
.gp_mode
)
515 def store_prefs(self
, context
):
516 # store_valierables <-< preferences
517 self
.use_drag_immediately
= context
.preferences
.inputs
.use_drag_immediately
518 self
.drag_threshold_mouse
= context
.preferences
.inputs
.drag_threshold_mouse
519 self
.drag_threshold_tablet
= context
.preferences
.inputs
.drag_threshold_tablet
520 self
.use_overlays
= context
.space_data
.overlay
.show_overlays
521 # maybe store in windows manager to keep around in case of modal revival ?
523 def restore_prefs(self
, context
):
524 # preferences <-< store_valierables
525 context
.preferences
.inputs
.use_drag_immediately
= self
.use_drag_immediately
526 context
.preferences
.inputs
.drag_threshold_mouse
= self
.drag_threshold_mouse
527 context
.preferences
.inputs
.drag_threshold_tablet
= self
.drag_threshold_tablet
528 context
.space_data
.overlay
.show_overlays
= self
.use_overlays
530 def set_prefs(self
, context
):
531 context
.preferences
.inputs
.use_drag_immediately
= True
532 context
.preferences
.inputs
.drag_threshold_mouse
= 1
533 context
.preferences
.inputs
.drag_threshold_tablet
= 3
534 context
.space_data
.overlay
.show_overlays
= True
536 def invoke(self
, context
, event
):
537 ## Restrict to 3D view
538 if context
.area
.type != 'VIEW_3D':
539 self
.report({'WARNING'}, "View3D not found, cannot run operator")
542 if not context
.object:#do it in poll ?
543 self
.report({'ERROR'}, "No active objects found")
546 if context
.window_manager
.boxdeform_running
:
549 self
.prefs
= get_addon_prefs()#get_prefs
550 self
.auto_interp
= self
.prefs
.auto_swap_deform_type
551 self
.org_lattice_toolset
= None
553 if self
.prefs
.use_clic_drag
:#Store the active tool since we will change it
554 self
.org_lattice_toolset
= bpy
.context
.workspace
.tools
.from_space_view3d_mode(bpy
.context
.mode
, create
=False).idname
# Tweaktoolcode
556 #store (scene properties needed in case of ctrlZ revival)
557 self
.store_prefs(context
)
558 self
.gp_mode
= 'EDIT_GPENCIL'
560 # --- special Case of lattice revive modal, just after ctrl+Z back into lattice with modal stopped
561 if context
.mode
== 'EDIT_LATTICE' and context
.object.name
== 'lattice_cage_deform' and len(context
.object.vertex_groups
):
562 self
.gp_obj
= context
.scene
.objects
.get(context
.object.vertex_groups
[0].name
)
564 self
.report({'ERROR'}, "/!\\ Box Deform : Cannot find object to target")
566 if not self
.gp_obj
.grease_pencil_modifiers
.get('tmp_lattice'):
567 self
.report({'ERROR'}, "/!\\ No 'tmp_lattice' modifiers on GP object")
569 self
.cage
= context
.object
570 self
.lat
= self
.cage
.data
571 self
.set_prefs(context
)
573 if self
.prefs
.use_clic_drag
:
574 bpy
.ops
.wm
.tool_set_by_id(name
="builtin.select")
575 context
.window_manager
.boxdeform_running
= True
576 context
.window_manager
.modal_handler_add(self
)
577 return {'RUNNING_MODAL'}
579 if context
.object.type != 'GPENCIL':
580 # self.report({'ERROR'}, "Works only on gpencil objects")
584 if context
.mode
not in ('EDIT_GPENCIL', 'OBJECT', 'PAINT_GPENCIL'):
585 # self.report({'WARNING'}, "Works only in following GPencil modes: object / edit/ paint")# ERROR
590 # bpy.ops.ed.undo_push(message="Box deform step")#don't work as expected (+ might be obsolete)
591 # https://developer.blender.org/D6147 <- undo forget
593 self
.gp_obj
= context
.object
595 self
.from_object
= context
.mode
== 'OBJECT'
596 self
.all_gps
= self
.other_gp
= []
598 self
.all_gps
= [o
for o
in bpy
.context
.selected_objects
if o
.type == 'GPENCIL']
599 self
.other_gp
= [o
for o
in self
.all_gps
if o
is not self
.gp_obj
]
601 # Clean potential failed previous job (delete tmp lattice)
602 mod
= self
.gp_obj
.grease_pencil_modifiers
.get('tmp_lattice')
604 print('Deleted remaining lattice modifiers')
605 self
.gp_obj
.grease_pencil_modifiers
.remove(mod
)
607 phantom_obj
= context
.scene
.objects
.get('lattice_cage_deform')
609 print('Deleted remaining lattice object')
610 delete_cage(phantom_obj
)
612 if bpy
.app
.version
< (2,93,0):
613 if [m
for m
in self
.gp_obj
.grease_pencil_modifiers
if m
.type == 'GP_LATTICE']:
614 self
.report({'ERROR'}, "Grease pencil object already has a lattice modifier (multi-lattices are enabled in blender 2.93+)")
617 self
.gp_mode
= context
.mode
# store mode for restore
619 # All good, create lattice and start modal
621 # Create lattice (and switch to lattice edit) ----
622 self
.cage
= view_cage(self
.gp_obj
)
623 if isinstance(self
.cage
, str):#error, cage not created, display error
624 self
.report({'ERROR'}, self
.cage
)
627 self
.lat
= self
.cage
.data
629 self
.set_prefs(context
)
630 context
.window_manager
.boxdeform_running
= True
631 context
.window_manager
.modal_handler_add(self
)
632 return {'RUNNING_MODAL'}
637 def register_keymaps():
638 addon
= bpy
.context
.window_manager
.keyconfigs
.addon
640 km
= addon
.keymaps
.new(name
= "Grease Pencil", space_type
= "EMPTY", region_type
='WINDOW')
641 kmi
= km
.keymap_items
.new("gp.latticedeform", type ='T', value
= "PRESS", ctrl
= True)
643 addon_keymaps
.append((km
, kmi
))
645 def unregister_keymaps():
646 for km
, kmi
in addon_keymaps
:
647 km
.keymap_items
.remove(kmi
)
648 addon_keymaps
.clear()
653 if bpy
.app
.background
:
655 bpy
.types
.WindowManager
.boxdeform_running
= bpy
.props
.BoolProperty(default
=False)
656 bpy
.utils
.register_class(GP_OT_latticeGpDeform
)
660 if bpy
.app
.background
:
663 bpy
.utils
.unregister_class(GP_OT_latticeGpDeform
)
664 wm
= bpy
.context
.window_manager
665 p
= 'boxdeform_running'