refactor: More changes to docs/layout
parent
8dece384ad
commit
ff5d71aeff
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
"""
|
||||||
|
|
|
@ -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__)
|
||||||
|
@ -30,8 +31,8 @@ def importable_addon_deps(path_deps: Path):
|
||||||
yield
|
yield
|
||||||
finally:
|
finally:
|
||||||
pass
|
pass
|
||||||
#log.info('Removing Path from sys.path: %s', str(os_path))
|
# log.info('Removing Path from sys.path: %s', str(os_path))
|
||||||
#sys.path.remove(os_path)
|
# sys.path.remove(os_path)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
|
@ -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))
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue