Skip to main content

brep_kernel/feature_pipeline/
extension.rs

1//! Scoped, interpreter-independent history extension provider.
2use super::{FeatureContext, FeatureResult};
3use serde::{Deserialize, Serialize};
4use std::{cell::RefCell, rc::Rc};
5#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
6#[serde(rename_all = "camelCase")]
7pub struct PluginPin {
8    pub id: String,
9    pub version: String,
10    pub api_version: u32,
11    pub digest: String,
12}
13pub trait ExtensionProvider {
14    fn identity(&self, pins: &[PluginPin], feature_type: &str) -> Result<String, String>;
15    fn execute(&self, pins: &[PluginPin], ctx: &FeatureContext<'_>) -> FeatureResult;
16    /// Separate typed annotation domain; never dispatched as a solid feature.
17    fn execute_annotation(
18        &self,
19        _pins: &[PluginPin],
20        annotation: &super::pmi::PmiAnnotation,
21        _context: &super::pmi::PmiContext<'_>,
22    ) -> Result<super::pmi::PluginAnnotationOutput, String> {
23        Err(format!("unavailable plugin annotation provider for '{}'", annotation.kind))
24    }
25}
26thread_local! {
27    static PROVIDER:RefCell<Option<Rc<dyn ExtensionProvider>>>=RefCell::new(None);
28    static PINS:RefCell<Vec<PluginPin>>=const {RefCell::new(Vec::new())};
29}
30pub fn with_provider<R>(provider: Rc<dyn ExtensionProvider>, f: impl FnOnce() -> R) -> R {
31    struct Restore(Option<Rc<dyn ExtensionProvider>>);
32    impl Drop for Restore {
33        fn drop(&mut self) {
34            PROVIDER.with(|p| *p.borrow_mut() = self.0.take());
35        }
36    }
37    let _restore = Restore(PROVIDER.with(|p| p.replace(Some(provider))));
38    f()
39}
40pub(crate) struct PinScope(Vec<PluginPin>);
41impl Drop for PinScope {
42    fn drop(&mut self) {
43        PINS.with(|p| *p.borrow_mut() = std::mem::take(&mut self.0));
44    }
45}
46pub(crate) fn enter(pins: &[PluginPin]) -> PinScope {
47    PinScope(PINS.with(|p| p.replace(pins.to_vec())))
48}
49pub(crate) fn identity(kind: &str) -> String {
50    if !kind.contains('/') {
51        return String::new();
52    }
53    let provider = PROVIDER.with(|p| p.borrow().clone());
54    let pins = PINS.with(|p| p.borrow().clone());
55    match provider {
56        Some(p) => p
57            .identity(&pins, kind)
58            .unwrap_or_else(|e| format!("unavailable:{e}")),
59        None => "unavailable plugin provider".into(),
60    }
61}
62pub(crate) fn execute(ctx: &FeatureContext<'_>) -> FeatureResult {
63    let provider = PROVIDER.with(|p| p.borrow().clone());
64    let pins = PINS.with(|p| p.borrow().clone());
65    match provider {
66        Some(p) => p.execute(&pins, ctx),
67        None => ctx.fail(format!(
68            "unavailable plugin provider for '{}'",
69            ctx.feature_type
70        )),
71    }
72}
73/// Clone an upstream solid after the enclosing feature's reference fence.
74pub fn clone_reference(ctx: &FeatureContext<'_>, name: &str) -> Result<crate::BrepSolid, String> {
75    let handle = ctx
76        .scene
77        .resolve_solid(name)
78        .ok_or_else(|| format!("unresolved plugin reference {name}"))?;
79    crate::with_registered_solid_str(handle, |s| Ok(s.clone()))
80}
81/// Publish only after every output has been validated by the extension owner.
82pub fn publish(mut solid: crate::BrepSolid, name: &str) -> super::AddedSolid {
83    for (i, face) in solid
84        .shells
85        .iter_mut()
86        .flat_map(|s| s.faces.iter_mut())
87        .enumerate()
88    {
89        face.name = Some(format!("{name}:face{i}"));
90    }
91    super::features::common::register_added(solid, name)
92}
93pub fn transform(
94    solid: &crate::BrepSolid,
95    position: [f64; 3],
96    rotation_degrees: [f64; 3],
97    scale: [f64; 3],
98) -> Result<crate::BrepSolid, String> {
99    let m = super::features::common::compose_trs_matrix(
100        position,
101        rotation_degrees.map(f64::to_radians),
102        scale,
103        [0.; 3],
104    );
105    crate::transform_brep(solid, crate::AffineTransform::new(m)?, false)
106}
107/// Extension-backed component snapshots require replay against the active trusted
108/// provider. A serialized display snapshot cannot establish package availability.
109pub(crate) fn document_uses_extensions(value: &serde_json::Value) -> bool {
110    match value {
111        serde_json::Value::Object(o) => {
112            o.get("type")
113                .and_then(|v| v.as_str())
114                .is_some_and(|t| t.contains('/'))
115                || o.values().any(document_uses_extensions)
116        }
117        serde_json::Value::Array(a) => a.iter().any(document_uses_extensions),
118        _ => false,
119    }
120}
121
122/// Resolve using explicit document pins even when invoked outside history replay.
123pub(crate) fn execute_annotation(
124    annotation: &super::pmi::PmiAnnotation,
125    context: &super::pmi::PmiContext<'_>,
126) -> Result<super::pmi::PluginAnnotationOutput, String> {
127    let provider = PROVIDER.with(|p| p.borrow().clone());
128    provider.ok_or_else(|| format!("unavailable plugin annotation provider for '{}'", annotation.kind))?
129        .execute_annotation(&context.request.plugins, annotation, context)
130}