@volter/editor-blender 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/LICENSE +1409 -0
  2. package/README.md +17 -0
  3. package/contributions/blender-header-menus.tsx +483 -0
  4. package/contributions/blender-icon-trace.mjs +403 -0
  5. package/contributions/blender-icons.source.mjs +2925 -0
  6. package/contributions/blender-node-editor.document.tsx +1402 -0
  7. package/contributions/blender-node-geometry.ts +1138 -0
  8. package/contributions/blender-node-panels.source.mjs +485 -0
  9. package/contributions/blender-outliner-authoring.ts +1729 -0
  10. package/contributions/blender-outliner-model.ts +389 -0
  11. package/contributions/blender-palette.source.mjs +319 -0
  12. package/contributions/blender-properties-model.ts +351 -0
  13. package/contributions/blender-properties-tab.tsx +100 -0
  14. package/contributions/blender-properties-view.tsx +1191 -0
  15. package/contributions/blender-runtime-skin.ts +619 -0
  16. package/contributions/blender-runtime.document.tsx +232 -0
  17. package/contributions/blender-timeline-geometry.ts +323 -0
  18. package/contributions/blender-timeline.document.tsx +1056 -0
  19. package/contributions/blender-uv-editor.document.tsx +483 -0
  20. package/contributions/blender-uv-geometry.ts +305 -0
  21. package/contributions/blender-version.status.tsx +93 -0
  22. package/contributions/blender.command.ts +102 -0
  23. package/contributions/blender.icons.json +1247 -0
  24. package/contributions/blender.icons.traced.json +1561 -0
  25. package/contributions/blender.keymap.ts +39 -0
  26. package/contributions/blender.node-panels.json +2436 -0
  27. package/contributions/blender.palette.json +93 -0
  28. package/contributions/blender.status.tsx +263 -0
  29. package/contributions/blender.style.ts +271 -0
  30. package/contributions/model.layout.ts +53 -0
  31. package/contributions/models.finder.ts +59 -0
  32. package/contributions/properties-bone-constraints.inspector.tsx +50 -0
  33. package/contributions/properties-bone.inspector.tsx +184 -0
  34. package/contributions/properties-collection.inspector.tsx +96 -0
  35. package/contributions/properties-constraints.inspector.tsx +69 -0
  36. package/contributions/properties-data.inspector.tsx +229 -0
  37. package/contributions/properties-material.inspector.tsx +121 -0
  38. package/contributions/properties-modifiers.inspector.tsx +74 -0
  39. package/contributions/properties-object.inspector.tsx +215 -0
  40. package/contributions/properties-output.inspector.tsx +210 -0
  41. package/contributions/properties-particles.inspector.tsx +494 -0
  42. package/contributions/properties-physics.inspector.tsx +614 -0
  43. package/contributions/properties-render.inspector.tsx +446 -0
  44. package/contributions/properties-scene.inspector.tsx +174 -0
  45. package/contributions/properties-texture.inspector.tsx +300 -0
  46. package/contributions/properties-view-layer.inspector.tsx +145 -0
  47. package/contributions/properties-world.inspector.tsx +130 -0
  48. package/contributions/sculpt.layout.ts +25 -0
  49. package/contributions/shading.layout.ts +99 -0
  50. package/contributions/texture.layout.ts +16 -0
  51. package/contributions/uv-editing.layout.ts +93 -0
  52. package/host/blender-runtime-host.ts +1256 -0
  53. package/package.json +77 -0
  54. package/src/layouts.tsx +48 -0
  55. package/src/looks.ts +14 -0
  56. package/src/node-view-state.ts +125 -0
  57. package/src/timeline-view-state.ts +154 -0
  58. package/src/uv-view-state.ts +125 -0
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The MODEL workspace — Blender-shaped modeling: a dominant document, outliner
3
+ * above properties (`@volter/editor-sdk/looks`, a `workspace.layout`
4
+ * contribution). Its arrangement carries Blender 5.2's own MODELING-workspace
5
+ * proportions; `../src/layouts.tsx` states the measurement and the host's
6
+ * fixed-pane clamp that bounds it.
7
+ */
8
+
9
+ import type { WorkspaceLayoutContribution } from '@volter/editor-sdk/looks';
10
+
11
+ export const point = 'workspace.layout';
12
+ export const layout: WorkspaceLayoutContribution = {
13
+ id: 'model',
14
+ title: 'Model',
15
+ description: 'Blender-shaped modeling: a dominant document, outliner above properties.',
16
+ // No `tabs` here ON PURPOSE. The host's default is already `shown`, so
17
+ // stating it changed nothing for a bare host — but the WORKSPACE layer wins
18
+ // over the style bundle (`workspace-regions.ts`), so restating the default
19
+ // silently shadowed `blender.style.ts`'s `tabs: 'hidden'` and the Model
20
+ // document still painted a 25px tab row (measured against Blender 5.2, which
21
+ // has none). Leave a region unstated unless this workspace genuinely needs
22
+ // it different from the host default.
23
+ regions: {
24
+ header: 'shown',
25
+ shelf: 'shown',
26
+ inspector: 'properties',
27
+ drawer: 'hidden',
28
+ },
29
+ // THE TIMELINE IS THE BOTTOM AREA (owner, 2026-09-20: "doesn't blender have
30
+ // a timeline at the bottom?"). It does — and the workspace this Model
31
+ // document stands in is Blender's LAYOUT screen for us, not its Modeling
32
+ // one: Layout is the four-area screen with the Timeline, and Modeling is the
33
+ // same screen WITHOUT it (measured below).
34
+ //
35
+ // THE PROPORTION IS BLENDER'S OWN, measured through bpy on the installed
36
+ // Blender 5.2.1 at factory settings in a 1920-wide window
37
+ // (`bpy.data.workspaces['Layout'].screens[0].areas`):
38
+ //
39
+ // PROPERTIES x1579 y23 w339 h839
40
+ // OUTLINER x1579 y865 w339 h189
41
+ // DOPESHEET_EDITOR x2 y23 w1574 h74 ui_type TIMELINE ← this area
42
+ // VIEW_3D x2 y100 w1574 h954
43
+ //
44
+ // and the same screen at a 1600×929 window gives the Timeline 63 of an
45
+ // 880-px centre column. Both are the same ratio to three places: 74/1029 =
46
+ // 0.0719 and 63/880 = 0.0716. The Modeling screen's own reading, for the
47
+ // contrast: VIEW_3D `x2 y23 w1574 h1031` and no DOPESHEET area at all.
48
+ //
49
+ // It is stated to four places because it is a measurement, not a preference;
50
+ // the dock uses it once, to size the group the first time this workspace
51
+ // stands up, and a capture of the settled grid wins afterwards.
52
+ areas: [{ id: 'timeline', document: 'blender-timeline.document', place: 'below', ratio: 0.0719 }],
53
+ };
@@ -0,0 +1,59 @@
1
+ /**
2
+ * The `modelsFromBlendFiles` FINDER (ARCHITECTURE-CORE §The project model,
3
+ * "A model is Blender data, and a prefab is not a model"): every `.blend`
4
+ * under the selection's `include` globs is a `model` document — a Blender
5
+ * file the engine in the tab opens, edits and saves. The bpy scripts that
6
+ * authored it are ordinary project files beside it (`src/models/<name>.py`);
7
+ * this document does not run them, a person or an agent does, through the
8
+ * session's Blender (`vgai blender-mcp`).
9
+ *
10
+ * It replaced `modelsFromMeshModules`, which listed every module exporting
11
+ * `build(): THREE.BufferGeometry` (owner ruling 2026-09-19, "do the delete":
12
+ * the TypeScript mesh kit was the modeling engine while Blender could not run
13
+ * in the tab; it now does, and a model is Blender data).
14
+ *
15
+ * Select it in `vgai.adapter.ts`:
16
+ *
17
+ * documents: { find: [{ finder: 'modelsFromBlendFiles', include: ['src/models/**\/*.blend'] }] }
18
+ *
19
+ * This module is a plain object the HOST registers; it mounts no UI. It reads
20
+ * the project only through `input.files` — the PATHS matching those globs.
21
+ * Deliberately not `input.sources`: a `.blend` is binary, there is nothing in
22
+ * it a text walk could honestly read, and the entry is the file itself.
23
+ */
24
+
25
+ import { z } from 'zod';
26
+
27
+ export const finder = {
28
+ name: 'modelsFromBlendFiles',
29
+ schema: z
30
+ .object({
31
+ finder: z.literal('modelsFromBlendFiles'),
32
+ include: z
33
+ .array(z.string().min(1))
34
+ .min(1)
35
+ .describe('Project-relative globs of the Blender files to list (`src/models/**/*.blend`)'),
36
+ })
37
+ .strict(),
38
+ run(
39
+ _selection: { finder: 'modelsFromBlendFiles'; include: readonly string[] },
40
+ input: { files?: readonly string[] },
41
+ ) {
42
+ const entries = [];
43
+ const notes: string[] = [];
44
+ for (const path of input.files ?? []) {
45
+ if (!path.endsWith('.blend')) continue;
46
+ const name = (path.split('/').pop() ?? path).replace(/\.blend$/, '');
47
+ entries.push({
48
+ id: `model:${path}`,
49
+ label: name,
50
+ kind: 'model',
51
+ region: null,
52
+ authorable: true,
53
+ reach: { kind: 'root-mount' as const },
54
+ source: { path },
55
+ });
56
+ }
57
+ return { entries, notes };
58
+ },
59
+ } as const;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * BLENDER'S BONE CONSTRAINTS TAB, as a Properties section (WORK.md §Blender in
3
+ * the tab is Blender, "Inspection parity", I2): the active POSE BONE's
4
+ * constraint stack, with the generic view beneath under "All properties".
5
+ *
6
+ * It is the Object Constraints tab over a different datablock, and Blender
7
+ * says so itself: `properties_constraint.py` registers
8
+ * `BONE_PT_constraints` beside `OBJECT_PT_constraints` with the same
9
+ * `template_constraints()` body, differing only in reading
10
+ * `context.pose_bone.constraints`. So it shares the header list that module
11
+ * exports rather than restating it — a second copy is a second thing to keep
12
+ * in step.
13
+ *
14
+ * It stands when: `buttons_context_path_pose_bone` — a pose channel for the
15
+ * active bone, and never in Edit mode.
16
+ */
17
+ import { blenderPropertiesTabMatch, blenderPropertiesTabSection } from './blender-properties-tab';
18
+ import type { BlenderCuratedPanel } from './blender-properties-view';
19
+ import { CONSTRAINT_HEADER } from './properties-constraints.inspector';
20
+
21
+ const CURATED: readonly BlenderCuratedPanel[] = [
22
+ {
23
+ title: 'Bone Constraints',
24
+ from: 'Constraints',
25
+ collection: true,
26
+ properties: CONSTRAINT_HEADER,
27
+ andDeclared: true,
28
+ },
29
+ ];
30
+
31
+ const TAB = { id: 'bone_constraint', curated: CURATED } as const;
32
+
33
+ export const point = 'selection.inspector';
34
+ export const title = 'Bone Constraints';
35
+ /** `blender.icons.json` — Blender's own mark, TRACED from
36
+ * `release/datafiles/icons_svg/` at the engine's pin (the icon this tab draws
37
+ * is named in `buttons_context_items`, `makesrna/intern/rna_space.cc:579`),
38
+ * and tinted by the group `UI_icons.hh` declares it in. */
39
+ export const icon = 'properties-bone-constraints';
40
+ /** `ED_buttons_tabs_list` (`space_buttons.cc:218-252`) is the rail's order;
41
+ * these are that order, spaced so a tab can be inserted between two. */
42
+ export const order = 140;
43
+ /** THE RAIL'S GROUPS, from `ED_buttons_tabs_list`'s own `add_spacer()` calls
44
+ * (`space_buttons/space_buttons.cc:201-255`): the tool tab, then the scene
45
+ * group (Render, Output, View Layer, Scene, World), then Collection, then the
46
+ * object group (Object … Material), then Texture. The presentation draws a
47
+ * separator wherever this changes. */
48
+ export const railGroup = 'object';
49
+ export const match = blenderPropertiesTabMatch(TAB);
50
+ export default blenderPropertiesTabSection(TAB);
@@ -0,0 +1,184 @@
1
+ /**
2
+ * BLENDER'S BONE TAB, as a Properties section (WORK.md §Blender in the tab is
3
+ * Blender, "Inspection parity", I2). Blender's own panels over the RNA door,
4
+ * with the generic view beneath under "All properties".
5
+ *
6
+ * THE SPECIFICATION IS `scripts/startup/bl_ui/properties_data_bone.py` at the
7
+ * engine's pin: its `classes` tuple is the panel order, `bl_label` the title,
8
+ * each `layout.prop(bone, "x")` an entry. Blender's UI layer is never run,
9
+ * ported or recorded.
10
+ *
11
+ * TWO DATABLOCKS, AND THAT IS THE WHOLE STRUCTURE OF THIS TAB. Blender's panels
12
+ * read `bone` (the armature's `Bone`, or the `EditBone` in Edit mode) for the
13
+ * rest pose — head, tail, roll, envelope, collections, display — and `pchan`
14
+ * (the `PoseBone`) for the POSE: its location/rotation/scale and its inverse
15
+ * kinematics. `buttons_context_path_bone` and `_pose_bone` build both, and the
16
+ * door hands this tab both as "Bone" and "Pose Bone", so each panel names the
17
+ * one Blender's draw function reads. That is why Transform below is split in
18
+ * two the way `BONE_PT_transform` (:39) is: its first half is `pchan`, its
19
+ * second `bone`.
20
+ *
21
+ * It stands when: `buttons_context_path_bone` — an armature with an ACTIVE bone
22
+ * (the edit bone in Edit mode).
23
+ */
24
+ import { blenderPropertiesTabMatch, blenderPropertiesTabSection } from './blender-properties-tab';
25
+ import type { BlenderCuratedPanel } from './blender-properties-view';
26
+
27
+ /** `properties_data_bone.py`, panel by panel. Line numbers are that file's. */
28
+ const CURATED: readonly BlenderCuratedPanel[] = [
29
+ {
30
+ // BONE_PT_transform, :39, pose half (:63-99) — drawn in Pose mode.
31
+ title: 'Transform (Pose)',
32
+ from: 'Pose Bone',
33
+ properties: [
34
+ 'location',
35
+ 'lock_location',
36
+ 'rotation_quaternion',
37
+ 'rotation_axis_angle',
38
+ 'rotation_euler',
39
+ 'lock_rotation_w',
40
+ 'lock_rotation',
41
+ 'rotation_mode',
42
+ 'scale',
43
+ 'lock_scale',
44
+ ],
45
+ },
46
+ {
47
+ // BONE_PT_transform, :39, rest half (:104-110) — the bone's own geometry.
48
+ // NO MATRICES. The first cut named `matrix` and `matrix_local` beside them
49
+ // and the live read showed why Blender does not: a 3x3 and a 4x4 draw as
50
+ // twenty-five numbered rows, which buries the five values the panel is
51
+ // about. They are still in "All properties" beneath, where a matrix is
52
+ // something you go looking for. (`roll` is an EditBone property, so it
53
+ // draws in Edit mode and not in Object mode — the datablock's answer, not
54
+ // a condition of ours.)
55
+ title: 'Transform',
56
+ from: 'Bone',
57
+ properties: ['head', 'tail', 'roll', 'length', 'lock'],
58
+ },
59
+ {
60
+ // BONE_PT_curved, :114 — "Bendy Bones".
61
+ title: 'Bendy Bones',
62
+ closed: true,
63
+ from: 'Bone',
64
+ properties: [
65
+ 'bbone_segments',
66
+ 'bbone_x',
67
+ 'bbone_z',
68
+ 'bbone_mapping_mode',
69
+ 'use_endroll_as_inroll',
70
+ 'use_scale_easing',
71
+ 'bbone_handle_type_start',
72
+ 'bbone_handle_use_scale_start',
73
+ 'bbone_handle_use_ease_start',
74
+ 'bbone_handle_type_end',
75
+ 'bbone_handle_use_scale_end',
76
+ 'bbone_handle_use_ease_end',
77
+ ],
78
+ },
79
+ {
80
+ // BONE_PT_relations, :213.
81
+ title: 'Relations',
82
+ from: 'Bone',
83
+ properties: [
84
+ 'parent',
85
+ 'children',
86
+ 'use_relative_parent',
87
+ 'use_connect',
88
+ 'use_local_location',
89
+ 'use_inherit_rotation',
90
+ 'inherit_scale',
91
+ ],
92
+ },
93
+ {
94
+ // BONE_PT_collections, :252 — the collections this bone is in.
95
+ title: 'Bone Collections',
96
+ closed: true,
97
+ from: 'Bone',
98
+ properties: ['collections'],
99
+ },
100
+ {
101
+ // BONE_PT_inverse_kinematics, :430 — every one of these is on the POSE
102
+ // channel, not the bone.
103
+ title: 'Inverse Kinematics',
104
+ closed: true,
105
+ from: 'Pose Bone',
106
+ properties: [
107
+ 'ik_stretch',
108
+ 'lock_ik_x',
109
+ 'lock_ik_y',
110
+ 'lock_ik_z',
111
+ 'ik_stiffness_x',
112
+ 'ik_stiffness_y',
113
+ 'ik_stiffness_z',
114
+ 'use_ik_limit_x',
115
+ 'ik_min_x',
116
+ 'ik_max_x',
117
+ 'use_ik_limit_y',
118
+ 'ik_min_y',
119
+ 'ik_max_y',
120
+ 'use_ik_limit_z',
121
+ 'ik_min_z',
122
+ 'ik_max_z',
123
+ 'use_ik_rotation_control',
124
+ ],
125
+ },
126
+ {
127
+ // BONE_PT_deform, :535 — `use_deform` is the panel's own header checkbox.
128
+ title: 'Deform',
129
+ closed: true,
130
+ from: 'Bone',
131
+ properties: [
132
+ 'use_deform',
133
+ 'envelope_distance',
134
+ 'envelope_weight',
135
+ 'use_envelope_multiply',
136
+ 'head_radius',
137
+ 'tail_radius',
138
+ ],
139
+ },
140
+ {
141
+ // BONE_PT_display, :304, and BONE_PT_display_custom_shape (:388), whose
142
+ // properties are all on the pose channel.
143
+ title: 'Viewport Display',
144
+ closed: true,
145
+ from: 'Bone',
146
+ properties: ['hide', 'hide_select', 'display_type', 'color', 'show_wire'],
147
+ sub: [
148
+ {
149
+ title: 'Custom Shape',
150
+ closed: true,
151
+ from: 'Pose Bone',
152
+ properties: [
153
+ 'custom_shape',
154
+ 'custom_shape_translation',
155
+ 'custom_shape_rotation_euler',
156
+ 'custom_shape_scale_xyz',
157
+ 'use_custom_shape_bone_size',
158
+ 'custom_shape_transform',
159
+ ],
160
+ },
161
+ ],
162
+ },
163
+ ];
164
+
165
+ const TAB = { id: 'bone', curated: CURATED } as const;
166
+
167
+ export const point = 'selection.inspector';
168
+ export const title = 'Bone';
169
+ /** `blender.icons.json` — Blender's own mark, TRACED from
170
+ * `release/datafiles/icons_svg/` at the engine's pin (the icon this tab draws
171
+ * is named in `buttons_context_items`, `makesrna/intern/rna_space.cc:579`),
172
+ * and tinted by the group `UI_icons.hh` declares it in. */
173
+ export const icon = 'properties-bone';
174
+ /** `ED_buttons_tabs_list` (`space_buttons.cc:218-252`) is the rail's order;
175
+ * these are that order, spaced so a tab can be inserted between two. */
176
+ export const order = 130;
177
+ /** THE RAIL'S GROUPS, from `ED_buttons_tabs_list`'s own `add_spacer()` calls
178
+ * (`space_buttons/space_buttons.cc:201-255`): the tool tab, then the scene
179
+ * group (Render, Output, View Layer, Scene, World), then Collection, then the
180
+ * object group (Object … Material), then Texture. The presentation draws a
181
+ * separator wherever this changes. */
182
+ export const railGroup = 'object';
183
+ export const match = blenderPropertiesTabMatch(TAB);
184
+ export default blenderPropertiesTabSection(TAB);
@@ -0,0 +1,96 @@
1
+ /**
2
+ * BLENDER'S COLLECTION TAB, as a Properties section (WORK.md §Blender in the
3
+ * tab is Blender, "Inspection parity", I2). Blender's own panels over the RNA
4
+ * door, with the generic view beneath under "All properties".
5
+ *
6
+ * THE SPECIFICATION IS `scripts/startup/bl_ui/properties_collection.py` at the
7
+ * engine's pin — `classes` (`:146-155`) is the panel order and `bl_label` the
8
+ * title. Blender's UI layer is never run, ported or recorded.
9
+ *
10
+ * THE FIRST PANEL IS CALLED "VISIBILITY" AT THIS PIN, not "Restrictions":
11
+ * `COLLECTION_PT_collection_flags.bl_label` is `"Visibility"` (`:32`). Its
12
+ * child View Layer (`:47`) does not read the collection at all — it reads
13
+ * `view_layer.active_layer_collection` (`:56`), a different datablock, which
14
+ * the context door now names beside the collection as this tab's second path.
15
+ *
16
+ * TWO CHECKBOXES BLENDER DRAWS INVERTED. `hide_select` is labelled
17
+ * "Selectable" with `invert_checkbox=True` (`:41`) and `exclude` is labelled
18
+ * "Include" the same way (`:60`). What stands here is the RNA's own value
19
+ * under the RNA's own name, because inverting a value in the presentation is
20
+ * exactly the fabrication the anti-shim rule forbids — the same reading the
21
+ * Object tab's Visibility panel already made.
22
+ *
23
+ * WHAT THE TRANSCRIPTION DELIBERATELY DROPS: `COLLECTION_PT_importer`
24
+ * (`:65`), whose poll is a PREFERENCE (`prefs.experimental.use_collection_
25
+ * importer`) and whose body is `template_collection_importer()` — an operator
26
+ * template. Exporters (`:80`) is the same widget over `collection.exporters`,
27
+ * and the collection itself is real RNA (`rna_collection.cc:954`), so its row
28
+ * stands. `COLLECTION_PT_collection_custom_props` (`:143`) is IDProperties
29
+ * rather than RNA.
30
+ *
31
+ * It stands when: `buttons_context_path_collection` — the view layer's ACTIVE
32
+ * collection, and never the scene's master collection, which only the engine
33
+ * knows.
34
+ */
35
+ import { blenderPropertiesTabMatch, blenderPropertiesTabSection } from './blender-properties-tab';
36
+ import type { BlenderCuratedPanel } from './blender-properties-view';
37
+
38
+ /** `properties_collection.py`, panel by panel. Line numbers are that file's. */
39
+ const CURATED: readonly BlenderCuratedPanel[] = [
40
+ {
41
+ // COLLECTION_PT_collection_flags, :31.
42
+ title: 'Visibility',
43
+ properties: ['hide_select', 'hide_render'],
44
+ sub: [
45
+ {
46
+ // COLLECTION_PT_viewlayer_flags, :47 — the LayerCollection's.
47
+ title: 'View Layer',
48
+ from: 'View Layer Collection',
49
+ properties: ['exclude', 'holdout', 'indirect_only'],
50
+ },
51
+ ],
52
+ },
53
+ {
54
+ // COLLECTION_PT_instancing, :99.
55
+ title: 'Instancing',
56
+ properties: ['instance_offset'],
57
+ },
58
+ {
59
+ // COLLECTION_PT_lineart_collection, :113 — the mask is drawn as eight
60
+ // indexed toggles (:130); the array row carries all eight.
61
+ title: 'Line Art',
62
+ properties: [
63
+ 'lineart_usage',
64
+ 'lineart_use_intersection_mask',
65
+ 'lineart_intersection_mask',
66
+ 'use_lineart_intersection_priority',
67
+ 'lineart_intersection_priority',
68
+ ],
69
+ },
70
+ {
71
+ // COLLECTION_PT_exporters, :80.
72
+ title: 'Exporters',
73
+ properties: ['exporters'],
74
+ },
75
+ ];
76
+
77
+ const TAB = { id: 'collection', curated: CURATED } as const;
78
+
79
+ export const point = 'selection.inspector';
80
+ export const title = 'Collection';
81
+ /** `blender.icons.json` — Blender's own mark, TRACED from
82
+ * `release/datafiles/icons_svg/` at the engine's pin (the icon this tab draws
83
+ * is named in `buttons_context_items`, `makesrna/intern/rna_space.cc:579`),
84
+ * and tinted by the group `UI_icons.hh` declares it in. */
85
+ export const icon = 'properties-collection';
86
+ /** `ED_buttons_tabs_list` (`space_buttons.cc:218-252`) is the rail's order;
87
+ * these are that order, spaced so a tab can be inserted between two. */
88
+ export const order = 60;
89
+ /** THE RAIL'S GROUPS, from `ED_buttons_tabs_list`'s own `add_spacer()` calls
90
+ * (`space_buttons/space_buttons.cc:201-255`): the tool tab, then the scene
91
+ * group (Render, Output, View Layer, Scene, World), then Collection, then the
92
+ * object group (Object … Material), then Texture. The presentation draws a
93
+ * separator wherever this changes. */
94
+ export const railGroup = 'collection';
95
+ export const match = blenderPropertiesTabMatch(TAB);
96
+ export default blenderPropertiesTabSection(TAB);
@@ -0,0 +1,69 @@
1
+ /**
2
+ * BLENDER'S OBJECT CONSTRAINTS TAB, as a Properties section (WORK.md §Blender
3
+ * in the tab is Blender, "Inspection parity", I2): the STACK, each constraint
4
+ * its own panel with its own settings, the generic view beneath under "All
5
+ * properties".
6
+ *
7
+ * THE SPECIFICATION IS `scripts/startup/bl_ui/properties_constraint.py`, and
8
+ * it has the same shape `properties_data_modifier.py` does: `draw` is
9
+ * `layout.template_constraints()` and the per-type panels are built in C from
10
+ * each constraint's RNA. So the stack is derived the same way — the shared
11
+ * `Constraint` header properties named, then everything THIS constraint's own
12
+ * struct declares (`andDeclared`, which reads `BlenderRnaRow.group`). A Copy
13
+ * Location constraint gets `CopyLocationConstraint`'s target, subtarget, use_x
14
+ * / use_y / use_z, invert flags and offset, in RNA's order.
15
+ *
16
+ * The ACTIVE constraint (`buttons_context_path_constraint`, reported as
17
+ * `activeConstraint`) is marked.
18
+ *
19
+ * It stands when: `buttons_context_path_object` — Constraints shares Object's
20
+ * path, so the engine grants it whenever it grants Object.
21
+ */
22
+ import { blenderPropertiesTabMatch, blenderPropertiesTabSection } from './blender-properties-tab';
23
+ import type { BlenderCuratedPanel } from './blender-properties-view';
24
+
25
+ /** The shared header every constraint panel carries — `Constraint`'s own
26
+ * properties, in the order `template_constraints` heads a panel with them. */
27
+ export const CONSTRAINT_HEADER: readonly string[] = [
28
+ 'name',
29
+ 'type',
30
+ 'enabled',
31
+ 'mute',
32
+ 'influence',
33
+ 'error_location',
34
+ 'error_rotation',
35
+ 'is_valid',
36
+ 'is_override_data',
37
+ ];
38
+
39
+ const CURATED: readonly BlenderCuratedPanel[] = [
40
+ {
41
+ title: 'Constraints',
42
+ from: 'Constraints',
43
+ collection: true,
44
+ active: 'activeConstraint',
45
+ properties: CONSTRAINT_HEADER,
46
+ andDeclared: true,
47
+ },
48
+ ];
49
+
50
+ const TAB = { id: 'constraint', curated: CURATED } as const;
51
+
52
+ export const point = 'selection.inspector';
53
+ export const title = 'Object Constraints';
54
+ /** `blender.icons.json` — Blender's own mark, TRACED from
55
+ * `release/datafiles/icons_svg/` at the engine's pin (the icon this tab draws
56
+ * is named in `buttons_context_items`, `makesrna/intern/rna_space.cc:579`),
57
+ * and tinted by the group `UI_icons.hh` declares it in. */
58
+ export const icon = 'properties-constraints';
59
+ /** `ED_buttons_tabs_list` (`space_buttons.cc:218-252`) is the rail's order;
60
+ * these are that order, spaced so a tab can be inserted between two. */
61
+ export const order = 110;
62
+ /** THE RAIL'S GROUPS, from `ED_buttons_tabs_list`'s own `add_spacer()` calls
63
+ * (`space_buttons/space_buttons.cc:201-255`): the tool tab, then the scene
64
+ * group (Render, Output, View Layer, Scene, World), then Collection, then the
65
+ * object group (Object … Material), then Texture. The presentation draws a
66
+ * separator wherever this changes. */
67
+ export const railGroup = 'object';
68
+ export const match = blenderPropertiesTabMatch(TAB);
69
+ export default blenderPropertiesTabSection(TAB);