feat(dimension): styled placement previews for creation commands

Show the styled dimension and its value while placing a dimension, using
the same builder the command commits with. Each creation command
(DIMLINEAR, DIMALIGNED, DIMANGULAR, DIMRADIUS, DIMDIAMETER, DIMARC,
DIMJOGGED, DIMORDINATE, DIMCONTINUE, DIMBASELINE, QDIM) reports the
dimensions it would commit at the current stage through
`CadCommand::dimension_preview`. The application applies the current
creation style, or the base dimension's style for DIMCONTINUEMODE=1,
and the same viewport measurement scale as commit, then tessellates the
transient entity through the normal dimension renderer.

While the second definition point is being acquired, a hovered model
snap supplies a provisional viewport scale; once both points are fixed,
hovering another viewport does not change the value. Angular dimensions
never receive length compensation. Degenerate geometry, an invalid
scale, or conflicting viewports fall back to the plain rubber-band
preview so no misleading value is shown.
This commit is contained in:
Christopher Overbeck committed 2026-09-16 13:58:44 -05:00
1 parent ca0ee37d26
commit acbbb23297
17 files changed
+724 -138

No files matched your search

+43 -21
View File
@@ -6,7 +6,8 @@ use acadrust::EntityType;
use glam::DVec3;
use crate::command::{
CadCommand, CmdOption, CmdResult, DimensionAssociationInput, InputKind, WorkingPlane,
CadCommand, CmdOption, CmdResult, DimensionAssociationInput, DimensionPreview, InputKind,
WorkingPlane,
};
use crate::modules::{IconKind, ModuleEvent, ToolDef};
use crate::scene::model::wire_model::WireModel;
@@ -61,6 +62,29 @@ impl AlignedDimensionCommand {
mtext_override: false,
}
}
fn build_dimension(&self, first: DVec3, second: DVec3, point: DVec3) -> EntityType {
let first = self.plane.to_local(first);
let second = self.plane.to_local(second);
let point = self.plane.to_local(point);
let mut dim = DimensionAligned::new(v3(first), v3(second));
// The placement point is stored so commit matches the preview.
dim.definition_point = v3(point);
dim.base.definition_point = v3(point);
let (d1, d2) = dim_line_endpoints(first, second, point);
dim.base.text_middle_point = v3((d1 + d2) * 0.5);
dim.base.insertion_point = dim.base.text_middle_point;
dim.base.actual_measurement = dim.measurement();
crate::entities::dimension::set_dimension_text_override(
&mut dim.base,
self.text_override.clone(),
);
// An explicit text angle overrides the default rotation.
if let Some(angle) = self.text_angle {
dim.base.text_rotation = angle;
}
self.plane.place_entity(EntityType::Dimension(Dimension::Aligned(dim)))
}
}
impl CadCommand for AlignedDimensionCommand {
@@ -115,27 +139,8 @@ impl CadCommand for AlignedDimensionCommand {
CmdResult::NeedPoint
}
Step::DimLine { p1, p2 } => {
let p1 = self.plane.to_local(p1);
let p2 = self.plane.to_local(p2);
let pt = self.plane.to_local(pt);
let mut dim = DimensionAligned::new(v3(p1), v3(p2));
// Store the cursor position so commit matches the preview.
dim.definition_point = v3(pt);
dim.base.definition_point = v3(pt);
let (d1, d2) = dim_line_endpoints(p1, p2, pt);
dim.base.text_middle_point = v3((d1 + d2) * 0.5);
dim.base.insertion_point = dim.base.text_middle_point;
dim.base.actual_measurement = dim.measurement();
crate::entities::dimension::set_dimension_text_override(
&mut dim.base,
self.text_override.clone(),
);
// An explicit text angle overrides the default rotation.
if let Some(a) = self.text_angle {
dim.base.text_rotation = a;
}
CmdResult::CommitDimension {
entity: self.plane.place_entity(EntityType::Dimension(Dimension::Aligned(dim))),
entity: self.build_dimension(p1, p2, pt),
association: DimensionAssociationInput::Infer(self.source_handle),
preserve_base_style: false,
continue_command: false,
@@ -351,6 +356,23 @@ impl CadCommand for AlignedDimensionCommand {
fill_tris_low: Vec::new(),
})
}
fn dimension_preview(&self, cursor: DVec3) -> Option<Vec<DimensionPreview>> {
if self.selecting_object {
return None;
}
let (first, second) = match self.step {
Step::First => return None,
Step::Second(first) => (first, cursor),
Step::DimLine { p1, p2 } => (p1, p2),
};
if first.distance_squared(second) <= 1e-24 {
return Some(Vec::new());
}
Some(vec![DimensionPreview::current_style(
self.build_dimension(first, second, cursor),
)])
}
}
fn v3(p: DVec3) -> Vector3 {
+28 -2
View File
@@ -7,8 +7,8 @@ use cadkernel::geom2d::{
};
use crate::command::{
CadCommand, CmdOption, CmdResult, DimensionAssociationInput,
DimensionAssociationSource, InputKind, WorkingPlane,
CadCommand, CmdOption, CmdResult, DimensionAssociationInput, DimensionAssociationSource,
DimensionPreview, InputKind, WorkingPlane,
};
use crate::modules::{IconKind, ModuleEvent, ToolDef};
use crate::scene::model::wire_model::WireModel;
@@ -807,6 +807,32 @@ impl CadCommand for AngularDimensionCommand {
};
Some(preview_wire(points))
}
fn dimension_preview(&self, cursor: DVec3) -> Option<Vec<DimensionPreview>> {
if self.selecting_object {
return None;
}
let result = match self.step {
Step::ArcPoint3 {
vertex,
first,
second,
} => self.finish_three_point(vertex, first, second, cursor),
Step::ArcPoint2 {
first_start,
first_end,
second_start,
second_end,
} => self.finish_two_line(first_start, first_end, second_start, second_end, cursor),
_ => return None,
};
match result {
CmdResult::CommitDimension { entity, .. } => {
Some(vec![DimensionPreview::current_style(entity)])
}
_ => Some(Vec::new()),
}
}
}
fn picked_curve(entity: &EntityType, click: DVec3) -> Option<PickedCurve> {
+14 -2
View File
@@ -5,8 +5,8 @@ use cadkernel::geom2d::tessellate::{arc, DEFAULT_SEGMENTS_PER_RADIAN};
use glam::DVec3;
use crate::command::{
CadCommand, CmdOption, CmdResult, DimensionAssociationInput,
DimensionAssociationSource, InputKind, WorkingPlane,
CadCommand, CmdOption, CmdResult, DimensionAssociationInput, DimensionAssociationSource,
DimensionPreview, InputKind, WorkingPlane,
};
use crate::modules::{IconKind, ModuleEvent, ToolDef};
use crate::scene::dimension_assoc::{
@@ -516,6 +516,18 @@ impl CadCommand for ArcLengthDimensionCommand {
}
}
}
fn dimension_preview(&self, cursor: DVec3) -> Option<Vec<DimensionPreview>> {
let Step::DimLine(selection) = self.step else {
return None;
};
match self.commit_dimension(selection, cursor) {
CmdResult::CommitDimension { entity, .. } => {
Some(vec![DimensionPreview::current_style(entity)])
}
_ => Some(Vec::new()),
}
}
}
fn positive_sweep(start: f64, end: f64) -> f64 {
+39 -24
View File
@@ -4,7 +4,8 @@ use acadrust::EntityType;
use glam::{DVec3, Vec3};
use crate::command::{
CadCommand, CmdOption, CmdResult, DimensionAssociationInput, InputKind, WorkingPlane,
CadCommand, CmdOption, CmdResult, DimensionAssociationInput, DimensionPreview, InputKind,
WorkingPlane,
};
use crate::modules::{IconKind, ModuleEvent, ToolDef};
use crate::scene::model::wire_model::WireModel;
@@ -55,6 +56,35 @@ impl DiameterDimensionCommand {
Step::DimLine(source) => dvec(source.point_at_angle(source.start_angle)),
}
}
fn build_dimension(
&self,
source: crate::scene::dimension_assoc::RadialSourceGeometry,
world: DVec3,
) -> EntityType {
let plane = WorkingPlane::new(
DVec3::from_array(source.plane.origin),
DVec3::from_array(source.plane.x_axis),
DVec3::from_array(source.plane.y_axis),
);
let chord = plane.to_local(dvec(source.chord_at(world.to_array())));
let far_chord = plane.to_local(dvec(source.opposite_chord_at(world.to_array())));
let point = plane.to_local(world);
let mut dim = DimensionDiameter::new(v3(chord), v3(far_chord));
dim.base.text_middle_point = v3(point);
dim.base.insertion_point = v3(point);
dim.base.text_user_positioned = true;
dim.leader_length = chord.distance(point);
dim.base.actual_measurement = dim.measurement();
crate::entities::dimension::set_dimension_text_override(
&mut dim.base,
self.text_override.clone(),
);
if let Some(angle) = self.text_angle {
dim.base.text_rotation = angle;
}
plane.place_entity(EntityType::Dimension(Dimension::Diameter(dim)))
}
}
impl CadCommand for DiameterDimensionCommand {
@@ -88,30 +118,8 @@ impl CadCommand for DiameterDimensionCommand {
match self.step {
Step::SelectObject => CmdResult::NeedPoint,
Step::DimLine(source) => {
let source_plane = WorkingPlane::new(
DVec3::from_array(source.plane.origin),
DVec3::from_array(source.plane.x_axis),
DVec3::from_array(source.plane.y_axis),
);
let chord = source_plane.to_local(dvec(source.chord_at(pt.to_array())));
let far_chord = source_plane.to_local(dvec(source.opposite_chord_at(pt.to_array())));
let pt = source_plane.to_local(pt);
let mut dim = DimensionDiameter::new(v3(chord), v3(far_chord));
dim.base.text_middle_point = v3(pt);
dim.base.insertion_point = v3(pt);
dim.base.text_user_positioned = true;
dim.leader_length = chord.distance(pt);
dim.base.actual_measurement = dim.measurement();
crate::entities::dimension::set_dimension_text_override(
&mut dim.base,
self.text_override.clone(),
);
if let Some(a) = self.text_angle {
dim.base.text_rotation = a;
}
CmdResult::CommitDimension {
entity: source_plane
.place_entity(EntityType::Dimension(Dimension::Diameter(dim))),
entity: self.build_dimension(source, pt),
association: DimensionAssociationInput::Infer(self.source_handle),
preserve_base_style: false,
continue_command: false,
@@ -278,6 +286,13 @@ impl CadCommand for DiameterDimensionCommand {
}
}
}
fn dimension_preview(&self, cursor: DVec3) -> Option<Vec<DimensionPreview>> {
let Step::DimLine(source) = self.step else {
return None;
};
Some(vec![DimensionPreview::current_style(self.build_dimension(source, cursor))])
}
}
fn v3(p: DVec3) -> Vector3 {
+13 -1
View File
@@ -13,7 +13,9 @@ use cadkernel::geom2d::{
use cadkernel::space::Plane;
use glam::DVec3;
use crate::command::{CadCommand, CmdOption, CmdResult, DimensionAssociationInput};
use crate::command::{
CadCommand, CmdOption, CmdResult, DimensionAssociationInput, DimensionPreview,
};
use crate::modules::{IconKind, ModuleEvent, ToolDef};
use crate::scene::model::wire_model::WireModel;
@@ -482,6 +484,16 @@ impl CadCommand for DimBaselineCommand {
self.build_dimension(point)
.map(|dimension| preview_for_dimension(&dimension))
}
fn dimension_preview(&self, cursor: DVec3) -> Option<Vec<DimensionPreview>> {
self.build_dimension(cursor)
.map(|dimension| {
vec![DimensionPreview {
entity: EntityType::Dimension(dimension),
preserve_base_style: self.preserve_base_style,
}]
})
}
}
fn linear_base(
+13 -1
View File
@@ -11,7 +11,9 @@ use cadkernel::geom2d::{
use cadkernel::space::Plane;
use glam::DVec3;
use crate::command::{CadCommand, CmdOption, CmdResult, DimensionAssociationInput};
use crate::command::{
CadCommand, CmdOption, CmdResult, DimensionAssociationInput, DimensionPreview,
};
use crate::modules::{IconKind, ModuleEvent, ToolDef};
use crate::scene::model::wire_model::WireModel;
@@ -423,6 +425,16 @@ impl CadCommand for DimContinueCommand {
self.build_dimension(point)
.map(|dimension| preview_for_dimension(&dimension))
}
fn dimension_preview(&self, cursor: DVec3) -> Option<Vec<DimensionPreview>> {
self.build_dimension(cursor)
.map(|dimension| {
vec![DimensionPreview {
entity: EntityType::Dimension(dimension),
preserve_base_style: self.preserve_base_style,
}]
})
}
}
fn build_linear(
+30 -1
View File
@@ -5,7 +5,7 @@ use glam::{DVec3, Vec3};
use crate::command::{
CadCommand, CmdOption, CmdResult, DimensionAssociationInput, DimensionAssociationSource,
InputKind, WorkingPlane,
DimensionPreview, InputKind, WorkingPlane,
};
use crate::modules::{IconKind, ModuleEvent, ToolDef};
use crate::scene::creation_style::DimensionCreationDefaults;
@@ -390,6 +390,35 @@ impl CadCommand for JoggedRadiusDimensionCommand {
}
}
}
fn dimension_preview(&self, cursor: DVec3) -> Option<Vec<DimensionPreview>> {
let result = match self.step {
Step::DimLine {
source,
override_center,
} => {
// The jog is not chosen yet; preview it midway along the
// dimension line so the styled value is visible while the
// text position is placed.
let chord = dvec(source.chord_at(cursor.to_array()));
let jog = (override_center + chord) * 0.5;
self.commit_dimension(source, override_center, chord, cursor, jog)
}
Step::Jog {
source,
override_center,
chord,
text_position,
} => self.commit_dimension(source, override_center, chord, text_position, cursor),
_ => return None,
};
match result {
CmdResult::CommitDimension { entity, .. } => {
Some(vec![DimensionPreview::current_style(entity)])
}
_ => Some(Vec::new()),
}
}
}
fn source_plane(source: RadialSourceGeometry) -> WorkingPlane {
+43 -23
View File
@@ -6,7 +6,8 @@ use cadkernel::geom2d::{
};
use crate::command::{
CadCommand, CmdOption, CmdResult, DimensionAssociationInput, InputKind, WorkingPlane,
CadCommand, CmdOption, CmdResult, DimensionAssociationInput, DimensionPreview, InputKind,
WorkingPlane,
};
use crate::modules::{IconKind, ModuleEvent, ToolDef};
use crate::scene::model::wire_model::WireModel;
@@ -107,6 +108,29 @@ impl LinearDimensionCommand {
mtext_override: false,
}
}
fn build_dimension(&self, first: DVec3, second: DVec3, point: DVec3) -> EntityType {
let first = self.plane.to_local(first);
let second = self.plane.to_local(second);
let point = self.plane.to_local(point);
let mut dim = DimensionLinear::new(v3(first), v3(second));
let axis = self.axis_mode.axis(first, second, point);
dim.rotation = axis.y.atan2(axis.x);
dim.set_offset(dimension_line_offset(second, point, axis));
dim.base.definition_point = dim.definition_point;
dim.base.text_middle_point = v3(linear_text_pos(first, second, point, axis));
dim.base.insertion_point = dim.base.text_middle_point;
dim.base.actual_measurement = dim.measurement();
crate::entities::dimension::set_dimension_text_override(
&mut dim.base,
self.text_override.clone(),
);
// An explicit text angle overrides the UCS-derived rotation.
if let Some(angle) = self.text_angle {
dim.base.text_rotation = angle;
}
self.plane.place_entity(EntityType::Dimension(Dimension::Linear(dim)))
}
}
impl CadCommand for LinearDimensionCommand {
@@ -164,28 +188,7 @@ impl CadCommand for LinearDimensionCommand {
CmdResult::NeedPoint
}
Step::DimensionLine { first, second } => {
let first = self.plane.to_local(first);
let second = self.plane.to_local(second);
let pt = self.plane.to_local(pt);
let mut dim = DimensionLinear::new(v3(first), v3(second));
let axis = self.axis_mode.axis(first, second, pt);
dim.rotation = axis.y.atan2(axis.x);
dim.set_offset(dimension_line_offset(second, pt, axis));
dim.base.definition_point = dim.definition_point;
dim.base.text_middle_point = v3(linear_text_pos(first, second, pt, axis));
dim.base.insertion_point = dim.base.text_middle_point;
dim.base.actual_measurement = dim.measurement();
crate::entities::dimension::set_dimension_text_override(
&mut dim.base,
self.text_override.clone(),
);
// An explicit text angle overrides the UCS-derived rotation.
if let Some(a) = self.text_angle {
dim.base.text_rotation = a;
}
let entity = self.plane.place_entity(EntityType::Dimension(
Dimension::Linear(dim),
));
let entity = self.build_dimension(first, second, pt);
CmdResult::CommitDimension {
entity,
association: DimensionAssociationInput::Infer(self.source_handle),
@@ -386,6 +389,23 @@ impl CadCommand for LinearDimensionCommand {
}
}
}
fn dimension_preview(&self, cursor: DVec3) -> Option<Vec<DimensionPreview>> {
if self.selecting_object {
return None;
}
let (first, second) = match self.step {
Step::FirstPoint => return None,
Step::SecondPoint(first) => (first, cursor),
Step::DimensionLine { first, second } => (first, second),
};
if first.distance_squared(second) <= 1e-24 {
return Some(Vec::new());
}
Some(vec![DimensionPreview::current_style(
self.build_dimension(first, second, cursor),
)])
}
}
fn v3(pt: DVec3) -> Vector3 {
+30 -20
View File
@@ -4,7 +4,8 @@ use acadrust::EntityType;
use glam::{DVec3, Vec3};
use crate::command::{
CadCommand, CmdOption, CmdResult, DimensionAssociationInput, InputKind, WorkingPlane,
CadCommand, CmdOption, CmdResult, DimensionAssociationInput, DimensionPreview, InputKind,
WorkingPlane,
};
use crate::modules::{IconKind, ModuleEvent, ToolDef};
use crate::scene::model::wire_model::WireModel;
@@ -60,6 +61,26 @@ impl OrdinateDimCommand {
Step::LeaderEndpoint { feature } => feature,
}
}
fn build_dimension(&self, feature: DVec3, point: DVec3) -> EntityType {
let feature = self.plane.to_local(feature);
let point = self.plane.to_local(point);
let is_x = match self.datum_mode {
DatumMode::Automatic => is_x_type(feature, point),
DatumMode::X => true,
DatumMode::Y => false,
};
let mut dim = DimensionOrdinate::new(v3(feature), v3(point), is_x);
crate::entities::dimension::set_dimension_text_override(
&mut dim.base,
self.text_override.clone(),
);
if let Some(angle) = self.text_angle {
dim.base.text_rotation = angle;
}
dim.refresh_measurement();
self.plane.place_entity(EntityType::Dimension(Dimension::Ordinate(dim)))
}
}
impl CadCommand for OrdinateDimCommand {
@@ -95,26 +116,8 @@ impl CadCommand for OrdinateDimCommand {
CmdResult::NeedPoint
}
Step::LeaderEndpoint { feature } => {
let feature = self.plane.to_local(feature);
let pt = self.plane.to_local(pt);
let is_x = match self.datum_mode {
DatumMode::Automatic => is_x_type(feature, pt),
DatumMode::X => true,
DatumMode::Y => false,
};
let mut dim = DimensionOrdinate::new(v3(feature), v3(pt), is_x);
crate::entities::dimension::set_dimension_text_override(
&mut dim.base,
self.text_override.clone(),
);
if let Some(angle) = self.text_angle {
dim.base.text_rotation = angle;
}
dim.refresh_measurement();
CmdResult::CommitDimension {
entity: self.plane.place_entity(EntityType::Dimension(
Dimension::Ordinate(dim),
)),
entity: self.build_dimension(feature, pt),
association: DimensionAssociationInput::Infer(None),
preserve_base_style: false,
continue_command: false,
@@ -258,6 +261,13 @@ impl CadCommand for OrdinateDimCommand {
.collect(),
))
}
fn dimension_preview(&self, cursor: DVec3) -> Option<Vec<DimensionPreview>> {
let Step::LeaderEndpoint { feature } = self.step else {
return None;
};
Some(vec![DimensionPreview::current_style(self.build_dimension(feature, cursor))])
}
}
fn v3(p: DVec3) -> Vector3 {
+10 -1
View File
@@ -8,7 +8,7 @@ use glam::DVec3;
use crate::command::{
CadCommand, CmdOption, CmdResult, DimensionAssociationInput, DimensionAssociationSource,
EntityTransform, SelectionEntity, WorkingPlane,
DimensionPreview, EntityTransform, SelectionEntity, WorkingPlane,
};
use crate::modules::{IconKind, ModuleEvent, ToolDef};
use crate::scene::model::wire_model::WireModel;
@@ -364,6 +364,15 @@ impl CadCommand for QdimCommand {
}
Vec::new()
}
fn dimension_preview(&self, cursor: DVec3) -> Option<Vec<DimensionPreview>> {
(self.step == Step::Place).then(|| {
self.build_dimensions(cursor)
.into_iter()
.map(|(entity, _)| DimensionPreview::current_style(entity))
.collect()
})
}
}
fn v3(point: DVec3) -> Vector3 { Vector3::new(point.x, point.y, point.z) }
+40 -26
View File
@@ -3,7 +3,8 @@ use acadrust::types::{Handle, Vector3};
use acadrust::EntityType;
use crate::command::{
CadCommand, CmdOption, CmdResult, DimensionAssociationInput, InputKind, WorkingPlane,
CadCommand, CmdOption, CmdResult, DimensionAssociationInput, DimensionPreview, InputKind,
WorkingPlane,
};
use crate::modules::{IconKind, ModuleEvent, ToolDef};
use crate::scene::model::wire_model::WireModel;
@@ -55,6 +56,36 @@ impl RadiusDimensionCommand {
Step::DimLine(source) => dvec(source.point_at_angle(source.start_angle)),
}
}
fn build_dimension(
&self,
source: crate::scene::dimension_assoc::RadialSourceGeometry,
world: DVec3,
) -> EntityType {
let plane = WorkingPlane::new(
DVec3::from_array(source.plane.origin),
DVec3::from_array(source.plane.x_axis),
DVec3::from_array(source.plane.y_axis),
);
let center = plane.to_local(dvec(source.center_world()));
let chord = plane.to_local(dvec(source.chord_at(world.to_array())));
let point = plane.to_local(world);
let mut dim = DimensionRadius::new(v3(center), v3(chord));
dim.base.definition_point = v3(chord);
dim.base.text_middle_point = v3(point);
dim.base.insertion_point = v3(point);
dim.base.text_user_positioned = true;
dim.leader_length = chord.distance(point);
dim.base.actual_measurement = dim.measurement();
crate::entities::dimension::set_dimension_text_override(
&mut dim.base,
self.text_override.clone(),
);
if let Some(angle) = self.text_angle {
dim.base.text_rotation = angle;
}
plane.place_entity(EntityType::Dimension(Dimension::Radius(dim)))
}
}
impl CadCommand for RadiusDimensionCommand {
@@ -84,32 +115,8 @@ impl CadCommand for RadiusDimensionCommand {
match self.step {
Step::SelectObject => CmdResult::NeedPoint,
Step::DimLine(source) => {
let source_plane = WorkingPlane::new(
DVec3::from_array(source.plane.origin),
DVec3::from_array(source.plane.x_axis),
DVec3::from_array(source.plane.y_axis),
);
let center_world = dvec(source.center_world());
let point_world = dvec(source.chord_at(pt.to_array()));
let center = source_plane.to_local(center_world);
let point = source_plane.to_local(point_world);
let pt = source_plane.to_local(pt);
let mut dim = DimensionRadius::new(v3(center), v3(point));
dim.base.definition_point = v3(point);
dim.base.text_middle_point = v3(pt);
dim.base.insertion_point = v3(pt);
dim.base.text_user_positioned = true;
dim.leader_length = point.distance(pt);
dim.base.actual_measurement = dim.measurement();
crate::entities::dimension::set_dimension_text_override(
&mut dim.base,
self.text_override.clone(),
);
if let Some(a) = self.text_angle {
dim.base.text_rotation = a;
}
CmdResult::CommitDimension {
entity: source_plane.place_entity(EntityType::Dimension(Dimension::Radius(dim))),
entity: self.build_dimension(source, pt),
association: DimensionAssociationInput::Infer(self.source_handle),
preserve_base_style: false,
continue_command: false,
@@ -284,6 +291,13 @@ impl CadCommand for RadiusDimensionCommand {
}
}
}
fn dimension_preview(&self, cursor: DVec3) -> Option<Vec<DimensionPreview>> {
let Step::DimLine(source) = self.step else {
return None;
};
Some(vec![DimensionPreview::current_style(self.build_dimension(source, cursor))])
}
}
fn v3(pt: DVec3) -> Vector3 {