forked from 1b-agent/opencadstudio-upstream
Merge pull request #1469 from yztink:feat/plugin-interactive-preview
This commit is contained in:
commit
8ae804d921
5 files changed
+416
-3
No files matched your search
@@ -603,6 +603,103 @@ pub trait InteractiveCommand: Send {
|
||||
fn on_object_pick(&mut self, _handle: Handle, _pt: [f64; 3]) -> CommandStep {
|
||||
CommandStep::Cancel
|
||||
}
|
||||
|
||||
/// Real-time preview geometry (lines, arcs, polylines) to render as the cursor moves.
|
||||
/// Each wire can specify custom vertices and an optional color (defaults to host cyan).
|
||||
fn on_cursor_move(&mut self, _pt: [f64; 3]) -> Vec<PreviewWire> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// A preview wire rendered in real-time during interactive commands.
|
||||
/// Supports straight polylines as well as analytical circles and arcs
|
||||
/// that render via GPU shaders with infinite smoothness.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "host", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum PreviewWire {
|
||||
/// A connected sequence of straight line segments in world space.
|
||||
Polyline {
|
||||
points: Vec<[f64; 3]>,
|
||||
color: Option<[f32; 4]>,
|
||||
},
|
||||
/// An analytical circle rendered with sub-pixel GPU anti-aliasing.
|
||||
Circle {
|
||||
center: [f64; 3],
|
||||
radius: f64,
|
||||
color: Option<[f32; 4]>,
|
||||
},
|
||||
/// An analytical circular arc swept counter-clockwise from `start_angle_rad` to `end_angle_rad`.
|
||||
Arc {
|
||||
center: [f64; 3],
|
||||
radius: f64,
|
||||
start_angle_rad: f64,
|
||||
end_angle_rad: f64,
|
||||
color: Option<[f32; 4]>,
|
||||
},
|
||||
}
|
||||
|
||||
impl PreviewWire {
|
||||
/// Create a polyline preview wire with default host cyan color.
|
||||
pub fn new(points: Vec<[f64; 3]>) -> Self {
|
||||
Self::Polyline { points, color: None }
|
||||
}
|
||||
|
||||
/// Optional RGBA color (0.0 to 1.0) specified for this preview wire.
|
||||
pub fn color(&self) -> Option<[f32; 4]> {
|
||||
match self {
|
||||
Self::Polyline { color, .. } => *color,
|
||||
Self::Circle { color, .. } => *color,
|
||||
Self::Arc { color, .. } => *color,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a polyline preview wire with a custom RGBA color.
|
||||
pub fn with_color(points: Vec<[f64; 3]>, color: [f32; 4]) -> Self {
|
||||
Self::Polyline {
|
||||
points,
|
||||
color: Some(color),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a straight line segment preview between two points.
|
||||
pub fn line(from: [f64; 3], to: [f64; 3], color: Option<[f32; 4]>) -> Self {
|
||||
Self::Polyline {
|
||||
points: vec![from, to],
|
||||
color,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an analytical circle preview.
|
||||
pub fn circle(center: [f64; 3], radius: f64, color: Option<[f32; 4]>) -> Self {
|
||||
Self::Circle {
|
||||
center,
|
||||
radius,
|
||||
color,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an analytical circular arc preview.
|
||||
pub fn arc(
|
||||
center: [f64; 3],
|
||||
radius: f64,
|
||||
start_angle_rad: f64,
|
||||
end_angle_rad: f64,
|
||||
color: Option<[f32; 4]>,
|
||||
) -> Self {
|
||||
Self::Arc {
|
||||
center,
|
||||
radius,
|
||||
start_angle_rad,
|
||||
end_angle_rad,
|
||||
color,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<[f64; 3]>> for PreviewWire {
|
||||
fn from(points: Vec<[f64; 3]>) -> Self {
|
||||
Self::new(points)
|
||||
}
|
||||
}
|
||||
|
||||
/// The outcome of an [`InteractiveCommand`] step.
|
||||
@@ -613,8 +710,12 @@ pub enum CommandStep {
|
||||
NeedPoint,
|
||||
/// Commit an entity to the document and keep collecting points.
|
||||
Commit(EntityType),
|
||||
/// Commit multiple entities to the document and keep collecting points.
|
||||
CommitMany(Vec<EntityType>),
|
||||
/// Commit an entity and end the command.
|
||||
CommitAndEnd(EntityType),
|
||||
/// Commit multiple entities to the document and end the command.
|
||||
CommitManyAndEnd(Vec<EntityType>),
|
||||
/// End the command without committing.
|
||||
Done,
|
||||
/// Cancel the command.
|
||||
|
||||
@@ -23,6 +23,7 @@ use crate::ribbon::owned::{OwnedPluginManifest, OwnedRibbonGroup};
|
||||
|
||||
pub use acadrust::xdata::{ExtendedDataRecord, XDataValue};
|
||||
pub use acadrust::{CadDocument, EntityType, Handle};
|
||||
pub use crate::host::PreviewWire;
|
||||
|
||||
/// Events the host forwards to an active plugin `InteractiveCommand`.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -83,6 +84,11 @@ pub enum HostRequest {
|
||||
},
|
||||
/// V7: release an interactive command after completion or cancellation.
|
||||
DropInteractive { command_id: u64 },
|
||||
/// V7: cursor move preview update during an interactive command.
|
||||
CursorMove {
|
||||
command_id: u64,
|
||||
pt: [f64; 3],
|
||||
},
|
||||
}
|
||||
|
||||
/// Responses the plugin runner sends back for `HostRequest`.
|
||||
@@ -95,6 +101,7 @@ pub enum HostResponse {
|
||||
Manifest(OwnedPluginManifest),
|
||||
Error(String),
|
||||
CodeExecutionResult(crate::host::ExecutionResult),
|
||||
PreviewWires(Vec<PreviewWire>),
|
||||
}
|
||||
|
||||
/// Requests the plugin runner sends to the host.
|
||||
|
||||
@@ -222,6 +222,7 @@ fn base_max_floor(base: Duration, kind: &'static str) -> Duration {
|
||||
"GetManifest" | "GetRibbon" => Duration::from_secs(5),
|
||||
"Dispatch" => Duration::from_secs(10),
|
||||
"InteractiveEvent" | "GetPrompt" | "NeedsEntityPick" => Duration::from_secs(2),
|
||||
"CursorMove" => Duration::from_millis(500),
|
||||
"ExecuteCode" => execute_code_timeout(),
|
||||
_ => Duration::from_secs(1),
|
||||
};
|
||||
@@ -238,6 +239,7 @@ pub(crate) fn request_kind(req: &HostRequest) -> &'static str {
|
||||
HostRequest::NeedsEntityPick { .. } => "NeedsEntityPick",
|
||||
HostRequest::ExecuteCode { .. } => "ExecuteCode",
|
||||
HostRequest::DropInteractive { .. } => "DropInteractive",
|
||||
HostRequest::CursorMove { .. } => "CursorMove",
|
||||
HostRequest::Shutdown => "Shutdown",
|
||||
}
|
||||
}
|
||||
@@ -681,6 +683,35 @@ impl PluginProcess {
|
||||
}
|
||||
}
|
||||
|
||||
/// Ask the plugin process for real-time preview wires for the cursor position.
|
||||
pub fn on_cursor_move(
|
||||
&self,
|
||||
command_id: u64,
|
||||
pt: [f64; 3],
|
||||
) -> Result<Vec<crate::host::PreviewWire>, PluginError> {
|
||||
if let Some(v4) = &self.v4 {
|
||||
let resp = v4.call(
|
||||
&mut NullHost,
|
||||
HostRequest::CursorMove { command_id, pt },
|
||||
&mut |_| {},
|
||||
)?;
|
||||
match resp {
|
||||
HostResponse::PreviewWires(w) => Ok(w),
|
||||
other => Err(PluginError::UnexpectedResponse(Box::new(other))),
|
||||
}
|
||||
} else {
|
||||
self.call_simple(
|
||||
HostRequest::CursorMove { command_id, pt },
|
||||
"CursorMove",
|
||||
"cursor_move",
|
||||
|resp| match resp {
|
||||
HostResponse::PreviewWires(w) => Ok(w),
|
||||
other => Err(Box::new(other)),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_alive(&self) -> bool {
|
||||
if let Some(v4) = &self.v4 {
|
||||
return v4.is_alive();
|
||||
|
||||
@@ -197,6 +197,7 @@ fn handle_host_request(
|
||||
CommandStep::Done
|
||||
| CommandStep::Cancel
|
||||
| CommandStep::CommitAndEnd(_)
|
||||
| CommandStep::CommitManyAndEnd(_)
|
||||
);
|
||||
(Ok(s), is_done)
|
||||
}
|
||||
@@ -243,6 +244,21 @@ fn handle_host_request(
|
||||
None => HostResponse::Error(format!("unknown interactive command {command_id}")),
|
||||
}
|
||||
}
|
||||
HostRequest::CursorMove { command_id, pt } => {
|
||||
let result = {
|
||||
let mut registry = interactive.borrow_mut();
|
||||
registry.get_mut(&command_id).map(|cmd| {
|
||||
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
cmd.on_cursor_move(pt)
|
||||
}))
|
||||
})
|
||||
};
|
||||
match result {
|
||||
Some(Ok(wires)) => HostResponse::PreviewWires(wires),
|
||||
Some(Err(_)) => HostResponse::PreviewWires(Vec::new()),
|
||||
None => HostResponse::PreviewWires(Vec::new()),
|
||||
}
|
||||
}
|
||||
HostRequest::ExecuteCode { .. } => {
|
||||
HostResponse::Error("ExecuteCode requires V4 protocol".to_string())
|
||||
}
|
||||
@@ -320,6 +336,7 @@ fn handle_host_request_v4(
|
||||
CommandStep::Done
|
||||
| CommandStep::Cancel
|
||||
| CommandStep::CommitAndEnd(_)
|
||||
| CommandStep::CommitManyAndEnd(_)
|
||||
);
|
||||
(Ok(s), is_done)
|
||||
}
|
||||
@@ -366,6 +383,21 @@ fn handle_host_request_v4(
|
||||
None => Some(HostResponse::Error(format!("unknown interactive command {command_id}"))),
|
||||
}
|
||||
}
|
||||
HostRequest::CursorMove { command_id, pt } => {
|
||||
let result = {
|
||||
let mut registry = interactive.borrow_mut();
|
||||
registry.get_mut(&command_id).map(|cmd| {
|
||||
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
cmd.on_cursor_move(pt)
|
||||
}))
|
||||
})
|
||||
};
|
||||
match result {
|
||||
Some(Ok(wires)) => Some(HostResponse::PreviewWires(wires)),
|
||||
Some(Err(_)) => Some(HostResponse::PreviewWires(Vec::new())),
|
||||
None => Some(HostResponse::PreviewWires(Vec::new())),
|
||||
}
|
||||
}
|
||||
HostRequest::ExecuteCode {
|
||||
command_id,
|
||||
source,
|
||||
|
||||
Reference in new issue
Block a user