Dimensional constraints: dynamic dimension persistence, lock mark, display variables, DIMCONSTRAINT

The dynamic dimension of a dimensional constraint is now linked in the
drawing file (AcDbAssocDependency + AcDbAssocDimDependencyBody on the
constraint node), reread on load, hidden by DCHIDE/DYNCONSTRAINTDISPLAY
and never plotted; it carries a lock mark before its text. New
CONSTRAINTNAMEFORMAT and DYNCONSTRAINTDISPLAY variables and the
DIMCONSTRAINT option front end. A transformed entity whose own end points
a driving dimension measures keeps only the first constraint point, so a
scaled line returns to its dimension value.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
ramox81andClaude Fable 5.1 committed 2026-09-21 12:13:36 +03:00
1 parent 72fd3fd35b
commit 971abe64a4
12 files changed
+433 -24

No files matched your search

+139 -3
View File
@@ -2,7 +2,8 @@
use acadrust::objects::{
Assoc2dConstraintGroup, AssocAction, AssocActionDependency, AssocConstraintNode,
AssocConstraintNodeData, AssocDependency, AssocEvalValue, AssocEvalVariant,
AssocConstraintNodeData, AssocDependency, AssocDimDependencyBody, AssocEvalValue,
AssocEvalVariant,
AssocGeomDependency, AssocNetwork, AssocPersistentSubentId, AssocValueDependency,
AssocVariable, AssociativeData, AssociativeObject, ObjectType,
};
@@ -1291,6 +1292,67 @@ impl Allocator<'_> {
}
handle
}
/// The `AcDbAssocDependency` + `AcDbAssocDimDependencyBody` pair that
/// ties a dimensional constraint to the dynamic dimension showing it.
fn dimension_dependency(
&mut self,
group_handle: Handle,
dimension: Handle,
name: &str,
dependency_id: i32,
) -> Handle {
let handle = self.document.allocate_handle();
let body = self.insert_associative(
handle,
"ACDBASSOCDIMDEPENDENCYBODY",
"AcDbAssocDimDependencyBody",
AssociativeData::DimDependencyBody(AssocDimDependencyBody {
dependency_body_version: 1,
base_version: 1,
name: name.to_string(),
class_version: 1,
}),
);
self.insert_associative_at(
handle,
group_handle,
"ACDBASSOCDEPENDENCY",
"AcDbAssocDependency",
AssociativeData::Dependency(AssocDependency {
class_version: 2,
is_read_dependency: true,
is_write_dependency: true,
is_attached_to_object: true,
is_delegating_to_owning_action: true,
dependent_on: dimension,
dependency_body: body,
dependency_body_id: dependency_id,
..Default::default()
}),
);
// Earlier materializations' dependencies are gone by now; drop
// their reactor entries before adding this one.
let live: Vec<Handle> = self
.document
.get_entity(dimension)
.map(|entity| {
entity
.common()
.reactors
.iter()
.copied()
.filter(|reactor| self.document.objects.contains_key(reactor))
.collect()
})
.unwrap_or_default();
if let Some(entity) = self.document.get_entity_mut(dimension) {
let reactors = &mut entity.common_mut().reactors;
*reactors = live;
reactors.push(handle);
}
handle
}
}
/// Sets the dependency on whole-geometry nodes. Implicit points retain a
@@ -1355,6 +1417,27 @@ fn set_value_dependency(data: &mut AssocConstraintNodeData, handle: Handle) {
}
}
/// Patches a dimensional-constraint node's `dimension_dependency` field.
fn set_dimension_dependency(data: &mut AssocConstraintNodeData, handle: Handle) {
match data {
AssocConstraintNodeData::Distance {
dimension_dependency,
..
}
| AssocConstraintNodeData::Angle {
dimension_dependency,
..
}
| AssocConstraintNodeData::RadiusDiameter {
dimension_dependency,
..
} => {
*dimension_dependency = handle;
}
_ => {}
}
}
/// Associative classes that must have real class numbers in DWG/DXF.
const ASSOC_CLASSES: &[(&str, &str, i32)] = &[
(
@@ -1366,6 +1449,8 @@ const ASSOC_CLASSES: &[(&str, &str, i32)] = &[
("ACDBASSOCVARIABLE", "AcDbAssocVariable", 45),
("ACDBASSOCGEOMDEPENDENCY", "AcDbAssocGeomDependency", 29),
("ACDBASSOCVALUEDEPENDENCY", "AcDbAssocValueDependency", 29),
("ACDBASSOCDEPENDENCY", "AcDbAssocDependency", 29),
("ACDBASSOCDIMDEPENDENCYBODY", "AcDbAssocDimDependencyBody", 45),
];
fn ensure_associative_classes_registered(document: &mut CadDocument) {
@@ -1806,6 +1891,19 @@ fn driving_value(
}
}
/// The dynamic dimension an `AcDbAssocDependency` ties to a dimensional
/// constraint node.
fn dependency_dimension(document: &CadDocument, dependency: Handle) -> Option<Handle> {
let ObjectType::Associative(object) = document.objects.get(&dependency)? else {
return None;
};
let AssociativeData::Dependency(dependency) = &object.data else {
return None;
};
let target = dependency.dependent_on;
matches!(document.get_entity(target)?, EntityType::Dimension(_)).then_some(target)
}
fn owning_composite(data: &AssocConstraintNodeData) -> Option<i32> {
match data {
AssocConstraintNodeData::Geometrical { owner_id, .. }
@@ -2250,7 +2348,25 @@ pub(super) fn native_constraint_set(
{
continue;
}
set.add(kind, targets, driving);
let dimension = match &node.data {
AssocConstraintNodeData::Angle {
dimension_dependency,
..
}
| AssocConstraintNodeData::Distance {
dimension_dependency,
..
}
| AssocConstraintNodeData::RadiusDiameter {
dimension_dependency,
..
} => dependency_dimension(document, *dimension_dependency),
_ => None,
};
let id = set.add(kind, targets, driving);
if let Some(dimension) = dimension {
set.dimensions.insert(id, dimension);
}
if let Some(constraint) = set.constraints.last_mut() {
constraint.enabled = enabled;
constraint.rigid_points = rigid_points;
@@ -2429,15 +2545,21 @@ fn materialize_scope(
// Pass 1 (read-only): constraint-node shapes.
let mut needs_value_dependency = Vec::new();
let mut dimension_links = Vec::new();
let (mut nodes, entities, referenced_entities) = {
let mut builder = GroupBuilder::new(document);
for constraint in &set.constraints {
constraint_node(
let node_id = constraint_node(
&mut builder,
document,
constraint,
&mut needs_value_dependency,
);
if let Some((node_id, dimension)) = node_id.zip(set.dimensions.get(&constraint.id)) {
if document.get_entity(*dimension).is_some() {
dimension_links.push((node_id, *dimension));
}
}
}
let GroupBuilder {
nodes,
@@ -2527,6 +2649,7 @@ fn materialize_scope(
}
let mut group_dependencies = geometry_dependencies;
let mut node_names: FxHashMap<i32, String> = FxHashMap::default();
for (node_id, driving) in needs_value_dependency {
let Some(driving) = driving else { continue };
let Ok(resolved) = driving.resolve(parameters) else {
@@ -2542,6 +2665,7 @@ fn materialize_scope(
}
DrivingValue::Literal(_) => (format!("d{node_id}"), resolved.to_string()),
};
node_names.insert(node_id, name.clone());
let variable_handle = allocator.variable(network_handle, &name, &formula, resolved);
let dependency_id = group_dependencies.len() as i32 + 1;
let dep_handle =
@@ -2551,6 +2675,18 @@ fn materialize_scope(
set_value_dependency(&mut node.data, dep_handle);
}
}
for (node_id, dimension) in dimension_links {
let Some(name) = node_names.get(&node_id) else {
continue;
};
let dependency_id = group_dependencies.len() as i32 + 1;
let dep_handle =
allocator.dimension_dependency(group_handle?, dimension, name, dependency_id);
group_dependencies.push(dep_handle);
if let Some(node) = nodes.iter_mut().find(|n| n.node_id == node_id) {
set_dimension_dependency(&mut node.data, dep_handle);
}
}
if let Some(group_handle) = group_handle {
let group = Assoc2dConstraintGroup {