use crate::attribute::{Attribute, SubPhysicalUnit};
use crate::description::util::IterUtil;
use crate::fixture_type::FixtureType;
use crate::geometry::Geometry;
use crate::physical_descriptions::{ColorSpace, DmxProfile, Emitter, Filter, Gamut};
use crate::validation::{ValidationError, ValidationErrorType, ValidationObject, ValidationResult};
use crate::values::{non_empty_string, DmxValue, Name, Node, NodeExt};
use crate::wheel::{Wheel, WheelSlot};
use serde::de::value::StrDeserializer;
use serde::de::{Error, Unexpected, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::Formatter;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DmxMode {
#[serde(rename = "@Name", skip_serializing_if = "Option::is_none")]
pub name: Option<Name>,
#[serde(rename = "@Description", default)]
pub description: String,
#[serde(rename = "@Geometry")]
pub geometry: Option<Name>,
#[serde(
rename = "DMXChannels",
skip_serializing_if = "Vec::is_empty",
serialize_with = "serialize_dmx_channels",
deserialize_with = "deserialize_dmx_channels"
)]
pub dmx_channels: Vec<DmxChannel>,
#[serde(
rename = "Relations",
skip_serializing_if = "Vec::is_empty",
serialize_with = "serialize_relations",
deserialize_with = "deserialize_relations",
default
)]
pub relations: Vec<Relation>,
#[serde(
rename = "FTMacros",
skip_serializing_if = "Vec::is_empty",
serialize_with = "serialize_ft_macros",
deserialize_with = "deserialize_ft_macros",
default
)]
pub ft_macros: Vec<FtMacro>,
}
impl DmxMode {
pub fn geometry<'s>(&self, parent_fixture_type: &'s FixtureType) -> Option<&'s Geometry> {
parent_fixture_type.root_geometry(self.geometry.as_ref()?)
}
pub fn dmx_channel(&self, name: &str) -> Option<&DmxChannel> {
self.dmx_channels
.iter()
.find(|channel| channel.name().as_ref() == name)
}
pub fn relation(&self, name: &str) -> Option<&Relation> {
self.relations
.iter()
.find(|relation| relation.name.as_ref().map(Name::as_ref) == Some(name))
}
pub fn ft_macro(&self, name: &str) -> Option<&FtMacro> {
self.ft_macros
.iter()
.find(|m| m.name.as_ref().map(Name::as_ref) == Some(name))
}
pub fn validate(&self, parent_fixture_type: &FixtureType, result: &mut ValidationResult) {
if self.name.is_none() {
result.errors.push(ValidationError::new(
ValidationObject::DmxMode,
None,
ValidationErrorType::MissingName,
));
}
let name = self.name.as_ref();
if let (Some(geometry), None) = (&self.geometry, self.geometry(parent_fixture_type)) {
result.errors.push(ValidationError::new(
ValidationObject::DmxMode,
name.map(Name::to_string),
ValidationErrorType::LinkNotFound(
ValidationObject::Geometry,
Node::new([geometry.clone()]),
),
));
}
let duplicate_channel_name = self
.dmx_channels
.iter()
.map(|channel| channel.name())
.find_duplicate();
if let Some(name) = duplicate_channel_name {
result.errors.push(ValidationError::new(
ValidationObject::DmxChannel,
name.to_string(),
ValidationErrorType::DuplicateName,
));
}
let duplicate_relation_name = self
.relations
.iter()
.filter_map(|relation| relation.name.as_ref())
.find_duplicate();
if let Some(name) = duplicate_relation_name {
result.errors.push(ValidationError::new(
ValidationObject::Relation,
name.to_string(),
ValidationErrorType::DuplicateName,
));
}
let duplicate_macro_name = self
.ft_macros
.iter()
.filter_map(|ft_macro| ft_macro.name.as_ref())
.find_duplicate();
if let Some(name) = duplicate_macro_name {
result.errors.push(ValidationError::new(
ValidationObject::FtMacro,
name.to_string(),
ValidationErrorType::DuplicateName,
));
}
for dmx_channel in &self.dmx_channels {
dmx_channel.validate(parent_fixture_type, self, result);
}
for relation in &self.relations {
relation.validate(self, result);
}
for ft_macro in &self.ft_macros {
ft_macro.validate(self, result);
}
}
}
define_collect_helper!("DMXChannel" (serialize_dmx_channels, deserialize_dmx_channels) -> DmxChannel);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DmxChannel {
#[serde(rename = "@DMXBreak", default)]
pub dmx_break: DmxBreak,
#[serde(
rename = "@Offset",
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_dmx_channel_offset",
deserialize_with = "deserialize_dmx_channel_offset"
)]
pub offset: Option<Vec<i32>>,
#[serde(rename = "@InitialFunction", skip_serializing_if = "Option::is_none")]
pub initial_function: Option<Node>,
#[serde(
rename = "@Highlight",
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_dmx_channel_highlight",
deserialize_with = "deserialize_dmx_channel_highlight"
)]
pub highlight: Option<DmxValue>,
#[serde(rename = "@Geometry")]
pub geometry: Name,
#[serde(
rename = "LogicalChannel",
skip_serializing_if = "Vec::is_empty",
default
)]
pub logical_channels: Vec<LogicalChannel>,
}
impl DmxChannel {
pub fn name(&self) -> Name {
let logical_channel_name = self
.logical_channels
.first()
.map(|channel| channel.name().as_ref())
.unwrap_or("");
Name::new(format!("{}_{}", self.geometry, logical_channel_name)).unwrap()
}
pub fn initial_function(&self) -> Option<(&LogicalChannel, &ChannelFunction)> {
let Some(node) = &self.initial_function else {
let first_channel = self.logical_channels.first()?;
let first_function = first_channel.channel_functions.first()?;
return Some((first_channel, first_function));
};
let (channel_name, tail) = node.split_first()?;
if channel_name != &self.name() {
return None;
}
let (logical_channel_name, tail) = tail.split_first()?;
let logical_channel = self.logical_channel(logical_channel_name)?;
let function_name = tail.single()?;
let function = logical_channel.channel_function(function_name)?;
Some((logical_channel, function))
}
pub fn geometry<'s>(&self, parent_fixture_type: &'s FixtureType) -> Option<&'s Geometry> {
parent_fixture_type.nested_geometry(&self.geometry)
}
pub fn logical_channel(&self, name: &str) -> Option<&LogicalChannel> {
self.logical_channels
.iter()
.find(|channel| channel.name().as_ref() == name)
}
pub fn validate(
&self,
parent_fixture_type: &FixtureType,
parent_dmx_mode: &DmxMode,
result: &mut ValidationResult,
) {
if let (Some(initial_function), None) = (&self.initial_function, self.initial_function()) {
result.errors.push(ValidationError::new(
ValidationObject::DmxChannel,
self.name().to_string(),
ValidationErrorType::LinkNotFound(
ValidationObject::ChannelFunction,
initial_function.clone(),
),
));
}
if self.geometry(parent_fixture_type).is_none() {
result.errors.push(ValidationError::new(
ValidationObject::DmxChannel,
self.name().to_string(),
ValidationErrorType::LinkNotFound(
ValidationObject::Geometry,
Node::new([self.geometry.clone()]),
),
))
}
let duplicate_logical_channel_name = self
.logical_channels
.iter()
.map(|logical_channel| logical_channel.name())
.find_duplicate();
if let Some(name) = duplicate_logical_channel_name {
result.errors.push(ValidationError::new(
ValidationObject::LogicalChannel,
name.to_string(),
ValidationErrorType::DuplicateName,
));
}
for logical_channel in &self.logical_channels {
logical_channel.validate(parent_fixture_type, parent_dmx_mode, result);
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DmxBreak {
Value(i32),
Overwrite,
}
impl Serialize for DmxBreak {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match *self {
DmxBreak::Value(value) => serializer.serialize_i32(value),
DmxBreak::Overwrite => serializer.serialize_str("Overwrite"),
}
}
}
impl<'de> Deserialize<'de> for DmxBreak {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct DmxBreakVisitor;
impl<'de> Visitor<'de> for DmxBreakVisitor {
type Value = DmxBreak;
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
formatter.write_str("`Overwrite` or an integer")
}
fn visit_i32<E>(self, v: i32) -> Result<Self::Value, E>
where
E: Error,
{
Ok(DmxBreak::Value(v))
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: Error,
{
if v == "Overwrite" {
return Ok(DmxBreak::Overwrite);
}
match v.parse::<i32>() {
Ok(value) => Ok(DmxBreak::Value(value)),
Err(_) => Err(E::invalid_value(Unexpected::Str(v), &self)),
}
}
}
deserializer.deserialize_str(DmxBreakVisitor)
}
}
impl Default for DmxBreak {
fn default() -> Self {
DmxBreak::Value(1)
}
}
fn serialize_dmx_channel_offset<S>(
value: &Option<Vec<i32>>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match value {
None => serializer.serialize_str("None"),
Some(values) => serializer.serialize_str(&values.iter().join(",")),
}
}
fn deserialize_dmx_channel_offset<'de, D>(deserializer: D) -> Result<Option<Vec<i32>>, D::Error>
where
D: Deserializer<'de>,
{
let s: String = Deserialize::deserialize(deserializer)?;
let s = s.trim();
if s == "None" || s.is_empty() {
return Ok(None);
}
let values: Result<Vec<_>, _> = s
.split(',')
.map(|value| {
value.parse::<i32>().map_err(|_| {
D::Error::invalid_value(
Unexpected::Str(value),
&"an integer between -2^31 and 2^31",
)
})
})
.collect();
Ok(Some(values?))
}
fn serialize_dmx_channel_highlight<S>(
value: &Option<DmxValue>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match value {
None => serializer.serialize_str("None"),
Some(value) => value.serialize(serializer),
}
}
fn deserialize_dmx_channel_highlight<'de, D>(deserializer: D) -> Result<Option<DmxValue>, D::Error>
where
D: Deserializer<'de>,
{
struct DmxHighlightVisitor;
impl<'de> Visitor<'de> for DmxHighlightVisitor {
type Value = Option<DmxValue>;
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
formatter.write_str("`None` or a DMX value in the format uint/n or uint/ns")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: Error,
{
if v == "None" {
Ok(None)
} else {
Ok(Some(DmxValue::deserialize(StrDeserializer::new(v))?))
}
}
}
deserializer.deserialize_str(DmxHighlightVisitor)
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LogicalChannel {
#[serde(rename = "@Attribute")]
pub attribute: Node,
#[serde(
rename = "@Snap",
skip_serializing_if = "skip_serializing_logical_channel_snap",
serialize_with = "serialize_logical_channel_snap",
deserialize_with = "deserialize_logical_channel_snap",
default
)]
pub snap: bool,
#[serde(rename = "@Master", default)]
pub master: LogicalChannelMaster,
#[serde(rename = "@MibFade", default)]
pub mib_fade: f64,
#[serde(rename = "@DMXChangeTimeLimit", default)]
pub dmx_change_time_limit: f64,
#[serde(
rename = "ChannelFunction",
skip_serializing_if = "Vec::is_empty",
default
)]
pub channel_functions: Vec<ChannelFunction>,
}
impl LogicalChannel {
pub fn name(&self) -> &Name {
self.attribute.first().unwrap()
}
pub fn attribute<'s>(&self, parent_fixture_type: &'s FixtureType) -> Option<&'s Attribute> {
let name = self.attribute.single()?;
parent_fixture_type.attribute_definitions.attribute(name)
}
pub fn channel_function(&self, name: &str) -> Option<&ChannelFunction> {
self.channel_functions
.iter()
.find(|func| func.name.as_ref().map(Name::as_ref) == Some(name))
}
pub fn validate(
&self,
parent_fixture_type: &FixtureType,
parent_dmx_mode: &DmxMode,
result: &mut ValidationResult,
) {
if self.attribute(parent_fixture_type).is_none() {
result.errors.push(ValidationError::new(
ValidationObject::LogicalChannel,
self.name().to_string(),
ValidationErrorType::LinkNotFound(
ValidationObject::Attribute,
self.attribute.clone(),
),
));
}
let duplicate_channel_function_name = self
.channel_functions
.iter()
.filter_map(|channel_function| channel_function.name.as_ref())
.find_duplicate();
if let Some(name) = duplicate_channel_function_name {
result.errors.push(ValidationError::new(
ValidationObject::ChannelFunction,
name.to_string(),
ValidationErrorType::DuplicateName,
));
}
for channel_function in &self.channel_functions {
channel_function.validate(parent_fixture_type, parent_dmx_mode, result);
}
}
}
fn skip_serializing_logical_channel_snap(value: &bool) -> bool {
!*value
}
fn serialize_logical_channel_snap<S>(value: &bool, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(match value {
true => "Yes",
false => "No",
})
}
fn deserialize_logical_channel_snap<'de, D>(deserializer: D) -> Result<bool, D::Error>
where
D: Deserializer<'de>,
{
struct SnapVisitor;
impl<'de> Visitor<'de> for SnapVisitor {
type Value = bool;
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
formatter.write_str("`Yes`, `No`, `On` or `Off`")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: Error,
{
match v {
"Yes" | "On" => Ok(true),
"No" | "Off" => Ok(false),
_ => Err(E::invalid_value(Unexpected::Str(v), &self)),
}
}
}
deserializer.deserialize_str(SnapVisitor)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
pub enum LogicalChannelMaster {
Grand,
Group,
#[default]
#[serde(other)]
None,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ChannelFunction {
#[serde(rename = "@Name", skip_serializing_if = "Option::is_none")]
pub name: Option<Name>,
#[serde(rename = "@Attribute", default = "default_channel_function_attribute")]
pub attribute: Node,
#[serde(rename = "@OriginalAttribute", default)]
pub original_attribute: String,
#[serde(rename = "@DMXFrom", default)]
pub dmx_from: DmxValue,
#[serde(rename = "@Default", default)]
pub default: DmxValue,
#[serde(
rename = "@PhysicalFrom",
default = "default_channel_function_physical_from"
)]
pub physical_from: f64,
#[serde(
rename = "@PhysicalTo",
default = "default_channel_function_physical_to"
)]
pub physical_to: f64,
#[serde(rename = "@RealFade", default)]
pub real_fade: f64,
#[serde(rename = "@RealAcceleration", default)]
pub real_acceleration: f64,
#[serde(rename = "@Wheel", skip_serializing_if = "Option::is_none")]
pub wheel: Option<Node>,
#[serde(rename = "@Emitter", skip_serializing_if = "Option::is_none")]
pub emitter: Option<Node>,
#[serde(rename = "@Filter", skip_serializing_if = "Option::is_none")]
pub filter: Option<Node>,
#[serde(rename = "@ColorSpace", skip_serializing_if = "Option::is_none")]
pub color_space: Option<Node>,
#[serde(rename = "@Gamut", skip_serializing_if = "Option::is_none")]
pub gamut: Option<Node>,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub mode_master: Option<ModeMasterNode>,
#[serde(rename = "@DMXProfile", skip_serializing_if = "Option::is_none")]
pub dmx_profile: Option<Node>,
#[serde(rename = "@Min", skip_serializing_if = "Option::is_none")]
pub min: Option<f64>,
#[serde(rename = "@Max", skip_serializing_if = "Option::is_none")]
pub max: Option<f64>,
#[serde(
rename = "@CustomName",
skip_serializing_if = "Option::is_none",
deserialize_with = "non_empty_string",
default
)]
pub custom_name: Option<String>,
#[serde(rename = "ChannelSet", skip_serializing_if = "Vec::is_empty", default)]
pub channel_sets: Vec<ChannelSet>,
#[serde(
rename = "SubChannelSet",
skip_serializing_if = "Vec::is_empty",
default
)]
pub sub_channel_sets: Vec<SubChannelSet>,
}
impl ChannelFunction {
pub fn attribute<'s>(&self, parent_fixture_type: &'s FixtureType) -> Option<&'s Attribute> {
let name = self.attribute.single()?;
parent_fixture_type.attribute_definitions.attribute(name)
}
pub fn wheel<'s>(&self, parent_fixture_type: &'s FixtureType) -> Option<&'s Wheel> {
let name = self.wheel.as_ref()?.single()?;
parent_fixture_type.wheel(name)
}
pub fn emitter<'s>(&self, parent_fixture_type: &'s FixtureType) -> Option<&'s Emitter> {
let name = self.emitter.as_ref()?.single()?;
parent_fixture_type.physical_descriptions.emitter(name)
}
pub fn filter<'s>(&self, parent_fixture_type: &'s FixtureType) -> Option<&'s Filter> {
let name = self.filter.as_ref()?.single()?;
parent_fixture_type.physical_descriptions.filter(name)
}
pub fn color_space<'s>(&self, parent_fixture_type: &'s FixtureType) -> Option<&'s ColorSpace> {
let name = self.color_space.as_ref()?.single()?;
parent_fixture_type.physical_descriptions.color_space(name)
}
pub fn gamut<'s>(&self, parent_fixture_type: &'s FixtureType) -> Option<&'s Gamut> {
let name = self.gamut.as_ref()?.single()?;
parent_fixture_type.physical_descriptions.gamut(name)
}
pub fn dmx_profile<'s>(&self, parent_fixture_type: &'s FixtureType) -> Option<&'s DmxProfile> {
let name = self.dmx_profile.as_ref()?.single()?;
parent_fixture_type.physical_descriptions.dmx_profile(name)
}
pub fn min(&self) -> f64 {
self.min.unwrap_or(self.physical_from)
}
pub fn max(&self) -> f64 {
self.max.unwrap_or(self.physical_to)
}
pub fn channel_set(&self, name: &str) -> Option<&ChannelSet> {
self.channel_sets
.iter()
.find(|channel_set| channel_set.name.as_ref().map(Name::as_ref) == Some(name))
}
pub fn sub_channel_set(&self, name: &str) -> Option<&SubChannelSet> {
self.sub_channel_sets
.iter()
.find(|sub| sub.name.as_ref().map(Name::as_ref) == Some(name))
}
pub fn validate(
&self,
parent_fixture_type: &FixtureType,
parent_dmx_mode: &DmxMode,
result: &mut ValidationResult,
) {
if self.name.is_none() {
result.errors.push(ValidationError::new(
ValidationObject::ChannelFunction,
None,
ValidationErrorType::MissingName,
));
}
let name = self.name.as_ref();
if self.attribute(parent_fixture_type).is_none() {
result.errors.push(ValidationError::new(
ValidationObject::ChannelFunction,
name.map(Name::to_string),
ValidationErrorType::LinkNotFound(
ValidationObject::Attribute,
self.attribute.clone(),
),
));
}
if let (Some(wheel), None) = (&self.wheel, self.wheel(parent_fixture_type)) {
result.errors.push(ValidationError::new(
ValidationObject::ChannelFunction,
name.map(Name::to_string),
ValidationErrorType::LinkNotFound(ValidationObject::Wheel, wheel.clone()),
));
}
if let (Some(emitter), None) = (&self.emitter, self.emitter(parent_fixture_type)) {
result.errors.push(ValidationError::new(
ValidationObject::ChannelFunction,
name.map(Name::to_string),
ValidationErrorType::LinkNotFound(ValidationObject::Emitter, emitter.clone()),
));
}
if let (Some(filter), None) = (&self.filter, self.filter(parent_fixture_type)) {
result.errors.push(ValidationError::new(
ValidationObject::ChannelFunction,
name.map(Name::to_string),
ValidationErrorType::LinkNotFound(ValidationObject::Filter, filter.clone()),
));
}
if let (Some(color_space), None) =
(&self.color_space, self.color_space(parent_fixture_type))
{
result.errors.push(ValidationError::new(
ValidationObject::ChannelFunction,
name.map(Name::to_string),
ValidationErrorType::LinkNotFound(
ValidationObject::ColorSpace,
color_space.clone(),
),
));
}
if let (Some(gamut), None) = (&self.gamut, self.gamut(parent_fixture_type)) {
result.errors.push(ValidationError::new(
ValidationObject::ChannelFunction,
name.map(Name::to_string),
ValidationErrorType::LinkNotFound(ValidationObject::Gamut, gamut.clone()),
));
}
if let Some(mode_master) = &self.mode_master {
if mode_master.mode_master(parent_dmx_mode).is_none() {
result.errors.push(ValidationError::new(
ValidationObject::ChannelFunction,
name.map(Name::to_string),
ValidationErrorType::LinkNotFound(
ValidationObject::ModeMaster,
mode_master.node.clone(),
),
));
}
}
if let (Some(dmx_profile), None) =
(&self.dmx_profile, self.dmx_profile(parent_fixture_type))
{
result.errors.push(ValidationError::new(
ValidationObject::ChannelFunction,
name.map(Name::to_string),
ValidationErrorType::LinkNotFound(
ValidationObject::DmxProfile,
dmx_profile.clone(),
),
));
}
for channel_set in &self.channel_sets {
channel_set.validate(parent_fixture_type, self, result);
}
for sub_channel_set in &self.sub_channel_sets {
sub_channel_set.validate(parent_fixture_type, result);
}
}
}
fn default_channel_function_attribute() -> Node {
Node::new(vec![Name::new("NoFeature").unwrap()])
}
fn default_channel_function_physical_from() -> f64 {
0.
}
fn default_channel_function_physical_to() -> f64 {
1.
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ModeMasterNode {
#[serde(rename = "@ModeMaster")]
pub node: Node,
#[serde(rename = "@ModeFrom", default)]
pub from: DmxValue,
#[serde(rename = "@ModeTo", default)]
pub to: DmxValue,
}
impl ModeMasterNode {
pub fn mode_master<'s>(&self, parent_dmx_mode: &'s DmxMode) -> Option<ModeMaster<'s>> {
let (channel_name, tail) = self.node.split_first()?;
let channel = parent_dmx_mode.dmx_channel(channel_name)?;
let Some((logical_channel_name, tail)) = tail.split_first() else {
return Some(ModeMaster::DmxChannel(channel));
};
let logical_channel = channel.logical_channel(logical_channel_name)?;
let function_name = tail.single()?;
let function = logical_channel.channel_function(function_name)?;
Some(ModeMaster::ChannelFunction(
channel,
logical_channel,
function,
))
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ModeMaster<'s> {
DmxChannel(&'s DmxChannel),
ChannelFunction(&'s DmxChannel, &'s LogicalChannel, &'s ChannelFunction),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ChannelSet {
#[serde(rename = "@Name", skip_serializing_if = "Option::is_none")]
pub name: Option<Name>,
#[serde(rename = "@DMXFrom")]
pub dmx_from: DmxValue,
#[serde(rename = "@PhysicalFrom", skip_serializing_if = "Option::is_none")]
pub physical_from: Option<f64>,
#[serde(rename = "@PhysicalTo", skip_serializing_if = "Option::is_none")]
pub physical_to: Option<f64>,
#[serde(
rename = "@WheelSlotIndex",
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_wheel_slot_index",
deserialize_with = "deserialize_wheel_slot_index",
default
)]
pub wheel_slot_index: Option<i32>,
}
impl ChannelSet {
pub fn physical_from(&self, parent_channel_function: &ChannelFunction) -> f64 {
self.physical_from
.unwrap_or(parent_channel_function.physical_from)
}
pub fn physical_to(&self, parent_channel_function: &ChannelFunction) -> f64 {
self.physical_to
.unwrap_or(parent_channel_function.physical_to)
}
pub fn wheel_slot<'s>(&self, parent_wheel: &'s Wheel) -> Option<&'s WheelSlot> {
parent_wheel.slots.get(self.wheel_slot_index? as usize)
}
pub fn validate(
&self,
parent_fixture_type: &FixtureType,
parent_channel_function: &ChannelFunction,
result: &mut ValidationResult,
) {
let name = self.name.as_ref();
if let (Some(wheel_slot_index), Some(wheel)) = (
self.wheel_slot_index,
parent_channel_function.wheel(parent_fixture_type),
) {
if self.wheel_slot(wheel).is_none() {
result.errors.push(ValidationError::new(
ValidationObject::ChannelSet,
name.map(Name::to_string),
ValidationErrorType::LinkNotFound(
ValidationObject::WheelSlot,
Node::new([Name::new_lossy(wheel_slot_index.to_string())]),
),
));
}
}
}
}
fn serialize_wheel_slot_index<S>(value: &Option<i32>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let serialize_value = value.map_or(0, |val| val.max(0) + 1);
serialize_value.serialize(serializer)
}
fn deserialize_wheel_slot_index<'de, D>(deserializer: D) -> Result<Option<i32>, D::Error>
where
D: Deserializer<'de>,
{
let serialize_value = i32::deserialize(deserializer)?;
if serialize_value <= 0 {
Ok(None)
} else {
Ok(Some(serialize_value - 1))
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SubChannelSet {
#[serde(rename = "@Name", skip_serializing_if = "Option::is_none")]
pub name: Option<Name>,
#[serde(rename = "@PhysicalFrom")]
pub physical_from: f64,
#[serde(rename = "@PhysicalTo")]
pub physical_to: f64,
#[serde(rename = "@SubPhysicalUnit")]
pub sub_physical_unit: Node,
#[serde(rename = "@DMXProfile", skip_serializing_if = "Option::is_none")]
pub dmx_profile: Option<Node>,
}
impl SubChannelSet {
pub fn sub_physical_unit<'s>(
&self,
parent_fixture_type: &'s FixtureType,
) -> Option<&'s SubPhysicalUnit> {
let (attribute_name, tail) = self.sub_physical_unit.split_first()?;
let attribute = parent_fixture_type
.attribute_definitions
.attribute(attribute_name)?;
let sub_name = tail.single()?;
attribute
.subphysical_units
.iter()
.find(|unit| unit.type_.as_str() == sub_name.as_ref())
}
pub fn dmx_profile<'s>(&self, parent_fixture_type: &'s FixtureType) -> Option<&'s DmxProfile> {
let name = self.dmx_profile.as_ref()?.single()?;
parent_fixture_type.physical_descriptions.dmx_profile(name)
}
pub fn validate(&self, parent_fixture_type: &FixtureType, result: &mut ValidationResult) {
if self.name.is_none() {
result.errors.push(ValidationError::new(
ValidationObject::SubChannelSet,
None,
ValidationErrorType::MissingName,
));
}
let name = self.name.as_ref();
if self.sub_physical_unit(parent_fixture_type).is_none() {
result.errors.push(ValidationError::new(
ValidationObject::SubChannelSet,
name.map(Name::to_string),
ValidationErrorType::LinkNotFound(
ValidationObject::SubPhysicalUnit,
self.sub_physical_unit.clone(),
),
));
}
if let (Some(dmx_profile), None) =
(&self.dmx_profile, self.dmx_profile(parent_fixture_type))
{
result.errors.push(ValidationError::new(
ValidationObject::SubChannelSet,
name.map(Name::to_string),
ValidationErrorType::LinkNotFound(
ValidationObject::DmxProfile,
dmx_profile.clone(),
),
));
}
}
}
define_collect_helper!("Relation" (serialize_relations, deserialize_relations) -> Relation);
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Relation {
#[serde(rename = "@Name", skip_serializing_if = "Option::is_none")]
pub name: Option<Name>,
#[serde(rename = "@Master")]
pub master: Node,
#[serde(rename = "@Follower")]
pub follower: Node,
#[serde(rename = "@Type")]
pub type_: RelationType,
}
impl Relation {
pub fn master<'s>(&self, parent_dmx_mode: &'s DmxMode) -> Option<&'s DmxChannel> {
let name = self.master.single()?;
parent_dmx_mode.dmx_channel(name)
}
pub fn follower<'s>(
&self,
parent_dmx_mode: &'s DmxMode,
) -> Option<(&'s DmxChannel, &'s LogicalChannel, &'s ChannelFunction)> {
let (channel_name, tail) = self.follower.split_first()?;
let channel = parent_dmx_mode.dmx_channel(channel_name)?;
let (logical_name, tail) = tail.split_first()?;
let logical = channel.logical_channel(logical_name)?;
let func_name = tail.single()?;
let func = logical.channel_function(func_name)?;
Some((channel, logical, func))
}
pub fn validate(&self, parent_dmx_mode: &DmxMode, result: &mut ValidationResult) {
if self.name.is_none() {
result.errors.push(ValidationError::new(
ValidationObject::Relation,
None,
ValidationErrorType::MissingName,
));
}
let name = self.name.as_ref();
if self.master(parent_dmx_mode).is_none() {
result.errors.push(ValidationError::new(
ValidationObject::Relation,
name.map(Name::to_string),
ValidationErrorType::LinkNotFound(
ValidationObject::DmxChannel,
self.master.clone(),
),
));
}
if self.follower(parent_dmx_mode).is_none() {
result.errors.push(ValidationError::new(
ValidationObject::Relation,
name.map(Name::to_string),
ValidationErrorType::LinkNotFound(
ValidationObject::ChannelFunction,
self.follower.clone(),
),
));
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum RelationType {
Multiply,
Override,
}
define_collect_helper!("FTMacro" (serialize_ft_macros, deserialize_ft_macros) -> FtMacro);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FtMacro {
#[serde(rename = "@Name", skip_serializing_if = "Option::is_none")]
pub name: Option<Name>,
#[serde(rename = "@ChannelFunction", skip_serializing_if = "Option::is_none")]
pub channel_function: Option<Node>,
#[serde(rename = "MacroDMX", skip_serializing_if = "Vec::is_empty", default)]
pub dmx: Vec<MacroDmx>,
}
impl FtMacro {
pub fn channel_function<'s>(
&self,
parent_dmx_mode: &'s DmxMode,
) -> Option<(&'s DmxChannel, &'s LogicalChannel, &'s ChannelFunction)> {
let (channel_name, tail) = self.channel_function.as_ref()?.split_first()?;
let channel = parent_dmx_mode.dmx_channel(channel_name)?;
let (logical_name, tail) = tail.split_first()?;
let logical = channel.logical_channel(logical_name)?;
let function_name = tail.single()?;
let function = logical.channel_function(function_name)?;
Some((channel, logical, function))
}
pub fn validate(&self, parent_dmx_mode: &DmxMode, result: &mut ValidationResult) {
if self.name.is_none() {
result.errors.push(ValidationError::new(
ValidationObject::FtMacro,
None,
ValidationErrorType::MissingName,
));
}
let name = self.name.as_ref();
if let (Some(channel_function), None) = (
&self.channel_function,
self.channel_function(parent_dmx_mode),
) {
result.errors.push(ValidationError::new(
ValidationObject::FtMacro,
name.map(Name::to_string),
ValidationErrorType::LinkNotFound(
ValidationObject::ChannelFunction,
channel_function.clone(),
),
));
}
let dmx_values = self
.dmx
.iter()
.flat_map(|dmx| dmx.steps.iter())
.flat_map(|step| step.values.iter());
for dmx_value in dmx_values {
if dmx_value.dmx_channel(parent_dmx_mode).is_none() {
result.errors.push(ValidationError::new(
ValidationObject::FtMacro,
name.map(Name::to_string),
ValidationErrorType::LinkNotFound(
ValidationObject::DmxChannel,
dmx_value.dmx_channel.clone(),
),
));
}
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MacroDmx {
#[serde(
rename = "MacroDMXStep",
skip_serializing_if = "Vec::is_empty",
default
)]
pub steps: Vec<MacroDmxStep>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MacroDmxStep {
#[serde(rename = "@Duration", default = "default_macro_dmx_step_duration")]
pub duration: f64,
#[serde(
rename = "MacroDMXValue",
skip_serializing_if = "Vec::is_empty",
default
)]
pub values: Vec<MacroDmxValue>,
}
fn default_macro_dmx_step_duration() -> f64 {
1.
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MacroDmxValue {
#[serde(rename = "@Value")]
pub value: DmxValue,
#[serde(rename = "@DMXChannel")]
pub dmx_channel: Node,
}
impl MacroDmxValue {
pub fn dmx_channel<'s>(&self, parent_dmx_mode: &'s DmxMode) -> Option<&'s DmxChannel> {
let name = self.dmx_channel.single()?;
parent_dmx_mode.dmx_channel(name)
}
}