use super::{
convert::CandidateSource,
dto::SavedDesignDto,
format::{RankedLayout, SerializeInput, serialize_plan_to_toml},
};
use indicatrix_cut_core::rough_plan::{
Axis, BarCut, CutOrder, CutPlan, PlacedStone, PlanSettings, RoughBase, RoughCut, RoughLayout,
RoughModel, SlabCut, fit::StonePose,
};
use indicatrix_vault::model::solid_extents::SOLID_EXTENTS_VERSION;
pub(super) const IDENTITY: [[f64; 3]; 3] = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
pub(super) const QUARTER_TURN: [[f64; 3]; 3] = [[0.0, 0.0, -1.0], [0.0, 1.0, 0.0], [1.0, 0.0, 0.0]];
pub(super) const TURN_3_4_5: [[f64; 3]; 3] = [[0.6, 0.0, -0.8], [0.0, 1.0, 0.0], [0.8, 0.0, 0.6]];
pub(super) const FACING_X: [[f64; 3]; 3] = [[0.0, 0.0, 1.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
pub(super) const MINIMAL_HEADER: &str = "format = \"indicatrix-rough-plan\"\nversion = 1\n";
pub(super) const SPECIFIC_GRAVITY: f64 = 3.51;
pub(super) const LIBRARY_STAMP: u32 = 77;
fn stone(
entry_id: i64,
origin: [f64; 3],
piece: [f64; 3],
volume_mm3: f64,
axes: [[f64; 3]; 3],
) -> PlacedStone {
PlacedStone {
entry_id,
piece_origin_mm: origin,
piece_size_mm: piece,
stone_size_mm: piece.map(|side| side - 0.4),
table_axis: Axis::Y,
carat: volume_mm3 * SPECIFIC_GRAVITY / 200.0,
volume_mm3,
pose: StonePose {
center_mm: [0, 1, 2].map(|i| 0.5f64.mul_add(piece[i], origin[i])),
axes,
mm_per_unit: 2.0,
},
}
}
pub(super) fn sample_stones() -> Vec<PlacedStone> {
vec![
stone(1, [0.0, 0.0, 0.0], [3.0, 4.0, 3.5], 10.0, IDENTITY),
stone(2, [0.0, 0.0, 3.8], [3.0, 4.0, 3.2], 8.0, QUARTER_TURN),
stone(1, [0.0, 4.3, 0.0], [2.5, 3.0, 3.0], 5.0, TURN_3_4_5),
]
}
pub(super) fn sample_layout_with_volume(rough_volume_mm3: f64) -> RoughLayout {
let stones = sample_stones();
let total_volume_mm3: f64 = stones.iter().map(|s| s.volume_mm3).sum();
let total_carat: f64 = stones.iter().map(|s| s.carat).sum();
RoughLayout {
cut_order: CutOrder::Yxz,
stones,
cut_plan: CutPlan {
slabs: vec![
SlabCut {
thickness_mm: 4.0,
bars: vec![BarCut {
width_mm: 3.0,
pieces_mm: vec![3.5, 3.2],
}],
},
SlabCut {
thickness_mm: 3.0,
bars: vec![BarCut {
width_mm: 2.5,
pieces_mm: vec![3.0],
}],
},
],
},
total_carat,
total_volume_mm3,
yield_fraction: total_volume_mm3 / rough_volume_mm3,
exact_fit: false,
}
}
pub(super) fn volume_of(model: &RoughModel) -> f64 {
model
.measure()
.expect("the test rough has a volume")
.volume_mm3
}
pub(super) fn sample_layout_in(model: &RoughModel) -> RoughLayout {
sample_layout_with_volume(volume_of(model))
}
pub(super) fn sample_layout() -> RoughLayout {
sample_layout_in(&plain_block())
}
pub(super) fn layout_with_axes(model: &RoughModel, axes: [[f64; 3]; 3]) -> RoughLayout {
let mut layout = sample_layout_in(model);
layout.stones[1].pose.axes = axes;
layout
}
pub(super) fn exact_fit_layout_in(model: &RoughModel) -> RoughLayout {
let mut placed = stone(1, [1.0, 1.0, 1.0], [2.0, 3.0, 2.5], 12.0, IDENTITY);
placed.stone_size_mm = placed.piece_size_mm;
RoughLayout {
cut_order: CutOrder::Xyz,
total_carat: placed.carat,
total_volume_mm3: placed.volume_mm3,
yield_fraction: placed.volume_mm3 / volume_of(model),
stones: vec![placed],
cut_plan: CutPlan {
slabs: vec![SlabCut {
thickness_mm: 2.0,
bars: vec![BarCut {
width_mm: 3.0,
pieces_mm: vec![2.5],
}],
}],
},
exact_fit: true,
}
}
pub(super) fn settings() -> PlanSettings {
PlanSettings {
count: 6,
min_count: 1,
kerf_mm: 0.3,
allowance_mm: 0.2,
skin_mm: 0.0,
min_width_mm: 1.0,
specific_gravity: SPECIFIC_GRAVITY,
}
}
pub(super) fn designs() -> Vec<SavedDesignDto> {
vec![
SavedDesignDto {
entry_id: 1,
title: "Barion Oval".to_string(),
fingerprint: [1.3912, 0.67512, 0.4123],
width_caliper: Some(1.25),
extents_version: SOLID_EXTENTS_VERSION,
},
SavedDesignDto {
entry_id: 2,
title: "Emerald".to_string(),
fingerprint: [1.5, 0.6, 0.35],
width_caliper: Some(0.8),
extents_version: SOLID_EXTENTS_VERSION,
},
]
}
pub(super) fn write_with(
model: &RoughModel,
layouts: &[RankedLayout<'_>],
designs: &[SavedDesignDto],
) -> String {
serialize_plan_to_toml(&SerializeInput {
name: "Test plan",
created_at: 1_790_000_000,
library_id: Some(LIBRARY_STAMP),
model,
material_name: "Aquamarine",
weighed_ct: Some(8.9),
settings: &settings(),
candidate_source: CandidateSource::Library,
designs,
layouts,
})
.expect("the plan serialises")
}
pub(super) fn write(model: &RoughModel, layouts: &[RoughLayout]) -> String {
let ranked: Vec<RankedLayout<'_>> = layouts
.iter()
.enumerate()
.map(|(i, layout)| RankedLayout {
rank: i + 1,
layout,
})
.collect();
let used: Vec<SavedDesignDto> = designs()
.into_iter()
.filter(|design| {
layouts
.iter()
.any(|layout| layout.stones.iter().any(|s| s.entry_id == design.entry_id))
})
.collect();
write_with(model, &ranked, &used)
}
pub(super) fn block(cuts: Vec<RoughCut>) -> RoughModel {
RoughModel::new(
RoughBase::Block {
x_mm: 20.0,
y_mm: 12.0,
z_mm: 10.0,
},
cuts,
)
}
pub(super) fn plain_block() -> RoughModel {
block(Vec::new())
}
pub(super) fn expect_error(text: &str) -> String {
super::format::parse_and_validate_plan(text).expect_err("the plan must be refused")
}