Sketch on a cylinder primitive's caps; format 15 (#460)
* Sketch on a cylinder primitive's caps (#420) A solid.cylinder's caps carried no construction certificate, so a sketch on its top or bottom was refused unless fixed:true (#420's crossed cylinders, built from primitives). - kernel: FaceSource::CylinderCap { producer, high, plane }; Body::with_cylinder_certificate turns a cylinder's two extrude caps into certified caps. kernel::cylinder's output is unchanged for its other callers. - doc: FaceCertificate::CylinderCap { high }, named F<id>:top / F<id>:bottom as the cylinder's face names already are; New Body and Join cylinders are certified, shells carry it as their planar source. - format 15: designs can store the new certificate kind; version 14 designs need no change, and older builds ask for an update instead of meeting an unknown kind. Tests: top and bottom attachments follow a height and base edit and a reopen; crossed joined cylinders keep both caps attachable; a shelled cylinder's floor keeps its cap identity; malformed certificate JSON is refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YDDPmNdRoBgkHcLeUDKVfb * Format 15 also covers sketch_axis links (#448) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YDDPmNdRoBgkHcLeUDKVfb --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b24e478bc1
commit
49956c2797
10 files changed
+247
-23
No files matched your search
+80
-10
@@ -42,13 +42,54 @@ pub struct Body {
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum FaceSource {
|
||||
ExtrudeSide { segment: solidcraft_geom::Seg2, plane: Option<solidcraft_geom::Plane>, coalesced: bool },
|
||||
CertifiedExtrudeSide { producer: u64, sketch: u64, curve: String, plane: Option<solidcraft_geom::Plane> },
|
||||
ExtrudeCap { high: bool, region: solidcraft_geom::Region2, plane: solidcraft_geom::Plane },
|
||||
CertifiedExtrudeCap { producer: u64, sketch: u64, high: bool, outer: Vec<String>, holes: Vec<Vec<String>>, plane: solidcraft_geom::Plane },
|
||||
Box { producer: Option<u64>, axis: u8, high: bool, plane: solidcraft_geom::Plane },
|
||||
ShellPlanar { producer: Option<u64>, source: Box<FaceSource>, inner: bool, plane: solidcraft_geom::Plane },
|
||||
Imported { item: u64, face: u64, payload_sha256: Option<String>, producer: Option<u64> },
|
||||
ExtrudeSide {
|
||||
segment: solidcraft_geom::Seg2,
|
||||
plane: Option<solidcraft_geom::Plane>,
|
||||
coalesced: bool,
|
||||
},
|
||||
CertifiedExtrudeSide {
|
||||
producer: u64,
|
||||
sketch: u64,
|
||||
curve: String,
|
||||
plane: Option<solidcraft_geom::Plane>,
|
||||
},
|
||||
ExtrudeCap {
|
||||
high: bool,
|
||||
region: solidcraft_geom::Region2,
|
||||
plane: solidcraft_geom::Plane,
|
||||
},
|
||||
CertifiedExtrudeCap {
|
||||
producer: u64,
|
||||
sketch: u64,
|
||||
high: bool,
|
||||
outer: Vec<String>,
|
||||
holes: Vec<Vec<String>>,
|
||||
plane: solidcraft_geom::Plane,
|
||||
},
|
||||
Box {
|
||||
producer: Option<u64>,
|
||||
axis: u8,
|
||||
high: bool,
|
||||
plane: solidcraft_geom::Plane,
|
||||
},
|
||||
/// A cylinder primitive's cap: `high` at the far end (its `top`), else at the base.
|
||||
CylinderCap {
|
||||
producer: u64,
|
||||
high: bool,
|
||||
plane: solidcraft_geom::Plane,
|
||||
},
|
||||
ShellPlanar {
|
||||
producer: Option<u64>,
|
||||
source: Box<FaceSource>,
|
||||
inner: bool,
|
||||
plane: solidcraft_geom::Plane,
|
||||
},
|
||||
Imported {
|
||||
item: u64,
|
||||
face: u64,
|
||||
payload_sha256: Option<String>,
|
||||
producer: Option<u64>,
|
||||
},
|
||||
}
|
||||
|
||||
pub type CapSignature = (Vec<String>, Vec<Vec<String>>);
|
||||
@@ -58,9 +99,11 @@ impl FaceSource {
|
||||
fn plane_mut(&mut self) -> Option<&mut solidcraft_geom::Plane> {
|
||||
match self {
|
||||
Self::ExtrudeSide { plane, .. } | Self::CertifiedExtrudeSide { plane, .. } => plane.as_mut(),
|
||||
Self::ExtrudeCap { plane, .. } | Self::CertifiedExtrudeCap { plane, .. } | Self::Box { plane, .. } | Self::ShellPlanar { plane, .. } => {
|
||||
Some(plane)
|
||||
}
|
||||
Self::ExtrudeCap { plane, .. }
|
||||
| Self::CertifiedExtrudeCap { plane, .. }
|
||||
| Self::Box { plane, .. }
|
||||
| Self::CylinderCap { plane, .. }
|
||||
| Self::ShellPlanar { plane, .. } => Some(plane),
|
||||
Self::Imported { .. } => None,
|
||||
}
|
||||
}
|
||||
@@ -504,6 +547,7 @@ impl Body {
|
||||
FaceSource::ExtrudeCap { plane, .. }
|
||||
| FaceSource::CertifiedExtrudeCap { plane, .. }
|
||||
| FaceSource::Box { plane, .. }
|
||||
| FaceSource::CylinderCap { plane, .. }
|
||||
| FaceSource::ShellPlanar { plane, .. } => Some(*plane),
|
||||
FaceSource::Imported { .. } => self.planar_face(index),
|
||||
}
|
||||
@@ -529,6 +573,32 @@ impl Body {
|
||||
.flatten()
|
||||
}
|
||||
|
||||
/// Certify the two caps of a body built by [`crate::cylinder`]. Its wall stays uncertified.
|
||||
pub fn with_cylinder_certificate(mut self, producer: u64) -> Result<Self> {
|
||||
if producer == 0 {
|
||||
return Err(KernelError::Invalid("invalid cylinder producer".into()));
|
||||
}
|
||||
let Some(sources) = &self.face_sources else {
|
||||
return Err(KernelError::Failed("cylinder construction source".into()));
|
||||
};
|
||||
let mut sources = sources.as_ref().clone();
|
||||
let mut ends = [false; 2];
|
||||
for source in sources.iter_mut() {
|
||||
if let Some(FaceSource::ExtrudeCap { high, plane, .. }) = source {
|
||||
let end = ends.get_mut(usize::from(*high)).ok_or_else(|| KernelError::Failed("cylinder cap role".into()))?;
|
||||
if std::mem::replace(end, true) {
|
||||
return Err(KernelError::Failed("duplicate cylinder cap role".into()));
|
||||
}
|
||||
*source = Some(FaceSource::CylinderCap { producer, high: *high, plane: *plane });
|
||||
}
|
||||
}
|
||||
if ends != [true; 2] {
|
||||
return Err(KernelError::Failed("cylinder cap role count".into()));
|
||||
}
|
||||
self.face_sources = Some(std::sync::Arc::new(sources));
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn with_box_certificate(mut self, producer: u64) -> Result<Self> {
|
||||
if producer == 0 {
|
||||
return Err(KernelError::Invalid("invalid box producer".into()));
|
||||
|
||||
@@ -30,6 +30,7 @@ fn base_plane(source: &FaceSource, actual: &Plane) -> Option<Plane> {
|
||||
FaceSource::CertifiedExtrudeSide { plane: Some(p), .. }
|
||||
| FaceSource::CertifiedExtrudeCap { plane: p, .. }
|
||||
| FaceSource::Box { producer: Some(_), plane: p, .. }
|
||||
| FaceSource::CylinderCap { plane: p, .. }
|
||||
| FaceSource::ShellPlanar { producer: Some(_), plane: p, .. } => Some(*p),
|
||||
FaceSource::Imported { producer: Some(_), payload_sha256: Some(_), .. } => Some(*actual),
|
||||
_ => None,
|
||||
@@ -41,6 +42,7 @@ pub(super) fn certified_plane(source: &FaceSource) -> Option<Plane> {
|
||||
FaceSource::CertifiedExtrudeSide { plane: Some(p), .. }
|
||||
| FaceSource::CertifiedExtrudeCap { plane: p, .. }
|
||||
| FaceSource::Box { producer: Some(_), plane: p, .. }
|
||||
| FaceSource::CylinderCap { plane: p, .. }
|
||||
| FaceSource::ShellPlanar { producer: Some(_), plane: p, .. } => Some(*p),
|
||||
_ => None,
|
||||
}
|
||||
@@ -93,7 +95,7 @@ pub(super) fn descriptor_cost(source: &FaceSource) -> Option<usize> {
|
||||
}
|
||||
cost = cost.checked_add(count.checked_mul(128)?)?;
|
||||
}
|
||||
FaceSource::ExtrudeSide { .. } | FaceSource::Imported { .. } | FaceSource::Box { .. } => {}
|
||||
FaceSource::ExtrudeSide { .. } | FaceSource::Imported { .. } | FaceSource::Box { .. } | FaceSource::CylinderCap { .. } => {}
|
||||
_ => return None,
|
||||
}
|
||||
Some(cost)
|
||||
|
||||
Reference in new issue
Block a user