fix(mleader): repair creation and editing

This commit is contained in:
Hakan Seven committed 2026-08-28 19:36:48 +03:00
commit ff1f873633
40 files changed
+4504 -1754

No files matched your search

+137 -57
View File
@@ -1502,54 +1502,113 @@ pub fn apply_mleader_style(
entity: &mut acadrust::entities::MultiLeader,
style: &acadrust::objects::MultiLeaderStyle,
) {
use acadrust::entities::MultiLeaderPropertyOverrideFlags as F;
let flags = entity.property_override_flags;
entity.style_handle = Some(style.handle);
entity.content_type = (style.content_type as i16).into();
entity.path_type = (style.path_type as i16).into();
entity.line_color = style.line_color;
entity.line_type_handle = style.line_type_handle;
entity.line_weight = style.line_weight;
entity.enable_landing = style.enable_landing;
entity.enable_dogleg = style.enable_dogleg;
entity.dogleg_length = style.landing_distance;
entity.arrowhead_handle = style.arrowhead_handle;
entity.arrowhead_size = style.arrowhead_size;
entity.context.arrowhead_size = style.arrowhead_size;
entity.context.landing_gap = style.landing_gap;
entity.text_style_handle = style.text_style_handle;
entity.text_color = style.text_color;
entity.text_frame = style.text_frame;
entity.text_height = style.text_height;
entity.context.text_height = style.text_height;
entity.context.text_style_handle = style.text_style_handle;
entity.context.text_color = style.text_color;
entity.text_left_attachment = (style.text_left_attachment as i16).into();
entity.text_right_attachment = (style.text_right_attachment as i16).into();
entity.text_top_attachment = (style.text_top_attachment as i16).into();
entity.text_bottom_attachment = (style.text_bottom_attachment as i16).into();
entity.text_attachment_direction = (style.text_attachment_direction as i16).into();
entity.text_alignment = (style.text_alignment as i16).into();
entity.text_angle_type = (style.text_angle_type as i16).into();
if !flags.contains(F::CONTENT_TYPE) {
entity.content_type = (style.content_type as i16).into();
}
if !flags.contains(F::PATH_TYPE) {
entity.path_type = (style.path_type as i16).into();
}
if !flags.contains(F::LINE_COLOR) {
entity.line_color = style.line_color;
}
if !flags.contains(F::LEADER_LINE_TYPE) {
entity.line_type_handle = style.line_type_handle;
}
if !flags.contains(F::LEADER_LINE_WEIGHT) {
entity.line_weight = style.line_weight;
}
if !flags.contains(F::ENABLE_LANDING) {
entity.enable_landing = style.enable_landing;
}
if !flags.contains(F::ENABLE_DOGLEG) {
entity.enable_dogleg = style.enable_dogleg;
}
if !flags.contains(F::LANDING_DISTANCE) {
entity.dogleg_length = style.landing_distance;
}
if !flags.contains(F::LANDING_GAP) {
entity.context.landing_gap = style.landing_gap;
}
if !flags.contains(F::ARROWHEAD) {
entity.arrowhead_handle = style.arrowhead_handle;
}
if !flags.contains(F::ARROWHEAD_SIZE) {
entity.arrowhead_size = style.arrowhead_size;
entity.context.arrowhead_size = style.arrowhead_size;
}
if !flags.contains(F::TEXT_STYLE) {
entity.text_style_handle = style.text_style_handle;
}
if !flags.contains(F::TEXT_COLOR) {
entity.text_color = style.text_color;
}
if !flags.contains(F::TEXT_FRAME) {
entity.text_frame = style.text_frame;
}
if !flags.contains(F::TEXT_HEIGHT) {
entity.text_height = style.text_height;
entity.context.text_height = style.text_height;
}
if !flags.contains(F::TEXT_LEFT_ATTACHMENT) {
entity.text_left_attachment = (style.text_left_attachment as i16).into();
}
if !flags.contains(F::TEXT_RIGHT_ATTACHMENT) {
entity.text_right_attachment = (style.text_right_attachment as i16).into();
}
if !flags.contains(F::TEXT_TOP_ATTACHMENT) {
entity.text_top_attachment = (style.text_top_attachment as i16).into();
}
if !flags.contains(F::TEXT_BOTTOM_ATTACHMENT) {
entity.text_bottom_attachment = (style.text_bottom_attachment as i16).into();
}
if !flags.contains(F::TEXT_ATTACHMENT_DIRECTION) {
entity.text_attachment_direction = (style.text_attachment_direction as i16).into();
}
if !flags.contains(F::TEXT_ALIGNMENT) {
entity.text_alignment = (style.text_alignment as i16).into();
}
if !flags.contains(F::TEXT_ANGLE) {
entity.text_angle_type = (style.text_angle_type as i16).into();
}
entity.context.text_left_attachment = entity.text_left_attachment;
entity.context.text_right_attachment = entity.text_right_attachment;
entity.context.text_top_attachment = entity.text_top_attachment;
entity.context.text_bottom_attachment = entity.text_bottom_attachment;
entity.context.text_alignment = entity.text_alignment;
entity.block_content_handle = style.block_content_handle;
entity.block_content_color = style.block_content_color;
entity.block_connection_type = (style.block_content_connection as i16).into();
entity.block_rotation = style.block_content_rotation;
entity.block_scale = Vector3::new(
style.block_content_scale_x,
style.block_content_scale_y,
style.block_content_scale_z,
);
entity.scale_factor = style.scale_factor;
entity.context.block_content_handle = style.block_content_handle;
entity.context.block_content_color = style.block_content_color;
entity.context.text_style_handle = entity.text_style_handle;
entity.context.text_color = entity.text_color;
if !flags.contains(F::BLOCK_CONTENT) {
entity.block_content_handle = style.block_content_handle;
}
if !flags.contains(F::BLOCK_CONTENT_COLOR) {
entity.block_content_color = style.block_content_color;
}
if !flags.contains(F::BLOCK_CONTENT_CONNECTION) {
entity.block_connection_type = (style.block_content_connection as i16).into();
}
if !flags.contains(F::BLOCK_CONTENT_ROTATION) {
entity.block_rotation = style.block_content_rotation;
}
if !flags.contains(F::BLOCK_CONTENT_SCALE) {
entity.block_scale = Vector3::new(
style.block_content_scale_x,
style.block_content_scale_y,
style.block_content_scale_z,
);
}
if !flags.contains(F::SCALE_FACTOR) {
entity.scale_factor = style.scale_factor;
entity.context.scale_factor = style.scale_factor;
}
entity.context.block_content_handle = entity.block_content_handle;
entity.context.block_content_color = entity.block_content_color;
entity.context.block_connection_type = entity.block_connection_type;
entity.context.block_rotation = style.block_content_rotation;
entity.context.block_rotation = entity.block_rotation;
entity.context.block_content_scale = entity.block_scale;
entity.context.scale_factor = style.scale_factor;
entity.enable_annotation_scale = style.is_annotative;
match entity.content_type {
acadrust::entities::LeaderContentType::MText => {
@@ -1567,7 +1626,9 @@ pub fn apply_mleader_style(
}
}
for root in &mut entity.context.leader_roots {
root.landing_distance = style.landing_distance;
if !flags.contains(F::LANDING_DISTANCE) {
root.landing_distance = entity.dogleg_length;
}
root.text_attachment_direction = entity.text_attachment_direction;
for line in &mut root.lines {
use acadrust::entities::LeaderLinePropertyOverrideFlags as F;
@@ -1575,19 +1636,19 @@ pub fn apply_mleader_style(
line.path_type = entity.path_type;
}
if !line.override_flags.contains(F::LINE_COLOR) {
line.line_color = style.line_color;
line.line_color = entity.line_color;
}
if !line.override_flags.contains(F::LINE_TYPE) {
line.line_type_handle = style.line_type_handle;
line.line_type_handle = entity.line_type_handle;
}
if !line.override_flags.contains(F::LINE_WEIGHT) {
line.line_weight = style.line_weight;
line.line_weight = entity.line_weight;
}
if !line.override_flags.contains(F::ARROWHEAD) {
line.arrowhead_handle = style.arrowhead_handle;
line.arrowhead_handle = entity.arrowhead_handle;
}
if !line.override_flags.contains(F::ARROWHEAD_SIZE) {
line.arrowhead_size = style.arrowhead_size;
line.arrowhead_size = entity.arrowhead_size;
}
}
}
@@ -1597,6 +1658,16 @@ fn apply_mleader_style_at_display_scale(
style: &acadrust::objects::MultiLeaderStyle,
display_scale: f64,
) {
use acadrust::entities::MultiLeaderPropertyOverrideFlags as F;
let old_scale = entity.context.scale_factor.max(1.0e-12);
let landing_gap = entity.context.landing_gap / old_scale;
let landing_distances = entity
.context
.leader_roots
.iter()
.map(|root| root.landing_distance / old_scale)
.collect::<Vec<_>>();
apply_mleader_style(entity, style);
let scale = if display_scale.is_finite()
@@ -1610,18 +1681,27 @@ fn apply_mleader_style_at_display_scale(
// Context sizes are stored at their display scale.
entity.context.scale_factor = scale;
entity.context.text_height =
style.text_height * scale;
entity.context.text_height = entity.text_height * scale;
entity.context.arrowhead_size = entity.arrowhead_size * scale;
entity.context.landing_gap = if entity.property_override_flags.contains(F::LANDING_GAP) {
landing_gap * scale
} else {
style.landing_gap * scale
};
entity.context.arrowhead_size =
style.arrowhead_size * scale;
entity.context.landing_gap =
style.landing_gap * scale;
for root in &mut entity.context.leader_roots {
root.landing_distance =
style.landing_distance * scale;
for (index, root) in entity.context.leader_roots.iter_mut().enumerate() {
root.landing_distance = if entity
.property_override_flags
.contains(F::LANDING_DISTANCE)
{
landing_distances
.get(index)
.copied()
.unwrap_or(entity.dogleg_length)
* scale
} else {
entity.dogleg_length * scale
};
root.text_attachment_direction =
entity.text_attachment_direction;
-8
View File
@@ -89,14 +89,6 @@ pub fn general_section(entity: &EntityType) -> PropSection {
],
};
if matches!(entity, EntityType::Point(_)) {
let hyperlink = section.props.iter().position(|prop| prop.field == "hyperlink");
let transparency = section.props.iter().position(|prop| prop.field == "transparency");
if let (Some(hyperlink), Some(transparency)) = (hyperlink, transparency) {
section.props.swap(hyperlink, transparency);
}
}
// Thickness (DXF 39) is a General-group property, but only the entity
// types that carry an extrusion thickness expose it (line, circle, arc,
// polyline, text, 2D solid, …). Show it right after Hyperlink for those.
+2
View File
@@ -1535,6 +1535,8 @@ pub fn tessellate(
| EntityType::Circle(_)
| EntityType::Arc(_)
| EntityType::Ellipse(_)
| EntityType::Spline(_)
| EntityType::LwPolyline(_)
) {
(pattern_length, pattern)
} else {
+49 -24
View File
@@ -408,12 +408,23 @@ fn resolve_reference(scene: &Scene, reference: &AssocDimensionReference) -> Opti
.copied()
}
fn dimension_inference_points(dimension: &Dimension) -> Option<[Option<Vector3>; 2]> {
fn dimension_inference_points(dimension: &Dimension) -> Vec<Option<Vector3>> {
match dimension {
Dimension::Linear(linear) => Some([Some(linear.first_point), Some(linear.second_point)]),
Dimension::Aligned(aligned) => Some([Some(aligned.first_point), Some(aligned.second_point)]),
Dimension::Ordinate(ordinate) => Some([Some(ordinate.feature_location), None]),
_ => None,
Dimension::Linear(linear) => vec![Some(linear.first_point), Some(linear.second_point)],
Dimension::Aligned(aligned) => vec![Some(aligned.first_point), Some(aligned.second_point)],
Dimension::Angular3Pt(angular) => vec![
Some(angular.angle_vertex),
Some(angular.first_point),
Some(angular.second_point),
],
Dimension::Angular2Ln(angular) => vec![
Some(angular.first_point),
Some(angular.second_point),
Some(angular.angle_vertex),
Some(angular.definition_point),
],
Dimension::Ordinate(ordinate) => vec![Some(ordinate.feature_location)],
_ => Vec::new(),
}
}
@@ -800,10 +811,10 @@ impl Scene {
pub(crate) fn infer_dimension_sources(
&self,
dimension: Handle,
) -> [Option<Handle>; 2] {
) -> Vec<Option<Handle>> {
let Some(EntityType::Dimension(entity)) = self.document.get_entity(dimension)
else {
return [None, None];
return Vec::new();
};
let radial_data = match entity {
Dimension::Radius(radius) => Some((
@@ -845,23 +856,25 @@ impl Scene {
})
.min_by(|first, second| first.0.total_cmp(&second.0))
.map(|(_, handle)| handle);
return [source, None];
return vec![source];
}
let Some(points) = dimension_inference_points(entity) else {
return [None, None];
};
points.map(|point| {
point.and_then(|point| self.document
.entities()
.filter(|entity| entity.common().handle != dimension)
.filter_map(|entity| {
source_distance_squared(entity, point)
.map(|distance| (distance, entity.common().handle))
dimension_inference_points(entity)
.into_iter()
.map(|point| {
point.and_then(|point| {
self.document
.entities()
.filter(|entity| entity.common().handle != dimension)
.filter_map(|entity| {
source_distance_squared(entity, point)
.map(|distance| (distance, entity.common().handle))
})
.filter(|(distance, _)| *distance <= 1e-16)
.min_by(|first, second| first.0.total_cmp(&second.0))
.map(|(_, handle)| handle)
})
.filter(|(distance, _)| *distance <= 1e-16)
.min_by(|first, second| first.0.total_cmp(&second.0))
.map(|(_, handle)| handle))
})
})
.collect()
}
pub(crate) fn refresh_associative_dimensions(
@@ -970,7 +983,7 @@ impl Scene {
if let Some(point) = resolved[3] {
angular.definition_point = point;
}
angular.base.definition_point = angular.definition_point;
angular.base.definition_point = angular.dimension_arc;
}
Dimension::Radius(radius) => {
let Some((radial, angle)) = radial_source else {
@@ -1137,9 +1150,21 @@ impl Scene {
arc.base.actual_measurement = arc.measurement();
}
}
dimension.base_mut().actual_measurement = dimension.measurement();
let measurement = match &*dimension {
Dimension::Linear(linear) => linear_measurement(linear),
_ => dimension.measurement(),
};
dimension.base_mut().actual_measurement = measurement;
refreshed.push((association.dimension, ChangeKind::Modified));
}
refreshed
}
}
fn linear_measurement(linear: &acadrust::entities::DimensionLinear) -> f64 {
let plane = plane_from_normal(linear.first_point, linear.base.normal);
let first = plane.project(dpoint(linear.first_point)).unwrap_or([0.0; 2]);
let second = plane.project(dpoint(linear.second_point)).unwrap_or(first);
let axis = [linear.rotation.cos(), linear.rotation.sin()];
((second[0] - first[0]) * axis[0] + (second[1] - first[1]) * axis[1]).abs()
}
+4
View File
@@ -1420,6 +1420,9 @@ pub struct Scene {
pub selection: Rc<RefCell<SelectionState>>,
/// The CAD document — single source of truth for all entities.
pub document: CadDocument,
/// Last chain-compatible dimension created this session.
/// Never reconstructed from loaded file handles.
pub last_created_dimension: Option<Handle>,
/// File-open-prepared index for non-graphical semantic object lookups.
pub(crate) object_data_cache: crate::entities::object_data::ObjectDataCache,
/// Native AcDbLight/Sun inputs, separated by rendered block and viewport
@@ -1848,6 +1851,7 @@ impl Scene {
model_pane_min_px: std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0)),
selection: Rc::new(RefCell::new(SelectionState::default())),
document: CadDocument::new(),
last_created_dimension: None,
object_data_cache: crate::entities::object_data::ObjectDataCache::default(),
lighting_cache: RefCell::new(HashMap::default()),
selected: HashSet::default(),
+2
View File
@@ -39,6 +39,8 @@ pub enum PropValue {
field: &'static str,
value: LineWeight,
},
/// Object-specific lineweight varies across the current selection.
FieldLwVaries { field: &'static str },
/// Lineweight varies across the current multi-selection.
LwVaries,
/// Linetype name — rendered as a combo_box.
+1 -1
View File
@@ -250,7 +250,7 @@ impl Scene {
let source_length = stationed.then(|| wire.pattern_stations[wire.points.len()]);
let (clipped, mut clipped_stations) = clip_polyline_to_rect(
&projected_pts,
stationed.then_some(&wire.pattern_stations[..wire.points.len()]),
stationed.then(|| &wire.pattern_stations[..wire.points.len()]),
vp_x0,
vp_y0,
vp_x1,
+5 -2
View File
@@ -568,7 +568,8 @@ impl Scene {
PropValue::AttrText { .. } => QSelectValueEditor::Text,
PropValue::Stepper { .. }
| PropValue::ColorVaries
| PropValue::LwVaries => continue,
| PropValue::LwVaries
| PropValue::FieldLwVaries { .. } => continue,
}
};
out.push(choice(prop.field, prop.label, editor));
@@ -706,7 +707,9 @@ impl Scene {
PropValue::BoolToggle { value, .. } => value.to_string(),
PropValue::AttrText { value, .. } => value,
PropValue::Stepper { display, .. } => display,
PropValue::ColorVaries | PropValue::LwVaries => return None,
PropValue::ColorVaries
| PropValue::LwVaries
| PropValue::FieldLwVaries { .. } => return None,
})
}
}
+9 -2
View File
@@ -3795,14 +3795,21 @@ impl Scene {
full_bounds,
self.document.header.lineweight_display || display_plot_lineweights,
);
if self.document.header.paper_space_linetype_scaling
// Model space: scale linetypes using the current annotation scale so their
// appearance can match a paper-space viewport at the same drawing scale.
if self.current_layout == "Model" {
if self.annotation_scale.is_finite() && self.annotation_scale > 1e-9 {
uniforms.linetype_scale = self.annotation_scale;
}
} else if self.document.header.paper_space_linetype_scaling
&& !inst.paper_sheet
&& inst.tile_idx.is_none()
&& inst.handle != Handle::NULL
{
if let Some(EntityType::Viewport(vp)) = self.document.get_entity(inst.handle) {
let viewport_scale =
vp_effective_scale(vp.custom_scale, vp.view_height, vp.height);
if viewport_scale.is_finite() && viewport_scale > 1e-9 {
uniforms.linetype_scale = (1.0 / viewport_scale) as f32;
}