-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathoperators_styles.py
More file actions
249 lines (208 loc) · 10 KB
/
Copy pathoperators_styles.py
File metadata and controls
249 lines (208 loc) · 10 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
import uuid
import bpy
from . import page_manager
from .operators_story import STORY_FRAME_KEY, STORY_SOURCE_KEY
STYLE_PREFIX = {
'TITLE': 'title',
'SUBTITLE': 'subtitle',
'BODY': 'body',
'CAPTION': 'caption',
}
def _settings(context):
return context.scene.editorial_gp
def _unique_name(settings, base):
names = {item.name for item in settings.text_styles}
if base not in names:
return base
index = 2
while f"{base} {index}" in names:
index += 1
return f"{base} {index}"
def _copy_values(source, target):
target.font = source.font
target.size_mm = source.size_mm
target.line_spacing = source.line_spacing
target.char_spacing = source.char_spacing
target.word_spacing = source.word_spacing
target.shear = source.shear
target.align_x = source.align_x
def _legacy_values(settings, prefix):
return {
'name': getattr(settings, f'{prefix}_style_name'),
'font': getattr(settings, f'{prefix}_font'),
'size_mm': getattr(settings, f'{prefix}_size_mm'),
'line_spacing': getattr(settings, f'{prefix}_line_spacing'),
'char_spacing': getattr(settings, f'{prefix}_char_spacing'),
'word_spacing': getattr(settings, f'{prefix}_word_spacing'),
'shear': getattr(settings, f'{prefix}_shear'),
'align_x': getattr(settings, f'{prefix}_align_x'),
}
def ensure_style_collection(settings):
if len(settings.text_styles):
settings.active_text_style_index = min(settings.active_text_style_index, len(settings.text_styles)-1)
if not settings.default_text_style_uid:
settings.default_text_style_uid = settings.text_styles[min(2, len(settings.text_styles)-1)].uid
return False
for slot, prefix in (('TITLE','title'), ('SUBTITLE','subtitle'), ('BODY','body'), ('CAPTION','caption')):
values = _legacy_values(settings, prefix)
item = settings.text_styles.add()
item.uid = str(uuid.uuid4())
for key, value in values.items():
setattr(item, key, value)
item['legacy_slot'] = slot
settings.active_text_style_index = 2
settings.default_text_style_uid = settings.text_styles[2].uid
return True
def ensure_all_style_collections():
changed = 0
try:
scenes = bpy.data.scenes
except AttributeError:
return 0
for scene in scenes:
settings = getattr(scene, 'editorial_gp', None)
if settings and ensure_style_collection(settings):
changed += 1
return changed
def active_style(settings):
ensure_style_collection(settings)
if not settings.text_styles:
return None
index = max(0, min(settings.active_text_style_index, len(settings.text_styles)-1))
settings.active_text_style_index = index
return settings.text_styles[index]
def _style_values(settings):
style = active_style(settings)
if style is None:
return None
return {
'uid': style.uid,
'name': style.name,
'size': style.size_mm * 0.001,
'space_line': style.line_spacing,
'space_character': style.char_spacing,
'space_word': style.word_spacing,
'shear': style.shear,
'align_x': style.align_x,
'font': style.font,
}
def _apply_to_object(obj, values):
if obj is None or obj.type != 'FONT' or values is None:
return False
curve = obj.data
curve.size = values['size']
curve.space_line = values['space_line']
curve.space_character = values['space_character']
curve.space_word = values['space_word']
curve.shear = values['shear']
try:
curve.align_x = values['align_x']
except (TypeError, ValueError):
curve.align_x = 'LEFT'
if values['font'] is not None:
curve.font = values['font']
obj['greasepress_text_style_uid'] = values['uid']
obj['greasepress_text_style_name'] = values['name']
return True
def _active_text(context):
obj = context.object
return obj if obj is not None and obj.type == 'FONT' else None
class EDITORIALGP_OT_style_add(bpy.types.Operator):
bl_idname = 'editorial_gp.style_add'
bl_label = 'New Style'
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
s = _settings(context); ensure_style_collection(s)
item = s.text_styles.add(); item.uid = str(uuid.uuid4()); item.name = _unique_name(s, 'New Style')
s.active_text_style_index = len(s.text_styles)-1
return {'FINISHED'}
class EDITORIALGP_OT_style_duplicate(bpy.types.Operator):
bl_idname = 'editorial_gp.style_duplicate'
bl_label = 'Duplicate Style'
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
s = _settings(context); source = active_style(s)
if source is None: return {'CANCELLED'}
item = s.text_styles.add(); item.uid = str(uuid.uuid4()); item.name = _unique_name(s, f'{source.name} copia'); _copy_values(source, item)
s.active_text_style_index = len(s.text_styles)-1
return {'FINISHED'}
class EDITORIALGP_OT_style_remove(bpy.types.Operator):
bl_idname = 'editorial_gp.style_remove'
bl_label = 'Delete Style'
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
s = getattr(context.scene, 'editorial_gp', None)
return s is not None and len(s.text_styles) > 1
def invoke(self, context, event):
return context.window_manager.invoke_confirm(self, event)
def execute(self, context):
s = _settings(context); style = active_style(s)
if style is None: return {'CANCELLED'}
used = [o for o in bpy.data.objects if o.type == 'FONT' and o.get('greasepress_text_style_uid') == style.uid]
if used:
self.report({'ERROR'}, f'Style used by {len(used)} objects: apply another style first')
return {'CANCELLED'}
removing_default = s.default_text_style_uid == style.uid
index = s.active_text_style_index
s.text_styles.remove(index)
s.active_text_style_index = min(index, len(s.text_styles)-1)
if removing_default:
s.default_text_style_uid = s.text_styles[s.active_text_style_index].uid
return {'FINISHED'}
class EDITORIALGP_OT_style_move(bpy.types.Operator):
bl_idname = 'editorial_gp.style_move'
bl_label = 'Reorder Style'
direction: bpy.props.EnumProperty(items=(('UP','Up',''),('DOWN','Down','')))
def execute(self, context):
s = _settings(context); ensure_style_collection(s); i=s.active_text_style_index
j = i-1 if self.direction=='UP' else i+1
if 0 <= j < len(s.text_styles):
s.text_styles.move(i,j); s.active_text_style_index=j
return {'FINISHED'}
class EDITORIALGP_OT_style_set_default(bpy.types.Operator):
bl_idname = 'editorial_gp.style_set_default'
bl_label = 'Set as Default'
def execute(self, context):
s=_settings(context); style=active_style(s)
if style: s.default_text_style_uid=style.uid
return {'FINISHED'}
class EDITORIALGP_OT_style_apply_selected(bpy.types.Operator):
bl_idname='editorial_gp.style_apply_selected'; bl_label='Apply to Selected Text'; bl_options={'REGISTER','UNDO'}
@classmethod
def poll(cls, context): return any(o.type=='FONT' for o in context.selected_objects)
def execute(self, context):
values=_style_values(_settings(context)); count=sum(1 for o in context.selected_objects if _apply_to_object(o,values)); self.report({'INFO'},f'Style applied to {count} Text objects'); return {'FINISHED'}
class EDITORIALGP_OT_style_apply_page(bpy.types.Operator):
bl_idname='editorial_gp.style_apply_page'; bl_label='Apply to All Page Texts'; bl_options={'REGISTER','UNDO'}
def execute(self, context):
page=page_manager.active_page(context.scene)
if page is None: self.report({'ERROR'},'Create a GreasePress document first'); return {'CANCELLED'}
values=_style_values(_settings(context)); count=sum(1 for o in page.objects if _apply_to_object(o,values)); self.report({'INFO'},f'Style applied to {count} page texts'); return {'FINISHED'}
class EDITORIALGP_OT_style_apply_story(bpy.types.Operator):
bl_idname='editorial_gp.style_apply_story'; bl_label='Apply to Multi-page Story'; bl_options={'REGISTER','UNDO'}
def execute(self, context):
s=_settings(context); obj=_active_text(context); source=''
if obj and obj.get(STORY_FRAME_KEY): source=obj.get(STORY_SOURCE_KEY,'')
if not source: source=s.story_source_name
if not source: self.report({'ERROR'},'Select a multi-page story'); return {'CANCELLED'}
values=_style_values(s); count=0
for candidate in bpy.data.objects:
if candidate.type=='FONT' and candidate.get(STORY_FRAME_KEY) and candidate.get(STORY_SOURCE_KEY)==source: count += int(_apply_to_object(candidate,values))
self.report({'INFO'},f'Style applied to {count} story frames'); return {'FINISHED'}
class EDITORIALGP_OT_style_capture_selected(bpy.types.Operator):
bl_idname='editorial_gp.style_capture_selected'; bl_label='Capture from Selected Text'; bl_options={'REGISTER','UNDO'}
@classmethod
def poll(cls, context): return _active_text(context) is not None
def execute(self, context):
obj=_active_text(context); curve=obj.data; style=active_style(_settings(context))
style.size_mm=curve.size*1000.0; style.line_spacing=curve.space_line; style.char_spacing=curve.space_character; style.word_spacing=curve.space_word; style.shear=curve.shear
try: style.align_x=curve.align_x
except (TypeError,ValueError): pass
if curve.font is not None: style.font=curve.font
self.report({'INFO'},'Style updated from selected text'); return {'FINISHED'}
classes=(EDITORIALGP_OT_style_add,EDITORIALGP_OT_style_duplicate,EDITORIALGP_OT_style_remove,EDITORIALGP_OT_style_move,EDITORIALGP_OT_style_set_default,EDITORIALGP_OT_style_apply_selected,EDITORIALGP_OT_style_apply_page,EDITORIALGP_OT_style_apply_story,EDITORIALGP_OT_style_capture_selected)
def register():
for cls in classes: bpy.utils.register_class(cls)
def unregister():
for cls in reversed(classes): bpy.utils.unregister_class(cls)