forked from 1b-agent/opencadstudio-upstream
fix(box): preserve construction semantics
This commit is contained in:
commit
fc93efeed7
52 files changed
+6700
-1754
No files matched your search
+164
-59
@@ -1502,66 +1502,153 @@ 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 => {
|
||||
entity.context.has_text_contents = true;
|
||||
entity.context.has_block_contents = false;
|
||||
}
|
||||
acadrust::entities::LeaderContentType::Block => {
|
||||
entity.context.has_text_contents = false;
|
||||
entity.context.has_block_contents = entity.block_content_handle.is_some();
|
||||
entity.context.block_content_location = entity.context.content_base_point;
|
||||
}
|
||||
_ => {
|
||||
entity.context.has_text_contents = false;
|
||||
entity.context.has_block_contents = false;
|
||||
}
|
||||
}
|
||||
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 {
|
||||
if line.override_flags.is_empty() {
|
||||
use acadrust::entities::LeaderLinePropertyOverrideFlags as F;
|
||||
if !line.override_flags.contains(F::PATH_TYPE) {
|
||||
line.path_type = entity.path_type;
|
||||
line.line_color = style.line_color;
|
||||
line.line_type_handle = style.line_type_handle;
|
||||
line.line_weight = style.line_weight;
|
||||
line.arrowhead_handle = style.arrowhead_handle;
|
||||
line.arrowhead_size = style.arrowhead_size;
|
||||
}
|
||||
if !line.override_flags.contains(F::LINE_COLOR) {
|
||||
line.line_color = entity.line_color;
|
||||
}
|
||||
if !line.override_flags.contains(F::LINE_TYPE) {
|
||||
line.line_type_handle = entity.line_type_handle;
|
||||
}
|
||||
if !line.override_flags.contains(F::LINE_WEIGHT) {
|
||||
line.line_weight = entity.line_weight;
|
||||
}
|
||||
if !line.override_flags.contains(F::ARROWHEAD) {
|
||||
line.arrowhead_handle = entity.arrowhead_handle;
|
||||
}
|
||||
if !line.override_flags.contains(F::ARROWHEAD_SIZE) {
|
||||
line.arrowhead_size = entity.arrowhead_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1571,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()
|
||||
@@ -1584,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;
|
||||
@@ -1854,4 +1960,3 @@ pub fn is_annotative(doc: &CadDocument, entity: &EntityType) -> bool {
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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(),
|
||||
|
||||
@@ -34,6 +34,13 @@ pub enum PropValue {
|
||||
ColorVaries,
|
||||
/// Line weight — rendered as a combo_box.
|
||||
LwChoice(LineWeight),
|
||||
/// Object-specific line weight routed by field name.
|
||||
FieldLwChoice {
|
||||
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.
|
||||
|
||||
@@ -259,19 +259,32 @@ pub fn apply_history_choice(
|
||||
let DynamicBlockData::SolidHistory(history) = &mut object.data else {
|
||||
return false;
|
||||
};
|
||||
let before = (history.record_history, history.show_history);
|
||||
match field {
|
||||
PROP_HISTORY => {
|
||||
history.record_history = value.eq_ignore_ascii_case("Record");
|
||||
history.record_history = if value.eq_ignore_ascii_case("Record") {
|
||||
true
|
||||
} else if value.eq_ignore_ascii_case("None") {
|
||||
false
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
if !history.record_history {
|
||||
history.show_history = false;
|
||||
}
|
||||
}
|
||||
PROP_SHOW_HISTORY if history.record_history => {
|
||||
history.show_history = value.eq_ignore_ascii_case("Yes");
|
||||
history.show_history = if value.eq_ignore_ascii_case("Yes") {
|
||||
true
|
||||
} else if value.eq_ignore_ascii_case("No") {
|
||||
false
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
}
|
||||
_ => return false,
|
||||
}
|
||||
true
|
||||
before != (history.record_history, history.show_history)
|
||||
}
|
||||
|
||||
fn apply_box_geometry_property(
|
||||
@@ -281,23 +294,37 @@ fn apply_box_geometry_property(
|
||||
) -> Option<bool> {
|
||||
if field == PROP_ROTATION {
|
||||
let target = crate::entities::common::parse_direction(text)?;
|
||||
if !target.is_finite() {
|
||||
return Some(false);
|
||||
}
|
||||
let current = matrix(value.base.transform)?;
|
||||
let center = current.transform_point3(glam::DVec3::new(
|
||||
value.length * 0.5,
|
||||
value.width * 0.5,
|
||||
0.0,
|
||||
));
|
||||
let (sin, cos) = target.sin_cos();
|
||||
let x_axis = glam::DVec3::new(cos, sin, 0.0);
|
||||
let y_axis = glam::DVec3::new(-sin, cos, 0.0);
|
||||
let origin = center - x_axis * value.length * 0.5 - y_axis * value.width * 0.5;
|
||||
value.base.transform = glam::DMat4::from_cols(
|
||||
x_axis.extend(0.0),
|
||||
y_axis.extend(0.0),
|
||||
glam::DVec3::Z.extend(0.0),
|
||||
origin.extend(1.0),
|
||||
)
|
||||
.to_cols_array();
|
||||
if !center.is_finite() {
|
||||
return Some(false);
|
||||
}
|
||||
let projected = current.x_axis.truncate();
|
||||
if projected.length_squared() <= 1e-12 {
|
||||
return Some(false);
|
||||
}
|
||||
let current_angle = projected.y.atan2(projected.x);
|
||||
let delta = (target - current_angle + std::f64::consts::PI)
|
||||
.rem_euclid(std::f64::consts::TAU)
|
||||
- std::f64::consts::PI;
|
||||
if delta.abs() <= 1e-12 {
|
||||
return Some(false);
|
||||
}
|
||||
let updated = glam::DMat4::from_translation(center)
|
||||
* glam::DMat4::from_rotation_z(delta)
|
||||
* glam::DMat4::from_translation(-center)
|
||||
* current;
|
||||
if !updated.is_finite() || updated.determinant().abs() <= 1e-12 {
|
||||
return Some(false);
|
||||
}
|
||||
value.base.transform = updated.to_cols_array();
|
||||
return Some(true);
|
||||
}
|
||||
let axis = match field {
|
||||
@@ -307,12 +334,21 @@ fn apply_box_geometry_property(
|
||||
_ => return None,
|
||||
};
|
||||
let target = crate::entities::common::parse_length(text)?;
|
||||
if !target.is_finite() {
|
||||
return Some(false);
|
||||
}
|
||||
let current = matrix(value.base.transform)?;
|
||||
let center = current.transform_point3(glam::DVec3::new(
|
||||
value.length * 0.5,
|
||||
value.width * 0.5,
|
||||
0.0,
|
||||
));
|
||||
if !center.is_finite() {
|
||||
return Some(false);
|
||||
}
|
||||
if target == center[axis] {
|
||||
return Some(false);
|
||||
}
|
||||
let mut delta = glam::DVec3::ZERO;
|
||||
delta[axis] = target - center[axis];
|
||||
value.base.transform = (glam::DMat4::from_translation(delta) * current).to_cols_array();
|
||||
@@ -340,8 +376,42 @@ pub fn apply_primitive_property(
|
||||
return false;
|
||||
}
|
||||
let positive = || (number > 0.0).then_some(number);
|
||||
if let SolidHistoryOperation::Box(value) = operation {
|
||||
let Some(next) = positive() else {
|
||||
return false;
|
||||
};
|
||||
let (current_size, local_shift) = match field {
|
||||
PROP_LENGTH => (
|
||||
value.length,
|
||||
glam::DVec3::new((value.length - next) * 0.5, 0.0, 0.0),
|
||||
),
|
||||
PROP_WIDTH => (
|
||||
value.width,
|
||||
glam::DVec3::new(0.0, (value.width - next) * 0.5, 0.0),
|
||||
),
|
||||
PROP_HEIGHT => (value.height, glam::DVec3::ZERO),
|
||||
_ => return false,
|
||||
};
|
||||
if !current_size.is_finite() || current_size < 1e-6 || next == current_size {
|
||||
return false;
|
||||
}
|
||||
if local_shift != glam::DVec3::ZERO {
|
||||
let Some(current) = matrix(value.base.transform) else {
|
||||
return false;
|
||||
};
|
||||
value.base.transform =
|
||||
(current * glam::DMat4::from_translation(local_shift)).to_cols_array();
|
||||
}
|
||||
match field {
|
||||
PROP_LENGTH => value.length = next,
|
||||
PROP_WIDTH => value.width = next,
|
||||
PROP_HEIGHT => value.height = next,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
return true;
|
||||
}
|
||||
match operation {
|
||||
SolidHistoryOperation::Box(value) | SolidHistoryOperation::Wedge(value) => match field {
|
||||
SolidHistoryOperation::Wedge(value) => match field {
|
||||
PROP_LENGTH => value.length = positive().unwrap_or(value.length),
|
||||
PROP_WIDTH => value.width = positive().unwrap_or(value.width),
|
||||
PROP_HEIGHT => value.height = positive().unwrap_or(value.height),
|
||||
@@ -692,10 +762,16 @@ pub fn apply_primitive_grip(
|
||||
let Some(local) = local_point(transform, world) else {
|
||||
return false;
|
||||
};
|
||||
if !local.is_finite() {
|
||||
return false;
|
||||
}
|
||||
let positive = |value: f64| value.abs().max(1e-6);
|
||||
match operation {
|
||||
SolidHistoryOperation::Box(value) => {
|
||||
let old_size = glam::DVec3::new(value.length, value.width, value.height);
|
||||
if !old_size.is_finite() || old_size.min_element() < 1e-6 {
|
||||
return false;
|
||||
}
|
||||
let mut low = glam::DVec3::ZERO;
|
||||
let mut high = old_size;
|
||||
if (GRIP_BOX_CORNER_FIRST..GRIP_BOX_CORNER_FIRST + 8).contains(&grip_id) {
|
||||
@@ -724,7 +800,12 @@ pub fn apply_primitive_grip(
|
||||
}
|
||||
}
|
||||
let size = high - low;
|
||||
if size.min_element() < 1e-6 {
|
||||
if !size.is_finite() || size.min_element() < 1e-6 {
|
||||
return false;
|
||||
}
|
||||
if low.abs().max_element() <= 1e-12
|
||||
&& (size - old_size).abs().max_element() <= 1e-12
|
||||
{
|
||||
return false;
|
||||
}
|
||||
let Some(current) = matrix(value.base.transform) else {
|
||||
|
||||
+3
-12
@@ -70,12 +70,7 @@ fn capture_text_orient(e: &EntityType) -> Option<TextOrient> {
|
||||
}
|
||||
}
|
||||
|
||||
/// MIRRTEXT off: after the mirror reflects a text's points and rotation, put
|
||||
/// the glyphs back to right-reading. Restores the captured rotation / oblique /
|
||||
/// x-scale, and for single-line TEXT also flips the horizontal justification
|
||||
/// (left ↔ right) so the box lands mirror-symmetric to the source instead of
|
||||
/// hugging the axis — AutoCAD's behaviour. Center/Middle/Aligned/Fit are already
|
||||
/// symmetric about their (reflected) anchor, so they stay.
|
||||
/// Restores readable text orientation after a mirror.
|
||||
fn restore_text_orient(e: &mut EntityType, o: &TextOrient) {
|
||||
match e {
|
||||
EntityType::Text(t) => {
|
||||
@@ -168,14 +163,10 @@ impl Scene {
|
||||
.filter(|&h| !self.is_layer_locked(h))
|
||||
.collect();
|
||||
let handles = &handles[..];
|
||||
// MIRRTEXT (header.mirror_text): when false AutoCAD positions text /
|
||||
// mtext / shape by the mirror but keeps the original rotation +
|
||||
// oblique so the text stays right-reading. Capture before the
|
||||
// transform and re-apply afterwards.
|
||||
// Preserve readable text orientation when MIRRTEXT is disabled.
|
||||
let preserve_text_orientation =
|
||||
matches!(t, EntityTransform::Mirror { .. }) && !self.document.header.mirror_text;
|
||||
// MIRRTEXT on: toggle the group-71 flags after the reflect so text
|
||||
// becomes a true glyph mirror (see `mirror_true_text_flags`).
|
||||
// Toggle group-71 flags when glyph mirroring is enabled.
|
||||
let mirror_true =
|
||||
matches!(t, EntityTransform::Mirror { .. }) && self.document.header.mirror_text;
|
||||
let mut text_orient_backup: Vec<(Handle, TextOrient)> = Vec::new();
|
||||
|
||||
+11
-4
@@ -539,7 +539,8 @@ impl Scene {
|
||||
"ByLayer".into(),
|
||||
"ByBlock".into(),
|
||||
]),
|
||||
PropValue::LwChoice(_) => QSelectValueEditor::Choice(vec![
|
||||
PropValue::LwChoice(_)
|
||||
| PropValue::FieldLwChoice { .. } => QSelectValueEditor::Choice(vec![
|
||||
"ByLayer".into(),
|
||||
"ByBlock".into(),
|
||||
"Default".into(),
|
||||
@@ -567,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));
|
||||
@@ -696,13 +698,18 @@ impl Scene {
|
||||
PropValue::Choice { selected, .. } => selected,
|
||||
PropValue::EditChoice { value, .. } => value,
|
||||
PropValue::ColorChoice(c) => Self::format_color(c),
|
||||
PropValue::LwChoice(lw) => Self::format_lineweight(lw),
|
||||
PropValue::LwChoice(lw)
|
||||
| PropValue::FieldLwChoice { value: lw, .. } => {
|
||||
Self::format_lineweight(lw)
|
||||
}
|
||||
PropValue::LinetypeChoice(s) => s,
|
||||
PropValue::HatchPatternChoice(s) => s,
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user