forked from 1b-agent/opencadstudio-upstream
feat(annotation): integrate object contexts
This commit is contained in:
1 parent
f39481ac49
commit
e03e091b3c
37 files changed
+2823
-584
No files matched your search
+650
-93
@@ -5,12 +5,14 @@
|
||||
//! scale) must agree on *which* entities are annotative — so that logic lives
|
||||
//! here, once. An entity is annotative if it carries a per-object annotation
|
||||
//! context, legacy annotative XDATA, or an entity-level annotative flag. Text
|
||||
//! style state is consulted only while creating a new text object: changing a
|
||||
//! style later must not retroactively scale existing text.
|
||||
//! style state is consulted while creating an object and when an explicit
|
||||
//! annotation-style update is requested; changing a style alone does not
|
||||
//! retroactively scale existing text.
|
||||
|
||||
use acadrust::entities::{EntityCommon, EntityType};
|
||||
use acadrust::objects::{
|
||||
Dictionary, HatchScaleContext, MTextContext, ObjectContextData, ObjectContextKind, ObjectType,
|
||||
Dictionary, DimContext, DimSubtype, EmbeddedMTextContext, HatchScaleContext,
|
||||
MTextAttributeContext, MTextContext, ObjectContextData, ObjectContextKind, ObjectType,
|
||||
};
|
||||
use acadrust::types::{Vector2, Vector3};
|
||||
use acadrust::{CadDocument, Handle};
|
||||
@@ -72,7 +74,7 @@ pub fn root_named_dict_handle(doc: &mut CadDocument) -> Handle {
|
||||
}
|
||||
|
||||
/// Set the per-object annotative flag on the entity types that carry one
|
||||
/// (MTEXT, MULTILEADER). Turning it off also strips the per-object annotation
|
||||
/// (MTEXT, MULTILEADER, ATTRIB and ATTDEF). Turning it off also strips the per-object annotation
|
||||
/// context and legacy markers via [`clear_annotation_context`] so the object
|
||||
/// stops resolving annotative; turning it on leaves the base geometry as the
|
||||
/// single (implicit, current-scale) representation. TEXT uses a context rather
|
||||
@@ -82,6 +84,8 @@ pub fn set_entity_annotative(doc: &mut CadDocument, handle: Handle, want: bool)
|
||||
match e {
|
||||
EntityType::MText(t) => t.is_annotative = want,
|
||||
EntityType::MultiLeader(m) => m.enable_annotation_scale = want,
|
||||
EntityType::AttributeEntity(attribute) => attribute.flags.annotative = want,
|
||||
EntityType::AttributeDefinition(attribute) => attribute.flags.annotative = want,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -92,9 +96,121 @@ pub fn set_entity_annotative(doc: &mut CadDocument, handle: Handle, want: bool)
|
||||
|
||||
/// Derive the per-scale context payload for an entity from its current
|
||||
/// placement. Returns the concrete class name and the context kind, or `None`
|
||||
/// for entity types that do not carry a per-object annotation context (their
|
||||
/// annotative state comes from a style, e.g. DIMENSION/TABLE).
|
||||
fn context_kind_for(entity: &EntityType) -> Option<(&'static str, ObjectContextKind)> {
|
||||
/// for entity types that do not carry a per-object annotation context.
|
||||
fn dimension_context_for(doc: &CadDocument, dimension: &acadrust::entities::Dimension) -> Option<DimContext> {
|
||||
use acadrust::entities::Dimension;
|
||||
|
||||
let subtype = match dimension {
|
||||
Dimension::Aligned(dim) => DimSubtype::Aligned {
|
||||
dimline_pt: dim.definition_point,
|
||||
},
|
||||
Dimension::Linear(dim) => DimSubtype::Aligned {
|
||||
dimline_pt: dim.definition_point,
|
||||
},
|
||||
Dimension::Angular2Ln(dim) => DimSubtype::Angular {
|
||||
arc_pt: dim.dimension_arc,
|
||||
},
|
||||
Dimension::Angular3Pt(dim) => DimSubtype::Angular {
|
||||
arc_pt: dim.definition_point,
|
||||
},
|
||||
Dimension::Diameter(dim) => DimSubtype::Diametric {
|
||||
first_arc_pt: dim.angle_vertex,
|
||||
def_pt: dim.definition_point,
|
||||
},
|
||||
Dimension::Radius(dim) => DimSubtype::Radial {
|
||||
first_arc_pt: dim.definition_point,
|
||||
},
|
||||
Dimension::LargeRadial(dim) => DimSubtype::RadialLarge {
|
||||
ovr_center: dim.override_center,
|
||||
jog_point: dim.jog_point,
|
||||
},
|
||||
Dimension::Ordinate(dim) => DimSubtype::Ordinate {
|
||||
feature_location_pt: dim.feature_location,
|
||||
leader_endpt: dim.leader_endpoint,
|
||||
},
|
||||
Dimension::Arc(_) => return None,
|
||||
};
|
||||
let base = dimension.base();
|
||||
let block = doc
|
||||
.block_records
|
||||
.iter()
|
||||
.find(|record| record.name.eq_ignore_ascii_case(&base.block_name))
|
||||
.map(|record| record.handle)
|
||||
.unwrap_or(Handle::NULL);
|
||||
Some(DimContext {
|
||||
def_pt: Vector2::new(base.text_middle_point.x, base.text_middle_point.y),
|
||||
is_def_textloc: base.text_user_positioned,
|
||||
text_rotation: base.text_rotation,
|
||||
block,
|
||||
b293: false,
|
||||
dimtofl: false,
|
||||
dimosxd: false,
|
||||
dimatfit: false,
|
||||
dimtix: false,
|
||||
dimtmove: false,
|
||||
override_code: 0,
|
||||
has_arrow2: false,
|
||||
flip_arrow2: base.flip_arrow2,
|
||||
flip_arrow1: base.flip_arrow1,
|
||||
subtype,
|
||||
})
|
||||
}
|
||||
|
||||
fn mtext_context_for(m: &acadrust::entities::MText) -> MTextContext {
|
||||
MTextContext {
|
||||
attachment: m.attachment_point as i32,
|
||||
x_axis_dir: m
|
||||
.dwg_x_direction
|
||||
.unwrap_or_else(|| Vector3::new(m.rotation.cos(), m.rotation.sin(), 0.0)),
|
||||
insertion: m.insertion_point,
|
||||
rect_width: m.rectangle_width,
|
||||
rect_height: m.rectangle_height.unwrap_or(0.0),
|
||||
extents_width: m.extents_width,
|
||||
extents_height: m.extents_height,
|
||||
column_type: m.column_data.column_type as i32,
|
||||
columns: (m.column_data.column_type != 0).then(|| acadrust::objects::MTextColumns {
|
||||
num_heights: m.column_data.column_count,
|
||||
width: m.column_data.width,
|
||||
gutter: m.column_data.gutter,
|
||||
auto_height: m.column_data.auto_height,
|
||||
flow_reversed: m.column_data.flow_reversed,
|
||||
heights: m.column_data.heights.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn attribute_context_for(
|
||||
insertion: Vector3,
|
||||
alignment: Vector3,
|
||||
rotation: f64,
|
||||
horizontal_mode: i16,
|
||||
embedded: Option<&acadrust::entities::MText>,
|
||||
scale: Handle,
|
||||
) -> MTextAttributeContext {
|
||||
MTextAttributeContext {
|
||||
horizontal_mode,
|
||||
rotation,
|
||||
insertion: Vector2::new(insertion.x, insertion.y),
|
||||
alignment: Vector2::new(alignment.x, alignment.y),
|
||||
enable_context: embedded.is_some(),
|
||||
context: embedded.map(|mtext| EmbeddedMTextContext {
|
||||
owner_handle: Handle::NULL,
|
||||
reactors: Vec::new(),
|
||||
xdictionary_handle: None,
|
||||
has_binary_data: false,
|
||||
class_version: 3,
|
||||
is_default: false,
|
||||
scale,
|
||||
mtext: mtext_context_for(mtext),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn context_kind_for(
|
||||
doc: &CadDocument,
|
||||
entity: &EntityType,
|
||||
scale: Handle,
|
||||
) -> Option<(&'static str, ObjectContextKind)> {
|
||||
match entity {
|
||||
EntityType::Insert(ins) => Some((
|
||||
"ACDB_BLKREFOBJECTCONTEXTDATA_CLASS",
|
||||
@@ -118,23 +234,126 @@ fn context_kind_for(entity: &EntityType) -> Option<(&'static str, ObjectContextK
|
||||
)),
|
||||
EntityType::MText(m) => Some((
|
||||
"ACDB_MTEXTOBJECTCONTEXTDATA_CLASS",
|
||||
ObjectContextKind::MText(MTextContext {
|
||||
attachment: m.attachment_point as i32,
|
||||
// MTEXT stores a text X-axis direction; derive it from rotation.
|
||||
x_axis_dir: Vector3::new(m.rotation.cos(), m.rotation.sin(), 0.0),
|
||||
insertion: m.insertion_point,
|
||||
rect_width: m.rectangle_width,
|
||||
rect_height: 0.0,
|
||||
extents_width: 0.0,
|
||||
extents_height: 0.0,
|
||||
column_type: 0,
|
||||
columns: None,
|
||||
ObjectContextKind::MText(mtext_context_for(m)),
|
||||
)),
|
||||
EntityType::Dimension(dimension) => {
|
||||
let context = dimension_context_for(doc, dimension)?;
|
||||
Some((context.subtype.class_name(), ObjectContextKind::Dim(context)))
|
||||
}
|
||||
EntityType::MultiLeader(mleader) => Some((
|
||||
"ACDB_MLEADEROBJECTCONTEXTDATA_CLASS",
|
||||
ObjectContextKind::MLeader(mleader.context.clone()),
|
||||
)),
|
||||
EntityType::AttributeEntity(attribute) => Some((
|
||||
"ACDB_MTEXTATTRIBUTEOBJECTCONTEXTDATA_CLASS",
|
||||
ObjectContextKind::MTextAttribute(attribute_context_for(
|
||||
attribute.insertion_point,
|
||||
attribute.alignment_point,
|
||||
attribute.rotation,
|
||||
attribute.horizontal_alignment.to_value(),
|
||||
attribute.embedded_mtext.as_deref(),
|
||||
scale,
|
||||
)),
|
||||
)),
|
||||
EntityType::AttributeDefinition(attribute) => Some((
|
||||
"ACDB_MTEXTATTRIBUTEOBJECTCONTEXTDATA_CLASS",
|
||||
ObjectContextKind::MTextAttribute(attribute_context_for(
|
||||
attribute.insertion_point,
|
||||
attribute.alignment_point,
|
||||
attribute.rotation,
|
||||
attribute.horizontal_alignment.to_value(),
|
||||
attribute.embedded_mtext.as_deref(),
|
||||
scale,
|
||||
)),
|
||||
)),
|
||||
EntityType::Leader(leader) => Some((
|
||||
"ACDB_LEADEROBJECTCONTEXTDATA_CLASS",
|
||||
ObjectContextKind::Leader(acadrust::objects::LeaderContext {
|
||||
points: leader.vertices.clone(),
|
||||
x_direction: leader.horizontal_direction,
|
||||
annotation_enabled: !leader.annotation_handle.is_null(),
|
||||
insertion_offset: Vector3::ZERO,
|
||||
endpoint_projection: leader.annotation_offset,
|
||||
}),
|
||||
)),
|
||||
EntityType::Tolerance(tolerance) => Some((
|
||||
"ACDB_FCFOBJECTCONTEXTDATA_CLASS",
|
||||
ObjectContextKind::Fcf {
|
||||
location: tolerance.insertion_point,
|
||||
horizontal_direction: tolerance.direction,
|
||||
},
|
||||
)),
|
||||
EntityType::Hatch(hatch) => Some((
|
||||
"ACDB_HATCHSCALECONTEXTDATA_CLASS",
|
||||
ObjectContextKind::HatchScale(HatchScaleContext {
|
||||
pattern_lines: hatch.pattern.lines.clone(),
|
||||
pattern_scale: hatch.pattern_scale,
|
||||
pattern_base: Vector3::ZERO,
|
||||
loop_types: hatch
|
||||
.paths
|
||||
.iter()
|
||||
.map(|path| path.flags.bits() as i32)
|
||||
.collect(),
|
||||
supports_context: true,
|
||||
}),
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn supports_annotation_context(entity: &EntityType) -> bool {
|
||||
match entity {
|
||||
EntityType::Insert(_)
|
||||
| EntityType::Text(_)
|
||||
| EntityType::MText(_)
|
||||
| EntityType::MultiLeader(_)
|
||||
| EntityType::AttributeEntity(_)
|
||||
| EntityType::AttributeDefinition(_)
|
||||
| EntityType::Leader(_)
|
||||
| EntityType::Tolerance(_)
|
||||
| EntityType::Hatch(_) => true,
|
||||
EntityType::Dimension(dimension) => {
|
||||
!matches!(dimension, acadrust::entities::Dimension::Arc(_))
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn register_context_class(doc: &mut CadDocument, dxf_name: &str) {
|
||||
doc.register_object_context_class(dxf_name);
|
||||
if doc.classes.get_by_name(dxf_name).is_some() {
|
||||
return;
|
||||
}
|
||||
let cpp_name = match dxf_name {
|
||||
"ACDB_MLEADEROBJECTCONTEXTDATA_CLASS" => "AcDbMLeaderObjectContextData",
|
||||
"ACDB_MTEXTATTRIBUTEOBJECTCONTEXTDATA_CLASS" => "AcDbMTextAttributeObjectContextData",
|
||||
"ACDB_LEADEROBJECTCONTEXTDATA_CLASS" => "AcDbLeaderObjectContextData",
|
||||
"ACDB_FCFOBJECTCONTEXTDATA_CLASS" => "AcDbFcfObjectContextData",
|
||||
_ => return,
|
||||
};
|
||||
use acadrust::classes::{DxfClass, ProxyFlags};
|
||||
let proxy_flags = ProxyFlags(
|
||||
ProxyFlags::ERASE_ALLOWED.0
|
||||
| ProxyFlags::CLONING_ALLOWED.0
|
||||
| ProxyFlags::DISABLES_PROXY_WARNING_DIALOG.0,
|
||||
);
|
||||
doc.classes.add_or_update(DxfClass {
|
||||
dxf_name: dxf_name.to_string(),
|
||||
cpp_class_name: cpp_name.to_string(),
|
||||
application_name: "ObjectDBX Classes".to_string(),
|
||||
proxy_flags,
|
||||
instance_count: 0,
|
||||
was_zombie: false,
|
||||
is_an_entity: false,
|
||||
class_number: 0,
|
||||
item_class_id: 0x1F3,
|
||||
dwg_version: 0,
|
||||
maintenance_version: 0,
|
||||
unknown1: 0,
|
||||
unknown2: 0,
|
||||
});
|
||||
}
|
||||
|
||||
/// Give an entity a per-object annotation context for `scale_handle`,
|
||||
/// synthesizing the extension-dictionary chain it hangs from when absent:
|
||||
///
|
||||
@@ -153,11 +372,14 @@ pub fn create_annotation_context(
|
||||
entity_handle: Handle,
|
||||
scale_handle: Handle,
|
||||
) -> bool {
|
||||
let Some((class_name, kind)) = doc.get_entity(entity_handle).and_then(context_kind_for) else {
|
||||
let Some((class_name, kind)) = doc
|
||||
.get_entity(entity_handle)
|
||||
.and_then(|entity| context_kind_for(doc, entity, scale_handle))
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
// The writer emits a 500+ class number only for registered classes.
|
||||
doc.register_object_context_class(class_name);
|
||||
register_context_class(doc, class_name);
|
||||
|
||||
// Extension dictionary (hard-owns its entries; 280 = 1). Create it if the
|
||||
// entity has none, and point the entity at it.
|
||||
@@ -231,7 +453,15 @@ pub fn create_annotation_context(
|
||||
/// False for non-annotative objects (no per-object context — the vast
|
||||
/// majority) and for objects whose contexts include the current scale. Gated
|
||||
/// on an extension dictionary so non-annotative entities skip the lookup.
|
||||
pub fn annotative_offscale(doc: &CadDocument, common: &EntityCommon) -> bool {
|
||||
pub fn annotative_offscale_for(
|
||||
doc: &CadDocument,
|
||||
common: &EntityCommon,
|
||||
scale_handle: Option<Handle>,
|
||||
all_visible: bool,
|
||||
) -> bool {
|
||||
if all_visible {
|
||||
return false;
|
||||
}
|
||||
if !common
|
||||
.xdictionary_handle
|
||||
.map(|h| !h.is_null())
|
||||
@@ -243,38 +473,60 @@ pub fn annotative_offscale(doc: &CadDocument, common: &EntityCommon) -> bool {
|
||||
if scales.is_empty() {
|
||||
return false;
|
||||
}
|
||||
let cur = &doc.header.current_annotation_scale;
|
||||
if scales.iter().any(|(name, _)| name.eq_ignore_ascii_case(cur)) {
|
||||
return false;
|
||||
match scale_handle {
|
||||
Some(handle) => !scales.iter().any(|(_, member)| *member == handle),
|
||||
None => !scales.iter().any(|(name, _)| {
|
||||
name.eq_ignore_ascii_case(&doc.header.current_annotation_scale)
|
||||
}),
|
||||
}
|
||||
// Off-scale (no context for the current scale). If some representation in
|
||||
// the drawing DOES provide the current scale, hide this one — the matching
|
||||
// representation is the one to show.
|
||||
if current_scale_provided(doc) {
|
||||
return true;
|
||||
}
|
||||
// The current scale is unsupported by any representation. Fall back to the
|
||||
// base "1:1" representation: keep it, hide the enlarged copies — otherwise
|
||||
// every scale representation stacks (or, if all were hidden, the object
|
||||
// vanishes). Without this, opening at e.g. CANNOSCALE 10:1 shows both a 1×
|
||||
// and a 10× copy of the same block.
|
||||
!scales.iter().any(|(name, _)| name.eq_ignore_ascii_case("1:1"))
|
||||
}
|
||||
|
||||
/// Whether any annotative representation in the drawing targets the current
|
||||
/// annotation scale.
|
||||
fn current_scale_provided(doc: &CadDocument) -> bool {
|
||||
let cur = &doc.header.current_annotation_scale;
|
||||
doc.objects.values().any(|o| {
|
||||
if let ObjectType::ObjectContextData(cd) = o {
|
||||
if let Some(ObjectType::Scale(s)) = doc.objects.get(&cd.scale) {
|
||||
return s.name.eq_ignore_ascii_case(cur);
|
||||
}
|
||||
pub fn scale_handle_by_name(doc: &CadDocument, name: &str) -> Option<Handle> {
|
||||
doc.objects.iter().find_map(|(handle, object)| match object {
|
||||
ObjectType::Scale(scale)
|
||||
if !scale.is_temporary && scale.name.eq_ignore_ascii_case(name) =>
|
||||
{
|
||||
Some(*handle)
|
||||
}
|
||||
false
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn ensure_scale_object(
|
||||
doc: &mut CadDocument,
|
||||
source: &acadrust::objects::Scale,
|
||||
) -> Handle {
|
||||
if let Some(handle) = scale_handle_by_name(doc, &source.name) {
|
||||
return handle;
|
||||
}
|
||||
let root = root_named_dict_handle(doc);
|
||||
let scale_dictionary = as_dict(doc, root)
|
||||
.and_then(|dictionary| dictionary.get("ACAD_SCALELIST"))
|
||||
.filter(|handle| matches!(doc.objects.get(handle), Some(ObjectType::Dictionary(_))))
|
||||
.unwrap_or_else(|| {
|
||||
let handle = doc.allocate_handle();
|
||||
let mut dictionary = Dictionary::new();
|
||||
dictionary.handle = handle;
|
||||
dictionary.owner = root;
|
||||
doc.objects
|
||||
.insert(handle, ObjectType::Dictionary(dictionary));
|
||||
if let Some(ObjectType::Dictionary(root_dictionary)) = doc.objects.get_mut(&root) {
|
||||
root_dictionary.add_entry("ACAD_SCALELIST", handle);
|
||||
}
|
||||
handle
|
||||
});
|
||||
let handle = doc.allocate_handle();
|
||||
let mut scale = source.clone();
|
||||
scale.handle = handle;
|
||||
scale.owner_handle = scale_dictionary;
|
||||
scale.is_temporary = false;
|
||||
doc.objects.insert(handle, ObjectType::Scale(scale));
|
||||
if let Some(ObjectType::Dictionary(dictionary)) = doc.objects.get_mut(&scale_dictionary) {
|
||||
dictionary.add_entry(source.name.clone(), handle);
|
||||
}
|
||||
handle
|
||||
}
|
||||
|
||||
/// The annotation scales an object currently carries a per-object context for,
|
||||
/// as `(scale name, scale handle)` pairs (one per representation). Empty when
|
||||
/// the object has no per-object context chain.
|
||||
@@ -348,9 +600,10 @@ fn annotation_scales_dict(doc: &CadDocument, entity: Handle) -> Option<Handle> {
|
||||
/// scale. Broken scale handles are ignored. When the current named scale is
|
||||
/// absent, the leaf explicitly marked as the native/default representation is
|
||||
/// preferred, followed by the first valid leaf.
|
||||
pub fn active_object_context(
|
||||
pub fn active_object_context_for_scale(
|
||||
doc: &CadDocument,
|
||||
entity: Handle,
|
||||
scale_handle: Option<Handle>,
|
||||
) -> Option<&ObjectContextData> {
|
||||
let coll_h = annotation_scales_dict(doc, entity)?;
|
||||
let coll = as_dict(doc, coll_h)?;
|
||||
@@ -364,29 +617,27 @@ pub fn active_object_context(
|
||||
if leaf.is_default {
|
||||
default = Some(leaf);
|
||||
}
|
||||
let Some(ObjectType::Scale(scale)) = doc.objects.get(&leaf.scale) else {
|
||||
continue;
|
||||
};
|
||||
if scale
|
||||
.name
|
||||
.eq_ignore_ascii_case(&doc.header.current_annotation_scale)
|
||||
{
|
||||
return Some(leaf);
|
||||
if let Some(target) = scale_handle {
|
||||
if leaf.scale == target {
|
||||
return Some(leaf);
|
||||
}
|
||||
} else if let Some(ObjectType::Scale(scale)) = doc.objects.get(&leaf.scale) {
|
||||
if scale
|
||||
.name
|
||||
.eq_ignore_ascii_case(&doc.header.current_annotation_scale)
|
||||
{
|
||||
return Some(leaf);
|
||||
}
|
||||
}
|
||||
}
|
||||
default.or(first)
|
||||
}
|
||||
|
||||
/// Resolve the display multiplier for an entity at the current annotation
|
||||
/// scale. A per-object context stores geometry relative to its native/default
|
||||
/// representation, so its current multiplier is the active scale's drawing
|
||||
/// factor divided by the default scale's drawing factor. Falling back preserves
|
||||
/// the legacy whole-drawing multiplier for annotative objects without a usable
|
||||
/// default context (including style-only DIMENSION and MULTILEADER entities).
|
||||
pub fn effective_annotation_scale(
|
||||
pub fn effective_annotation_scale_for(
|
||||
doc: &CadDocument,
|
||||
entity: &EntityType,
|
||||
fallback: f32,
|
||||
scale_handle: Option<Handle>,
|
||||
) -> f32 {
|
||||
if !is_annotative(doc, entity) {
|
||||
return 1.0;
|
||||
@@ -397,7 +648,9 @@ pub fn effective_annotation_scale(
|
||||
// height as stored; make `ml.scale_factor * anno_scale` resolve to the
|
||||
// active context's scale factor for arrows, doglegs, and fallback text.
|
||||
if let EntityType::MultiLeader(mleader) = entity {
|
||||
let Some(active) = active_object_context(doc, entity.common().handle) else {
|
||||
let Some(active) =
|
||||
active_object_context_for_scale(doc, entity.common().handle, scale_handle)
|
||||
else {
|
||||
return fallback;
|
||||
};
|
||||
let ObjectContextKind::MLeader(context) = &active.kind else {
|
||||
@@ -422,7 +675,7 @@ pub fn effective_annotation_scale(
|
||||
return fallback;
|
||||
};
|
||||
|
||||
let active = active_object_context(doc, entity.common().handle);
|
||||
let active = active_object_context_for_scale(doc, entity.common().handle, scale_handle);
|
||||
let native = coll.entries.iter().find_map(|(_, leaf_h)| {
|
||||
match doc.objects.get(leaf_h) {
|
||||
Some(ObjectType::ObjectContextData(leaf)) if leaf.is_default => Some(leaf),
|
||||
@@ -613,15 +866,14 @@ fn apply_hatch_context(hatch: &mut acadrust::entities::Hatch, context: &HatchSca
|
||||
}
|
||||
}
|
||||
|
||||
/// Return an ephemeral entity representation with the active scale leaf
|
||||
/// overlaid on its base geometry. The source document remains unchanged, which
|
||||
/// keeps save/round-trip data intact while render, picking and block expansion
|
||||
/// all see the scale-specific placement.
|
||||
pub fn entity_for_active_context<'a>(
|
||||
pub fn entity_for_annotation_context<'a>(
|
||||
doc: &'a CadDocument,
|
||||
entity: &'a EntityType,
|
||||
scale_handle: Option<Handle>,
|
||||
) -> Cow<'a, EntityType> {
|
||||
let Some(context) = active_object_context(doc, entity.common().handle) else {
|
||||
let Some(context) =
|
||||
active_object_context_for_scale(doc, entity.common().handle, scale_handle)
|
||||
else {
|
||||
return Cow::Borrowed(entity);
|
||||
};
|
||||
let mut placed = entity.clone();
|
||||
@@ -824,15 +1076,15 @@ fn sync_dimension_context(
|
||||
}
|
||||
}
|
||||
|
||||
/// Copy an edited entity's placement back into its active per-scale leaf.
|
||||
/// Geometry edits therefore remain visible at the current annotation scale and
|
||||
/// round-trip as genuine `AcDb*ObjectContextData`, while the base entity stays
|
||||
/// usable as the default representation.
|
||||
pub fn sync_active_context_from_entity(
|
||||
/// Copy an edited entity's placement back into one per-scale leaf so geometry
|
||||
/// edits remain attached to the representation displayed by the caller.
|
||||
pub fn sync_annotation_context_from_entity(
|
||||
doc: &mut CadDocument,
|
||||
entity_handle: Handle,
|
||||
scale_handle: Option<Handle>,
|
||||
) -> bool {
|
||||
let Some(leaf_handle) = active_object_context(doc, entity_handle).map(|leaf| leaf.handle)
|
||||
let Some(leaf_handle) =
|
||||
active_object_context_for_scale(doc, entity_handle, scale_handle).map(|leaf| leaf.handle)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
@@ -978,6 +1230,81 @@ pub fn sync_active_context_from_entity(
|
||||
true
|
||||
}
|
||||
|
||||
/// Move every stored scale representation with a pasted entity. The base
|
||||
/// entity has already moved when this runs; each context leaf still contains
|
||||
/// its source placement, so it is materialized, translated, and written back
|
||||
/// without disturbing the transformed base representation.
|
||||
pub fn translate_annotation_contexts(
|
||||
doc: &mut CadDocument,
|
||||
entity_handle: Handle,
|
||||
delta: glam::DVec3,
|
||||
) -> bool {
|
||||
let Some(base_entity) = doc.get_entity(entity_handle).cloned() else {
|
||||
return false;
|
||||
};
|
||||
let leaves: Vec<_> = annotation_scales_dict(doc, entity_handle)
|
||||
.and_then(|collection| as_dict(doc, collection))
|
||||
.map(|collection| {
|
||||
collection
|
||||
.entries
|
||||
.iter()
|
||||
.filter_map(|(_, leaf_handle)| match doc.objects.get(leaf_handle) {
|
||||
Some(ObjectType::ObjectContextData(leaf)) => {
|
||||
Some((leaf.handle, leaf.scale))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
if leaves.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut changed = false;
|
||||
for (_, scale) in leaves {
|
||||
let mut placed = entity_for_annotation_context(doc, &base_entity, Some(scale)).into_owned();
|
||||
crate::scene::view::dispatch::apply_transform(
|
||||
&mut placed,
|
||||
&crate::command::EntityTransform::Translate(delta),
|
||||
);
|
||||
|
||||
// The entity translator keeps the compatibility break list in sync,
|
||||
// while the complete per-segment list is a separate persisted field.
|
||||
if let EntityType::MultiLeader(mleader) = &mut placed {
|
||||
let offset = Vector3::new(delta.x, delta.y, delta.z);
|
||||
for root in &mut mleader.context.leader_roots {
|
||||
for line in &mut root.lines {
|
||||
for info in &mut line.break_infos {
|
||||
for pair in &mut info.break_points {
|
||||
pair.start_point = pair.start_point + offset;
|
||||
pair.end_point = pair.end_point + offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A pasted dimension owns a newly generated graphics block. A source
|
||||
// context can still carry the old block handle, so retain the block
|
||||
// selected for the transformed base entity before synchronizing it.
|
||||
if let (EntityType::Dimension(placed), EntityType::Dimension(base)) =
|
||||
(&mut placed, &base_entity)
|
||||
{
|
||||
placed.base_mut().block_name.clone_from(&base.base().block_name);
|
||||
}
|
||||
|
||||
if let Some(entity) = doc.get_entity_mut(entity_handle) {
|
||||
*entity = placed;
|
||||
}
|
||||
changed |= sync_annotation_context_from_entity(doc, entity_handle, Some(scale));
|
||||
if let Some(entity) = doc.get_entity_mut(entity_handle) {
|
||||
*entity = base_entity.clone();
|
||||
}
|
||||
}
|
||||
changed
|
||||
}
|
||||
|
||||
/// Get the child dictionary stored under `key` in `parent_h`, creating an empty
|
||||
/// one (owned by `parent_h`) and registering the entry when absent.
|
||||
fn get_or_create_child_dict(doc: &mut CadDocument, parent_h: Handle, key: &str) -> Handle {
|
||||
@@ -1026,6 +1353,7 @@ pub fn clear_annotation_context(doc: &mut CadDocument, handle: Handle) {
|
||||
}
|
||||
}
|
||||
// Strip the legacy annotative XDATA markers the detection also honours.
|
||||
crate::scene::view::dispatch::set_entity_xdata(doc, handle, "AcadAnnotative", None);
|
||||
crate::scene::view::dispatch::set_entity_xdata(doc, handle, "AcAnnoPO", None);
|
||||
crate::scene::view::dispatch::set_entity_xdata(doc, handle, "AcAnnotativeData", None);
|
||||
}
|
||||
@@ -1048,7 +1376,7 @@ pub fn text_style_is_annotative(doc: &CadDocument, name: &str) -> bool {
|
||||
.is_some_and(|s| s.annotative)
|
||||
}
|
||||
|
||||
fn dim_style_annotative(doc: &CadDocument, name: &str) -> bool {
|
||||
pub fn dim_style_is_annotative(doc: &CadDocument, name: &str) -> bool {
|
||||
doc.dim_styles
|
||||
.iter()
|
||||
.find(|s| name_matches(&s.name, name))
|
||||
@@ -1064,15 +1392,6 @@ fn mleader_style_annotative(doc: &CadDocument, handle: Option<Handle>) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
fn table_style_annotative(doc: &CadDocument, handle: Option<Handle>) -> bool {
|
||||
let Some(h) = handle else {
|
||||
return false;
|
||||
};
|
||||
doc.objects
|
||||
.iter()
|
||||
.any(|(oh, o)| matches!(o, ObjectType::TableStyle(s) if *oh == h && s.annotative))
|
||||
}
|
||||
|
||||
/// Whether an object carries a per-object annotation context with at least one
|
||||
/// per-scale representation — its extension dictionary holds an
|
||||
/// `AcDbContextDataManager` whose `ACDB_ANNOTATIONSCALES` collection is
|
||||
@@ -1105,14 +1424,241 @@ fn has_context_manager(doc: &CadDocument, common: &EntityCommon) -> bool {
|
||||
}
|
||||
|
||||
/// Whether a MULTILEADER participates in annotation scaling through its
|
||||
/// per-object context, entity flag, or assigned annotative style.
|
||||
/// per-object context or entity flag. A later style edit is applied only by an
|
||||
/// explicit style update, so it cannot retroactively change existing objects.
|
||||
pub fn mleader_is_annotative(
|
||||
doc: &CadDocument,
|
||||
mleader: &acadrust::entities::MultiLeader,
|
||||
) -> bool {
|
||||
has_context_manager(doc, &mleader.common)
|
||||
|| mleader.enable_annotation_scale
|
||||
|| mleader_style_annotative(doc, mleader.style_handle)
|
||||
}
|
||||
|
||||
pub fn annotation_style_is_annotative(doc: &CadDocument, entity: &EntityType) -> bool {
|
||||
match entity {
|
||||
EntityType::Text(text) => text_style_is_annotative(doc, &text.style),
|
||||
EntityType::MText(text) => text_style_is_annotative(doc, &text.style),
|
||||
EntityType::AttributeEntity(attribute) => {
|
||||
text_style_is_annotative(doc, &attribute.text_style)
|
||||
}
|
||||
EntityType::AttributeDefinition(attribute) => {
|
||||
text_style_is_annotative(doc, &attribute.text_style)
|
||||
}
|
||||
EntityType::Dimension(dimension) => {
|
||||
dim_style_is_annotative(doc, &dimension.base().style_name)
|
||||
}
|
||||
EntityType::Leader(leader) => dim_style_is_annotative(doc, &leader.dimension_style),
|
||||
EntityType::Tolerance(tolerance) => {
|
||||
dim_style_is_annotative(doc, &tolerance.dimension_style_name)
|
||||
}
|
||||
EntityType::MultiLeader(leader) => {
|
||||
mleader_style_annotative(doc, leader.style_handle)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_mleader_style(
|
||||
entity: &mut acadrust::entities::MultiLeader,
|
||||
style: &acadrust::objects::MultiLeaderStyle,
|
||||
) {
|
||||
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.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();
|
||||
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.block_connection_type = entity.block_connection_type;
|
||||
entity.context.block_rotation = style.block_content_rotation;
|
||||
entity.context.block_content_scale = entity.block_scale;
|
||||
entity.context.scale_factor = style.scale_factor;
|
||||
entity.enable_annotation_scale = style.is_annotative;
|
||||
}
|
||||
|
||||
pub fn apply_mleader_style_to_object(
|
||||
doc: &mut CadDocument,
|
||||
handle: Handle,
|
||||
style: &acadrust::objects::MultiLeaderStyle,
|
||||
) -> bool {
|
||||
let Some(EntityType::MultiLeader(original)) = doc.get_entity(handle).cloned() else {
|
||||
return false;
|
||||
};
|
||||
let mut styled = original.clone();
|
||||
apply_mleader_style(&mut styled, style);
|
||||
if let Some(EntityType::MultiLeader(entity)) = doc.get_entity_mut(handle) {
|
||||
*entity = styled;
|
||||
}
|
||||
|
||||
let leaf_handles: Vec<_> = annotation_scales_dict(doc, handle)
|
||||
.and_then(|collection| as_dict(doc, collection))
|
||||
.map(|collection| collection.entries.iter().map(|(_, leaf)| *leaf).collect())
|
||||
.unwrap_or_default();
|
||||
for leaf_handle in leaf_handles {
|
||||
let Some(ObjectType::ObjectContextData(leaf)) = doc.objects.get_mut(&leaf_handle) else {
|
||||
continue;
|
||||
};
|
||||
let ObjectContextKind::MLeader(context) = &mut leaf.kind else {
|
||||
continue;
|
||||
};
|
||||
let context_scale = context.scale_factor;
|
||||
let text_height_ratio = if original.text_height.abs() > 1.0e-12 {
|
||||
context.text_height / original.text_height
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
let mut per_scale = original.clone();
|
||||
per_scale.context.clone_from(context);
|
||||
apply_mleader_style(&mut per_scale, style);
|
||||
per_scale.context.scale_factor = context_scale;
|
||||
if style.text_height > 0.0 && text_height_ratio.is_finite() {
|
||||
per_scale.context.text_height = style.text_height * text_height_ratio;
|
||||
}
|
||||
context.clone_from(&per_scale.context);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn update_entity_from_annotation_style(
|
||||
doc: &mut CadDocument,
|
||||
handle: Handle,
|
||||
current_scale: Option<Handle>,
|
||||
) -> bool {
|
||||
enum StyleUpdate {
|
||||
Text { annotative: bool, height: f64 },
|
||||
Dimension { annotative: bool },
|
||||
MultiLeader(acadrust::objects::MultiLeaderStyle),
|
||||
ContextOnly,
|
||||
}
|
||||
|
||||
let Some(entity) = doc.get_entity(handle) else {
|
||||
return false;
|
||||
};
|
||||
let update = match entity {
|
||||
EntityType::Text(text) => doc.text_styles.get(&text.style).map(|style| {
|
||||
StyleUpdate::Text {
|
||||
annotative: style.annotative,
|
||||
height: style.height,
|
||||
}
|
||||
}),
|
||||
EntityType::MText(text) => doc.text_styles.get(&text.style).map(|style| {
|
||||
StyleUpdate::Text {
|
||||
annotative: style.annotative,
|
||||
height: style.height,
|
||||
}
|
||||
}),
|
||||
EntityType::AttributeEntity(attribute) => doc
|
||||
.text_styles
|
||||
.get(&attribute.text_style)
|
||||
.map(|style| StyleUpdate::Text {
|
||||
annotative: style.annotative,
|
||||
height: style.height,
|
||||
}),
|
||||
EntityType::AttributeDefinition(attribute) => doc
|
||||
.text_styles
|
||||
.get(&attribute.text_style)
|
||||
.map(|style| StyleUpdate::Text {
|
||||
annotative: style.annotative,
|
||||
height: style.height,
|
||||
}),
|
||||
EntityType::Dimension(dimension) => doc
|
||||
.dim_styles
|
||||
.get(&dimension.base().style_name)
|
||||
.map(|style| StyleUpdate::Dimension {
|
||||
annotative: style.annotative,
|
||||
}),
|
||||
EntityType::Leader(leader) => doc
|
||||
.dim_styles
|
||||
.get(&leader.dimension_style)
|
||||
.map(|style| StyleUpdate::Dimension {
|
||||
annotative: style.annotative,
|
||||
}),
|
||||
EntityType::Tolerance(tolerance) => doc
|
||||
.dim_styles
|
||||
.get(&tolerance.dimension_style_name)
|
||||
.map(|style| StyleUpdate::Dimension {
|
||||
annotative: style.annotative,
|
||||
}),
|
||||
EntityType::MultiLeader(leader) => leader.style_handle.and_then(|style_handle| {
|
||||
match doc.objects.get(&style_handle) {
|
||||
Some(ObjectType::MultiLeaderStyle(style)) => {
|
||||
Some(StyleUpdate::MultiLeader(style.clone()))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}),
|
||||
_ if is_annotative(doc, entity) => Some(StyleUpdate::ContextOnly),
|
||||
_ => None,
|
||||
};
|
||||
let Some(update) = update else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let annotative = match update {
|
||||
StyleUpdate::Text { annotative, height } => {
|
||||
if height > 0.0 {
|
||||
if let Some(entity) = doc.get_entity_mut(handle) {
|
||||
match entity {
|
||||
EntityType::Text(text) => text.height = height,
|
||||
EntityType::MText(text) => text.height = height,
|
||||
EntityType::AttributeEntity(attribute) => attribute.height = height,
|
||||
EntityType::AttributeDefinition(attribute) => attribute.height = height,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
annotative
|
||||
}
|
||||
StyleUpdate::Dimension { annotative } => annotative,
|
||||
StyleUpdate::MultiLeader(style) => {
|
||||
apply_mleader_style_to_object(doc, handle, &style);
|
||||
style.is_annotative
|
||||
}
|
||||
StyleUpdate::ContextOnly => return true,
|
||||
};
|
||||
|
||||
set_entity_annotative(doc, handle, annotative);
|
||||
if annotative {
|
||||
if let Some(scale) = current_scale {
|
||||
create_annotation_context(doc, handle, scale);
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Whether an entity participates in annotation scaling.
|
||||
@@ -1123,19 +1669,30 @@ pub fn is_annotative(doc: &CadDocument, entity: &EntityType) -> bool {
|
||||
}
|
||||
// Legacy annotative XDATA markers.
|
||||
let xd = &entity.common().extended_data;
|
||||
if xd.get_record("AcAnnoPO").is_some() || xd.get_record("AcAnnotativeData").is_some() {
|
||||
let standard_marker = xd
|
||||
.get_record("AcadAnnotative")
|
||||
.and_then(|record| {
|
||||
record.values.iter().filter_map(|value| match value {
|
||||
acadrust::xdata::XDataValue::Integer16(value) => Some(*value),
|
||||
_ => None,
|
||||
}).last()
|
||||
})
|
||||
.is_some_and(|value| value != 0);
|
||||
if standard_marker
|
||||
|| xd.get_record("AcAnnoPO").is_some()
|
||||
|| xd.get_record("AcAnnotativeData").is_some()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// Annotative via the entity's own flag or assigned non-text style.
|
||||
// Annotative via the entity's own flag.
|
||||
// Text styles can be made annotative without converting existing text;
|
||||
// those objects must keep their stored height until explicitly updated.
|
||||
match entity {
|
||||
EntityType::Text(_) => false,
|
||||
EntityType::MText(t) => t.is_annotative,
|
||||
EntityType::Dimension(d) => dim_style_annotative(doc, &d.base().style_name),
|
||||
EntityType::Leader(l) => dim_style_annotative(doc, &l.dimension_style),
|
||||
EntityType::AttributeEntity(attribute) => attribute.flags.annotative,
|
||||
EntityType::AttributeDefinition(attribute) => attribute.flags.annotative,
|
||||
EntityType::MultiLeader(ml) => mleader_is_annotative(doc, ml),
|
||||
EntityType::Table(t) => table_style_annotative(doc, t.table_style_handle),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
Vendored
+68
-9
@@ -196,6 +196,8 @@ impl BlockCache {
|
||||
pub fn build(
|
||||
doc: &CadDocument,
|
||||
anno_scale: f32,
|
||||
annotation_scale_handle: Option<Handle>,
|
||||
all_visible: bool,
|
||||
bg_color: [f32; 4],
|
||||
// Scene draw-depth map ([depth, half] per handle) — source of each
|
||||
// block child's in-block rank, so band depth composition agrees with
|
||||
@@ -228,7 +230,15 @@ impl BlockCache {
|
||||
.map(|name| {
|
||||
(
|
||||
name.clone(),
|
||||
Arc::new(build_defn(doc, name, anno_scale, bg_color, depth_map)),
|
||||
Arc::new(build_defn(
|
||||
doc,
|
||||
name,
|
||||
anno_scale,
|
||||
annotation_scale_handle,
|
||||
all_visible,
|
||||
bg_color,
|
||||
depth_map,
|
||||
)),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
@@ -345,6 +355,8 @@ fn build_defn(
|
||||
doc: &CadDocument,
|
||||
block_name: &str,
|
||||
anno_scale: f32,
|
||||
annotation_scale_handle: Option<Handle>,
|
||||
all_visible: bool,
|
||||
bg_color: [f32; 4],
|
||||
depth_map: &HashMap<u64, [f32; 2]>,
|
||||
) -> BlockDefn {
|
||||
@@ -362,8 +374,11 @@ fn build_defn(
|
||||
let Some(source_entity) = doc.get_entity(eh) else {
|
||||
continue;
|
||||
};
|
||||
let contextual =
|
||||
crate::scene::annotative::entity_for_active_context(doc, source_entity);
|
||||
let contextual = crate::scene::annotative::entity_for_annotation_context(
|
||||
doc,
|
||||
source_entity,
|
||||
annotation_scale_handle,
|
||||
);
|
||||
let entity = contextual.as_ref();
|
||||
// Skip entities flagged invisible. Dynamic blocks (e.g. a visibility-
|
||||
// state parametric block) keep the geometry for every state in one
|
||||
@@ -383,7 +398,12 @@ fn build_defn(
|
||||
// Annotative scale representation: bake only the current scale's copy
|
||||
// into the defn so off-scale representations don't stack (e.g. a 1×
|
||||
// copy under a 10×). See `annotative::annotative_offscale`.
|
||||
if crate::scene::annotative::annotative_offscale(doc, entity.common()) {
|
||||
if crate::scene::annotative::annotative_offscale_for(
|
||||
doc,
|
||||
entity.common(),
|
||||
annotation_scale_handle,
|
||||
all_visible,
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
match entity {
|
||||
@@ -452,7 +472,12 @@ fn build_defn(
|
||||
)));
|
||||
} else {
|
||||
for lw in tessellate_sub_local(
|
||||
doc, &placed, anno_scale, bg_color, depth_map,
|
||||
doc,
|
||||
&placed,
|
||||
anno_scale,
|
||||
annotation_scale_handle,
|
||||
bg_color,
|
||||
depth_map,
|
||||
) {
|
||||
subs.push(LocalSub::Wire(lw));
|
||||
}
|
||||
@@ -504,7 +529,14 @@ fn build_defn(
|
||||
)));
|
||||
} else {
|
||||
for lw in
|
||||
tessellate_sub_local(doc, &placed, anno_scale, bg_color, depth_map)
|
||||
tessellate_sub_local(
|
||||
doc,
|
||||
&placed,
|
||||
anno_scale,
|
||||
annotation_scale_handle,
|
||||
bg_color,
|
||||
depth_map,
|
||||
)
|
||||
{
|
||||
subs.push(LocalSub::Wire(lw));
|
||||
}
|
||||
@@ -513,7 +545,14 @@ fn build_defn(
|
||||
}
|
||||
if !used_baked {
|
||||
for lw in
|
||||
tessellate_sub_local(doc, entity, anno_scale, bg_color, depth_map)
|
||||
tessellate_sub_local(
|
||||
doc,
|
||||
entity,
|
||||
anno_scale,
|
||||
annotation_scale_handle,
|
||||
bg_color,
|
||||
depth_map,
|
||||
)
|
||||
{
|
||||
subs.push(LocalSub::Wire(lw));
|
||||
}
|
||||
@@ -536,7 +575,14 @@ fn build_defn(
|
||||
// the LocalWire; `emit_wire` scales it by the insert transform
|
||||
// so the shader band matches the scaled geometry (same band the
|
||||
// top-level path draws — depth-tested + linetype-dashed).
|
||||
for lw in tessellate_sub_local(doc, entity, anno_scale, bg_color, depth_map) {
|
||||
for lw in tessellate_sub_local(
|
||||
doc,
|
||||
entity,
|
||||
anno_scale,
|
||||
annotation_scale_handle,
|
||||
bg_color,
|
||||
depth_map,
|
||||
) {
|
||||
subs.push(LocalSub::Wire(lw));
|
||||
}
|
||||
}
|
||||
@@ -609,6 +655,7 @@ fn tessellate_sub_local(
|
||||
doc: &CadDocument,
|
||||
sub: &EntityType,
|
||||
anno_scale: f32,
|
||||
annotation_scale_handle: Option<Handle>,
|
||||
bg_color: [f32; 4],
|
||||
depth_map: &HashMap<u64, [f32; 2]>,
|
||||
) -> Vec<LocalWire> {
|
||||
@@ -657,7 +704,19 @@ fn tessellate_sub_local(
|
||||
// before casting to f32 — same precision-preservation trick used for
|
||||
// top-level entities, applied per-defn.
|
||||
let wires_out = tessellate::tessellate(
|
||||
doc, h, sub, false, sub_color, pat_len, pat, lw_px, anno_scale, None, bg_color, false,
|
||||
doc,
|
||||
h,
|
||||
sub,
|
||||
false,
|
||||
sub_color,
|
||||
pat_len,
|
||||
pat,
|
||||
lw_px,
|
||||
anno_scale,
|
||||
annotation_scale_handle,
|
||||
None,
|
||||
bg_color,
|
||||
false,
|
||||
);
|
||||
if wires_out.is_empty() {
|
||||
return vec![];
|
||||
|
||||
+34
-2
@@ -920,7 +920,23 @@ impl Scene {
|
||||
|
||||
// Paper entities and viewport borders belong to the sheet. Model
|
||||
// content projected through those viewports deliberately does not.
|
||||
for wire in self.wires_for_block_culled(layout_block, None, None, None, None) {
|
||||
let scale = if self.current_layout == "Model" {
|
||||
crate::scene::annotative::scale_handle_by_name(
|
||||
&self.document,
|
||||
&self.document.header.current_annotation_scale,
|
||||
)
|
||||
} else {
|
||||
self.paper_annotation_scale_handle()
|
||||
};
|
||||
for wire in self.wires_for_block_culled(
|
||||
layout_block,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
scale,
|
||||
self.annotation_all_visible(),
|
||||
) {
|
||||
let is_infinite = Self::handle_from_wire_name(&wire.name)
|
||||
.and_then(|handle| self.document.get_entity(handle))
|
||||
.is_some_and(|entity| {
|
||||
@@ -1000,7 +1016,23 @@ impl Scene {
|
||||
// (issue #51). `wpp = None` also tessellates at a fixed tolerance so
|
||||
// the bounds don't drift with zoom-adaptive curve sampling.
|
||||
let layout_block = self.current_layout_block_handle();
|
||||
let mut wires = self.wires_for_block_culled(layout_block, None, None, None, None);
|
||||
let scale = if self.current_layout == "Model" {
|
||||
crate::scene::annotative::scale_handle_by_name(
|
||||
&self.document,
|
||||
&self.document.header.current_annotation_scale,
|
||||
)
|
||||
} else {
|
||||
self.paper_annotation_scale_handle()
|
||||
};
|
||||
let mut wires = self.wires_for_block_culled(
|
||||
layout_block,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
scale,
|
||||
self.annotation_all_visible(),
|
||||
);
|
||||
// Ray / XLine tessellate as ±DISPLAY_EXTENT display segments
|
||||
// (entities/ray.rs) — their endpoints are rendering artifacts, not
|
||||
// drawing extent. A construction line through the drawing defeats
|
||||
|
||||
@@ -321,6 +321,7 @@ pub fn place_block_wires(
|
||||
[0.0; 8],
|
||||
line_weight_px,
|
||||
anno_scale,
|
||||
None,
|
||||
world_per_pixel,
|
||||
bg_color,
|
||||
false,
|
||||
|
||||
+21
-10
@@ -321,6 +321,7 @@ pub(crate) fn tessellate_entity_dim_text(
|
||||
active_viewport,
|
||||
bg_color,
|
||||
anno_scale,
|
||||
None,
|
||||
e,
|
||||
None,
|
||||
view_aabb,
|
||||
@@ -345,6 +346,7 @@ pub(crate) fn tessellate_entity(
|
||||
active_viewport: Option<Handle>,
|
||||
bg_color: [f32; 4],
|
||||
anno_scale: f32,
|
||||
annotation_scale_handle: Option<Handle>,
|
||||
e: &EntityType,
|
||||
block_cache: Option<&cache::block_cache::BlockCache>,
|
||||
// World-space XY view AABB (post `world_offset` subtraction). When
|
||||
@@ -357,7 +359,11 @@ pub(crate) fn tessellate_entity(
|
||||
// by the viewport's GPU uniform so it never changes resident wire content.
|
||||
paper_space: bool,
|
||||
) -> Vec<WireModel> {
|
||||
let contextual = crate::scene::annotative::entity_for_active_context(document, e);
|
||||
let contextual = crate::scene::annotative::entity_for_annotation_context(
|
||||
document,
|
||||
e,
|
||||
annotation_scale_handle,
|
||||
);
|
||||
let e = contextual.as_ref();
|
||||
let h = e.common().handle;
|
||||
let sel = selected.contains(&h);
|
||||
@@ -371,7 +377,12 @@ pub(crate) fn tessellate_entity(
|
||||
| EntityType::Dimension(_)
|
||||
| EntityType::MultiLeader(_)
|
||||
) {
|
||||
crate::scene::annotative::effective_annotation_scale(document, e, anno_scale)
|
||||
crate::scene::annotative::effective_annotation_scale_for(
|
||||
document,
|
||||
e,
|
||||
anno_scale,
|
||||
annotation_scale_handle,
|
||||
)
|
||||
} else {
|
||||
anno_scale
|
||||
};
|
||||
@@ -505,6 +516,7 @@ pub(crate) fn tessellate_entity(
|
||||
pattern,
|
||||
1.5,
|
||||
1.0,
|
||||
annotation_scale_handle,
|
||||
world_per_pixel,
|
||||
bg_color,
|
||||
false,
|
||||
@@ -788,6 +800,7 @@ pub(crate) fn tessellate_entity(
|
||||
// Block contents are baked at the final WCS size —
|
||||
// don't let downstream paths re-apply anno_scale.
|
||||
1.0,
|
||||
None,
|
||||
sub,
|
||||
block_cache,
|
||||
view_aabb,
|
||||
@@ -954,6 +967,7 @@ pub(crate) fn tessellate_entity(
|
||||
active_viewport,
|
||||
bg_color,
|
||||
anno_scale,
|
||||
annotation_scale_handle,
|
||||
&placed,
|
||||
block_cache,
|
||||
view_aabb,
|
||||
@@ -994,14 +1008,7 @@ pub(crate) fn tessellate_entity(
|
||||
// No baked block (e.g. a table created in-app) — synthesise coloured
|
||||
// geometry from the rows + TableStyle so fills/colours/borders/margins
|
||||
// are honoured instead of the monochrome fallback.
|
||||
// Annotative tables scale with the current annotation scale (their
|
||||
// stored geometry is at paper size); non-annotative tables are already
|
||||
// model-size, so pass 1.0.
|
||||
let table_anno = if crate::scene::annotative::is_annotative(document, e) {
|
||||
anno_scale
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
let table_anno = 1.0;
|
||||
let mut wires = crate::entities::table::tessellate_table(
|
||||
tab,
|
||||
document,
|
||||
@@ -1019,6 +1026,7 @@ pub(crate) fn tessellate_entity(
|
||||
active_viewport,
|
||||
bg_color,
|
||||
1.0,
|
||||
None,
|
||||
&EntityType::Insert(insert),
|
||||
block_cache,
|
||||
view_aabb,
|
||||
@@ -1198,6 +1206,7 @@ pub(crate) fn tessellate_entity(
|
||||
sub_pattern,
|
||||
sub_line_weight_px,
|
||||
anno_scale,
|
||||
annotation_scale_handle,
|
||||
world_per_pixel,
|
||||
bg_color,
|
||||
false,
|
||||
@@ -1260,6 +1269,7 @@ pub(crate) fn tessellate_entity(
|
||||
pattern,
|
||||
line_weight_px,
|
||||
anno_scale,
|
||||
annotation_scale_handle,
|
||||
world_per_pixel,
|
||||
bg_color,
|
||||
false,
|
||||
@@ -1398,6 +1408,7 @@ pub(crate) fn tessellate_entity(
|
||||
pattern,
|
||||
line_weight_px,
|
||||
anno_scale,
|
||||
annotation_scale_handle,
|
||||
world_per_pixel,
|
||||
bg_color,
|
||||
false,
|
||||
|
||||
@@ -131,6 +131,7 @@ pub fn tessellate(
|
||||
pattern: [f32; 8],
|
||||
line_weight_px: f32,
|
||||
anno_scale: f32,
|
||||
annotation_scale_handle: Option<Handle>,
|
||||
world_per_pixel: Option<f32>,
|
||||
// Canvas background colour — used for the MTEXT background *mask* fill
|
||||
// (flag 0x02, "use drawing window colour") so the mask erases geometry
|
||||
@@ -156,8 +157,12 @@ pub fn tessellate(
|
||||
// oversized text). Annotative-ness is resolved centrally from the entity's
|
||||
// per-object context, legacy XDATA, or annotative style (see
|
||||
// `scene::annotative::is_annotative`) so the bake and the panel agree.
|
||||
let anno_scale =
|
||||
crate::scene::annotative::effective_annotation_scale(document, entity, anno_scale);
|
||||
let anno_scale = crate::scene::annotative::effective_annotation_scale_for(
|
||||
document,
|
||||
entity,
|
||||
anno_scale,
|
||||
annotation_scale_handle,
|
||||
);
|
||||
|
||||
// A HATCH is drawn as a fill by the hatch pipeline and highlighted via a
|
||||
// fill tint when selected (issue #71), so it carries no boundary outline in
|
||||
|
||||
+96
-29
@@ -685,7 +685,7 @@ impl Scene {
|
||||
entities: Vec<EntityType>,
|
||||
name: &str,
|
||||
base: glam::DVec3,
|
||||
) -> Result<(), String> {
|
||||
) -> Result<Vec<Handle>, String> {
|
||||
let name = name.trim();
|
||||
if name.is_empty() {
|
||||
return Err("Block name cannot be empty.".into());
|
||||
@@ -729,20 +729,23 @@ impl Scene {
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let local = EntityTransform::Translate(-base);
|
||||
let mut entity_handles = Vec::with_capacity(entities.len());
|
||||
for mut entity in entities {
|
||||
view::dispatch::apply_transform(&mut entity, &local);
|
||||
entity = crate::modules::draw::modify::explode::normalize_entity_for_block(entity);
|
||||
Self::reset_clone_subhandles(&mut self.document, &mut entity);
|
||||
entity.common_mut().handle = Handle::NULL;
|
||||
entity.common_mut().owner_handle = br_handle;
|
||||
self.document
|
||||
let handle = self
|
||||
.document
|
||||
.add_entity(entity)
|
||||
.map_err(|e| e.to_string())?;
|
||||
entity_handles.push(handle);
|
||||
}
|
||||
// Block defns don't render on their own, but the geometry cache must
|
||||
// pick up the new definition so the interactive insert can preview it.
|
||||
self.bump_geometry();
|
||||
Ok(())
|
||||
Ok(entity_handles)
|
||||
}
|
||||
|
||||
/// Recreate a block definition verbatim — the entities are already in
|
||||
@@ -794,6 +797,8 @@ impl Scene {
|
||||
&self,
|
||||
target_block: Handle,
|
||||
frozen: Option<&rustc_hash::FxHashSet<Handle>>,
|
||||
annotation_scale_handle: Option<Handle>,
|
||||
all_visible: bool,
|
||||
) -> Vec<HatchModel> {
|
||||
let layer_hidden = |layer: &str| {
|
||||
self.document
|
||||
@@ -832,6 +837,12 @@ impl Scene {
|
||||
if c.invisible
|
||||
|| self.entity_temporarily_hidden(handle)
|
||||
|| layer_hidden(&c.layer)
|
||||
|| crate::scene::annotative::annotative_offscale_for(
|
||||
&self.document,
|
||||
c,
|
||||
annotation_scale_handle,
|
||||
all_visible,
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -851,17 +862,19 @@ impl Scene {
|
||||
.document
|
||||
.get_entity(handle)
|
||||
.map(|entity| {
|
||||
crate::scene::annotative::entity_for_active_context(
|
||||
crate::scene::annotative::entity_for_annotation_context(
|
||||
&self.document,
|
||||
entity,
|
||||
annotation_scale_handle,
|
||||
)
|
||||
});
|
||||
let entity = contextual.as_deref();
|
||||
let mut m = match entity {
|
||||
Some(EntityType::Hatch(dxf))
|
||||
if crate::scene::annotative::active_object_context(
|
||||
if crate::scene::annotative::active_object_context_for_scale(
|
||||
&self.document,
|
||||
handle,
|
||||
annotation_scale_handle,
|
||||
)
|
||||
.is_some() =>
|
||||
{
|
||||
@@ -935,12 +948,7 @@ impl Scene {
|
||||
if dxf.pattern.lines.is_empty() =>
|
||||
{
|
||||
m.angle_offset = dxf.pattern_angle as f32;
|
||||
let anno = if self.current_layout == "Model" {
|
||||
self.annotation_scale
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
m.scale = dxf.pattern_scale as f32 * anno;
|
||||
m.scale = dxf.pattern_scale as f32;
|
||||
}
|
||||
model::hatch_model::HatchPattern::Gradient { angle_deg, .. } => {
|
||||
*angle_deg = dxf.pattern_angle.to_degrees() as f32;
|
||||
@@ -975,6 +983,8 @@ impl Scene {
|
||||
hatch_bg,
|
||||
true,
|
||||
frozen,
|
||||
annotation_scale_handle,
|
||||
all_visible,
|
||||
));
|
||||
|
||||
// Wide LwPolyline / Polyline2D bands are no longer hatch fills at
|
||||
@@ -1007,12 +1017,16 @@ impl Scene {
|
||||
hatch_bg: [f32; 4],
|
||||
tint_selected: bool,
|
||||
frozen: Option<&rustc_hash::FxHashSet<Handle>>,
|
||||
annotation_scale_handle: Option<Handle>,
|
||||
all_visible: bool,
|
||||
) -> Vec<HatchModel> {
|
||||
self.exploded_insert_hatch_models_filtered(
|
||||
layout_block,
|
||||
hatch_bg,
|
||||
tint_selected,
|
||||
frozen,
|
||||
annotation_scale_handle,
|
||||
all_visible,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
@@ -1049,6 +1063,8 @@ impl Scene {
|
||||
hatch_bg,
|
||||
true,
|
||||
(!frozen.is_empty()).then_some(&frozen),
|
||||
self.displayed_annotation_scale_handle(),
|
||||
self.annotation_all_visible(),
|
||||
Some(&targets),
|
||||
true,
|
||||
)
|
||||
@@ -1060,6 +1076,8 @@ impl Scene {
|
||||
hatch_bg: [f32; 4],
|
||||
tint_selected: bool,
|
||||
frozen: Option<&rustc_hash::FxHashSet<Handle>>,
|
||||
annotation_scale_handle: Option<Handle>,
|
||||
all_visible: bool,
|
||||
targets: Option<&rustc_hash::FxHashSet<Handle>>,
|
||||
include_preview_hidden: bool,
|
||||
) -> Vec<HatchModel> {
|
||||
@@ -1148,8 +1166,11 @@ impl Scene {
|
||||
out
|
||||
}
|
||||
for entity in self.document.entities() {
|
||||
let contextual =
|
||||
crate::scene::annotative::entity_for_active_context(&self.document, entity);
|
||||
let contextual = crate::scene::annotative::entity_for_annotation_context(
|
||||
&self.document,
|
||||
entity,
|
||||
annotation_scale_handle,
|
||||
);
|
||||
let EntityType::Insert(ins) = contextual.as_ref() else {
|
||||
continue;
|
||||
};
|
||||
@@ -1179,9 +1200,12 @@ impl Scene {
|
||||
// Off-scale annotative representation (model space only — paper
|
||||
// viewports use their per-viewport frozen scale layers). Skips its
|
||||
// whole fill subtree, matching the wire path.
|
||||
if frozen.is_none()
|
||||
&& crate::scene::annotative::annotative_offscale(&self.document, &ins.common)
|
||||
{
|
||||
if crate::scene::annotative::annotative_offscale_for(
|
||||
&self.document,
|
||||
&ins.common,
|
||||
annotation_scale_handle,
|
||||
all_visible,
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if !self.block_has_hatch(&ins.block_name, &mut hatch_block_memo)
|
||||
@@ -1232,9 +1256,13 @@ impl Scene {
|
||||
// use their per-viewport frozen scale layers. `explode` preserves
|
||||
// the child handle, so the membership lookup still resolves here.
|
||||
let offscale = |e: &EntityType| -> bool {
|
||||
frozen.is_none()
|
||||
&& matches!(e, EntityType::Insert(ni)
|
||||
if crate::scene::annotative::annotative_offscale(&self.document, &ni.common))
|
||||
matches!(e, EntityType::Insert(ni)
|
||||
if crate::scene::annotative::annotative_offscale_for(
|
||||
&self.document,
|
||||
&ni.common,
|
||||
annotation_scale_handle,
|
||||
all_visible,
|
||||
))
|
||||
};
|
||||
type ResolvedStyle = ([f32; 4], f32, [f32; 8], f32, u8);
|
||||
let mut stack: Vec<(
|
||||
@@ -1248,7 +1276,11 @@ impl Scene {
|
||||
.into_iter()
|
||||
.filter(|e| !offscale(e))
|
||||
.map(|e| {
|
||||
crate::scene::annotative::entity_for_active_context(&self.document, &e)
|
||||
crate::scene::annotative::entity_for_annotation_context(
|
||||
&self.document,
|
||||
&e,
|
||||
annotation_scale_handle,
|
||||
)
|
||||
.into_owned()
|
||||
})
|
||||
.map(|e| {
|
||||
@@ -1338,9 +1370,10 @@ impl Scene {
|
||||
continue;
|
||||
}
|
||||
let e =
|
||||
crate::scene::annotative::entity_for_active_context(
|
||||
crate::scene::annotative::entity_for_annotation_context(
|
||||
&self.document,
|
||||
&e,
|
||||
annotation_scale_handle,
|
||||
)
|
||||
.into_owned();
|
||||
stack.push((
|
||||
@@ -1451,6 +1484,8 @@ impl Scene {
|
||||
&self,
|
||||
target_block: Handle,
|
||||
frozen: Option<&rustc_hash::FxHashSet<Handle>>,
|
||||
annotation_scale_handle: Option<Handle>,
|
||||
all_visible: bool,
|
||||
) -> Vec<HatchModel> {
|
||||
let is_paper = self.current_layout != "Model";
|
||||
let bg_color: [f32; 4] = if is_paper {
|
||||
@@ -1537,8 +1572,11 @@ impl Scene {
|
||||
// apply_rotation) rather than through Insert::explode — the latter
|
||||
// double-scales the u/v basis.
|
||||
for entity in self.document.entities() {
|
||||
let contextual =
|
||||
crate::scene::annotative::entity_for_active_context(&self.document, entity);
|
||||
let contextual = crate::scene::annotative::entity_for_annotation_context(
|
||||
&self.document,
|
||||
entity,
|
||||
annotation_scale_handle,
|
||||
);
|
||||
let EntityType::Insert(ins) = contextual.as_ref() else {
|
||||
continue;
|
||||
};
|
||||
@@ -1558,9 +1596,12 @@ impl Scene {
|
||||
if !self.belongs_to_visible_block(c.handle, c.owner_handle, target_block) {
|
||||
continue;
|
||||
}
|
||||
if frozen.is_none()
|
||||
&& crate::scene::annotative::annotative_offscale(&self.document, c)
|
||||
{
|
||||
if crate::scene::annotative::annotative_offscale_for(
|
||||
&self.document,
|
||||
c,
|
||||
annotation_scale_handle,
|
||||
all_visible,
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
self.collect_block_wipeouts(
|
||||
@@ -1571,6 +1612,8 @@ impl Scene {
|
||||
bg_color,
|
||||
&depth_map,
|
||||
&mut models,
|
||||
annotation_scale_handle,
|
||||
all_visible,
|
||||
);
|
||||
}
|
||||
models
|
||||
@@ -1588,6 +1631,8 @@ impl Scene {
|
||||
bg_color: [f32; 4],
|
||||
depth_map: &HashMap<u64, [f32; 2]>,
|
||||
models: &mut Vec<HatchModel>,
|
||||
annotation_scale_handle: Option<Handle>,
|
||||
all_visible: bool,
|
||||
) {
|
||||
if depth > 32 {
|
||||
return;
|
||||
@@ -1601,9 +1646,15 @@ impl Scene {
|
||||
return;
|
||||
};
|
||||
for &eh in &br.entity_handles {
|
||||
let Some(e) = self.document.get_entity(eh) else {
|
||||
let Some(source) = self.document.get_entity(eh) else {
|
||||
continue;
|
||||
};
|
||||
let contextual = crate::scene::annotative::entity_for_annotation_context(
|
||||
&self.document,
|
||||
source,
|
||||
annotation_scale_handle,
|
||||
);
|
||||
let e = contextual.as_ref();
|
||||
let c = e.common();
|
||||
if c.invisible
|
||||
|| self
|
||||
@@ -1613,6 +1664,12 @@ impl Scene {
|
||||
.map(|l| l.flags.off || l.flags.frozen)
|
||||
.unwrap_or(false)
|
||||
|| self.layer_frozen_in(&c.layer, frozen)
|
||||
|| crate::scene::annotative::annotative_offscale_for(
|
||||
&self.document,
|
||||
c,
|
||||
annotation_scale_handle,
|
||||
all_visible,
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -1655,6 +1712,8 @@ impl Scene {
|
||||
bg_color,
|
||||
depth_map,
|
||||
models,
|
||||
annotation_scale_handle,
|
||||
all_visible,
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
@@ -2240,7 +2299,11 @@ impl Scene {
|
||||
.document
|
||||
.get_entity(handle)
|
||||
.map(|entity| {
|
||||
crate::scene::annotative::entity_for_active_context(&self.document, entity)
|
||||
crate::scene::annotative::entity_for_annotation_context(
|
||||
&self.document,
|
||||
entity,
|
||||
self.displayed_annotation_scale_handle(),
|
||||
)
|
||||
});
|
||||
let new_model = match contextual.as_deref() {
|
||||
Some(EntityType::Hatch(dxf)) => {
|
||||
@@ -2272,7 +2335,11 @@ impl Scene {
|
||||
.filter_map(|e| match e {
|
||||
EntityType::Hatch(h) => Some((
|
||||
h.common.handle,
|
||||
crate::scene::annotative::entity_for_active_context(&self.document, e)
|
||||
crate::scene::annotative::entity_for_annotation_context(
|
||||
&self.document,
|
||||
e,
|
||||
self.displayed_annotation_scale_handle(),
|
||||
)
|
||||
.into_owned(),
|
||||
)),
|
||||
EntityType::Solid(s) => Some((s.common.handle, e.clone())),
|
||||
|
||||
+955
-130
File diff suppressed because it is too large.
Load diff
+12
-12
@@ -115,6 +115,15 @@ fn mirror_true_text_flags(e: &mut EntityType) {
|
||||
}
|
||||
|
||||
impl Scene {
|
||||
pub(crate) fn sync_displayed_annotation_context(&mut self, handle: Handle) -> bool {
|
||||
let scale = self.displayed_annotation_scale_handle();
|
||||
crate::scene::annotative::sync_annotation_context_from_entity(
|
||||
&mut self.document,
|
||||
handle,
|
||||
scale,
|
||||
)
|
||||
}
|
||||
|
||||
/// Invalidate a dimension's baked block while capturing every removed
|
||||
/// sub-entity for an active history transaction.
|
||||
pub fn invalidate_dim_block_recorded(&mut self, handle: Handle) {
|
||||
@@ -239,10 +248,7 @@ impl Scene {
|
||||
}
|
||||
}
|
||||
for &h in handles {
|
||||
if crate::scene::annotative::sync_active_context_from_entity(
|
||||
&mut self.document,
|
||||
h,
|
||||
) {
|
||||
if self.sync_displayed_annotation_context(h) {
|
||||
self.poison_undo_recording();
|
||||
}
|
||||
}
|
||||
@@ -414,10 +420,7 @@ impl Scene {
|
||||
}
|
||||
|
||||
for handle in changed.iter().copied() {
|
||||
let _ = crate::scene::annotative::sync_active_context_from_entity(
|
||||
&mut self.document,
|
||||
handle,
|
||||
);
|
||||
let _ = self.sync_displayed_annotation_context(handle);
|
||||
}
|
||||
if !changed.is_empty() {
|
||||
self.rebuild_derived_caches();
|
||||
@@ -639,10 +642,7 @@ impl Scene {
|
||||
if let Some(entity) = self.document.get_entity_mut(handle) {
|
||||
view::dispatch::apply_grip(entity, grip_id, apply);
|
||||
}
|
||||
if crate::scene::annotative::sync_active_context_from_entity(
|
||||
&mut self.document,
|
||||
handle,
|
||||
) {
|
||||
if self.sync_displayed_annotation_context(handle) {
|
||||
self.poison_undo_recording();
|
||||
}
|
||||
// A dimension loaded from a file renders through its baked *D block;
|
||||
|
||||
+78
-62
@@ -385,12 +385,17 @@ impl Scene {
|
||||
.unwrap_or(false)
|
||||
};
|
||||
let mut models: Vec<HatchModel> = Vec::new();
|
||||
let annotation_scale_handle = self.paper_annotation_scale_handle();
|
||||
let all_visible = self.annotation_all_visible();
|
||||
for (&handle, model) in self.hatches.iter() {
|
||||
let Some(source) = self.document.get_entity(handle) else {
|
||||
continue;
|
||||
};
|
||||
let contextual =
|
||||
crate::scene::annotative::entity_for_active_context(&self.document, source);
|
||||
let contextual = crate::scene::annotative::entity_for_annotation_context(
|
||||
&self.document,
|
||||
source,
|
||||
annotation_scale_handle,
|
||||
);
|
||||
let entity = contextual.as_ref();
|
||||
// Paper-space SOLIDs already carry WCS-aware wire fill triangles.
|
||||
// Keep their cached XY HatchModel out of the sheet set so the same
|
||||
@@ -403,6 +408,12 @@ impl Scene {
|
||||
if c.invisible
|
||||
|| self.entity_temporarily_hidden(handle)
|
||||
|| layer_hidden(&c.layer)
|
||||
|| crate::scene::annotative::annotative_offscale_for(
|
||||
&self.document,
|
||||
c,
|
||||
annotation_scale_handle,
|
||||
all_visible,
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -411,9 +422,10 @@ impl Scene {
|
||||
}
|
||||
let mut m = match entity {
|
||||
EntityType::Hatch(dxf)
|
||||
if crate::scene::annotative::active_object_context(
|
||||
if crate::scene::annotative::active_object_context_for_scale(
|
||||
&self.document,
|
||||
handle,
|
||||
annotation_scale_handle,
|
||||
)
|
||||
.is_some() =>
|
||||
{
|
||||
@@ -453,7 +465,14 @@ impl Scene {
|
||||
} else {
|
||||
self.bg_color
|
||||
};
|
||||
let exploded = self.exploded_insert_hatch_models(layout_block, hatch_bg, false, None);
|
||||
let exploded = self.exploded_insert_hatch_models(
|
||||
layout_block,
|
||||
hatch_bg,
|
||||
false,
|
||||
None,
|
||||
annotation_scale_handle,
|
||||
all_visible,
|
||||
);
|
||||
models.extend(exploded);
|
||||
Arc::new(models)
|
||||
}
|
||||
@@ -465,6 +484,8 @@ impl Scene {
|
||||
&self,
|
||||
block: Handle,
|
||||
frozen: Option<&rustc_hash::FxHashSet<Handle>>,
|
||||
annotation_scale_handle: Option<Handle>,
|
||||
all_visible: bool,
|
||||
) -> Vec<HatchModel> {
|
||||
let layer_hidden = |layer: &str| {
|
||||
self.document
|
||||
@@ -478,23 +499,33 @@ impl Scene {
|
||||
let Some(source) = self.document.get_entity(handle) else {
|
||||
continue;
|
||||
};
|
||||
let contextual =
|
||||
crate::scene::annotative::entity_for_active_context(&self.document, source);
|
||||
let contextual = crate::scene::annotative::entity_for_annotation_context(
|
||||
&self.document,
|
||||
source,
|
||||
annotation_scale_handle,
|
||||
);
|
||||
let entity = contextual.as_ref();
|
||||
let common = entity.common();
|
||||
if common.invisible
|
||||
|| self.entity_temporarily_hidden(handle)
|
||||
|| layer_hidden(&common.layer)
|
||||
|| self.layer_frozen_in(&common.layer, frozen)
|
||||
|| crate::scene::annotative::annotative_offscale_for(
|
||||
&self.document,
|
||||
common,
|
||||
annotation_scale_handle,
|
||||
all_visible,
|
||||
)
|
||||
|| !self.belongs_to_visible_block(handle, common.owner_handle, block)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let mut hatch = match entity {
|
||||
EntityType::Hatch(dxf)
|
||||
if crate::scene::annotative::active_object_context(
|
||||
if crate::scene::annotative::active_object_context_for_scale(
|
||||
&self.document,
|
||||
handle,
|
||||
annotation_scale_handle,
|
||||
)
|
||||
.is_some() =>
|
||||
{
|
||||
@@ -522,6 +553,8 @@ impl Scene {
|
||||
self.paper_bg_color,
|
||||
false,
|
||||
frozen,
|
||||
annotation_scale_handle,
|
||||
all_visible,
|
||||
));
|
||||
models
|
||||
}
|
||||
@@ -532,6 +565,9 @@ impl Scene {
|
||||
&self,
|
||||
block: Handle,
|
||||
frozen: Option<&rustc_hash::FxHashSet<Handle>>,
|
||||
annotation_scale_handle: Option<Handle>,
|
||||
all_visible: bool,
|
||||
highlight_selection: bool,
|
||||
) -> Vec<HatchModel> {
|
||||
let depth_map = self.draw_depth_map();
|
||||
let mut models = Vec::new();
|
||||
@@ -562,7 +598,11 @@ impl Scene {
|
||||
boundary_wcs: None,
|
||||
pattern: model::hatch_model::HatchPattern::Solid,
|
||||
name: "WIPEOUT_FILL".into(),
|
||||
color: self.paper_bg_color,
|
||||
color: if highlight_selection && self.selected.contains(&common.handle) {
|
||||
[0.15, 0.55, 1.00, 0.35]
|
||||
} else {
|
||||
self.paper_bg_color
|
||||
},
|
||||
aci: 0,
|
||||
line_weight_px: 1.0,
|
||||
angle_offset: 0.0,
|
||||
@@ -574,8 +614,11 @@ impl Scene {
|
||||
});
|
||||
}
|
||||
for entity in self.document.entities() {
|
||||
let contextual =
|
||||
crate::scene::annotative::entity_for_active_context(&self.document, entity);
|
||||
let contextual = crate::scene::annotative::entity_for_annotation_context(
|
||||
&self.document,
|
||||
entity,
|
||||
annotation_scale_handle,
|
||||
);
|
||||
let EntityType::Insert(insert) = contextual.as_ref() else {
|
||||
continue;
|
||||
};
|
||||
@@ -589,6 +632,12 @@ impl Scene {
|
||||
.map(|layer| layer.flags.off || layer.flags.frozen)
|
||||
.unwrap_or(false)
|
||||
|| self.layer_frozen_in(&common.layer, frozen)
|
||||
|| crate::scene::annotative::annotative_offscale_for(
|
||||
&self.document,
|
||||
common,
|
||||
annotation_scale_handle,
|
||||
all_visible,
|
||||
)
|
||||
|| !self.belongs_to_visible_block(common.handle, common.owner_handle, block)
|
||||
{
|
||||
continue;
|
||||
@@ -598,9 +647,15 @@ impl Scene {
|
||||
&insert.block_name,
|
||||
0,
|
||||
frozen,
|
||||
self.paper_bg_color,
|
||||
if highlight_selection && self.selected.contains(&common.handle) {
|
||||
[0.15, 0.55, 1.00, 0.35]
|
||||
} else {
|
||||
self.paper_bg_color
|
||||
},
|
||||
&depth_map,
|
||||
&mut models,
|
||||
annotation_scale_handle,
|
||||
all_visible,
|
||||
);
|
||||
}
|
||||
models
|
||||
@@ -613,54 +668,13 @@ impl Scene {
|
||||
/// copy on the paper sheet.
|
||||
pub fn paper_canvas_wipeouts(&self) -> Arc<Vec<HatchModel>> {
|
||||
let layout_block = self.current_layout_block_handle();
|
||||
let bg_color = self.paper_bg_color;
|
||||
let mut models = Vec::new();
|
||||
for entity in self.document.entities() {
|
||||
let EntityType::Wipeout(wo) = entity else {
|
||||
continue;
|
||||
};
|
||||
if wo.common.invisible
|
||||
|| self.entity_temporarily_hidden(wo.common.handle)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if self
|
||||
.document
|
||||
.layers
|
||||
.get(&wo.common.layer)
|
||||
.map(|l| l.flags.off || l.flags.frozen)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if !self.belongs_to_visible_block(wo.common.handle, wo.common.owner_handle, layout_block)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Paper-block wipeouts live in paper coords — no `world_offset`.
|
||||
let (fill_origin, boundary) = Self::wipeout_boundary_2d(wo);
|
||||
if boundary.len() < 3 {
|
||||
continue;
|
||||
}
|
||||
let mut fill_color = bg_color;
|
||||
if self.selected.contains(&wo.common.handle) {
|
||||
fill_color = [0.15, 0.55, 1.00, 0.35];
|
||||
}
|
||||
models.push(HatchModel {
|
||||
boundary: Arc::new(boundary),
|
||||
boundary_wcs: None,
|
||||
pattern: model::hatch_model::HatchPattern::Solid,
|
||||
name: "WIPEOUT_FILL".into(),
|
||||
color: fill_color,
|
||||
aci: 0,
|
||||
line_weight_px: 1.0,
|
||||
angle_offset: 0.0,
|
||||
scale: 1.0,
|
||||
world_origin: fill_origin,
|
||||
draw_depth: 0.0,
|
||||
});
|
||||
}
|
||||
Arc::new(models)
|
||||
Arc::new(self.plot_wipeouts_for_block(
|
||||
layout_block,
|
||||
None,
|
||||
self.paper_annotation_scale_handle(),
|
||||
self.annotation_all_visible(),
|
||||
true,
|
||||
))
|
||||
}
|
||||
|
||||
/// Build a Camera oriented and scaled to match a paper-space Viewport entity.
|
||||
@@ -805,8 +819,8 @@ impl Scene {
|
||||
// Its live zoom is camera magnification, not CANNOSCALE: tying
|
||||
// annotation geometry to view_height rebuilt the entire model on every
|
||||
// wheel tick whenever the drawing contained one annotative object.
|
||||
// Explicit annotation-scale changes still rebuild through
|
||||
// `self.annotation_scale`; PSLTSCALE is a viewport GPU uniform.
|
||||
// Explicit viewport annotation-scale changes still rebuild the resident
|
||||
// set; PSLTSCALE is a viewport GPU uniform.
|
||||
let frozen = match self.document.get_entity(vp_handle) {
|
||||
Some(EntityType::Viewport(vp)) => {
|
||||
let f: HSet<Handle> = vp.frozen_layers.iter().cloned().collect();
|
||||
@@ -815,9 +829,11 @@ impl Scene {
|
||||
_ => HSet::default(),
|
||||
};
|
||||
|
||||
let scale_handle = self.viewport_scale_handle(vp_handle);
|
||||
self.resident_wires_for(
|
||||
self.model_space_block_handle(),
|
||||
Some(self.annotation_scale),
|
||||
Some(self.viewport_annotation_multiplier(vp_handle)),
|
||||
scale_handle,
|
||||
Some(&frozen),
|
||||
)
|
||||
}
|
||||
|
||||
+13
-2
@@ -430,7 +430,12 @@ impl Scene {
|
||||
|
||||
let frozen: rustc_hash::FxHashSet<Handle> =
|
||||
viewport.frozen_layers.iter().copied().collect();
|
||||
let hatches = self.plot_hatches_for_block(model_block, Some(&frozen));
|
||||
let hatches = self.plot_hatches_for_block(
|
||||
model_block,
|
||||
Some(&frozen),
|
||||
self.viewport_scale_handle(viewport.common.handle),
|
||||
self.annotation_all_visible(),
|
||||
);
|
||||
for hatch in hatches {
|
||||
if matches!(&hatch.pattern, HatchPattern::Pattern(_)) {
|
||||
let mut points = Vec::new();
|
||||
@@ -482,7 +487,13 @@ impl Scene {
|
||||
}
|
||||
}
|
||||
|
||||
for wipeout in self.plot_wipeouts_for_block(model_block, Some(&frozen)) {
|
||||
for wipeout in self.plot_wipeouts_for_block(
|
||||
model_block,
|
||||
Some(&frozen),
|
||||
self.viewport_scale_handle(viewport.common.handle),
|
||||
self.annotation_all_visible(),
|
||||
false,
|
||||
) {
|
||||
if let Some(wipeout) =
|
||||
project_plot_fill(wipeout, &project, xmin, ymin, xmax, ymax)
|
||||
{
|
||||
|
||||
@@ -2243,15 +2243,15 @@ impl Scene {
|
||||
(hatches, wipeouts, Some(images))
|
||||
} else {
|
||||
(
|
||||
self.hatch_models_for_viewport(&vp_frozen),
|
||||
self.wipeout_models_for_viewport(&vp_frozen),
|
||||
self.hatch_models_for_viewport(inst.handle, &vp_frozen),
|
||||
self.wipeout_models_for_viewport(inst.handle, &vp_frozen),
|
||||
None,
|
||||
)
|
||||
};
|
||||
let images = if let Some(images) = paper_images {
|
||||
images
|
||||
} else {
|
||||
self.images_for_viewport(&vp_frozen)
|
||||
self.images_for_viewport(inst.handle, &vp_frozen)
|
||||
};
|
||||
// The paper sheet shows the layout's own 2-D content (fills, borders,
|
||||
// annotation) — never the model's 3-D solids. Those are drawn inside
|
||||
@@ -2263,7 +2263,7 @@ impl Scene {
|
||||
let meshes = if inst.paper_sheet {
|
||||
Arc::new(Vec::new())
|
||||
} else {
|
||||
self.meshes_for_viewport(&vp_frozen)
|
||||
self.meshes_for_viewport(inst.handle, &vp_frozen)
|
||||
};
|
||||
|
||||
// SDF text quads (behind OCS_TEXT_SDF). The glyph quads ride on each
|
||||
|
||||
Reference in new issue
Block a user