| … | … | @@ -33,6 +33,7 @@ import traceback |
| 33 | 33 | import bpy |
| 34 | 34 | from bpy.props import ( |
| 35 | 35 | BoolProperty, |
| 36 | FloatVectorProperty, |
| 36 | 37 | CollectionProperty, |
| 37 | 38 | EnumProperty, |
| 38 | 39 | FloatProperty, |
| … | … | @@ -88,16 +89,50 @@ def library(): |
| 88 | 89 | |
| 89 | 90 | # ------------------------------------------------------------------- properties |
| 90 | 91 | |
| 92 | def _touch(kind): |
| 93 | """ |
| 94 | Property update callback: flags the live session and asks for a redraw. |
| 95 | |
| 96 | Edits only set a flag; the repack happens once in the draw handler. A slider |
| 97 | drag fires this on every pixel, so doing the work here would rebuild several |
| 98 | UBOs per mouse-move. |
| 99 | """ |
| 100 | def update(self, context): |
| 101 | if ifs_viewport.is_running(): |
| 102 | ifs_viewport.session().mark_dirty(kind) |
| 103 | _redraw(context) |
| 104 | return update |
| 105 | |
| 106 | |
| 107 | # Sized from the shipped library: the widest plugin declares 7 real params (SSA) |
| 108 | # and 3 vec3 params (Affine, Mobius). Fixed-size vectors keep this a plain |
| 109 | # PropertyGroup; only the count each plugin declares is drawn. |
| 110 | MAX_REAL_UI = 8 |
| 111 | MAX_VEC3_UI = 3 |
| 112 | |
| 113 | |
| 91 | 114 | class IFSIteratorItem(PropertyGroup): |
| 92 | | """One iterator, mirrored from the loaded document for display and tweaking.""" |
| 115 | """One iterator, mirrored from the loaded document and editable live.""" |
| 93 | 116 | |
| 94 | 117 | # A `name` property is required or every row in the UIList shows the same |
| 95 | 118 | # label — getname_anim_fcurve falls back to the type name without it. |
| 96 | 119 | name: StringProperty(name="Name", default="Iterator") |
| 97 | 120 | transform: StringProperty(name="Transform", default="") |
| 98 | | opacity: FloatProperty(name="Opacity", default=1.0, min=0.0, max=1.0) |
| 99 | | color_index: FloatProperty(name="Color", default=0.0, min=0.0, max=1.0) |
| 100 | | base_weight: FloatProperty(name="Weight", default=1.0, min=0.0) |
| 121 | opacity: FloatProperty(name="Opacity", default=1.0, min=0.0, max=1.0, |
| 122 | update=_touch('params')) |
| 123 | color_index: FloatProperty(name="Color", default=0.0, min=0.0, max=1.0, |
| 124 | update=_touch('params')) |
| 125 | color_speed: FloatProperty(name="Color speed", default=0.5, min=0.0, max=1.0, |
| 126 | update=_touch('params')) |
| 127 | # Changing a base weight re-normalises every xaos row, so the alias texture |
| 128 | # has to be rebuilt, not just the iterator block. |
| 129 | base_weight: FloatProperty(name="Weight", default=1.0, min=0.0, soft_max=10.0, |
| 130 | update=_touch('weights')) |
| 131 | |
| 132 | real_values: FloatVectorProperty(size=MAX_REAL_UI, update=_touch('params')) |
| 133 | vec3_values: FloatVectorProperty(size=MAX_VEC3_UI * 3, update=_touch('params')) |
| 134 | real_names: StringProperty(default="") # comma-joined, for labelling |
| 135 | vec3_names: StringProperty(default="") |
| 101 | 136 | |
| 102 | 137 | |
| 103 | 138 | class IFSSceneSettings(PropertyGroup): |
| … | … | @@ -109,7 +144,10 @@ class IFSSceneSettings(PropertyGroup): |
| 109 | 144 | dispatches: IntProperty( |
| 110 | 145 | name="Dispatches", default=132, min=1, max=100000, |
| 111 | 146 | description="Batches of 16384 x 500 iterations. More is cleaner and slower") |
| 112 | | brightness: FloatProperty(name="Brightness", default=8.0, min=0.01, max=1000.0) |
| 147 | # Tonemap only - the accumulated samples stay valid, so dragging this does not |
| 148 | # restart the render. |
| 149 | brightness: FloatProperty(name="Brightness", default=8.0, min=0.01, |
| 150 | soft_max=64.0, max=1000.0, update=_touch('tonemap')) |
| 113 | 151 | use_document_resolution: BoolProperty( |
| 114 | 152 | name="Use document resolution", default=False, |
| 115 | 153 | description="Render at the size stored in the document instead of the above") |
| … | … | @@ -126,14 +164,27 @@ class IFSSceneSettings(PropertyGroup): |
| 126 | 164 | |
| 127 | 165 | def _sync_iterators(settings, doc): |
| 128 | 166 | settings.iterators.clear() |
| 167 | plugins = {p.name: p for p in library().plugins} |
| 129 | 168 | for index, it in enumerate(doc.iterators): |
| 169 | name = _transform_name(it) |
| 130 | 170 | item = settings.iterators.add() |
| 131 | | item.name = f"{index}: {_transform_name(it)}" |
| 132 | | item.transform = _transform_name(it) |
| 171 | item.name = f"{index}: {name}" |
| 172 | item.transform = name |
| 133 | 173 | item.opacity = it.opacity |
| 134 | 174 | item.color_index = it.color_index |
| 175 | item.color_speed = it.color_speed |
| 135 | 176 | item.base_weight = it.base_weight |
| 136 | 177 | |
| 178 | plugin = plugins.get(name) |
| 179 | if plugin is not None: |
| 180 | item.real_names = ",".join(plugin.real_params) |
| 181 | item.vec3_names = ",".join(plugin.vec3_params) |
| 182 | for i, value in enumerate(it.real_params[:MAX_REAL_UI]): |
| 183 | item.real_values[i] = value |
| 184 | for i, vec in enumerate(it.vec3_params[:MAX_VEC3_UI]): |
| 185 | for c in range(3): |
| 186 | item.vec3_values[i * 3 + c] = vec[c] |
| 187 | |
| 137 | 188 | |
| 138 | 189 | def _transform_name(iterator): |
| 139 | 190 | for name, tf_id in library().tf_ids.items(): |
| … | … | @@ -350,14 +401,34 @@ class IFS_PT_iterators(Panel): |
| 350 | 401 | "IFS_UL_iterators", "", settings, "iterators", |
| 351 | 402 | settings, "active_iterator", rows=4) |
| 352 | 403 | |
| 353 | | if 0 <= settings.active_iterator < len(settings.iterators): |
| 354 | | item = settings.iterators[settings.active_iterator] |
| 355 | | column = self.layout.column(align=True) |
| 356 | | column.label(text=f"Transform: {item.transform}") |
| 357 | | column.prop(item, "color_index") |
| 358 | | column.prop(item, "base_weight") |
| 359 | | column.label(text="Edits are not fed back to the render yet", |
| 360 | | icon='ERROR') |
| 404 | if not (0 <= settings.active_iterator < len(settings.iterators)): |
| 405 | return |
| 406 | item = settings.iterators[settings.active_iterator] |
| 407 | |
| 408 | column = self.layout.column(align=True) |
| 409 | column.label(text=f"Transform: {item.transform}") |
| 410 | column.prop(item, "opacity", slider=True) |
| 411 | column.prop(item, "color_index", slider=True) |
| 412 | column.prop(item, "color_speed", slider=True) |
| 413 | column.prop(item, "base_weight") |
| 414 | |
| 415 | # Only the parameters this plugin actually declares, under their own names. |
| 416 | real_names = [n for n in item.real_names.split(",") if n] |
| 417 | vec3_names = [n for n in item.vec3_names.split(",") if n] |
| 418 | if real_names or vec3_names: |
| 419 | box = self.layout.box() |
| 420 | box.label(text=f"{item.transform} parameters") |
| 421 | for i, pname in enumerate(real_names[:MAX_REAL_UI]): |
| 422 | box.prop(item, "real_values", index=i, text=pname) |
| 423 | for i, pname in enumerate(vec3_names[:MAX_VEC3_UI]): |
| 424 | row = box.row(align=True) |
| 425 | row.label(text=pname) |
| 426 | for c in range(3): |
| 427 | row.prop(item, "vec3_values", index=i * 3 + c, text="") |
| 428 | |
| 429 | if not ifs_viewport.is_running(): |
| 430 | self.layout.label(text="Start the live viewport to see edits", |
| 431 | icon='INFO') |
| 361 | 432 | |
| 362 | 433 | |
| 363 | 434 | # --------------------------------------------------------------------- register |