1 # SPDX-FileCopyrightText: 2020-2023 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 '''Based on Box_deform standalone addon - Author: Samuel Bernou'''
7 from .prefs
import get_addon_prefs
12 def location_to_region(worldcoords
):
13 from bpy_extras
import view3d_utils
14 return view3d_utils
.location_3d_to_region_2d(bpy
.context
.region
, bpy
.context
.space_data
.region_3d
, worldcoords
)
16 def region_to_location(viewcoords
, depthcoords
):
17 from bpy_extras
import view3d_utils
18 return view3d_utils
.region_2d_to_location_3d(bpy
.context
.region
, bpy
.context
.space_data
.region_3d
, viewcoords
, depthcoords
)
20 def store_cage(self
, vg_name
):
22 unique_id
= time
.strftime(r
'%y%m%d%H%M%S') # ex: 20210711111117
23 # name = f'gp_lattice_{unique_id}'
24 name
= f
'{self.gp_obj.name}_lat{unique_id}'
25 vg
= self
.gp_obj
.vertex_groups
.get(vg_name
)
28 for o
in self
.other_gp
:
29 vg
= o
.vertex_groups
.get(vg_name
)
34 self
.cage
.data
.name
= name
35 mod
= self
.gp_obj
.grease_pencil_modifiers
.get('tmp_lattice')
37 mod
.name
= name
#f'Lattice_{unique_id}'
38 mod
.vertex_group
= name
39 for o
in self
.other_gp
:
40 mod
= o
.grease_pencil_modifiers
.get('tmp_lattice')
43 mod
.vertex_group
= name
45 def assign_vg(obj
, vg_name
, delete
=False):
46 ## create vertex group
47 vg
= obj
.vertex_groups
.get(vg_name
)
49 # remove to start clean
50 obj
.vertex_groups
.remove(vg
)
54 vg
= obj
.vertex_groups
.new(name
=vg_name
)
55 bpy
.ops
.gpencil
.vertex_group_assign()
59 prefs
= get_addon_prefs()
60 lattice_interp
= prefs
.default_deform_type
65 from_obj
= bpy
.context
.mode
== 'OBJECT'
66 all_gps
= [o
for o
in bpy
.context
.selected_objects
if o
.type == 'GPENCIL']
67 other_gp
= [o
for o
in all_gps
if o
is not obj
]
70 initial_mode
= bpy
.context
.mode
73 if bpy
.context
.mode
== 'EDIT_GPENCIL':
75 if l
.lock
or l
.hide
or not l
.active_frame
:#or len(l.frames)
78 target_frames
= [f
for f
in l
.frames
if f
.select
]
80 target_frames
= [l
.active_frame
]
82 for f
in target_frames
:
89 coords
.append(obj
.matrix_world
@ p
.co
)
91 elif bpy
.context
.mode
== 'OBJECT': # object mode -> all points of all selected gp objects
93 for l
in gpo
.data
.layers
:# if l.hide:continue# only visible ? (might break things)
95 continue # skip frameless layer
96 for s
in l
.active_frame
.strokes
:
98 coords
.append(gpo
.matrix_world
@ p
.co
)
100 elif bpy
.context
.mode
== 'PAINT_GPENCIL':
101 # get last stroke points coordinated
102 if not gpl
.active
or not gpl
.active
.active_frame
:
103 return 'No frame to deform'
105 if not len(gpl
.active
.active_frame
.strokes
):
106 return 'No stroke found to deform'
109 if bpy
.context
.scene
.tool_settings
.use_gpencil_draw_onback
:
111 coords
= [obj
.matrix_world
@ p
.co
for p
in gpl
.active
.active_frame
.strokes
[paint_id
].points
]
117 ## maybe silent return instead (need special str code to manage errorless return)
118 return 'No points found!'
120 if bpy
.context
.mode
in ('EDIT_GPENCIL', 'PAINT_GPENCIL') and len(coords
) < 2:
121 # Dont block object mod
122 return 'Less than two point selected'
124 vg_name
= 'lattice_cage_deform_group'
126 if bpy
.context
.mode
== 'EDIT_GPENCIL':
127 vg
= assign_vg(obj
, vg_name
)
129 if bpy
.context
.mode
== 'PAINT_GPENCIL':
130 # points cannot be assign to API yet(ugly and slow workaround but only way)
131 # -> https://developer.blender.org/T56280 so, hop'in'ops !
133 # store selection and deselect all
135 for s
in gpl
.active
.active_frame
.strokes
:
137 plist
.append([p
, p
.select
])
141 ## foreach_set does not update
142 # gpl.active.active_frame.strokes[paint_id].points.foreach_set('select', [True]*len(gpl.active.active_frame.strokes[paint_id].points))
143 for p
in gpl
.active
.active_frame
.strokes
[paint_id
].points
:
147 bpy
.ops
.object.mode_set(mode
='EDIT_GPENCIL')
148 vg
= assign_vg(obj
, vg_name
)
155 ## View axis Mode ---
157 ## get view coordinate of all points
158 coords2D
= [location_to_region(co
) for co
in coords
]
160 # find centroid for depth (or more economic, use obj origin...)
161 centroid
= np
.mean(coords
, axis
=0)
163 # not a mean ! a mean of extreme ! centroid2d = np.mean(coords2D, axis=0)
164 all_x
, all_y
= np
.array(coords2D
)[:, 0], np
.array(coords2D
)[:, 1]
165 min_x
, min_y
= np
.min(all_x
), np
.min(all_y
)
166 max_x
, max_y
= np
.max(all_x
), np
.max(all_y
)
168 width
= (max_x
- min_x
)
169 height
= (max_y
- min_y
)
170 center_x
= min_x
+ (width
/2)
171 center_y
= min_y
+ (height
/2)
173 centroid2d
= (center_x
,center_y
)
174 center
= region_to_location(centroid2d
, centroid
)
175 # bpy.context.scene.cursor.location = center#Dbg
178 #corner Bottom-left to Bottom-right
179 x0
= region_to_location((min_x
, min_y
), centroid
)
180 x1
= region_to_location((max_x
, min_y
), centroid
)
181 x_worldsize
= (x0
- x1
).length
183 #corner Bottom-left to top-left
184 y0
= region_to_location((min_x
, min_y
), centroid
)
185 y1
= region_to_location((min_x
, max_y
), centroid
)
186 y_worldsize
= (y0
- y1
).length
190 lattice_name
= 'lattice_cage_deform'
192 cage
= bpy
.data
.objects
.get(lattice_name
)
194 bpy
.data
.objects
.remove(cage
)
196 lattice
= bpy
.data
.lattices
.get(lattice_name
)
198 bpy
.data
.lattices
.remove(lattice
)
200 # create lattice object
201 lattice
= bpy
.data
.lattices
.new(lattice_name
)
202 cage
= bpy
.data
.objects
.new(lattice_name
, lattice
)
203 cage
.show_in_front
= True
205 ## Master (root) collection
206 bpy
.context
.scene
.collection
.objects
.link(cage
)
208 # spawn cage and align it to view
210 r3d
= bpy
.context
.space_data
.region_3d
211 viewmat
= r3d
.view_matrix
213 cage
.matrix_world
= viewmat
.inverted()
214 cage
.scale
= (x_worldsize
, y_worldsize
, 1)
215 ## Z aligned in view direction (need minus X 90 degree to be aligned FRONT)
216 # cage.rotation_euler.x -= radians(90)
217 # cage.scale = (x_worldsize, 1, y_worldsize)
218 cage
.location
= center
224 lattice
.interpolation_type_u
= lattice_interp
#'KEY_LINEAR'-'KEY_BSPLINE'
225 lattice
.interpolation_type_v
= lattice_interp
226 lattice
.interpolation_type_w
= lattice_interp
228 mod
= obj
.grease_pencil_modifiers
.new('tmp_lattice', 'GP_LATTICE')
232 mods
.append( o
.grease_pencil_modifiers
.new('tmp_lattice', 'GP_LATTICE') )
234 # move to top if modifiers exists
235 for _
in range(len(obj
.grease_pencil_modifiers
)):
236 bpy
.ops
.object.gpencil_modifier_move_up(modifier
='tmp_lattice')
239 for _
in range(len(o
.grease_pencil_modifiers
)):
240 context_override
= {'object': o
}
241 with bpy
.context
.temp_override(**context_override
):
242 bpy
.ops
.object.gpencil_modifier_move_up(modifier
='tmp_lattice')
249 if initial_mode
== 'PAINT_GPENCIL':
250 mod
.layer
= gpl
.active
.info
252 # note : if initial was Paint, changed to Edit
253 # so vertex attribution is valid even for paint
254 if bpy
.context
.mode
== 'EDIT_GPENCIL':
255 mod
.vertex_group
= vg
.name
257 # Go in object mode if not already
258 if bpy
.context
.mode
!= 'OBJECT':
259 bpy
.ops
.object.mode_set(mode
='OBJECT')
261 # Store name of deformed object in case of 'revive modal'
262 cage
.vertex_groups
.new(name
=obj
.name
)
265 cage
.vertex_groups
.new(name
=o
.name
)
267 ## select and make cage active
268 # cage.select_set(True)
269 bpy
.context
.view_layer
.objects
.active
= cage
270 obj
.select_set(False) # deselect GP object
271 bpy
.ops
.object.mode_set(mode
='EDIT') # go in lattice edit mode
272 bpy
.ops
.lattice
.select_all(action
='SELECT') # select all points
274 if prefs
.use_clic_drag
:
275 ## Eventually change tool mode to tweak for direct point editing (reset after before leaving)
276 bpy
.ops
.wm
.tool_set_by_id(name
="builtin.select") # Tweaktoolcode
280 def back_to_obj(obj
, gp_mode
, org_lattice_toolset
, context
):
281 if context
.mode
== 'EDIT_LATTICE' and org_lattice_toolset
: # Tweaktoolcode - restore the active tool used by lattice edit..
282 bpy
.ops
.wm
.tool_set_by_id(name
= org_lattice_toolset
) # Tweaktoolcode
284 # gp object active and selected
285 bpy
.ops
.object.mode_set(mode
='OBJECT')
287 bpy
.context
.view_layer
.objects
.active
= obj
290 def delete_cage(cage
):
292 bpy
.data
.objects
.remove(cage
)
293 bpy
.data
.lattices
.remove(lattice
)
295 def apply_cage(gp_obj
, context
):
296 mod
= gp_obj
.grease_pencil_modifiers
.get('tmp_lattice')
299 if gp_obj
.data
.users
> 1:
301 multi_user
= old
.name
302 other_user
= [o
for o
in bpy
.data
.objects
if o
is not gp_obj
and o
.data
is old
]
303 gp_obj
.data
= gp_obj
.data
.copy()
305 with context
.temp_override(object=gp_obj
):
306 bpy
.ops
.object.gpencil_modifier_apply(apply_as
='DATA', modifier
=mod
.name
)
309 for o
in other_user
: # relink
311 bpy
.data
.grease_pencils
.remove(old
)
312 gp_obj
.data
.name
= multi_user
315 print('tmp_lattice modifier not found to apply...')
317 def cancel_cage(self
):
319 mod
= self
.gp_obj
.grease_pencil_modifiers
.get('tmp_lattice')
321 self
.gp_obj
.grease_pencil_modifiers
.remove(mod
)
323 print(f
'tmp_lattice modifier not found to remove on {self.gp_obj.name}')
325 for ob
in self
.other_gp
:
326 mod
= ob
.grease_pencil_modifiers
.get('tmp_lattice')
328 ob
.grease_pencil_modifiers
.remove(mod
)
330 print(f
'tmp_lattice modifier not found to remove on {ob.name}')
332 delete_cage(self
.cage
)
335 class VIEW3D_OT_gp_box_deform(bpy
.types
.Operator
):
336 bl_idname
= "view3d.gp_box_deform"
337 bl_label
= "Box Deform"
338 bl_description
= "Use lattice for free box transforms on grease pencil points\
340 bl_options
= {"REGISTER", "UNDO"}
343 def poll(cls
, context
):
344 return context
.object is not None and context
.object.type in ('GPENCIL','LATTICE')
349 def modal(self
, context
, event
):
350 display_text
= f
"Deform Cage size: {self.lat.points_u}x{self.lat.points_v} (1-9 or ctrl + ←→↑↓) | \
351 mode (M) : {'Linear' if self.lat.interpolation_type_u == 'KEY_LINEAR' else 'Spline'} | \
352 valid:Spacebar/Enter, cancel:Del/Backspace/Tab/{self.shortcut_ui}"
353 context
.area
.header_text_set(display_text
)
357 if event
.type in {'Z'} and event
.value
== 'PRESS' and event
.ctrl
:
358 ## Disable (capture key)
359 return {"RUNNING_MODAL"}
360 ## Not found how possible to find modal start point in undo stack to
361 # print('ops list', context.window_manager.operators.keys())
362 # if context.window_manager.operators:#can be empty
363 # print('\nlast name', context.window_manager.operators[-1].name)
367 if event
.type in {'TWO', 'THREE', 'FOUR', 'FIVE', 'SIX', 'SEVEN', 'EIGHT', 'NINE', 'ZERO',} and event
.value
== 'PRESS':
368 self
.set_lattice_interp('KEY_BSPLINE')
369 if event
.type in {'DOWN_ARROW', "UP_ARROW", "RIGHT_ARROW", "LEFT_ARROW"} and event
.value
== 'PRESS' and event
.ctrl
:
370 self
.set_lattice_interp('KEY_BSPLINE')
371 if event
.type in {'ONE'} and event
.value
== 'PRESS':
372 self
.set_lattice_interp('KEY_LINEAR')
375 if event
.type in {'H'} and event
.value
== 'PRESS':
376 # self.report({'INFO'}, "Can't hide")
377 return {"RUNNING_MODAL"}
379 if event
.type in {'ONE'} and event
.value
== 'PRESS':# , 'NUMPAD_1'
380 self
.lat
.points_u
= self
.lat
.points_v
= 2
381 return {"RUNNING_MODAL"}
383 if event
.type in {'TWO'} and event
.value
== 'PRESS':# , 'NUMPAD_2'
384 self
.lat
.points_u
= self
.lat
.points_v
= 3
385 return {"RUNNING_MODAL"}
387 if event
.type in {'THREE'} and event
.value
== 'PRESS':# , 'NUMPAD_3'
388 self
.lat
.points_u
= self
.lat
.points_v
= 4
389 return {"RUNNING_MODAL"}
391 if event
.type in {'FOUR'} and event
.value
== 'PRESS':# , 'NUMPAD_4'
392 self
.lat
.points_u
= self
.lat
.points_v
= 5
393 return {"RUNNING_MODAL"}
395 if event
.type in {'FIVE'} and event
.value
== 'PRESS':# , 'NUMPAD_5'
396 self
.lat
.points_u
= self
.lat
.points_v
= 6
397 return {"RUNNING_MODAL"}
399 if event
.type in {'SIX'} and event
.value
== 'PRESS':# , 'NUMPAD_6'
400 self
.lat
.points_u
= self
.lat
.points_v
= 7
401 return {"RUNNING_MODAL"}
403 if event
.type in {'SEVEN'} and event
.value
== 'PRESS':# , 'NUMPAD_7'
404 self
.lat
.points_u
= self
.lat
.points_v
= 8
405 return {"RUNNING_MODAL"}
407 if event
.type in {'EIGHT'} and event
.value
== 'PRESS':# , 'NUMPAD_8'
408 self
.lat
.points_u
= self
.lat
.points_v
= 9
409 return {"RUNNING_MODAL"}
411 if event
.type in {'NINE'} and event
.value
== 'PRESS':# , 'NUMPAD_9'
412 self
.lat
.points_u
= self
.lat
.points_v
= 10
413 return {"RUNNING_MODAL"}
415 if event
.type in {'ZERO'} and event
.value
== 'PRESS':# , 'NUMPAD_0'
416 self
.lat
.points_u
= 2
417 self
.lat
.points_v
= 1
418 return {"RUNNING_MODAL"}
420 if event
.type in {'RIGHT_ARROW'} and event
.value
== 'PRESS' and event
.ctrl
:
421 if self
.lat
.points_u
< 20:
422 self
.lat
.points_u
+= 1
423 return {"RUNNING_MODAL"}
425 if event
.type in {'LEFT_ARROW'} and event
.value
== 'PRESS' and event
.ctrl
:
426 if self
.lat
.points_u
> 1:
427 self
.lat
.points_u
-= 1
428 return {"RUNNING_MODAL"}
430 if event
.type in {'UP_ARROW'} and event
.value
== 'PRESS' and event
.ctrl
:
431 if self
.lat
.points_v
< 20:
432 self
.lat
.points_v
+= 1
433 return {"RUNNING_MODAL"}
435 if event
.type in {'DOWN_ARROW'} and event
.value
== 'PRESS' and event
.ctrl
:
436 if self
.lat
.points_v
> 1:
437 self
.lat
.points_v
-= 1
438 return {"RUNNING_MODAL"}
442 if event
.type in {'M'} and event
.value
== 'PRESS':
443 self
.auto_interp
= False
444 interp
= 'KEY_BSPLINE' if self
.lat
.interpolation_type_u
== 'KEY_LINEAR' else 'KEY_LINEAR'
445 self
.set_lattice_interp(interp
)
446 return {"RUNNING_MODAL"}
449 if event
.type in {'RET', 'SPACE', 'NUMPAD_ENTER'}:
450 if event
.value
== 'PRESS':
451 context
.window_manager
.boxdeform_running
= False
452 self
.restore_prefs(context
)
453 back_to_obj(self
.gp_obj
, self
.gp_mode
, self
.org_lattice_toolset
, context
)
455 # Let the cage as is with a unique ID
456 store_cage(self
, 'lattice_cage_deform_group')
458 apply_cage(self
.gp_obj
, context
) # must be in object mode
459 assign_vg(self
.gp_obj
, 'lattice_cage_deform_group', delete
=True)
460 for o
in self
.other_gp
:
461 apply_cage(o
, context
)
462 assign_vg(o
, 'lattice_cage_deform_group', delete
=True)
463 delete_cage(self
.cage
)
465 # back to original mode
466 if self
.gp_mode
!= 'OBJECT':
467 bpy
.ops
.object.mode_set(mode
=self
.gp_mode
)
468 context
.area
.header_text_set(None) # Reset header
473 # One Warning for Tab cancellation.
474 if event
.type == 'TAB' and event
.value
== 'PRESS':
475 self
.tab_press_ct
+= 1
476 if self
.tab_press_ct
< 2:
477 self
.report({'WARNING'}, "Pressing TAB again will Cancel")
478 return {"RUNNING_MODAL"}
481 if all(getattr(event
, k
) == v
for k
,v
in self
.shortcut_d
.items()):
482 # Cancel when retyped same shortcut
486 if event
.type in {'DEL', 'BACK_SPACE'} or self
.tab_press_ct
>= 2:#'ESC',
490 return {'PASS_THROUGH'}
492 def set_lattice_interp(self
, interp
):
493 self
.lat
.interpolation_type_u
= self
.lat
.interpolation_type_v
= self
.lat
.interpolation_type_w
= interp
495 def cancel(self
, context
):
496 context
.window_manager
.boxdeform_running
= False
497 self
.restore_prefs(context
)
498 back_to_obj(self
.gp_obj
, self
.gp_mode
, self
.org_lattice_toolset
, context
)
500 assign_vg(self
.gp_obj
, 'lattice_cage_deform_group', delete
=True)
501 context
.area
.header_text_set(None)
502 if self
.gp_mode
!= 'OBJECT':
503 bpy
.ops
.object.mode_set(mode
=self
.gp_mode
)
505 def store_prefs(self
, context
):
506 # store_valierables <-< preferences
507 self
.use_drag_immediately
= context
.preferences
.inputs
.use_drag_immediately
508 self
.drag_threshold_mouse
= context
.preferences
.inputs
.drag_threshold_mouse
509 self
.drag_threshold_tablet
= context
.preferences
.inputs
.drag_threshold_tablet
510 self
.use_overlays
= context
.space_data
.overlay
.show_overlays
511 # maybe store in windows manager to keep around in case of modal revival ?
513 def restore_prefs(self
, context
):
514 # preferences <-< store_valierables
515 context
.preferences
.inputs
.use_drag_immediately
= self
.use_drag_immediately
516 context
.preferences
.inputs
.drag_threshold_mouse
= self
.drag_threshold_mouse
517 context
.preferences
.inputs
.drag_threshold_tablet
= self
.drag_threshold_tablet
518 context
.space_data
.overlay
.show_overlays
= self
.use_overlays
520 def set_prefs(self
, context
):
521 context
.preferences
.inputs
.use_drag_immediately
= True
522 context
.preferences
.inputs
.drag_threshold_mouse
= 1
523 context
.preferences
.inputs
.drag_threshold_tablet
= 3
524 context
.space_data
.overlay
.show_overlays
= True
526 def invoke(self
, context
, event
):
527 ## Store cancel shortcut
528 if event
.type not in ('LEFTMOUSE', 'RIGHTMOUSE') and event
.value
== 'PRESS':
529 self
.shortcut_d
= {'type': event
.type, 'value': event
.value
, 'ctrl': event
.ctrl
,
530 'shift': event
.shift
, 'alt': event
.alt
, 'oskey': event
.oskey
}
532 self
.shortcut_d
= {'type': 'T', 'value': 'PRESS', 'ctrl': True,
533 'shift': False, 'alt': False, 'oskey': False}
534 self
.shortcut_ui
= '+'.join([k
.title() for k
,v
in self
.shortcut_d
.items() if v
is True] + [self
.shortcut_d
['type']])
536 ## Restrict to 3D view
537 if context
.area
.type != 'VIEW_3D':
538 self
.report({'WARNING'}, "View3D not found, cannot run operator")
541 if not context
.object:#do it in poll ?
542 self
.report({'ERROR'}, "No active objects found")
545 if context
.window_manager
.boxdeform_running
:
548 self
.prefs
= get_addon_prefs()#get_prefs
549 self
.auto_interp
= self
.prefs
.auto_swap_deform_type
550 self
.org_lattice_toolset
= None
552 if self
.prefs
.use_clic_drag
:#Store the active tool since we will change it
553 self
.org_lattice_toolset
= bpy
.context
.workspace
.tools
.from_space_view3d_mode(bpy
.context
.mode
, create
=False).idname
# Tweaktoolcode
555 #store (scene properties needed in case of ctrlZ revival)
556 self
.store_prefs(context
)
557 self
.gp_mode
= 'EDIT_GPENCIL'
559 # --- special Case of lattice revive modal, just after ctrl+Z back into lattice with modal stopped
560 if context
.mode
== 'EDIT_LATTICE' and context
.object.name
== 'lattice_cage_deform' and len(context
.object.vertex_groups
):
561 self
.gp_obj
= context
.scene
.objects
.get(context
.object.vertex_groups
[0].name
)
563 self
.report({'ERROR'}, "/!\\ Box Deform : Cannot find object to target")
565 if not self
.gp_obj
.grease_pencil_modifiers
.get('tmp_lattice'):
566 self
.report({'ERROR'}, "/!\\ No 'tmp_lattice' modifiers on GP object")
568 self
.cage
= context
.object
569 self
.lat
= self
.cage
.data
570 self
.set_prefs(context
)
572 if self
.prefs
.use_clic_drag
:
573 bpy
.ops
.wm
.tool_set_by_id(name
="builtin.select")
574 context
.window_manager
.boxdeform_running
= True
575 context
.window_manager
.modal_handler_add(self
)
576 return {'RUNNING_MODAL'}
578 if context
.object.type != 'GPENCIL':
579 # self.report({'ERROR'}, "Works only on gpencil objects")
583 if context
.mode
not in ('EDIT_GPENCIL', 'OBJECT', 'PAINT_GPENCIL'):
584 # self.report({'WARNING'}, "Works only in following GPencil modes: object / edit/ paint")# ERROR
589 # bpy.ops.ed.undo_push(message="Box deform step")#don't work as expected (+ might be obsolete)
590 # https://developer.blender.org/D6147 <- undo forget
592 self
.gp_obj
= context
.object
594 self
.from_object
= context
.mode
== 'OBJECT'
595 self
.all_gps
= self
.other_gp
= []
597 self
.all_gps
= [o
for o
in bpy
.context
.selected_objects
if o
.type == 'GPENCIL']
598 self
.other_gp
= [o
for o
in self
.all_gps
if o
is not self
.gp_obj
]
600 # Clean potential failed previous job (delete tmp lattice)
601 mod
= self
.gp_obj
.grease_pencil_modifiers
.get('tmp_lattice')
603 print('Deleted remaining lattice modifiers')
604 self
.gp_obj
.grease_pencil_modifiers
.remove(mod
)
606 phantom_obj
= context
.scene
.objects
.get('lattice_cage_deform')
608 print('Deleted remaining lattice object')
609 delete_cage(phantom_obj
)
611 if bpy
.app
.version
< (2,93,0):
612 if [m
for m
in self
.gp_obj
.grease_pencil_modifiers
if m
.type == 'GP_LATTICE']:
613 self
.report({'ERROR'}, "Grease pencil object already has a lattice modifier (multi-lattices are enabled in blender 2.93+)")
616 self
.gp_mode
= context
.mode
# store mode for restore
618 # All good, create lattice and start modal
620 # Create lattice (and switch to lattice edit) ----
621 self
.cage
= view_cage(self
.gp_obj
)
622 if isinstance(self
.cage
, str):#error, cage not created, display error
623 self
.report({'ERROR'}, self
.cage
)
626 self
.lat
= self
.cage
.data
628 self
.set_prefs(context
)
629 context
.window_manager
.boxdeform_running
= True
630 context
.window_manager
.modal_handler_add(self
)
631 return {'RUNNING_MODAL'}
636 def register_keymaps():
637 kc
= bpy
.context
.window_manager
.keyconfigs
.addon
641 km
= kc
.keymaps
.new(name
= "Grease Pencil", space_type
= "EMPTY", region_type
='WINDOW')
642 kmi
= km
.keymap_items
.new("view3d.gp_box_deform", type ='T', value
= "PRESS", ctrl
= True)
644 addon_keymaps
.append((km
, kmi
))
646 def unregister_keymaps():
647 for km
, kmi
in addon_keymaps
:
648 km
.keymap_items
.remove(kmi
)
649 addon_keymaps
.clear()
654 if bpy
.app
.background
:
656 bpy
.types
.WindowManager
.boxdeform_running
= bpy
.props
.BoolProperty(default
=False)
657 bpy
.utils
.register_class(VIEW3D_OT_gp_box_deform
)
661 if bpy
.app
.background
:
664 bpy
.utils
.unregister_class(VIEW3D_OT_gp_box_deform
)
665 wm
= bpy
.context
.window_manager
666 p
= 'boxdeform_running'