use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
pub(super) fn point_index(doc: &SketchDoc) -> HashMap<String, &SketchPoint> {
let mut by_id: HashMap<String, &SketchPoint> = HashMap::with_capacity(doc.points.len());
for p in &doc.points {
by_id.insert(id_key(&p.id), p);
}
by_id
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct SketchPoint {
pub id: Value,
pub x: f64,
pub y: f64,
#[serde(default)]
pub fixed: bool,
#[serde(default)]
pub construction: bool,
#[serde(default, rename = "externalReference")]
pub external_reference: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct SketchGeometry {
pub id: Value,
#[serde(rename = "type")]
pub geom_type: String,
#[serde(default)]
pub points: Vec<Value>,
#[serde(flatten)]
pub extra: Map<String, Value>,
}
impl SketchGeometry {
pub fn construction(&self) -> bool {
self.extra
.get("construction")
.and_then(Value::as_bool)
.unwrap_or(false)
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(transparent)]
pub struct SketchConstraint {
pub raw: Map<String, Value>,
}
impl SketchConstraint {
pub fn ctype(&self) -> Option<&str> {
self.raw.get("type").and_then(Value::as_str)
}
pub fn points(&self) -> &[Value] {
self.raw
.get("points")
.and_then(Value::as_array)
.map(Vec::as_slice)
.unwrap_or(&[])
}
pub fn temporary(&self) -> bool {
self.raw
.get("temporary")
.and_then(Value::as_bool)
.unwrap_or(false)
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
pub struct SketchDoc {
#[serde(default)]
pub points: Vec<SketchPoint>,
#[serde(default)]
pub geometries: Vec<SketchGeometry>,
#[serde(default)]
pub constraints: Vec<SketchConstraint>,
}
impl SketchDoc {
pub fn point(&self, id: &Value) -> Option<&SketchPoint> {
let key = id_key(id);
self.points.iter().find(|p| id_key(&p.id) == key)
}
pub fn point_mut(&mut self, id: &Value) -> Option<&mut SketchPoint> {
let key = id_key(id);
self.points.iter_mut().find(|p| id_key(&p.id) == key)
}
pub fn constrained_point_keys(&self) -> std::collections::HashSet<String> {
let mut set = std::collections::HashSet::new();
for c in &self.constraints {
if c.temporary() {
continue;
}
for pid in c.points() {
set.insert(id_key(pid));
}
}
set
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
pub struct SketchDiagnostics {
#[serde(default)]
pub dof: i64,
#[serde(default)]
pub rank: i64,
#[serde(default)]
pub unknowns: i64,
#[serde(default)]
pub equations: i64,
#[serde(default)]
pub redundant: i64,
#[serde(default)]
pub status: String,
#[serde(default)]
pub conflicting: bool,
#[serde(default, rename = "conflictingConstraints")]
pub conflicting_constraints: Vec<String>,
#[serde(default, rename = "pointMobility")]
pub point_mobility: std::collections::BTreeMap<String, String>,
#[serde(default, rename = "geometryMobility")]
pub geometry_mobility: std::collections::BTreeMap<String, String>,
}
impl SketchDiagnostics {
pub fn constraint_conflicting(&self, id: &Value) -> bool {
if self.conflicting_constraints.is_empty() {
return false;
}
let key = id_key(id);
self.conflicting_constraints.iter().any(|c| *c == key)
}
}
impl SketchDiagnostics {
pub fn point_movable(&self, id: &Value) -> Option<bool> {
self.point_mobility
.get(&id_key(id))
.map(|v| v == "movable")
}
pub fn geometry_movable(&self, id: &Value) -> Option<bool> {
self.geometry_mobility
.get(&id_key(id))
.map(|v| v == "movable")
}
}
pub use brep_kernel::sketch_id_key as id_key;
impl SketchDoc {
pub fn next_point_id(&self) -> Value {
let ids: Vec<&Value> = self.points.iter().map(|p| &p.id).collect();
mint_next_id(&ids)
}
pub fn next_geometry_id(&self) -> Value {
let ids: Vec<&Value> = self.geometries.iter().map(|g| &g.id).collect();
mint_next_id(&ids)
}
pub fn snap_or_add_point(&mut self, u: f64, v: f64, radius: f64) -> Value {
let mut best: Option<(f64, Value)> = None;
for p in &self.points {
let d = ((p.x - u).powi(2) + (p.y - v).powi(2)).sqrt();
if d <= radius && best.as_ref().map_or(true, |(bd, _)| d < *bd) {
best = Some((d, p.id.clone()));
}
}
if let Some((_, id)) = best {
return id;
}
let id = self.next_point_id();
self.points.push(SketchPoint {
id: id.clone(),
x: u,
y: v,
fixed: false,
construction: false,
external_reference: false,
});
id
}
}
impl SketchDoc {
pub fn next_constraint_id(&self) -> Value {
let ids: Vec<&Value> = self
.constraints
.iter()
.filter_map(|c| c.raw.get("id"))
.collect();
mint_next_id(&ids)
}
pub fn geometry(&self, id: &Value) -> Option<&SketchGeometry> {
let key = id_key(id);
self.geometries.iter().find(|g| id_key(&g.id) == key)
}
pub fn geometry_mut(&mut self, id: &Value) -> Option<&mut SketchGeometry> {
let key = id_key(id);
self.geometries.iter_mut().find(|g| id_key(&g.id) == key)
}
}
fn id_num(value: &Value) -> Option<f64> {
match value {
Value::Number(number) => number.as_f64(),
Value::String(text) => text.trim().parse::<f64>().ok(),
_ => None,
}
}
fn mint_next_id(ids: &[&Value]) -> Value {
let mut max: Option<i64> = None;
for &id in ids {
if let Some(n) = id_num(id) {
if n.is_finite() {
let i = n.floor() as i64;
max = Some(max.map_or(i, |m| m.max(i)));
}
}
}
let mut candidate = max.map(|m| m + 1).unwrap_or(ids.len() as i64);
let existing: std::collections::HashSet<String> = ids.iter().map(|&id| id_key(id)).collect();
while existing.contains(&id_key(&Value::from(candidate))) {
candidate += 1;
}
Value::from(candidate)
}