Skip to content

Commit 03979fa

Browse files
committed
First commit
1 parent c50bd04 commit 03979fa

8 files changed

Lines changed: 644 additions & 0 deletions

File tree

‎__init__.py‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
bl_info = {
2+
"name": "Editorial GP",
3+
"author": "Atlas / OpenAI",
4+
"version": (1, 0, 0),
5+
"blender": (5, 0, 1),
6+
"location": "View3D > Sidebar > Editorial GP",
7+
"description": "Simple page-based editorial workspace for Grease Pencil projects",
8+
"category": "Grease Pencil",
9+
}
10+
11+
import bpy
12+
from bpy.props import PointerProperty
13+
14+
from . import properties, operators_document, operators_pages, guides, panels
15+
16+
MODULES = (properties, guides, operators_document, operators_pages, panels)
17+
18+
19+
def register():
20+
for module in MODULES:
21+
module.register()
22+
bpy.types.Scene.editorial_gp = PointerProperty(type=properties.EDITORIALGP_PG_settings)
23+
24+
25+
def unregister():
26+
if hasattr(bpy.types.Scene, "editorial_gp"):
27+
del bpy.types.Scene.editorial_gp
28+
for module in reversed(MODULES):
29+
module.unregister()

‎constants.py‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
ROOT_COLLECTION = "EDITORIAL_DOCUMENT"
2+
GUIDES_COLLECTION = "EDITORIAL_GUIDES"
3+
PAGE_PREFIX = "PAGE_"
4+
GUIDE_PREFIX = "EGP_GUIDE_"
5+
MM_TO_M = 0.001
6+
7+
PAGE_PRESETS_MM = {
8+
"A4": (210.0, 297.0),
9+
"A5": (148.0, 210.0),
10+
}

‎guides.py‎

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import bpy
2+
from mathutils import Vector
3+
from .constants import GUIDE_PREFIX, MM_TO_M
4+
from . import page_manager
5+
6+
7+
def _make_polyline(name, points, collection, cyclic=False):
8+
curve = bpy.data.curves.new(name, type='CURVE')
9+
curve.dimensions = '3D'
10+
curve.resolution_u = 1
11+
curve.bevel_depth = 0.00018
12+
curve.bevel_resolution = 0
13+
spline = curve.splines.new('POLY')
14+
spline.points.add(len(points) - 1)
15+
for point, coordinate in zip(spline.points, points):
16+
point.co = (*coordinate, 1.0)
17+
spline.use_cyclic_u = cyclic
18+
obj = bpy.data.objects.new(name, curve)
19+
obj.display_type = 'WIRE'
20+
obj.show_in_front = True
21+
obj.hide_render = True
22+
obj["editorial_gp_guide"] = True
23+
collection.objects.link(obj)
24+
return obj
25+
26+
27+
def _rectangle_points(width_m, height_m, inset_m=0.0, z=0.0):
28+
half_w = max(width_m / 2.0 - inset_m, 0.0)
29+
half_h = max(height_m / 2.0 - inset_m, 0.0)
30+
return [
31+
(-half_w, -half_h, z),
32+
(half_w, -half_h, z),
33+
(half_w, half_h, z),
34+
(-half_w, half_h, z),
35+
]
36+
37+
38+
def clear_guides(scene):
39+
collection = page_manager.ensure_guides_collection(scene)
40+
for obj in list(collection.objects):
41+
if obj.get("editorial_gp_guide"):
42+
bpy.data.objects.remove(obj, do_unlink=True)
43+
44+
45+
def rebuild_guides(scene):
46+
s = scene.editorial_gp
47+
collection = page_manager.ensure_guides_collection(scene)
48+
clear_guides(scene)
49+
width_mm, height_mm = page_manager.page_dimensions_mm(scene)
50+
width_m, height_m = width_mm * MM_TO_M, height_mm * MM_TO_M
51+
52+
if s.show_bleed and s.bleed_mm > 0:
53+
bleed = s.bleed_mm * MM_TO_M
54+
_make_polyline(GUIDE_PREFIX + "BLEED", _rectangle_points(width_m + 2 * bleed, height_m + 2 * bleed, z=-0.004), collection, True)
55+
if s.show_page_edge:
56+
_make_polyline(GUIDE_PREFIX + "PAGE", _rectangle_points(width_m, height_m, z=-0.003), collection, True)
57+
if s.show_margin and s.margin_mm > 0:
58+
_make_polyline(GUIDE_PREFIX + "MARGIN", _rectangle_points(width_m, height_m, s.margin_mm * MM_TO_M, -0.002), collection, True)
59+
if s.show_safe_area and s.safe_area_mm > 0:
60+
inset = (s.margin_mm + s.safe_area_mm) * MM_TO_M
61+
_make_polyline(GUIDE_PREFIX + "SAFE", _rectangle_points(width_m, height_m, inset, -0.001), collection, True)
62+
if s.show_center_guides:
63+
half_w, half_h = width_m / 2.0, height_m / 2.0
64+
_make_polyline(GUIDE_PREFIX + "CENTER_H", [(-half_w, 0.0, 0.0), (half_w, 0.0, 0.0)], collection)
65+
_make_polyline(GUIDE_PREFIX + "CENTER_V", [(0.0, -half_h, 0.0), (0.0, half_h, 0.0)], collection)
66+
s.guides_dirty = False
67+
68+
69+
class EDITORIALGP_OT_rebuild_guides(bpy.types.Operator):
70+
bl_idname = "editorial_gp.rebuild_guides"
71+
bl_label = "Aggiorna guide"
72+
bl_options = {'REGISTER', 'UNDO'}
73+
74+
def execute(self, context):
75+
rebuild_guides(context.scene)
76+
self.report({'INFO'}, "Guide aggiornate")
77+
return {'FINISHED'}
78+
79+
80+
class EDITORIALGP_OT_frame_page(bpy.types.Operator):
81+
bl_idname = "editorial_gp.frame_page"
82+
bl_label = "Inquadra pagina"
83+
84+
def execute(self, context):
85+
if context.area.type != 'VIEW_3D':
86+
self.report({'WARNING'}, "Comando disponibile nella Viewport 3D")
87+
return {'CANCELLED'}
88+
width_mm, height_mm = page_manager.page_dimensions_mm(context.scene)
89+
region_3d = context.space_data.region_3d
90+
region_3d.view_perspective = 'ORTHO'
91+
region_3d.view_rotation = (Vector((0.0, 0.0, 1.0))).to_track_quat('Z', 'Y')
92+
region_3d.view_location = Vector((0.0, 0.0, 0.0))
93+
region_3d.view_distance = max(width_mm, height_mm) * MM_TO_M * 0.65
94+
return {'FINISHED'}
95+
96+
97+
classes = (EDITORIALGP_OT_rebuild_guides, EDITORIALGP_OT_frame_page)
98+
99+
100+
def register():
101+
for cls in classes:
102+
bpy.utils.register_class(cls)
103+
104+
105+
def unregister():
106+
for cls in reversed(classes):
107+
bpy.utils.unregister_class(cls)

‎operators_document.py‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import bpy
2+
from bpy.props import IntProperty
3+
from . import page_manager
4+
from .guides import rebuild_guides
5+
6+
7+
class EDITORIALGP_OT_create_document(bpy.types.Operator):
8+
bl_idname = "editorial_gp.create_document"
9+
bl_label = "Crea documento editoriale"
10+
bl_options = {'REGISTER', 'UNDO'}
11+
12+
page_count: IntProperty(name="Numero pagine", default=4, min=1, max=100)
13+
14+
def invoke(self, context, event):
15+
return context.window_manager.invoke_props_dialog(self)
16+
17+
def execute(self, context):
18+
scene = context.scene
19+
existing = page_manager.page_collections(scene)
20+
if existing:
21+
self.report({'ERROR'}, "Esiste già un documento editoriale nella scena")
22+
return {'CANCELLED'}
23+
page_manager.ensure_root(scene)
24+
page_manager.ensure_guides_collection(scene)
25+
for _ in range(self.page_count):
26+
page_manager.create_page(scene)
27+
scene.editorial_gp.initialized = True
28+
scene.editorial_gp.current_page_index = 0
29+
page_manager.set_active_page(scene, 0)
30+
rebuild_guides(scene)
31+
self.report({'INFO'}, f"Documento creato con {self.page_count} pagine")
32+
return {'FINISHED'}
33+
34+
35+
class EDITORIALGP_OT_apply_document_settings(bpy.types.Operator):
36+
bl_idname = "editorial_gp.apply_document_settings"
37+
bl_label = "Applica formato e guide"
38+
bl_options = {'REGISTER', 'UNDO'}
39+
40+
def execute(self, context):
41+
rebuild_guides(context.scene)
42+
self.report({'INFO'}, "Formato e guide aggiornati")
43+
return {'FINISHED'}
44+
45+
46+
classes = (EDITORIALGP_OT_create_document, EDITORIALGP_OT_apply_document_settings)
47+
48+
49+
def register():
50+
for cls in classes:
51+
bpy.utils.register_class(cls)
52+
53+
54+
def unregister():
55+
for cls in reversed(classes):
56+
bpy.utils.unregister_class(cls)

‎operators_pages.py‎

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import bpy
2+
from bpy.props import StringProperty
3+
from . import page_manager
4+
5+
6+
class EDITORIALGP_OT_page_add(bpy.types.Operator):
7+
bl_idname = "editorial_gp.page_add"
8+
bl_label = "Nuova pagina"
9+
bl_options = {'REGISTER', 'UNDO'}
10+
11+
def execute(self, context):
12+
page_manager.create_page(context.scene)
13+
pages = page_manager.page_collections(context.scene)
14+
page_manager.set_active_page(context.scene, len(pages) - 1)
15+
return {'FINISHED'}
16+
17+
18+
class EDITORIALGP_OT_page_duplicate(bpy.types.Operator):
19+
bl_idname = "editorial_gp.page_duplicate"
20+
bl_label = "Duplica pagina"
21+
bl_options = {'REGISTER', 'UNDO'}
22+
23+
def execute(self, context):
24+
source = page_manager.active_page(context.scene)
25+
if source is None:
26+
return {'CANCELLED'}
27+
page_manager.duplicate_page(context.scene, source)
28+
pages = page_manager.page_collections(context.scene)
29+
page_manager.set_active_page(context.scene, len(pages) - 1)
30+
return {'FINISHED'}
31+
32+
33+
class EDITORIALGP_OT_page_delete(bpy.types.Operator):
34+
bl_idname = "editorial_gp.page_delete"
35+
bl_label = "Elimina pagina"
36+
bl_options = {'REGISTER', 'UNDO'}
37+
38+
@classmethod
39+
def poll(cls, context):
40+
return len(page_manager.page_collections(context.scene)) > 1
41+
42+
def invoke(self, context, event):
43+
return context.window_manager.invoke_confirm(self, event)
44+
45+
def execute(self, context):
46+
scene = context.scene
47+
page = page_manager.active_page(scene)
48+
if page is None:
49+
return {'CANCELLED'}
50+
old_index = scene.editorial_gp.current_page_index
51+
page_manager.remove_collection_recursive(page)
52+
pages = page_manager.page_collections(scene)
53+
page_manager.set_active_page(scene, min(old_index, len(pages) - 1))
54+
return {'FINISHED'}
55+
56+
57+
class EDITORIALGP_OT_page_previous(bpy.types.Operator):
58+
bl_idname = "editorial_gp.page_previous"
59+
bl_label = "Pagina precedente"
60+
61+
def execute(self, context):
62+
s = context.scene.editorial_gp
63+
page_manager.set_active_page(context.scene, s.current_page_index - 1)
64+
return {'FINISHED'}
65+
66+
67+
class EDITORIALGP_OT_page_next(bpy.types.Operator):
68+
bl_idname = "editorial_gp.page_next"
69+
bl_label = "Pagina successiva"
70+
71+
def execute(self, context):
72+
s = context.scene.editorial_gp
73+
page_manager.set_active_page(context.scene, s.current_page_index + 1)
74+
return {'FINISHED'}
75+
76+
77+
class EDITORIALGP_OT_page_move_up(bpy.types.Operator):
78+
bl_idname = "editorial_gp.page_move_up"
79+
bl_label = "Sposta pagina su"
80+
bl_options = {'REGISTER', 'UNDO'}
81+
82+
def execute(self, context):
83+
s = context.scene.editorial_gp
84+
if s.current_page_index <= 0:
85+
return {'CANCELLED'}
86+
page_manager.move_page(context.scene, s.current_page_index, s.current_page_index - 1)
87+
page_manager.set_active_page(context.scene, s.current_page_index)
88+
return {'FINISHED'}
89+
90+
91+
class EDITORIALGP_OT_page_move_down(bpy.types.Operator):
92+
bl_idname = "editorial_gp.page_move_down"
93+
bl_label = "Sposta pagina giù"
94+
bl_options = {'REGISTER', 'UNDO'}
95+
96+
def execute(self, context):
97+
s = context.scene.editorial_gp
98+
pages = page_manager.page_collections(context.scene)
99+
if s.current_page_index >= len(pages) - 1:
100+
return {'CANCELLED'}
101+
page_manager.move_page(context.scene, s.current_page_index, s.current_page_index + 1)
102+
page_manager.set_active_page(context.scene, s.current_page_index)
103+
return {'FINISHED'}
104+
105+
106+
class EDITORIALGP_OT_page_rename(bpy.types.Operator):
107+
bl_idname = "editorial_gp.page_rename"
108+
bl_label = "Rinomina pagina"
109+
bl_options = {'REGISTER', 'UNDO'}
110+
111+
label: StringProperty(name="Nome pagina", default="Pagina")
112+
113+
def invoke(self, context, event):
114+
page = page_manager.active_page(context.scene)
115+
if page:
116+
self.label = page.get("editorial_gp_label", page.name)
117+
return context.window_manager.invoke_props_dialog(self)
118+
119+
def execute(self, context):
120+
page = page_manager.active_page(context.scene)
121+
if page is None:
122+
return {'CANCELLED'}
123+
page["editorial_gp_label"] = self.label.strip() or page.name
124+
return {'FINISHED'}
125+
126+
127+
classes = (
128+
EDITORIALGP_OT_page_add,
129+
EDITORIALGP_OT_page_duplicate,
130+
EDITORIALGP_OT_page_delete,
131+
EDITORIALGP_OT_page_previous,
132+
EDITORIALGP_OT_page_next,
133+
EDITORIALGP_OT_page_move_up,
134+
EDITORIALGP_OT_page_move_down,
135+
EDITORIALGP_OT_page_rename,
136+
)
137+
138+
139+
def register():
140+
for cls in classes:
141+
bpy.utils.register_class(cls)
142+
143+
144+
def unregister():
145+
for cls in reversed(classes):
146+
bpy.utils.unregister_class(cls)

0 commit comments

Comments
 (0)