Solids Engine
DAG CSG
DAG CSG is the primary modeling representation for Constructive Solid Geometry (CSG) in the SDK. It provides built-in support for common unary and binary operations, along with extensible building blocks that enable developers to create more advanced and customized operations.
What DAG CSG Represents
DAG CSG is designed specifically for the attribute-rich DAG data model. Unlike traditional CSG systems that operate solely on geometry, DAG CSG processes both geometry and associated attributes as first-class data.
CSG operations evaluate not only the spatial relationships between input solids, but also their attributes. During an operation, attributes can be combined, replaced, or selectively propagated from either input model. The exact behavior is determined by the attribute type, allowing fine-grained and predictable control over attribute resolution.
At its core, DAG CSG defines how a solid is constructed—not just the resulting mesh. This procedural representation preserves modeling intent and enables robust downstream operations.
Why Use DAG CSG
- Non-Destructive Authoring: Preserve full construction history and editable model intent.
- Version-Friendly: DAG CSG integrates cleanly with SDK state/version workflows for commit, diff, and rollback.
- Composable Modeling: Reuse graph fragments and compose larger models from smaller authored parts.
- Resolution-Aware Processing: Expensive operations can run at the precision required by the task.
- Deterministic Behavior: The same graph and inputs produce consistent geometry outputs.
Integration with the Solids Stack
DAG CSG serves as the authoring and modeling layer within the Solids stack. CSG operations are evaluated eagerly and locally, leaving the input objects unmodified and producing a new solid that can be passed to downstream systems
This makes DAG CSG particularly well-suited for workflows centered on interactive modeling, edit history, and reusable design intent. If your workflow prioritizes procedural construction and non-destructive edits, DAG CSG is the natural entry point.
DAG CSG API
For DAG-vs-DAG boolean operations, the primary API is dag_csg (or dag_csg_with_combinators when explicit attribute combinators are required).
dag_csg Inputs
dag_csg takes the following conceptual inputs:
txn: &dyn DagCsgTransaction
Active transaction used for all reads/writes during the operation.config: &DagCsgConfig
Execution/configuration settings for DAG CSG evaluation.op: DagCsgOp
Boolean mode (Replace,Subtract,Intersect,Merge,Select).relative_isometry: Isometry3<f64>
Relative pose between the two models so CSG is performed in a shared frame.model_a: &DagModel<G>andmodel_b: &DagModel<G>
The two input DAG models (same leaf geometry typeG).
When attribute merge behavior must be explicitly controlled, use dag_csg_with_combinators(...), which adds:
combinators: Option<HashMap<FseHeader, GradesCombinatorType>>
Per-attribute combinator strategy for overlap handling during merge operations.
Typical Call Flow
- Resolve models via
DagCsgContext/SharedDagModelContext. - Begin a transaction:
let txn = dag_context.begin().await?;. - Run
dag_csg(...)(ordag_csg_with_combinators(...)) with&*txnand config. - Persist output model and commit with
txn.commit().await?.
Example: dag_csg
use fdk_client_plugin::fsl_lib::prelude::*;
use fdk_client_plugin::fsl_lib::solids_engine::core::dag_model::{
DagCsgConfig, DagCsgOp, LeafAabb, dag_csg,
};
async fn my_subtract(
dag_context: &DagCsgContext,
model_a: &DagModel<LeafAabb>,
model_b: &DagModel<LeafAabb>,
) -> Result<DagModel<LeafAabb>, DagCsgError> {
let txn = dag_context.begin().await?;
let result = dag_csg(
&*txn,
&DagCsgConfig::default(),
DagCsgOp::Subtract,
Isometry3::identity(),
model_a,
model_b,
)
.await?;
txn.commit().await?;
Ok(result)
}This example performs model_a - model_b in a shared coordinate frame (Isometry3::identity()).
General DAG CSG Operations
In addition to DAG-vs-DAG boolean ops, general CSG helpers exist for targeted workflows:
dag_subtract_classifier(...): Subtract a classifier (for example, mesh-derived cutter geometry) from a DAG model.dag_filter_map(...): Construct a new DAG model from the filter-mapped leaves of the input DAG model.
Use these general operations when the workflow is not a DAG-vs-DAG boolean in a shared coordinate frame.
Lazy DAG CSG
Lazy DAG CSG is the high-performance execution layer for DAG-based CSG operations. It is designed for large datasets and deferred computation, where geometry and spatial queries are evaluated only when needed.
Execution Model
Unlike authoring-focused DAG CSG, Lazy DAG CSG emphasizes when and where computation happens:
- Deferred Evaluation: Operations are planned first and executed on demand.
- Composable Operations: Build complex lazy DAG compute graphs from reusable components.
- Server-Side Scale: Heavy operations can be offloaded to backend infrastructure.
This model is ideal for large block models, point clouds, and terrain-aligned analysis.
Typical Use Cases
- Subtracting a designed excavation shape from a large terrain or block model.
- Running repeated spatial classifications over changing regions of interest.
- Performing compute-intensive boolean analysis without materializing full geometry each frame.
DAG CSG + Lazy DAG CSG
The two systems are complementary:
- Author in DAG CSG: Define the logic of your operation in dag csg code.
- Execute in Lazy DAG: Heavy compute is offloaded to backend infrastructure.
This separation lets teams keep modeling workflows expressive while keeping heavy compute fast and economical.
When to Choose Lazy DAG CSG
Use Lazy DAG CSG when your bottleneck is data scale or query cost rather than model authoring complexity. For user-facing modeling and history-aware edits, begin with DAG CSG and hand off to Lazy DAG CSG for execution.
Building Your Own LazyDagOperation
- Define an operation struct with lazy model handles (
LazyDagModel<G>) and immutable inputs. - Implement
LazyDagOperation(execute,description,name). - In
execute(...), load input model(s) fromLazyDagOperationsState, run the DAG function, and set outputs back into state. - Queue the operation in
LazyDagOperationQueue, then run the queue viaexecute(...)orexecute_minimal(...).
Example operation:
use fdk::libs::solids_engine::core::block_model::SamplingParams;
use fdk::libs::solids_engine::core::classifier::SharedClassifier;
use fdk::libs::solids_engine::dag_lazy::{
CustomInputLoader, LazyDagModel, LazyDagOperation, LazyDagOperationDescription,
LazyDagOperationsState, LazyDagResult,
};
use fdk::libs::solids_engine::core::dag_model::{
DagCsgConfig, DagCsgTransaction, LeafAabb, dag_subtract_classifier,
};
#[derive(Clone)]
pub struct LazyDagSubtractClassifier {
model: LazyDagModel<LeafAabb>,
classifier_with_pose: SharedClassifier,
subtract_exterior: bool,
sampling_params: SamplingParams,
result: LazyDagModel<LeafAabb>,
}
#[async_trait::async_trait]
impl LazyDagOperation for LazyDagSubtractClassifier {
async fn execute(
&self,
txn: &dyn DagCsgTransaction,
config: &DagCsgConfig,
_loader: &dyn CustomInputLoader,
state: &mut LazyDagOperationsState,
) -> LazyDagResult<()> {
let model = state.get_or_cache(&self.model, txn).await?;
let result = dag_subtract_classifier(
txn,
config,
&model,
self.classifier_with_pose.classifier.clone(),
self.classifier_with_pose.position,
self.subtract_exterior,
self.sampling_params,
|block| Some(*block),
)
.await?;
state.set(self.result, result)?;
Ok(())
}
fn description(&self) -> LazyDagOperationDescription {
LazyDagOperationDescription::unary_op(self.model, self.result)
}
fn name(&self) -> String {
"SubtractClassifier".to_string()
}
}Register the operation type token:
crate::implementor_token_generic_leaf_type!(
LazyDagSubtractClassifier,
"b09e8a27-0b41-4f89-a2fe-5780b8e56759",
"2a480ad6-0f79-48c1-aca0-9c60f3ce6107"
);This registration step is required so the lazy operation can be serialized/deserialized and resolved by the lazy execution system for each supported leaf geometry type.
Executor-style usage:
use fdk::libs::solids_engine::dag_lazy::{
CustomInputLoaderRegistry, LazyDagOperationQueue, LazyDagOperationsState,
};
let mut queue = LazyDagOperationQueue::default();
let mut state = LazyDagOperationsState::default();
let input = LazyDagModel::<LeafAabb>::new();
let output = LazyDagModel::<LeafAabb>::new();
queue.queue(LazyDagSubtractClassifier {
model: input,
classifier_with_pose,
subtract_exterior: false,
sampling_params: SamplingParams::default(),
result: output,
});
let txn = dag_context.begin().await?;
let input_loader = CustomInputLoaderRegistry::default();
let models = queue
.execute(
&*txn,
&DagCsgConfig::default(),
&input_loader,
&mut state,
&[output],
)
.await?;
let out_model = &models[0];Implementation tips:
- Keep
execute(...)deterministic and write outputs viastate.set(...). - Define
description()dependencies accurately so the executor can schedule correctly.