refactor: More changes to docs/layout

main
Sofus Albert Høgsbro Rose 2024-04-18 08:42:53 +02:00
parent 8dece384ad
commit ff5d71aeff
Signed by: so-rose
GPG Key ID: AD901CB0F3701434
6 changed files with 287 additions and 153 deletions

View File

@ -108,7 +108,25 @@ class ArrayFlow:
""" """
values: jax.Array values: jax.Array
unit: spu.Quantity | None unit: spu.Quantity | None = None
def correct_unit(self, real_unit: spu.Quantity) -> typ.Self:
if self.unit is not None:
return ArrayFlow(values=self.values, unit=real_unit)
msg = f'Tried to correct unit of unitless LazyDataValueRange "{real_unit}"'
raise ValueError(msg)
def rescale_to_unit(self, unit: spu.Quantity) -> typ.Self:
if self.unit is not None:
return ArrayFlow(
values=float(spux.scaling_factor(self.unit, unit)) * self.values,
unit=unit,
)
## TODO: Is this scaling numerically stable?
msg = f'Tried to rescale unitless LazyDataValueRange to unit {unit}'
raise ValueError(msg)
#################### ####################
@ -213,14 +231,26 @@ class LazyArrayRangeFlow:
steps: int steps: int
scaling: typx.Literal['lin', 'geom', 'log'] = 'lin' scaling: typx.Literal['lin', 'geom', 'log'] = 'lin'
has_unit: bool = False unit: spu.Quantity | None = False
unit: spu.Quantity = False
def rescale_to_unit(self, unit: spu.Quantity) -> typ.Self: def correct_unit(self, real_unit: spu.Quantity) -> typ.Self:
if self.has_unit: if self.unit is not None:
return LazyArrayRangeFlow(
symbols=self.symbols,
unit=real_unit,
start=self.start,
stop=self.stop,
steps=self.steps,
scaling=self.scaling,
)
msg = f'Tried to correct unit of unitless LazyDataValueRange "{real_unit}"'
raise ValueError(msg)
def rescale_to_unit(self, unit: spu.Quantity) -> typ.Self:
if self.unit is not None:
return LazyArrayRangeFlow( return LazyArrayRangeFlow(
symbols=self.symbols, symbols=self.symbols,
has_unit=self.has_unit,
unit=unit, unit=unit,
start=spu.convert_to(self.start, unit), start=spu.convert_to(self.start, unit),
stop=spu.convert_to(self.stop, unit), stop=spu.convert_to(self.stop, unit),
@ -239,7 +269,6 @@ class LazyArrayRangeFlow:
"""Call a function on both bounds (start and stop), creating a new `LazyDataValueRange`.""" """Call a function on both bounds (start and stop), creating a new `LazyDataValueRange`."""
return LazyArrayRangeFlow( return LazyArrayRangeFlow(
symbols=self.symbols, symbols=self.symbols,
has_unit=self.has_unit,
unit=self.unit, unit=self.unit,
start=spu.convert_to( start=spu.convert_to(
bound_cb(self.start if not reverse else self.stop), self.unit bound_cb(self.start if not reverse else self.stop), self.unit
@ -255,7 +284,7 @@ class LazyArrayRangeFlow:
self, symbol_values: dict[sp.Symbol, ValueFlow] = MappingProxyType({}) self, symbol_values: dict[sp.Symbol, ValueFlow] = MappingProxyType({})
) -> ArrayFlow: ) -> ArrayFlow:
# Realize Symbols # Realize Symbols
if not self.has_unit: if self.unit is None:
start = spux.sympy_to_python(self.start.subs(symbol_values)) start = spux.sympy_to_python(self.start.subs(symbol_values))
stop = spux.sympy_to_python(self.stop.subs(symbol_values)) stop = spux.sympy_to_python(self.stop.subs(symbol_values))
else: else:

View File

@ -347,6 +347,8 @@ class MaxwellSimTree(bpy.types.NodeTree):
consent_removal = to_socket.allow_remove_link(from_socket) consent_removal = to_socket.allow_remove_link(from_socket)
if not consent_removal: if not consent_removal:
link_corrections['to_add'].append((from_socket, to_socket)) link_corrections['to_add'].append((from_socket, to_socket))
else:
to_socket.on_link_removed(from_socket)
# Ensure Removal of Socket PTRs, PTRs->REFs # Ensure Removal of Socket PTRs, PTRs->REFs
self.node_link_cache.remove_sockets_by_link_ptr(link_ptr) self.node_link_cache.remove_sockets_by_link_ptr(link_ptr)

View File

@ -192,7 +192,6 @@ class MaxwellSimNode(bpy.types.Node):
'active_socket_set', 'active_socket_set',
bpy.props.EnumProperty, bpy.props.EnumProperty,
name='Active Socket Set', name='Active Socket Set',
description='Selector of active sockets',
items=[ items=[
(socket_set_name, socket_set_name, socket_set_name) (socket_set_name, socket_set_name, socket_set_name)
for socket_set_name in socket_set_names for socket_set_name in socket_set_names
@ -742,13 +741,13 @@ class MaxwellSimNode(bpy.types.Node):
) -> None: ) -> None:
"""Draws the UI of the node. """Draws the UI of the node.
- Locked (`self.locked`): The UI will be unusable. - **Locked** (`self.locked`): The UI will be unusable.
- Active Preset (`self.active_preset`): The preset selector will display. - **Active Preset** (`self.active_preset`): The preset selector will display.
- Active Socket Set (`self.active_socket_set`): The socket set selector will display. - **Active Socket Set** (`self.active_socket_set`): The socket set selector will display.
- Use Sim Node Name (`self.use_sim_node_name`): The "Sim Node Name will display. - **Use Sim Node Name** (`self.use_sim_node_name`): The `self.sim_node_name` will display.
- Properties (`self.draw_props()`): Node properties will display. - **Properties**: Node properties will display, if `self.draw_props()` is overridden.
- Operators (`self.draw_operators()`): Node operators will display. - **Operators**: Node operators will display, if `self.draw_operators()` is overridden.
- Info (`self.draw_operators()`): Node information will display. - **Info**: Node information will display, if `self.draw_info()` is overridden.
Parameters: Parameters:
context: The current Blender context. context: The current Blender context.

View File

@ -125,6 +125,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
#################### ####################
# - Initialization # - Initialization
#################### ####################
## TODO: Common implementation of this for both sockets and nodes - perhaps a BLInstance base class?
@classmethod @classmethod
def set_prop( def set_prop(
cls, cls,
@ -183,103 +184,84 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
# Configure Use of Units # Configure Use of Units
if cls.use_units: if cls.use_units:
# Set Shape :)
cls.socket_shape += '_DOT'
if not (socket_units := ct.SOCKET_UNITS.get(cls.socket_type)): if not (socket_units := ct.SOCKET_UNITS.get(cls.socket_type)):
msg = 'Tried to `use_units` on {cls.bl_idname} socket, but `SocketType` has no units defined in `contracts.SOCKET_UNITS`' msg = f'Tried to define "use_units" on socket {cls.bl_label} socket, but there is no unit for {cls.socket_type} defined in "contracts.SOCKET_UNITS"'
raise RuntimeError(msg) raise RuntimeError(msg)
# Current Unit cls.set_prop(
cls.__annotations__['active_unit'] = bpy.props.EnumProperty( 'active_unit',
bpy.props.EnumProperty,
name='Unit', name='Unit',
description='Choose a unit',
items=[ items=[
(unit_name, str(unit_value), str(unit_value)) (unit_name, str(unit_value), str(unit_value))
for unit_name, unit_value in socket_units['values'].items() for unit_name, unit_value in socket_units['values'].items()
], ],
default=socket_units['default'], default=socket_units['default'],
update=lambda self, _: self.sync_unit_change(),
) )
cls.set_prop(
# Previous Unit (for conversion) 'prev_active_unit',
cls.__annotations__['prev_active_unit'] = bpy.props.StringProperty( bpy.props.StringProperty,
default=socket_units['default'], default=socket_units['default'],
) )
#################### ####################
# - Event Chain # - Property Event: On Update
#################### ####################
def trigger_event( def _on_active_kind_changed(self) -> None:
self, """Matches the display shape to the active `FlowKind`.
event: ct.FlowEvent,
) -> None:
"""Called whenever the socket's output value has changed.
This also invalidates any of the socket's caches. Notes:
Called by `self.on_prop_changed()` when `self.active_kind` was changed.
When called on an input node, the containing node's
`trigger_event` method will be called with this socket.
When called on a linked output node, the linked socket's
`trigger_event` method will be called.
""" """
# Forwards Chains self.display_shape = (
if event in {ct.FlowEvent.DataChanged}: 'SQUARE'
## Input Socket if self.active_kind in {ct.FlowKind.LazyValue, ct.FlowKind.LazyValueRange}
if not self.is_output: else 'CIRCLE'
self.node.trigger_event(event, socket_name=self.name) ) + ('_DOT' if self.use_units else '')
## Linked Output Socket def _on_unit_changed(self) -> None:
elif self.is_output and self.is_linked: """Synchronizes the `FlowKind` data to the newly set unit.
for link in self.links:
link.to_socket.trigger_event(event)
# Backwards Chains When a new unit is set, the internal ex. floating point properties become out of sync.
elif event in { This function applies a rescaling operation based on the factor between the previous unit (`self.prev_unit`) and the new unit `(self.unit)`.
ct.FlowEvent.EnableLock,
ct.FlowEvent.DisableLock,
ct.FlowEvent.OutputRequested,
ct.FlowEvent.DataChanged,
ct.FlowEvent.ShowPreview,
ct.FlowEvent.ShowPlot,
}:
if event == ct.FlowEvent.EnableLock:
self.locked = True
if event == ct.FlowEvent.DisableLock: - **Value**: Retrieve the value (with incorrect new unit), exchange the new unit for the old unit, and assign it back.
self.locked = False - **Array**: Replace the internal unit with the old (correct) unit, and rescale all values in the array to the new unit.
## Output Socket Notes:
if self.is_output: Called by `self.on_prop_changed()` when `self.active_unit` is changed.
self.node.trigger_event(event, socket_name=self.name)
## Linked Input Socket This allows for a unit-scaling operation **without needing to know anything about the data representation** (at the cost of performance).
elif not self.is_output and self.is_linked: """
for link in self.links: if self.active_kind == ct.FlowKind.Value:
link.from_socket.trigger_event(event) self.value = self.value / self.unit * self.prev_unit
elif self.active_kind in [ct.FlowKind.Array, ct.FlowKind.LazyArrayRange]:
self.lazy_value_range = self.lazy_value_range.correct_unit(
self.prev_unit
).rescale_to_unit(self.unit)
else:
msg = f'Active kind {self.active_kind} has no way of scaling units (from {self.prev_active_unit} to {self.active_unit}). Please check the node definition'
raise RuntimeError(msg)
self.prev_active_unit = self.active_unit
####################
# - Event Chain: Event Handlers
####################
def sync_prop(self, prop_name: str, _: bpy.types.Context) -> None: def sync_prop(self, prop_name: str, _: bpy.types.Context) -> None:
"""Called when a property has been updated. """Called when a property has been updated.
Contrary to `node.on_prop_changed()`, socket-specific callbacks are baked into this function: Contrary to `node.on_prop_changed()`, socket-specific callbacks are baked into this function:
- **Active Kind** (`active_kind`): Sets the socket shape to reflect the active `FlowKind`. - **Active Kind** (`self.active_kind`): Sets the socket shape to reflect the active `FlowKind`.
- **Unit** (`self.unit`): Corrects the internal `FlowKind` representation to match the new unit.
Attributes: Attributes:
prop_name: The name of the property that was changed. prop_name: The name of the property that was changed.
""" """
# Property: Active Kind # Property: Active Kind
if prop_name == 'active_kind': if prop_name == 'active_kind':
self.display_shape( self._on_active_kind_changed()
'SQUARE' elif prop_name == 'unit':
if self.active_kind self._on_unit_changed()
in {ct.FlowKind.LazyValue, ct.FlowKind.LazyValueRange}
else 'CIRCLE'
) + ('_DOT' if self.use_units else '')
# Valid Properties # Valid Properties
elif hasattr(self, prop_name): elif hasattr(self, prop_name):
@ -290,6 +272,9 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
msg = f'Property {prop_name} not defined on socket {self}' msg = f'Property {prop_name} not defined on socket {self}'
raise RuntimeError(msg) raise RuntimeError(msg)
####################
# - Link Event: Consent / On Change
####################
def allow_add_link(self, link: bpy.types.NodeLink) -> bool: def allow_add_link(self, link: bpy.types.NodeLink) -> bool:
"""Called to ask whether a link may be added to this (input) socket. """Called to ask whether a link may be added to this (input) socket.
@ -300,7 +285,6 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
In practice, the link in question has already been added. In practice, the link in question has already been added.
This function determines **whether the new link should be instantly removed** - if so, the removal producing the _practical effect_ of the link "not being added" at all. This function determines **whether the new link should be instantly removed** - if so, the removal producing the _practical effect_ of the link "not being added" at all.
Attributes: Attributes:
link: The node link that was already added, whose continued existance is in question. link: The node link that was already added, whose continued existance is in question.
@ -341,21 +325,19 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
return True return True
def on_link_added(self, link: bpy.types.NodeLink) -> None: def on_link_added(self, link: bpy.types.NodeLink) -> None: # noqa: ARG002
"""Triggers a `ct.FlowEvent.LinkChanged` event on link add. """Triggers a `ct.FlowEvent.LinkChanged` event when a link is added.
Notes:
Called by the node tree, generally (but not guaranteed) after `self.allow_add_link()` has given consent to add the link.
Attributes: Attributes:
link: The node link that was added. link: The node link that was added.
Currently unused. Currently unused.
Returns:
Whether or not consent is given to add the link.
In practice, the link will simply remain if consent is given.
If consent is not given, the new link will be removed.
""" """
self.trigger_event(ct.FlowEvent.DataChanged) self.trigger_event(ct.FlowEvent.LinkChanged)
def allow_remove_link(self, from_socket: bpy.types.NodeSocket) -> bool: def allow_remove_link(self, from_socket: bpy.types.NodeSocket) -> bool: # noqa: ARG002
"""Called to ask whether a link may be removed from this `to_socket`. """Called to ask whether a link may be removed from this `to_socket`.
- **Locked**: Locked sockets may not have links removed. - **Locked**: Locked sockets may not have links removed.
@ -386,9 +368,67 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
if self.locked: if self.locked:
return False return False
self.trigger_event(ct.FlowEvent.DataChanged)
return True return True
def on_link_removed(self, from_socket: bpy.types.NodeSocket) -> None: # noqa: ARG002
"""Triggers a `ct.FlowEvent.LinkChanged` event when a link is removed.
Notes:
Called by the node tree, generally (but not guaranteed) after `self.allow_remove_link()` has given consent to remove the link.
Attributes:
from_socket: The node socket that was attached to before link removal.
Currently unused.
"""
self.trigger_event(ct.FlowEvent.LinkChanged)
####################
# - Event Chain
####################
def trigger_event(
self,
event: ct.FlowEvent,
) -> None:
"""Recursively triggers an event along the node tree, depending on whether the socket is an input or output socket.
Notes:
This can be an unpredictably heavy function, depending on the node graph topology.
Parameters:
event: The event to report along the node tree.
The value of `ct.FlowEvent.flow_direction[event]` must match either `input` or `output`, depending on whether the socket is input/output.
"""
flow_direction = ct.FlowEvent.flow_direction[event]
# Input Socket | Input Flow
if not self.is_output and flow_direction == 'input':
if event in [ct.FlowEvent.EnableLock, ct.FlowEvent.DisableLock]:
self.locked = event == ct.FlowEvent.EnableLock
for link in self.links:
link.from_socket.trigger_event(event)
# Input Socket | Output Flow
if not self.is_output and flow_direction == 'output':
## THIS IS A WORKAROUND (bc Node only understands DataChanged)
## TODO: Handle LinkChanged on the node.
if event == ct.FlowEvent.LinkChanged:
self.node.trigger_event(ct.FlowEvent.DataChanged, socket_name=self.name)
self.node.trigger_event(event, socket_name=self.name)
# Output Socket | Input Flow
if self.is_output and flow_direction == 'input':
if event in [ct.FlowEvent.EnableLock, ct.FlowEvent.DisableLock]:
self.locked = event == ct.FlowEvent.EnableLock
self.node.trigger_event(event, socket_name=self.name)
# Output Socket | Output Flow
if self.is_output and flow_direction == 'output':
for link in self.links:
link.to_socket.trigger_event(event)
#################### ####################
# - Data Chain # - Data Chain
#################### ####################
@ -437,7 +477,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
def lazy_array_range(self, value: tuple[ct.DataValue, ct.DataValue, int]) -> None: def lazy_array_range(self, value: tuple[ct.DataValue, ct.DataValue, int]) -> None:
raise NotImplementedError raise NotImplementedError
# LazyArrayRange # Param
@property @property
def param(self) -> ct.ParamsFlow: def param(self) -> ct.ParamsFlow:
raise NotImplementedError raise NotImplementedError
@ -446,6 +486,15 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
def param(self, value: tuple[ct.DataValue, ct.DataValue, int]) -> None: def param(self, value: tuple[ct.DataValue, ct.DataValue, int]) -> None:
raise NotImplementedError raise NotImplementedError
# Info
@property
def info(self) -> ct.ParamsFlow:
raise NotImplementedError
@info.setter
def info(self, value: tuple[ct.DataValue, ct.DataValue, int]) -> None:
raise NotImplementedError
#################### ####################
# - Data Chain Computation # - Data Chain Computation
#################### ####################
@ -546,41 +595,19 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
self.active_unit = matching_unit_names[0] self.active_unit = matching_unit_names[0]
def sync_unit_change(self) -> None:
"""In unit-aware sockets, the internal `value()` property multiplies the Blender property value by the current active unit.
When the unit is changed, `value()` will display the old scalar with the new unit.
To fix this, we need to update the scalar to use the new unit.
Can be overridden if more specific logic is required.
"""
if self.active_kind == ct.FlowKind.Value:
self.value = self.value / self.unit * self.prev_unit
elif self.active_kind == ct.FlowKind.LazyValueRange:
lazy_value_range = self.lazy_value_range
self.lazy_value_range = (
lazy_value_range.start / self.unit * self.prev_unit,
lazy_value_range.stop / self.unit * self.prev_unit,
lazy_value_range.steps,
)
self.prev_active_unit = self.active_unit
#################### ####################
# - Style # - Theme
#################### ####################
def draw_color(
self,
context: bpy.types.Context,
node: bpy.types.Node,
) -> ct.BLColorRGBA:
"""Color of the socket icon, when embedded in a node."""
return self.socket_color
@classmethod @classmethod
def draw_color_simple(cls) -> ct.BLColorRGBA: def draw_color_simple(cls) -> ct.BLColorRGBA:
"""Fallback color of the socket icon (ex.when not embedded in a node).""" """Sets the socket's color to `cls.socket_color`.
Notes:
Blender calls this method to determine the socket color.
Returns:
A Blender-compatible RGBA value, with no explicit color space.
"""
return cls.socket_color return cls.socket_color
#################### ####################
@ -593,7 +620,17 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
node: bpy.types.Node, node: bpy.types.Node,
text: str, text: str,
) -> None: ) -> None:
"""Called by Blender to draw the socket UI.""" """Draw the socket UI.
- **Input Socket**: Will use `self.draw_input()`.
- **Output Socket**: Will use `self.draw_output()`.
Parameters:
context: The current Blender context.
layout: Target for defining UI elements.
node: The node within which the socket is embedded.
text: The socket's name in the UI.
"""
if self.is_output: if self.is_output:
self.draw_output(context, layout, node, text) self.draw_output(context, layout, node, text)
else: else:
@ -606,8 +643,21 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
node: bpy.types.Node, node: bpy.types.Node,
text: str, text: str,
) -> None: ) -> None:
pass """Draw the "prelock" UI, which is usable regardless of the `self.locked` state.
Notes:
If a "prelock" UI is needed by a socket, it should set `self.use_prelock` and override this method.
Parameters:
context: The current Blender context.
col: Target for defining UI elements.
node: The node within which the socket is embedded.
text: The socket's name in the UI.
"""
####################
# - UI: Input / Output Socket
####################
def draw_input( def draw_input(
self, self,
context: bpy.types.Context, context: bpy.types.Context,
@ -615,7 +665,23 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
node: bpy.types.Node, node: bpy.types.Node,
text: str, text: str,
) -> None: ) -> None:
"""Draws the socket UI, when the socket is an input socket.""" """Draw the UI of the input socket.
- **Locked** (`self.locked`): The UI will be unusable.
- **Linked** (`self.is_linked`): Only the socket label will display.
- **Use Units** (`self.use_units`): The currently active unit will display as a dropdown menu.
- **Use Prelock** (`self.use_prelock`): The "prelock" UI drawn with `self.draw_prelock()`, which shows **regardless of `self.locked`**.
- **FlowKind**: The `FlowKind`-specific UI corresponding to the current `self.active_kind`.
Notes:
Shouldn't be overridden.
Parameters:
context: The current Blender context.
layout: Target for defining UI elements.
node: The node within which the socket is embedded.
text: The socket's name in the UI.
"""
col = layout.column(align=False) col = layout.column(align=False)
# Label Row # Label Row
@ -653,25 +719,33 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
elif self.locked: elif self.locked:
row.enabled = False row.enabled = False
# Data Column(s) # FlowKind Column(s)
col = row.column(align=True) col = row.column(align=True)
{ {
ct.FlowKind.Value: self.draw_value, ct.FlowKind.Value: self.draw_value,
ct.FlowKind.ValueArray: self.draw_value_array, ct.FlowKind.Array: self.draw_value_array,
ct.FlowKind.ValueSpectrum: self.draw_value_spectrum,
ct.FlowKind.LazyValue: self.draw_lazy_value, ct.FlowKind.LazyValue: self.draw_lazy_value,
ct.FlowKind.LazyValueRange: self.draw_lazy_value_range, ct.FlowKind.LazyValueRange: self.draw_lazy_value_range,
ct.FlowKind.LazyValueSpectrum: self.draw_lazy_value_spectrum,
}[self.active_kind](col) }[self.active_kind](col)
def draw_output( def draw_output(
self, self,
context: bpy.types.Context, context: bpy.types.Context, # noqa: ARG002
layout: bpy.types.UILayout, layout: bpy.types.UILayout,
node: bpy.types.Node, node: bpy.types.Node, # noqa: ARG002
text: str, text: str,
) -> None: ) -> None:
"""Draws the socket UI, when the socket is an output socket.""" """Draw the label text on the output socket.
Notes:
Shouldn't be overridden.
Parameters:
context: The current Blender context.
layout: Target for defining UI elements.
node: The node within which the socket is embedded.
text: The socket's name in the UI.
"""
layout.label(text=text) layout.label(text=text)
#################### ####################
@ -682,29 +756,53 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
row: bpy.types.UILayout, row: bpy.types.UILayout,
text: str, text: str,
) -> None: ) -> None:
"""Called to draw the label row (same height as socket shape). """Draw the label row, which is at the same height as the socket shape.
Can be overridden. Notes:
Can be overriden by individual socket classes, if they need to alter the way that the label row is drawn.
Parameters:
row: Target for defining UI elements.
text: The socket's name in the UI.
""" """
row.label(text=text) row.label(text=text)
####################
# - FlowKind draw() Methods
####################
def draw_value(self, col: bpy.types.UILayout) -> None: def draw_value(self, col: bpy.types.UILayout) -> None:
pass """Draws the socket value on its own line.
def draw_value_array(self, col: bpy.types.UILayout) -> None: Notes:
pass Should be overriden by individual socket classes, if they have an editable `FlowKind.Value`.
def draw_value_spectrum(self, col: bpy.types.UILayout) -> None: Parameters:
pass col: Target for defining UI elements.
"""
def draw_array(self, col: bpy.types.UILayout) -> None:
"""Draws the socket array on its own line.
Notes:
Should be overriden by individual socket classes, if they have an editable `FlowKind.Array`.
Parameters:
col: Target for defining UI elements.
"""
def draw_lazy_value(self, col: bpy.types.UILayout) -> None: def draw_lazy_value(self, col: bpy.types.UILayout) -> None:
pass """Draws the socket lazy value on its own line.
def draw_lazy_value_range(self, col: bpy.types.UILayout) -> None: Notes:
pass Should be overriden by individual socket classes, if they have an editable `FlowKind.LazyValue`.
def draw_lazy_value_spectrum(self, col: bpy.types.UILayout) -> None: Parameters:
pass col: Target for defining UI elements.
"""
def draw_lazy_array_range(self, col: bpy.types.UILayout) -> None:
"""Draws the socket lazy array range on its own line.
Notes:
Should be overriden by individual socket classes, if they have an editable `FlowKind.LazyArrayRange`.
Parameters:
col: Target for defining UI elements.
"""

View File

@ -4,7 +4,8 @@ import os
import sys import sys
from pathlib import Path from pathlib import Path
from ... import info import blender_maxwell.contracts as ct
from . import simple_logger from . import simple_logger
log = simple_logger.get(__name__) log = simple_logger.get(__name__)
@ -43,7 +44,7 @@ def importable_addon_deps(path_deps: Path):
def syspath_from_bpy_prefs() -> bool: def syspath_from_bpy_prefs() -> bool:
import bpy import bpy
addon_prefs = bpy.context.preferences.addons[info.ADDON_NAME].preferences addon_prefs = bpy.context.preferences.addons[ct.addon.NAME].preferences
if hasattr(addon_prefs, 'path_addon_pydeps'): if hasattr(addon_prefs, 'path_addon_pydeps'):
log.info('Retrieved PyDeps Path from Addon Prefs') log.info('Retrieved PyDeps Path from Addon Prefs')
path_pydeps = addon_prefs.path_addon_pydeps path_pydeps = addon_prefs.path_addon_pydeps
@ -67,7 +68,7 @@ def _check_pydeps(
""" """
def conform_pypi_package_deplock(deplock: str): def conform_pypi_package_deplock(deplock: str):
"""Conforms a <package>==<version> de-lock to match if pypi considers them the same (PyPi is case-insensitive and considers -/_ to be the same) """Conforms a <package>==<version> de-lock to match if pypi considers them the same (PyPi is case-insensitive and considers -/_ to be the same).
See <https://peps.python.org/pep-0426/#name> See <https://peps.python.org/pep-0426/#name>
""" """
@ -127,7 +128,7 @@ def check_pydeps(path_deps: Path):
global DEPS_OK # noqa: PLW0603 global DEPS_OK # noqa: PLW0603
global DEPS_ISSUES # noqa: PLW0603 global DEPS_ISSUES # noqa: PLW0603
if len(issues := _check_pydeps(info.PATH_REQS, path_deps)) > 0: if len(issues := _check_pydeps(ct.addon.PATH_REQS, path_deps)) > 0:
log.info('PyDeps Check Failed') log.info('PyDeps Check Failed')
log.debug('%s', ', '.join(issues)) log.debug('%s', ', '.join(issues))

View File

@ -105,6 +105,11 @@ def parse_abbrev_symbols_to_units(expr: sp.Basic) -> sp.Basic:
#################### ####################
# - Units <-> Scalars # - Units <-> Scalars
#################### ####################
def scaling_factor(unit_from: spu.Quantity, unit_to: spu.Quantity) -> sp.Basic:
if unit_from.dimension == unit_to.dimension:
return spu.convert_to(unit_from, unit_to) / unit_to
def scale_to_unit(expr: sp.Expr, unit: spu.Quantity) -> typ.Any: def scale_to_unit(expr: sp.Expr, unit: spu.Quantity) -> typ.Any:
## TODO: An LFU cache could do better than an LRU. ## TODO: An LFU cache could do better than an LRU.
unitless_expr = spu.convert_to(expr, unit) / unit unitless_expr = spu.convert_to(expr, unit) / unit