OverviewFilesCommitsIssuesDocsReleasesPeers

cthuloid commits / 145585e

blender-addon: demote the two operators that fought the mental model
Output is automatic whenever LIVE is armed -- playback, timeline seeks and any colour or energy edit all push on their own. Two buttons sat in the main panel implying otherwise, and both were misread in review: "Adopt Existing Lights" read as a per-light action. It is a one-time migration for .blend files that stored wiring in object names, and does nothing at all in a scene built with Add Fixture. Renamed "Convert Name-Encoded Lights" and moved into a Tools menu. "Send Now" read as a required step. It is recovery only: a one-shot with LIVE off, or a re-send after a fixture reconnects, which matters because the sender suppresses an identical payload and a returning fixture can otherwise sit dark until the next real change. Renamed "Resend / Force Refresh" and moved to the same menu. The main panel now states plainly that it is sending while armed, rather than offering a button that suggests it is not. Scale by Energy stays OFF and is now labelled provisional. Turning it on was considered and rejected: Blender Power is watts against an arbitrary reference, whereas a diode's luminous output is fixed by the part -- PWM duty sets perceived brightness but cannot raise the ceiling, so normalising Power against energy_ref maps to nothing physical. Recorded as future work, in both the property comment and the README: identify the exact diode type per channel and derive a measured per-channel PWM ceiling from its spec. Same shape as red_max_pwm, but grounded in the part rather than picked by hand -- and it should replace the Scale by Energy switch rather than sit beside it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
145585e3cc8d1eb6f29e2ae7d7c6622df768ecfb
by plankatron <fleeky@gmail.com> · 2026-08-30T19:19:05.000Z

Changed files (4) +73 -12

blender-addon/README.md +27 -2
……@@ -45,6 +45,20 @@ and `gamma` echoes its arguments back as `B= G= R=`.
4545Empty slots pad `0 0 0`. With `INVERT_HIGH_AND_LOW` in the firmware, input 0 is
4646LED-off, so padding is dark.
4747
48## Intensity, and what does not work yet
49
50Only **colour** reaches the hardware. `Scale by Energy` is off by default and is
51marked provisional, because it is not physically motivated as it stands: Blender
52Power is watts against an arbitrary reference, while a diode's luminous output
53is fixed by the part. PWM duty sets perceived brightness; it cannot raise the
54ceiling. So normalising Power against `Energy Reference` maps to nothing real.
55
56**Future work:** identify the exact diode type per channel and derive a measured
57per-channel PWM ceiling from its spec — the same shape as Red Max PWM below, but
58grounded in the part rather than picked by hand. That is the correct way to say
59"how bright can this channel actually get", and it should replace the Scale by
60Energy switch rather than sit beside it.
61
4862## Red Max PWM
4963
5064Per-fixture setting (the fleet is mixed, so a single global value would be
……@@ -101,8 +115,19 @@ channelbag and only the slot bound to that datablock may be read.
101115
102116## Sending
103117
104`LIVE` arms output. Frames are sent on frame change and on viewport edit
105(both toggleable under Output).
118**Arm `LIVE` and output is automatic.** There is no "send" step in normal use --
119playback, scrubbing/seeking anywhere in the timeline, and any light colour or
120energy edit all push on their own (both sources toggleable under Output).
121
122The Tools menu (▾ beside Add/Remove) holds the two things that are *not* part of
123that loop:
124
125- **Convert Name-Encoded Lights** — one-time migration for older .blend files
126 that stored wiring in object names. Does nothing in a scene built with
127 Add Fixture.
128- **Resend / Force Refresh** — recovery only: push once with `LIVE` off, or
129 re-send after a fixture reconnects (an identical payload is otherwise
130 suppressed, so a returning fixture can sit dark until the next real change).
106131
107132- **Coalescing** — one pending frame per fixture; if the UI outruns the wire,
108133 intermediate frames are dropped rather than queued, so output never plays back
blender-addon/cthuloid_lights/ops.py +12 -4
……@@ -112,8 +112,12 @@ class CTHULOID_OT_sync_names(Operator):
112112
113113class CTHULOID_OT_adopt_scene(Operator):
114114 bl_idname = "cthuloid.adopt_scene"
115 bl_label = "Adopt Existing Lights"
116 bl_description = "Import legacy bgr-<slot>-<host> named lights into fixtures"
115 bl_label = "Convert Name-Encoded Lights"
116 bl_description = (
117 "One-time migration for older .blend files. Older rigs stored the "
118 "wiring in the object name (bgr-1-192.168.50.138 meant slot 1 on that "
119 "host); this converts them to fixtures. Does nothing in a scene built "
120 "with Add Fixture")
117121 bl_options = {'REGISTER', 'UNDO'}
118122
119123 def execute(self, context):
……@@ -156,8 +160,12 @@ class CTHULOID_OT_assign_light(Operator):
156160
157161class CTHULOID_OT_send_now(Operator):
158162 bl_idname = "cthuloid.send_now"
159 bl_label = "Send Now"
160 bl_description = "Push the current scene state to all enabled fixtures"
163 bl_label = "Resend / Force Refresh"
164 bl_description = (
165 "Recovery only -- output is normally automatic while LIVE is armed. "
166 "Use this to push once with LIVE off, or to re-send after a fixture "
167 "reconnects, since an identical payload is otherwise suppressed and a "
168 "returning fixture can sit dark until the next real change")
161169
162170 def execute(self, context):
163171 n = engine.push_scene(context.scene, force=True)
blender-addon/cthuloid_lights/props.py +17 -3
……@@ -62,12 +62,26 @@ class CthuloidFixture(PropertyGroup):
6262 "rig shipped with; the firmware itself hard-rejects anything above "
6363 "45000 and will discard the entire command, so that is the maximum"))
6464
65 # PROVISIONAL -- off by default, and physically unmotivated as it stands.
66 # Blender Power is watts against an arbitrary reference, whereas a diode's
67 # luminous output is fixed by the part: PWM duty sets perceived brightness,
68 # it cannot raise the ceiling. Normalising Power against energy_ref
69 # therefore maps nothing real.
70 #
71 # TODO: identify the exact diode type per channel and derive a measured
72 # per-channel PWM ceiling from its spec -- the same shape as red_max_pwm,
73 # but grounded in the part rather than picked by hand. That is the correct
74 # way to express "how bright can this channel actually get", and it should
75 # replace this switch rather than sit beside it.
6576 use_energy: BoolProperty(
66 name="Scale by Energy", default=False,
77 name="Scale by Energy (provisional)", default=False,
6778 description=(
6879 "Multiply each light's colour by its Power, normalised against "
69 "Energy Reference. Off matches the original script, which read "
70 "colour only and ignored Power"))
80 "Energy Reference. Provisional and off by default: a diode's output "
81 "ceiling is fixed by the part, so Blender Power does not map to "
82 "anything physical. Pending per-channel ceilings derived from the "
83 "actual diode spec. Off matches the original script, which read "
84 "colour only"))
7185
7286 energy_ref: FloatProperty(
7387 name="Energy Reference", default=1000.0, min=0.001,
blender-addon/cthuloid_lights/ui.py +17 -3
……@@ -25,6 +25,19 @@ class CTHULOID_UL_fixtures(UIList):
2525 row.label(text="", icon=icons.get(status, 'RADIOBUT_OFF'))
2626
2727
28class CTHULOID_MT_tools(bpy.types.Menu):
29 """Occasional operations. Everything here is either a one-time migration or
30 a recovery action -- normal output is automatic while LIVE is armed."""
31 bl_label = "Tools"
32 bl_idname = "CTHULOID_MT_tools"
33
34 def draw(self, context):
35 layout = self.layout
36 layout.operator("cthuloid.send_now", icon='FILE_REFRESH')
37 layout.separator()
38 layout.operator("cthuloid.adopt_scene", icon='IMPORT')
39
40
2841class CthuloidPanelBase:
2942 bl_space_type = 'VIEW_3D'
3043 bl_region_type = 'UI'
……@@ -50,10 +63,10 @@ class CTHULOID_PT_main(CthuloidPanelBase, Panel):
5063 col.operator("cthuloid.add_fixture", text="", icon='ADD')
5164 col.operator("cthuloid.remove_fixture", text="", icon='REMOVE')
5265 col.separator()
53 col.operator("cthuloid.adopt_scene", text="", icon='IMPORT')
66 col.menu("CTHULOID_MT_tools", text="", icon='DOWNARROW_HLT')
5467
55 row = layout.row(align=True)
56 row.operator("cthuloid.send_now", icon='EXPORT')
68 if props.live:
69 layout.label(text="Sending on playback, seek and edits", icon='CHECKMARK')
5770
5871
5972class CTHULOID_PT_fixture(CthuloidPanelBase, Panel):
……@@ -206,6 +219,7 @@ class CTHULOID_PT_light(CthuloidPanelBase, Panel):
206219
207220CLASSES = (
208221 CTHULOID_UL_fixtures,
222 CTHULOID_MT_tools,
209223 CTHULOID_PT_main,
210224 CTHULOID_PT_fixture,
211225 CTHULOID_PT_hierarchy,