From c500a8ccdacf68d597013f02dc83c0aea728ea46 Mon Sep 17 00:00:00 2001 From: Evan Jones Date: Thu, 11 Dec 2025 19:25:08 -0600 Subject: [PATCH 01/11] Add explicit lifetime annotations to parse functions --- step/src/parse.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/step/src/parse.rs b/step/src/parse.rs index 2e30e11..340a786 100644 --- a/step/src/parse.rs +++ b/step/src/parse.rs @@ -41,7 +41,7 @@ pub(crate) trait Parse<'a> { } impl Parse<'_> for f64 { - fn parse(s: &str) -> IResult { + fn parse(s: &str) -> IResult<'_, Self> { match fast_float::parse_partial::(s) { Err(_) => nom_err(s, ErrorKind::Float), Ok((x, n)) => Ok((&s[n..], x)), @@ -50,7 +50,7 @@ impl Parse<'_> for f64 { } impl Parse<'_> for i64 { - fn parse(s: &str) -> IResult { + fn parse(s: &str) -> IResult<'_, Self> { map_res(tuple((opt(char('-')), digit1)), |(sign, digits)| -> Result::Err> { let num = str::parse::(digits)?; @@ -131,7 +131,7 @@ impl<'a> Parse<'a> for bool { } } impl<'a, T> Parse<'a> for Id { - fn parse(s: &str) -> IResult { + fn parse(s: &str) -> IResult<'_, Self> { alt(( map_res( preceded(char('#'), digit1), @@ -158,7 +158,7 @@ impl<'a, T: ParseFromChunks<'a>> Parse<'a> for T { // optionally followed by a comma pub struct Derived; impl<'a> Parse<'a> for Derived { - fn parse(s: &str) -> IResult { + fn parse(s: &str) -> IResult<'_, Self> { map(char('*'), |_| Derived)(s) } } @@ -190,7 +190,7 @@ pub(crate) fn param_from_chunks<'a, T: Parse<'a>>( Ok((check_str(s, i, strs), out)) } -pub(crate) fn parse_enum_tag(s: &str) -> IResult<&str> { +pub(crate) fn parse_enum_tag(s: &str) -> IResult<'_, &str> { delimited(char('.'), nom::bytes::complete::take_while( |c: char| c == '_' || @@ -201,7 +201,7 @@ pub(crate) fn parse_enum_tag(s: &str) -> IResult<&str> { //////////////////////////////////////////////////////////////////////////////// -pub(crate) fn parse_entity_decl(s: &[u8]) -> IResult<(usize, Entity)> { +pub(crate) fn parse_entity_decl<'a>(s: &'a [u8]) -> IResult<'a, (usize, Entity<'a>)> { let s = match std::str::from_utf8(s) { Ok(s) => s, Err(_) => return nom_err("", ErrorKind::Escaped), // TODO correct code? @@ -210,7 +210,7 @@ pub(crate) fn parse_entity_decl(s: &[u8]) -> IResult<(usize, Entity)> { |(i, _, e)| (i.0, e))(s) } -pub(crate) fn parse_entity_fallback(s: &[u8]) -> IResult<(usize, Entity)> { +pub(crate) fn parse_entity_fallback<'a>(s: &'a [u8]) -> IResult<'a, (usize, Entity<'a>)> { let s = match std::str::from_utf8(s) { Ok(s) => s, Err(_) => return nom_err("", ErrorKind::Escaped), @@ -218,14 +218,14 @@ pub(crate) fn parse_entity_fallback(s: &[u8]) -> IResult<(usize, Entity)> { map(Id::<()>::parse, |i| (i.0, Entity::_FailedToParse))(s) } -pub(crate) fn parse_complex_mapping(s: &str) -> IResult { +pub(crate) fn parse_complex_mapping<'a>(s: &'a str) -> IResult<'a, Entity<'a>> { // We'll maintain a map from sub-entity name to its argument string, then // use this map to figure out the tree and construct it. - let mut subentities: HashMap<&str, &str> = HashMap::new(); + let mut subentities: HashMap<&'a str, &'a str> = HashMap::new(); // Map from sub-entity name to the str slice which contains the name plus // the open parens, used for parsing slices - let mut name_tags: HashMap<&str, &str> = HashMap::new(); + let mut name_tags: HashMap<&'a str, &'a str> = HashMap::new(); let bstr = s.as_bytes(); let mut depth = 0; let mut index = 0; From f7ebdeeaeef557f466f87bf48c1a6381f97928de Mon Sep 17 00:00:00 2001 From: Evan Jones Date: Thu, 11 Dec 2025 19:36:31 -0600 Subject: [PATCH 02/11] Add explicit lifetime annotations to Type return values and IResult types --- express/src/gen.rs | 18 ++-- express/src/parse.rs | 236 ++++++++++++++++++++++--------------------- 2 files changed, 130 insertions(+), 124 deletions(-) diff --git a/express/src/gen.rs b/express/src/gen.rs index 05db20f..a92f191 100644 --- a/express/src/gen.rs +++ b/express/src/gen.rs @@ -655,7 +655,7 @@ impl<'a> TypeDecl<'a> { } } impl<'a> UnderlyingType<'a> { - fn to_type(&'a self, type_map: &mut TypeMap<'a>) -> Type { + fn to_type(&'a self, type_map: &mut TypeMap<'a>) -> Type<'a> { match self { UnderlyingType::Concrete(c) => c.to_type(type_map), UnderlyingType::Constructed(c) => c.to_type(), @@ -663,7 +663,7 @@ impl<'a> UnderlyingType<'a> { } } impl<'a> ConcreteTypes<'a> { - fn to_type(&self, type_map: &mut TypeMap<'a>) -> Type { + fn to_type(&self, type_map: &mut TypeMap<'a>) -> Type<'_> { match self { ConcreteTypes::Aggregation(a) => a.to_type(type_map), ConcreteTypes::Simple(s) => s.to_type(), @@ -718,7 +718,7 @@ impl<'a> SimpleExpression<'a> { } } impl<'a> AggregationTypes<'a> { - fn to_type(&self, type_map: &mut TypeMap<'a>) -> Type { + fn to_type(&self, type_map: &mut TypeMap<'a>) -> Type<'_> { let (optional, instantiable) = match self { AggregationTypes::Array(a) => (a.optional, &a.instantiable_type), AggregationTypes::Bag(a) => (false, &a.1), @@ -737,7 +737,7 @@ impl<'a> AggregationTypes<'a> { } } impl<'a> ConstructedTypes<'a> { - fn to_type(&'a self) -> Type { + fn to_type(&'a self) -> Type<'a> { match self { ConstructedTypes::Enumeration(e) => e.to_type(), ConstructedTypes::Select(s) => s.to_type(), @@ -745,7 +745,7 @@ impl<'a> ConstructedTypes<'a> { } } impl<'a> EnumerationType<'a> { - fn to_type(&self) -> Type { + fn to_type(&self) -> Type<'_> { assert!(!self.extensible, "Extensible enumerations are not supported"); match self.items_or_extension.as_ref().unwrap() { EnumerationItemsOrExtension::Items(e) => e.to_type(), @@ -754,7 +754,7 @@ impl<'a> EnumerationType<'a> { } } impl<'a> EnumerationItems<'a> { - fn to_type(&self) -> Type { + fn to_type(&self) -> Type<'_> { let mut out = Vec::new(); for e in &self.0 { out.push(e.0); @@ -763,7 +763,7 @@ impl<'a> EnumerationItems<'a> { } } impl<'a> SelectType<'a> { - fn to_type(&'a self) -> Type { + fn to_type(&'a self) -> Type<'a> { assert!(!self.extensible, "Cannot handle extensible lists"); assert!(!self.generic_entity, "Cannot handle generic entity lists"); match &self.list_or_extension { @@ -773,7 +773,7 @@ impl<'a> SelectType<'a> { } } impl<'a> SelectList<'a> { - fn to_type(&'a self) -> Type { + fn to_type(&'a self) -> Type<'a> { let mut out = Vec::new(); for e in &self.0 { out.push(e.name()); @@ -950,7 +950,7 @@ impl <'a> SimpleTypes<'a> { SimpleTypes::String(_) => "&'a str", } } - fn to_type(&self) -> Type { + fn to_type(&self) -> Type<'_> { Type::RedeclaredPrimitive(self.to_attr_type_str()) } } diff --git a/express/src/parse.rs b/express/src/parse.rs index e53aa95..f5932c8 100644 --- a/express/src/parse.rs +++ b/express/src/parse.rs @@ -71,14 +71,20 @@ macro_rules! alias { #[derive(Debug)] pub struct $a $(< $lt >)?(pub $b $(< $lt >)?); impl $(< $lt >)? $a $(< $lt >)? { - fn parse(s: &$( $lt )? str) -> IResult { + fn parse(s: &$( $lt )? str) -> IResult<$( $lt, )? Self> { map($b::parse, Self)(s) } } }; - ($a:ident $(< $lt:lifetime >)?, $b:ident, $parse_a:ident) => { - alias!($a$(< $lt >)?, $b); - fn $parse_a(s: &str) -> IResult<$a> { + ($a:ident < $lt:lifetime >, $b:ident, $parse_a:ident) => { + alias!($a < $lt >, $b); + fn $parse_a(s: &str) -> IResult<'_, $a<'_>> { + $a::parse(s) + } + }; + ($a:ident, $b:ident, $parse_a:ident) => { + alias!($a, $b); + fn $parse_a(s: &str) -> IResult<'_, $a> { $a::parse(s) } }; @@ -92,7 +98,7 @@ macro_rules! alias { macro_rules! id_type { ($a:ident, $parse_a:ident) => { id_type!($a); - fn $parse_a(s: &str) -> IResult<$a> { + fn $parse_a(s: &str) -> IResult<'_, $a<'_>> { let (s, r) = SimpleId::parse(s)?; Ok((s, $a(r.0))) } @@ -133,14 +139,14 @@ pub fn strip_comments_and_lower(data: &[u8]) -> String { } /// Main entry function for the parser -pub fn parse(s: &str) -> IResult { +pub fn parse(s: &str) -> IResult<'_, Syntax<'_>> { syntax(s) } //////////////////////////////////////////////////////////////////////////////// // 124 -fn digit(s: &str) -> IResult { +fn digit(s: &str) -> IResult<'_, char> { nom::character::complete::one_of("0123456789")(s) } @@ -148,29 +154,29 @@ fn digit(s: &str) -> IResult { // skipped due to using fast_float // 126 -fn encoded_character(s: &str) -> IResult { +fn encoded_character(s: &str) -> IResult<'_, char> { map(recognize(tuple((octet, octet, octet, octet))), |v| std::char::from_u32(u32::from_str_radix(v, 16).unwrap()).unwrap()) (s) } // 127 -fn hex_digit(s: &str) -> IResult { +fn hex_digit(s: &str) -> IResult<'_, char> { alt((digit, nom::character::complete::one_of("abcdef")))(s) } // 128 -fn letter(s: &str) -> IResult { +fn letter(s: &str) -> IResult<'_, char> { nom::character::complete::one_of("abcdefghijklmnopqrstuvwxyz")(s) } // 132 -fn not_paren_star_quote_special(s: &str) -> IResult { +fn not_paren_star_quote_special(s: &str) -> IResult<'_, char> { nom::character::complete::one_of("!\"#$%&+,-./:;<=>?@[\\]^_‘{|}~")(s) } // 134 -fn not_quote(s: &str) -> IResult { +fn not_quote(s: &str) -> IResult<'_, char> { alt((not_paren_star_quote_special, letter, digit, nom::character::complete::one_of("()*")))(s) } @@ -181,14 +187,14 @@ fn octet(s: &str) -> IResult<&str> { } // 139 -fn binary_literal(s: &str) -> IResult { +fn binary_literal(s: &str) -> IResult<'_, usize> { let bits = fold_many1(alt((char('0'), char('1'))), 0, |acc, item| acc * 2 + item.to_digit(10).unwrap() as usize); preceded(char('%'), bits)(s) } // 140 -fn encoded_string_literal(s: &str) -> IResult { +fn encoded_string_literal(s: &str) -> IResult<'_, String> { delimited( char('"'), fold_many0(encoded_character, String::new(), @@ -200,13 +206,13 @@ fn encoded_string_literal(s: &str) -> IResult { // skipped because we're using fast_float instead // 142 -fn real_literal_(s: &str) -> IResult { +fn real_literal_(s: &str) -> IResult<'_, f64> { match fast_float::parse_partial::(s) { Err(_) => build_err(s, "Could not parse float"), Ok((x, n)) => Ok((&s[n..], x)), } } -fn real_literal(s: &str) -> IResult { +fn real_literal(s: &str) -> IResult<'_, f64> { ws(real_literal_)(s) } @@ -250,7 +256,7 @@ impl<'a> SimpleId<'a> { fn simple_id(s: &str) -> IResult { SimpleId::parse(s) } // 144 simple_string_literal = \q { ( \q \q ) | not_quote | \s | \x9 | \xA | \xD } \q . -fn simple_string_literal(s: &str) -> IResult { +fn simple_string_literal(s: &str) -> IResult<'_, String> { let f = alt(( map(tag("''"), |_| '\''), not_quote, @@ -281,12 +287,12 @@ id_type!(TypeRef, type_ref); id_type!(VariableRef); // 164 abstract_entity_declaration = ABSTRACT . -fn abstract_entity_declaration(s: &str) -> IResult<()> { +fn abstract_entity_declaration(s: &str) -> IResult<'_, ()> { map(kw("abstract"), |_| ())(s) } // 165 abstract_supertype = ABSTRACT SUPERTYPE ’;’ . -fn abstract_supertype(s: &str) -> IResult<()> { +fn abstract_supertype(s: &str) -> IResult<'_, ()> { map(tuple(( kw("abstract"), kw("supertype"), @@ -297,7 +303,7 @@ fn abstract_supertype(s: &str) -> IResult<()> { // 166 abstract_supertype_declaration = ABSTRACT SUPERTYPE [ subtype_constraint ] . #[derive(Debug)] pub struct AbstractSupertypeDeclaration<'a>(Option>); -fn abstract_supertype_declaration(s: &str) -> IResult { +fn abstract_supertype_declaration(s: &str) -> IResult<'_, AbstractSupertypeDeclaration<'_>> { map(tuple(( kw("abstract"), kw("supertype"), @@ -308,14 +314,14 @@ fn abstract_supertype_declaration(s: &str) -> IResult(Vec>); -fn actual_parameter_list(s: &str) -> IResult { +fn actual_parameter_list(s: &str) -> IResult<'_, ActualParameterList<'_>> { map(parens(list1(',', parameter)), ActualParameterList)(s) } // 168 #[derive(Debug)] pub enum AddLikeOp { Add, Sub, Or, Xor } -fn add_like_op(s: &str) -> IResult { +fn add_like_op(s: &str) -> IResult<'_, AddLikeOp> { use AddLikeOp::*; alt(( map(char('+'), |_| Add), @@ -328,7 +334,7 @@ fn add_like_op(s: &str) -> IResult { // 169 #[derive(Debug)] pub struct AggregateInitializer<'a>(Vec>); -fn aggregate_initializer(s: &str) -> IResult { +fn aggregate_initializer(s: &str) -> IResult<'_, AggregateInitializer<'_>> { map(delimited( char('['), list0(',', element), @@ -342,7 +348,7 @@ alias!(AggregateSource<'a>, SimpleExpression, aggregate_source); // 171 aggregate_type = AGGREGATE [ ’:’ type_label ] OF parameter_type . #[derive(Debug)] pub struct AggregateType<'a>(Option>, Box>); -fn aggregate_type(s: &str) -> IResult { +fn aggregate_type(s: &str) -> IResult<'_, AggregateType<'_>> { map(tuple(( kw("aggregate"), opt(preceded(char(':'), type_label)), @@ -359,7 +365,7 @@ pub enum AggregationTypes<'a> { List(ListType<'a>), Set(SetType<'a>), } -fn aggregation_types(s: &str) -> IResult { +fn aggregation_types(s: &str) -> IResult<'_, AggregationTypes<'_>> { use AggregationTypes::*; alt(( map(array_type, Array), @@ -376,7 +382,7 @@ pub struct AlgorithmHead<'a> { pub constant: Option>, pub local: Option>, } -fn algorithm_head(s: &str) -> IResult { +fn algorithm_head(s: &str) -> IResult<'_, AlgorithmHead<'_>> { map(tuple(( many0(declaration), opt(constant_decl), @@ -397,7 +403,7 @@ pub struct AliasStmt<'a> { pub qualifiers: Vec>, pub stmts: Vec>, } -fn alias_stmt(s: &str) -> IResult { +fn alias_stmt(s: &str) -> IResult<'_, AliasStmt<'_>> { map(tuple(( kw("alias"), variable_id, @@ -422,7 +428,7 @@ pub struct ArrayType<'a> { pub unique: bool, pub instantiable_type: Box>, } -fn array_type(s: &str) -> IResult { +fn array_type(s: &str) -> IResult<'_, ArrayType<'_>> { map(tuple(( kw("array"), bound_spec, @@ -446,7 +452,7 @@ pub struct AssignmentStmt<'a> { pub qualifiers: Vec>, pub expression: Expression<'a>, } -fn assignment_stmt(s: &str) -> IResult { +fn assignment_stmt(s: &str) -> IResult<'_, AssignmentStmt<'_>> { map(tuple(( general_ref, many0(qualifier), @@ -466,7 +472,7 @@ pub enum AttributeDecl<'a> { Id(AttributeId<'a>), Redeclared(RedeclaredAttribute<'a>), } -fn attribute_decl(s: &str) -> IResult { +fn attribute_decl(s: &str) -> IResult<'_, AttributeDecl<'_>> { use AttributeDecl::*; alt(( map(attribute_id, Id), @@ -480,14 +486,14 @@ id_type!(AttributeId, attribute_id); // 179 #[derive(Debug)] pub struct AttributeQualifier<'a>(pub AttributeRef<'a>); -fn attribute_qualifier(s: &str) -> IResult { +fn attribute_qualifier(s: &str) -> IResult<'_, AttributeQualifier<'_>> { map(preceded(char('.'), attribute_ref), AttributeQualifier)(s) } // 180 #[derive(Debug)] pub struct BagType<'a>(Option>, pub Box>); -fn bag_type(s: &str) -> IResult { +fn bag_type(s: &str) -> IResult<'_, BagType<'_>> { map(tuple(( kw("bag"), opt(bound_spec), @@ -500,7 +506,7 @@ fn bag_type(s: &str) -> IResult { // 181 binary_type = BINARY [ width_spec ] . #[derive(Debug)] pub struct BinaryType<'a>(Option>); -fn binary_type(s: &str) -> IResult { +fn binary_type(s: &str) -> IResult<'_, BinaryType<'_>> { map(preceded(kw("binary"), opt(width_spec)), BinaryType)(s) } @@ -518,7 +524,7 @@ alias!(Bound2<'a>, NumericExpression, bound_2); // 185 #[derive(Debug)] pub struct BoundSpec<'a>(Bound1<'a>, pub Bound2<'a>); -fn bound_spec(s: &str) -> IResult { +fn bound_spec(s: &str) -> IResult<'_, BoundSpec<'_>> { map(tuple(( char('['), bound_1, @@ -531,7 +537,7 @@ fn bound_spec(s: &str) -> IResult { // 186 #[derive(Debug)] pub enum BuiltInConstant { ConstE, Pi, Self_, Indeterminant } -fn built_in_constant(s: &str) -> IResult { +fn built_in_constant(s: &str) -> IResult<'_, BuiltInConstant> { use BuiltInConstant::*; alt(( map(kw("const_e"), |_| ConstE), @@ -585,7 +591,7 @@ fn to_built_in_function(s: &str) -> Option { _ => return None, }) } -fn built_in_function(s: &str) -> IResult { +fn built_in_function(s: &str) -> IResult<'_, BuiltInFunction> { // Tokenize then match the keyword, instead of doing a huge alt(...) ws(map_opt(alpha1, to_built_in_function))(s) } @@ -593,7 +599,7 @@ fn built_in_function(s: &str) -> IResult { // 188 built_in_procedure = INSERT | REMOVE . #[derive(Debug)] pub enum BuiltInProcedure { Insert, Remove } -fn built_in_procedure(s: &str) -> IResult { +fn built_in_procedure(s: &str) -> IResult<'_, BuiltInProcedure> { use BuiltInProcedure::*; alt(( map(kw("insert"), |_| Insert), @@ -604,7 +610,7 @@ fn built_in_procedure(s: &str) -> IResult { // 189 case_action = case_label { ’,’ case_label } ’:’ stmt . #[derive(Debug)] pub struct CaseAction<'a>(Vec>, Stmt<'a>); -fn case_action(s: &str) -> IResult { +fn case_action(s: &str) -> IResult<'_, CaseAction<'_>> { map(tuple(( list1(',', case_label), char(':'), @@ -623,7 +629,7 @@ pub struct CaseStmt<'a> { pub actions: Vec>, pub otherwise: Option>>, } -fn case_stmt(s: &str) -> IResult { +fn case_stmt(s: &str) -> IResult<'_, CaseStmt<'_>> { map(tuple(( kw("case"), selector, @@ -645,7 +651,7 @@ fn case_stmt(s: &str) -> IResult { // 192 compound_stmt = BEGIN stmt { stmt } END ’;’ . #[derive(Debug)] pub struct CompoundStmt<'a>(Vec>); -fn compound_stmt(s: &str) -> IResult { +fn compound_stmt(s: &str) -> IResult<'_, CompoundStmt<'_>> { map(delimited( kw("begin"), many1(stmt), @@ -660,7 +666,7 @@ pub enum ConcreteTypes<'a> { Simple(SimpleTypes<'a>), TypeRef(TypeRef<'a>), } -fn concrete_types(s: &str) -> IResult { +fn concrete_types(s: &str) -> IResult<'_, ConcreteTypes<'_>> { use ConcreteTypes::*; alt(( map(aggregation_types, Aggregation), @@ -676,7 +682,7 @@ pub struct ConstantBody<'a> { pub instantiable_type: InstantiableType<'a>, pub expression: Expression<'a>, } -fn constant_body(s: &str) -> IResult { +fn constant_body(s: &str) -> IResult<'_, ConstantBody<'_>> { map(tuple(( constant_id, char(':'), @@ -694,7 +700,7 @@ fn constant_body(s: &str) -> IResult { // 195 #[derive(Debug)] pub struct ConstantDecl<'a>(Vec>); -fn constant_decl(s: &str) -> IResult { +fn constant_decl(s: &str) -> IResult<'_, ConstantDecl<'_>> { map(tuple(( kw("constant"), many1(constant_body), @@ -709,7 +715,7 @@ pub enum ConstantFactor<'a> { BuiltIn(BuiltInConstant), ConstantRef(ConstantRef<'a>), } -fn constant_factor(s: &str) -> IResult { +fn constant_factor(s: &str) -> IResult<'_, ConstantFactor<'_>> { use ConstantFactor::*; alt(( map(built_in_constant, BuiltIn), @@ -726,7 +732,7 @@ pub enum ConstructedTypes<'a> { Enumeration(EnumerationType<'a>), Select(SelectType<'a>), } -fn constructed_types(s: &str) -> IResult { +fn constructed_types(s: &str) -> IResult<'_, ConstructedTypes<'_>> { use ConstructedTypes::*; alt(( map(enumeration_type, Enumeration), @@ -744,7 +750,7 @@ pub enum Declaration<'a> { SubtypeConstraint(SubtypeConstraintDecl<'a>), Type(TypeDecl<'a>), } -fn declaration(s: &str) -> IResult { +fn declaration(s: &str) -> IResult<'_, Declaration<'_>> { use Declaration::*; alt(( map(entity_decl, Entity), @@ -760,7 +766,7 @@ fn declaration(s: &str) -> IResult { pub struct DerivedAttr<'a>(pub AttributeDecl<'a>, ParameterType<'a>, Expression<'a>); -fn derived_attr(s: &str) -> IResult { +fn derived_attr(s: &str) -> IResult<'_, DerivedAttr<'_>> { map(tuple(( attribute_decl, char(':'), @@ -774,7 +780,7 @@ fn derived_attr(s: &str) -> IResult { // 201 derive_clause = DERIVE derived_attr { derived_attr } . #[derive(Debug)] pub struct DeriveClause<'a>(pub Vec>); -fn derive_clause(s: &str) -> IResult { +fn derive_clause(s: &str) -> IResult<'_, DeriveClause<'_>> { map(preceded(kw("derive"), many1(derived_attr)), DeriveClause)(s) } @@ -784,7 +790,7 @@ pub struct DomainRule<'a> { pub rule_label_id: Option>, pub expression: Expression<'a>, } -fn domain_rule(s: &str) -> IResult { +fn domain_rule(s: &str) -> IResult<'_, DomainRule<'_>> { let (s, rule_label_id) = opt(terminated(rule_label_id, char(':')))(s)?; let (s, expression) = expression(s)?; Ok((s, DomainRule { rule_label_id, expression })) @@ -793,7 +799,7 @@ fn domain_rule(s: &str) -> IResult { // 203 #[derive(Debug)] pub struct Element<'a>(Expression<'a>, Option>); -fn element(s: &str) -> IResult { +fn element(s: &str) -> IResult<'_, Element<'_>> { map(pair(expression, opt(preceded(char(':'), repetition))), |(a, b)| Element(a, b))(s) } @@ -808,7 +814,7 @@ pub struct EntityBody<'a> { pub unique: Option>, pub where_: Option>, } -fn entity_body(s: &str) -> IResult { +fn entity_body(s: &str) -> IResult<'_, EntityBody<'_>> { let (s, explicit_attr) = many0(explicit_attr)(s)?; let (s, derive) = opt(derive_clause)(s)?; let (s, inverse) = opt(inverse_clause)(s)?; @@ -829,7 +835,7 @@ pub struct EntityConstructor<'a> { // 206 entity_decl = entity_head entity_body END_ENTITY ’;’ . #[derive(Debug)] pub struct EntityDecl<'a>(pub EntityHead<'a>, pub EntityBody<'a>); -fn entity_decl(s: &str) -> IResult { +fn entity_decl(s: &str) -> IResult<'_, EntityDecl<'_>> { let (s, a) = entity_head(s)?; let (s, b) = entity_body(s)?; let (s, _) = kw("end_entity")(s)?; @@ -840,7 +846,7 @@ fn entity_decl(s: &str) -> IResult { // 207 entity_head = ENTITY entity_id subsuper ’;’ . #[derive(Debug)] pub struct EntityHead<'a>(pub EntityId<'a>, pub Subsuper<'a>); -fn entity_head(s: &str) -> IResult { +fn entity_head(s: &str) -> IResult<'_, EntityHead<'_>> { map(tuple(( kw("entity"), entity_id, @@ -858,7 +864,7 @@ pub struct EnumerationExtension<'a> { pub type_ref: TypeRef<'a>, pub enumeration_items: Option>, } -fn enumeration_extension(s: &str) -> IResult { +fn enumeration_extension(s: &str) -> IResult<'_, EnumerationExtension<'_>> { map(preceded( kw("based_on"), pair(type_ref, opt(preceded(kw("with"), enumeration_items)))), @@ -871,14 +877,14 @@ id_type!(EnumerationId, enumeration_id); // 211 enumeration_items = ’(’ enumeration_id { ’,’ enumeration_id } ’)’ . #[derive(Debug)] pub struct EnumerationItems<'a>(pub Vec>); -fn enumeration_items(s: &str) -> IResult { +fn enumeration_items(s: &str) -> IResult<'_, EnumerationItems<'_>> { map(parens(list1(',', enumeration_id)), EnumerationItems)(s) } // 212 enumeration_reference = [ type_ref ’.’ ] enumeration_ref . #[derive(Debug)] pub struct EnumerationReference<'a>(Option>, EnumerationRef<'a>); -fn enumeration_reference(s: &str) -> IResult { +fn enumeration_reference(s: &str) -> IResult<'_, EnumerationReference<'_>> { map(tuple(( opt(terminated(type_ref, char('.'))), enumeration_ref @@ -896,7 +902,7 @@ pub struct EnumerationType<'a> { pub extensible: bool, pub items_or_extension: Option> } -fn enumeration_type(s: &str) -> IResult { +fn enumeration_type(s: &str) -> IResult<'_, EnumerationType<'_>> { map(tuple(( opt(kw("extensible")), kw("enumeration"), @@ -911,7 +917,7 @@ fn enumeration_type(s: &str) -> IResult { } // 214 escape_stmt = ESCAPE ’;’ . -fn escape_stmt(s: &str) -> IResult<()> { +fn escape_stmt(s: &str) -> IResult<'_, ()> { map(pair(kw("escape"), char(';')), |_| ())(s) } @@ -947,12 +953,12 @@ impl<'a> Expression<'a> { Ok((s, Self(a, b))) } } -fn expression(s: &str) -> IResult { Expression::parse(s) } +fn expression(s: &str) -> IResult<'_, Expression<'_>> { Expression::parse(s) } // 217 factor = simple_factor [ ’**’ simple_factor ] . #[derive(Debug)] pub struct Factor<'a>(pub SimpleFactor<'a>, pub Option>); -fn factor(s: &str) -> IResult { +fn factor(s: &str) -> IResult<'_, Factor<'_>> { map(pair(simple_factor, opt(preceded(tag("**"), simple_factor))), |(a, b)| Factor(a, b))(s) } @@ -960,7 +966,7 @@ fn factor(s: &str) -> IResult { // 218 formal_parameter = parameter_id { ’,’ parameter_id } ’:’ parameter_type . #[derive(Debug)] pub struct FormalParameter<'a>(Vec>, ParameterType<'a>); -fn formal_parameter(s: &str) -> IResult { +fn formal_parameter(s: &str) -> IResult<'_, FormalParameter<'_>> { map(tuple(( list1(',', parameter_id), char(':'), @@ -976,7 +982,7 @@ pub enum BuiltInOrFunctionRef<'a> { } #[derive(Debug)] pub struct FunctionCall<'a>(BuiltInOrFunctionRef<'a>, ActualParameterList<'a>); -fn function_call(s: &str) -> IResult { +fn function_call(s: &str) -> IResult<'_, FunctionCall<'_>> { map(pair( alt((map(built_in_function, BuiltInOrFunctionRef::BuiltIn), map(function_ref, BuiltInOrFunctionRef::Ref))), @@ -990,7 +996,7 @@ pub struct FunctionDecl<'a> { pub algorithm_head: AlgorithmHead<'a>, pub stmts: Vec>, } -fn function_decl(s: &str) -> IResult { +fn function_decl(s: &str) -> IResult<'_, FunctionDecl<'_>> { map(tuple(( function_head, algorithm_head, @@ -1012,7 +1018,7 @@ pub struct FunctionHead<'a> { pub params: Option>>, pub out: ParameterType<'a>, } -fn function_head(s: &str) -> IResult { +fn function_head(s: &str) -> IResult<'_, FunctionHead<'_>> { map(tuple(( kw("function"), function_id, @@ -1769,7 +1775,7 @@ pub struct ReferenceClause<'a> { pub schema_ref: SchemaRef<'a>, pub resource_or_rename: Option>>, } -fn reference_clause(s: &str) -> IResult { +fn reference_clause(s: &str) -> IResult<'_, ReferenceClause<'_>> { map(tuple(( kw("reference"), kw("front"), @@ -1786,7 +1792,7 @@ fn reference_clause(s: &str) -> IResult { #[derive(Debug)] pub enum RelOp { LessThan, GreaterThan, LessThanOrEqual, GreaterThanOrEqual, NotEqual, Equal, InstanceEqual, InstanceNotEqual } -fn rel_op(s: &str) -> IResult { +fn rel_op(s: &str) -> IResult<'_, RelOp> { use RelOp::*; alt(( // Sorted by length to avoid prefix issues @@ -1804,7 +1810,7 @@ fn rel_op(s: &str) -> IResult { // 283 #[derive(Debug)] pub enum RelOpExtended { RelOp(RelOp), In, Like } -fn rel_op_extended(s: &str) -> IResult { +fn rel_op_extended(s: &str) -> IResult<'_, RelOpExtended> { use RelOpExtended::*; alt(( map(kw("in"), |_| In), @@ -1822,7 +1828,7 @@ pub enum RenameId<'a> { Type(TypeId<'a>), _Ambiguous(SimpleId<'a>), } -fn rename_id(s: &str) -> IResult { +fn rename_id(s: &str) -> IResult<'_, RenameId<'_>> { map(simple_id, RenameId::_Ambiguous)(s) } @@ -1832,7 +1838,7 @@ pub struct RepeatControl<'a>( Option>, Option>, Option>); -fn repeat_control(s: &str) -> IResult { +fn repeat_control(s: &str) -> IResult<'_, RepeatControl<'_>> { map(tuple(( opt(increment_control), opt(while_control), @@ -1843,7 +1849,7 @@ fn repeat_control(s: &str) -> IResult { // 286 repeat_stmt = REPEAT repeat_control ’;’ stmt { stmt } END_REPEAT ’;’ . #[derive(Debug)] pub struct RepeatStmt<'a>(RepeatControl<'a>, Vec>); -fn repeat_stmt(s: &str) -> IResult { +fn repeat_stmt(s: &str) -> IResult<'_, RepeatStmt<'_>> { map(tuple(( kw("repeat"), repeat_control, @@ -1860,7 +1866,7 @@ alias!(Repetition<'a>, NumericExpression, repetition); // 288 #[derive(Debug)] pub struct ResourceOrRename<'a>(ResourceRef<'a>, Option>); -fn resource_or_rename(s: &str) -> IResult { +fn resource_or_rename(s: &str) -> IResult<'_, ResourceOrRename<'_>> { map(pair(resource_ref, opt(preceded(kw("as"), rename_id))), |(a, b)| ResourceOrRename(a, b))(s) } @@ -1876,14 +1882,14 @@ pub enum ResourceRef<'a> { _Ambiguous(SimpleId<'a>), } -fn resource_ref(s: &str) -> IResult { +fn resource_ref(s: &str) -> IResult<'_, ResourceRef<'_>> { map(simple_id, ResourceRef::_Ambiguous)(s) } // 290 return_stmt = RETURN [ ’(’ expression ’)’ ] ’;’ . #[derive(Debug)] pub struct ReturnStmt<'a>(Option>); -fn return_stmt(s: &str) -> IResult { +fn return_stmt(s: &str) -> IResult<'_, ReturnStmt<'_>> { map(delimited( kw("return"), opt(parens(expression)), @@ -1898,7 +1904,7 @@ pub struct RuleDecl<'a> { pub stmt: Vec>, pub where_clause: WhereClause<'a>, } -fn rule_decl(s: &str) -> IResult { +fn rule_decl(s: &str) -> IResult<'_, RuleDecl<'_>> { map(tuple(( rule_head, algorithm_head, @@ -1920,7 +1926,7 @@ pub struct RuleHead<'a> { pub rule_id: RuleId<'a>, pub entities: Vec>, } -fn rule_head(s: &str) -> IResult { +fn rule_head(s: &str) -> IResult<'_, RuleHead<'_>> { map(tuple(( kw("rule"), rule_id, @@ -1952,7 +1958,7 @@ pub struct SchemaBody<'a> { pub constants: Option>, pub declarations: Vec>, } -fn schema_body(s: &str) -> IResult { +fn schema_body(s: &str) -> IResult<'_, SchemaBody<'_>> { map(tuple(( many0(interface_specification), opt(constant_decl), @@ -1973,7 +1979,7 @@ pub struct SchemaDecl<'a> { pub version: Option, pub body: SchemaBody<'a>, } -fn schema_decl(s: &str) -> IResult { +fn schema_decl(s: &str) -> IResult<'_, SchemaDecl<'_>> { map(tuple(( kw("schema"), schema_id, @@ -2002,7 +2008,7 @@ pub struct SelectExtension<'a> { pub type_ref: TypeRef<'a>, pub select_list: Option>, } -fn select_extension(s: &str) -> IResult { +fn select_extension(s: &str) -> IResult<'_, SelectExtension<'_>> { map(tuple(( kw("based_on"), type_ref, opt(preceded(kw("with"), select_list)) @@ -2014,7 +2020,7 @@ fn select_extension(s: &str) -> IResult { // 301 #[derive(Debug)] pub struct SelectList<'a>(pub Vec>); -fn select_list(s: &str) -> IResult { +fn select_list(s: &str) -> IResult<'_, SelectList<'_>> { map(parens(list1(',', named_types)), SelectList)(s) } @@ -2031,7 +2037,7 @@ pub struct SelectType<'a> { pub generic_entity: bool, pub list_or_extension: SelectListOrExtension<'a>, } -fn select_type(s: &str) -> IResult { +fn select_type(s: &str) -> IResult<'_, SelectType<'_>> { map(tuple(( opt(pair(kw("extensible"), opt(kw("generic_entity")))), kw("select"), @@ -2052,7 +2058,7 @@ pub struct SetType<'a> { pub bounds: Option>, pub instantiable_type: Box>, } -fn set_type(s: &str) -> IResult { +fn set_type(s: &str) -> IResult<'_, SetType<'_>> { map(tuple(( kw("set"), opt(bound_spec), @@ -2078,7 +2084,7 @@ impl<'a> SimpleExpression<'a> { Ok((s, SimpleExpression(Box::new(a), b))) } } -fn simple_expression(s: &str) -> IResult { +fn simple_expression(s: &str) -> IResult<'_, SimpleExpression<'_>> { SimpleExpression::parse(s) } @@ -2105,7 +2111,7 @@ pub enum SimpleFactor<'a> { Unary(Option, ExpressionOrPrimary<'a>) } -fn ambiguous_function_call(s: &str) -> IResult { +fn ambiguous_function_call(s: &str) -> IResult<'_, SimpleFactor<'_>> { map(terminated( // simple_id already refuses to eat built-in functions pair(simple_id, parens(list0(',', expression))), @@ -2115,7 +2121,7 @@ fn ambiguous_function_call(s: &str) -> IResult { |(a, b)| SimpleFactor::_AmbiguousFunctionCall(a, b))(s) } -fn simple_factor(s: &str) -> IResult { +fn simple_factor(s: &str) -> IResult<'_, SimpleFactor<'_>> { use SimpleFactor::*; alt(( map(aggregate_initializer, AggregateInitializer), @@ -2148,7 +2154,7 @@ pub enum SimpleTypes<'a> { Binary(BinaryType<'a>), Boolean, Integer, Logical, Number, Real(RealType<'a>), String(StringType<'a>), } -fn simple_types(s: &str) -> IResult { +fn simple_types(s: &str) -> IResult<'_, SimpleTypes<'_>> { use SimpleTypes::*; alt(( map(binary_type, Binary), @@ -2162,7 +2168,7 @@ fn simple_types(s: &str) -> IResult { } // 308 skip_stmt = SKIP ’;’ . -fn skip_stmt(s: &str) -> IResult<()> { +fn skip_stmt(s: &str) -> IResult<'_, ()> { map(pair(kw("skip"), char(';')), |_| ())(s) } @@ -2183,7 +2189,7 @@ pub enum Stmt<'a> { Return(ReturnStmt<'a>), Skip, } -fn stmt(s: &str) -> IResult { +fn stmt(s: &str) -> IResult<'_, Stmt<'_>> { use Stmt::*; alt(( map(alias_stmt, Alias), @@ -2204,16 +2210,16 @@ fn stmt(s: &str) -> IResult { #[derive(Debug)] pub struct StringLiteral(String); impl StringLiteral { - fn parse(s: &str) -> IResult { + fn parse(s: &str) -> IResult<'_, Self> { map(alt((simple_string_literal, encoded_string_literal)), Self)(s) } } -fn string_literal(s: &str) -> IResult { StringLiteral::parse(s) } +fn string_literal(s: &str) -> IResult<'_, StringLiteral> { StringLiteral::parse(s) } // 311 string_type = STRING [ width_spec ] . #[derive(Debug)] pub struct StringType<'a>(Option>); -fn string_type(s: &str) -> IResult { +fn string_type(s: &str) -> IResult<'_, StringType<'_>> { map(preceded(kw("string"), opt(width_spec)), StringType)(s) } @@ -2221,7 +2227,7 @@ fn string_type(s: &str) -> IResult { #[derive(Debug)] pub struct Subsuper<'a>(pub Option>, pub Option>); -fn subsuper(s: &str) -> IResult { +fn subsuper(s: &str) -> IResult<'_, Subsuper<'_>> { map(pair(opt(supertype_constraint), opt(subtype_declaration)), |(a, b)| Subsuper(a, b))(s) } @@ -2229,7 +2235,7 @@ fn subsuper(s: &str) -> IResult { // 313 subtype_constraint = OF ’(’ supertype_expression ’)’ . #[derive(Debug)] pub struct SubtypeConstraint<'a>(SupertypeExpression<'a>); -fn subtype_constraint(s: &str) -> IResult { +fn subtype_constraint(s: &str) -> IResult<'_, SubtypeConstraint<'_>> { map(preceded(kw("of"), parens(supertype_expression)), SubtypeConstraint)(s) } @@ -2242,7 +2248,7 @@ pub struct SubtypeConstraintBody<'a> { pub total_over: Option>, pub supertype: Option>, } -fn subtype_constraint_body(s: &str) -> IResult { +fn subtype_constraint_body(s: &str) -> IResult<'_, SubtypeConstraintBody<'_>> { map(tuple(( opt(abstract_supertype), opt(total_over), @@ -2259,7 +2265,7 @@ fn subtype_constraint_body(s: &str) -> IResult { #[derive(Debug)] pub struct SubtypeConstraintDecl<'a>(SubtypeConstraintHead<'a>, SubtypeConstraintBody<'a>); -fn subtype_constraint_decl(s: &str) -> IResult { +fn subtype_constraint_decl(s: &str) -> IResult<'_, SubtypeConstraintDecl<'_>> { map(tuple(( subtype_constraint_head, subtype_constraint_body, @@ -2272,7 +2278,7 @@ fn subtype_constraint_decl(s: &str) -> IResult { // entity_ref ’;’ . #[derive(Debug)] pub struct SubtypeConstraintHead<'a>(SubtypeConstraintId<'a>, EntityRef<'a>); -fn subtype_constraint_head(s: &str) -> IResult { +fn subtype_constraint_head(s: &str) -> IResult<'_, SubtypeConstraintHead<'_>> { map(tuple(( kw("subtype_constraint"), subtype_constraint_id, @@ -2288,7 +2294,7 @@ id_type!(SubtypeConstraintId, subtype_constraint_id); // 318 subtype_declaration = SUBTYPE OF ’(’ entity_ref { ’,’ entity_ref } ’)’ . #[derive(Debug)] pub struct SubtypeDeclaration<'a>(pub Vec>); -fn subtype_declaration(s: &str) -> IResult { +fn subtype_declaration(s: &str) -> IResult<'_, SubtypeDeclaration<'_>> { map(preceded(tuple((kw("subtype"), kw("of"))), parens(list1(',', entity_ref))), SubtypeDeclaration)(s) @@ -2302,7 +2308,7 @@ pub enum SupertypeConstraint<'a> { AbstractSupertype(AbstractSupertypeDeclaration<'a>), SupertypeRule(SupertypeRule<'a>) } -fn supertype_constraint(s: &str) -> IResult { +fn supertype_constraint(s: &str) -> IResult<'_, SupertypeConstraint<'_>> { use SupertypeConstraint::*; alt(( // Ordered so that "abstract supertype" is parsed before "abstract" @@ -2316,7 +2322,7 @@ fn supertype_constraint(s: &str) -> IResult { #[derive(Debug)] pub struct SupertypeExpression<'a>(SupertypeFactor<'a>, Vec>); -fn supertype_expression(s: &str) -> IResult { +fn supertype_expression(s: &str) -> IResult<'_, SupertypeExpression<'_>> { let (s, a) = supertype_factor(s)?; let (s, b) = many0(preceded(kw("andor"), supertype_factor))(s)?; Ok((s, SupertypeExpression(a, b))) @@ -2325,7 +2331,7 @@ fn supertype_expression(s: &str) -> IResult { // 321 supertype_factor = supertype_term { AND supertype_term } . #[derive(Debug)] pub struct SupertypeFactor<'a>(Vec>); -fn supertype_factor(s: &str) -> IResult { +fn supertype_factor(s: &str) -> IResult<'_, SupertypeFactor<'_>> { map(separated_list1(kw("and"), supertype_term), SupertypeFactor)(s) } @@ -2333,7 +2339,7 @@ fn supertype_factor(s: &str) -> IResult { // 322 supertype_rule = SUPERTYPE subtype_constraint . #[derive(Debug)] pub struct SupertypeRule<'a>(SubtypeConstraint<'a>); -fn supertype_rule(s: &str) -> IResult { +fn supertype_rule(s: &str) -> IResult<'_, SupertypeRule<'_>> { map(preceded(kw("supertype"), subtype_constraint), SupertypeRule)(s) } @@ -2344,7 +2350,7 @@ pub enum SupertypeTerm<'a> { OneOf(OneOf<'a>), Expression(SupertypeExpression<'a>), } -fn supertype_term(s: &str) -> IResult { +fn supertype_term(s: &str) -> IResult<'_, SupertypeTerm<'_>> { use SupertypeTerm::*; alt(( map(entity_ref, Entity), @@ -2356,14 +2362,14 @@ fn supertype_term(s: &str) -> IResult { // 324 syntax = schema_decl { schema_decl } . #[derive(Debug)] pub struct Syntax<'a>(pub Vec>); -fn syntax(s: &str) -> IResult { +fn syntax(s: &str) -> IResult<'_, Syntax<'_>> { preceded(multispace0, map(many1(schema_decl), Syntax))(s) } // 325 term = factor { multiplication_like_op factor } . #[derive(Debug)] pub struct Term<'a>(pub Factor<'a>, pub Vec<(MultiplicationLikeOp, Factor<'a>)>); -fn term(s: &str) -> IResult { +fn term(s: &str) -> IResult<'_, Term<'_>> { map(pair(factor, many0(pair(multiplication_like_op, factor))), |(a, b)| Term(a, b))(s) } @@ -2371,7 +2377,7 @@ fn term(s: &str) -> IResult { // 326 total_over = TOTAL_OVER ’(’ entity_ref { ’,’ entity_ref } ’)’ ’;’ . #[derive(Debug)] pub struct TotalOver<'a>(Vec>); -fn total_over(s: &str) -> IResult { +fn total_over(s: &str) -> IResult<'_, TotalOver<'_>> { map(delimited( kw("total_over"), parens(list1(',', entity_ref)), @@ -2386,7 +2392,7 @@ pub struct TypeDecl<'a> { pub underlying_type: UnderlyingType<'a>, pub where_clause: Option>, } -fn type_decl(s: &str) -> IResult { +fn type_decl(s: &str) -> IResult<'_, TypeDecl<'_>> { map(tuple(( kw("type"), type_id, @@ -2413,7 +2419,7 @@ pub enum TypeLabel<'a> { Ref(TypeLabelRef<'a>), _Ambiguous(SimpleId<'a>), } -fn type_label(s: &str) -> IResult { +fn type_label(s: &str) -> IResult<'_, TypeLabel<'_>> { map(simple_id, TypeLabel::_Ambiguous)(s) } @@ -2424,7 +2430,7 @@ pub struct TypeLabelId<'a>(SimpleId<'a>); // 331 #[derive(Debug, Eq, PartialEq)] pub enum UnaryOp { Add, Sub, Not } -fn unary_op(s: &str) -> IResult { +fn unary_op(s: &str) -> IResult<'_, UnaryOp> { use UnaryOp::*; alt(( map(char('+'), |_| Add), @@ -2439,7 +2445,7 @@ pub enum UnderlyingType<'a> { Concrete(ConcreteTypes<'a>), Constructed(ConstructedTypes<'a>), } -fn underlying_type(s: &str) -> IResult { +fn underlying_type(s: &str) -> IResult<'_, UnderlyingType<'_>> { use UnderlyingType::*; alt(( // Read constructed types first, so that 'select' doesn't get @@ -2452,7 +2458,7 @@ fn underlying_type(s: &str) -> IResult { // 333 unique_clause = UNIQUE unique_rule ’;’ { unique_rule ’;’ } . #[derive(Debug)] pub struct UniqueClause<'a>(Vec>); -fn unique_clause(s: &str) -> IResult { +fn unique_clause(s: &str) -> IResult<'_, UniqueClause<'_>> { map(preceded(kw("unique"), many1(terminated(unique_rule, char(';')))), UniqueClause)(s) } @@ -2463,7 +2469,7 @@ pub struct UniqueRule<'a> { pub label: Option>, pub attrs: Vec>, } -fn unique_rule(s: &str) -> IResult { +fn unique_rule(s: &str) -> IResult<'_, UniqueRule<'_>> { map(pair(opt(terminated(rule_label_id, char(':'))), list1(',', referenced_attribute)), |(a, b)| UniqueRule { label: a, attrs: b })(s) @@ -2472,7 +2478,7 @@ fn unique_rule(s: &str) -> IResult { // 335 until_control = UNTIL logical_expression . #[derive(Debug)] pub struct UntilControl<'a>(LogicalExpression<'a>); -fn until_control(s: &str) -> IResult { +fn until_control(s: &str) -> IResult<'_, UntilControl<'_>> { map(preceded(kw("until"), logical_expression), UntilControl)(s) } @@ -2483,7 +2489,7 @@ pub struct UseClause<'a> { pub schema_ref: SchemaRef<'a>, pub named_type_or_rename: Option>>, } -fn use_clause(s: &str) -> IResult { +fn use_clause(s: &str) -> IResult<'_, UseClause<'_>> { map(tuple(( kw("use"), kw("from"), @@ -2502,7 +2508,7 @@ id_type!(VariableId, variable_id); // 338 where_clause = WHERE domain_rule ’;’ { domain_rule ’;’ } . #[derive(Debug)] pub struct WhereClause<'a>(Vec>); -fn where_clause(s: &str) -> IResult { +fn where_clause(s: &str) -> IResult<'_, WhereClause<'_>> { let (s, _) = kw("where")(s)?; let (s, v) = many1(terminated(domain_rule, char(';')))(s)?; Ok((s, WhereClause(v))) @@ -2511,7 +2517,7 @@ fn where_clause(s: &str) -> IResult { // 339 while_control = WHILE logical_expression . #[derive(Debug)] pub struct WhileControl<'a>(LogicalExpression<'a>); -fn while_control(s: &str) -> IResult { +fn while_control(s: &str) -> IResult<'_, WhileControl<'_>> { map(preceded(kw("while"), logical_expression), WhileControl)(s) } @@ -2521,7 +2527,7 @@ alias!(Width<'a>, NumericExpression, width); // 341 width_spec = ’(’ width ’)’ [ FIXED ] . #[derive(Debug)] pub struct WidthSpec<'a> { pub expression: Width<'a>, pub fixed: bool } -fn width_spec(s: &str) -> IResult { +fn width_spec(s: &str) -> IResult<'_, WidthSpec<'_>> { map(pair(parens(width), opt(kw("fixed"))), |(w, f)| WidthSpec { expression: w, fixed: f.is_some() })(s) } From a8fcf6e9c31340bb772d1f751008c59fafbc6a6c Mon Sep 17 00:00:00 2001 From: Evan Jones Date: Thu, 11 Dec 2025 19:49:12 -0600 Subject: [PATCH 03/11] Fix all build warnings in express library - Add #![allow(dead_code)] at module level for AST struct fields (these are meant for library consumers) - Fix lifetime elision warnings by adding explicit '_ annotations to IResult type signatures throughout parse.rs - Update alias macro to handle lifetime and non-lifetime cases separately - Fix SimpleId::parse, Expression::parse, and SimpleExpression::parse to use explicit lifetime annotations --- express/src/parse.rs | 125 +++++++++++++++++++++++-------------------- 1 file changed, 68 insertions(+), 57 deletions(-) diff --git a/express/src/parse.rs b/express/src/parse.rs index f5932c8..62dc621 100644 --- a/express/src/parse.rs +++ b/express/src/parse.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + use memchr::{memchr, memchr_iter}; use nom::{ branch::{alt}, @@ -30,14 +32,14 @@ fn char<'a>(c: char) -> impl FnMut(&'a str) -> IResult<'a, char> { } /// Overloaded version of nom's `tag` that eats trailing whitespace -fn tag<'a>(s: &'a str) -> impl FnMut(&'a str) -> IResult<&'a str> { +fn tag<'a>(s: &'a str) -> impl FnMut(&'a str) -> IResult<'a, &'a str> { ws(nom::bytes::complete::tag(s)) } /// Matches a specific keyword, which ensuring that it's not followed by /// a letter. This avoids cases like `generic_expression` being parsed as /// `generic`, `_expression`. -fn kw<'a>(s: &'a str) -> impl FnMut(&'a str) -> IResult<&'a str> { +fn kw<'a>(s: &'a str) -> impl FnMut(&'a str) -> IResult<'a, &'a str> { ws(terminated(nom::bytes::complete::tag(s), not(alt((letter, digit, char('_')))))) } @@ -67,11 +69,20 @@ fn list1<'a, U, F>(c: char, p: F) -> impl FnMut(&'a str) -> IResult<'a, Vec> /// lets you define them without as much boilerplate, with or without a /// separate parser function. macro_rules! alias { - ($a:ident $(< $lt:lifetime >)?, $b:ident) => { + ($a:ident < $lt:lifetime >, $b:ident) => { + #[derive(Debug)] + pub struct $a<$lt>(pub $b<$lt>); + impl<$lt> $a<$lt> { + fn parse(s: &$lt str) -> IResult<$lt, Self> { + map($b::parse, Self)(s) + } + } + }; + ($a:ident, $b:ident) => { #[derive(Debug)] - pub struct $a $(< $lt >)?(pub $b $(< $lt >)?); - impl $(< $lt >)? $a $(< $lt >)? { - fn parse(s: &$( $lt )? str) -> IResult<$( $lt, )? Self> { + pub struct $a(pub $b); + impl $a { + fn parse(s: &str) -> IResult<'_, Self> { map($b::parse, Self)(s) } } @@ -182,7 +193,7 @@ fn not_quote(s: &str) -> IResult<'_, char> { } // 136 -fn octet(s: &str) -> IResult<&str> { +fn octet(s: &str) -> IResult<'_, &str> { recognize(pair(hex_digit, hex_digit))(s) } @@ -220,7 +231,7 @@ fn real_literal(s: &str) -> IResult<'_, f64> { #[derive(Debug, Eq, PartialEq)] pub struct SimpleId<'a>(pub &'a str); impl<'a> SimpleId<'a> { - fn parse(s: &'a str) -> IResult { + fn parse(s: &'a str) -> IResult<'a, Self> { let r = ws(map(pair( letter, many0_count(alt((letter, digit, char('_'))))), @@ -253,7 +264,7 @@ impl<'a> SimpleId<'a> { } } } -fn simple_id(s: &str) -> IResult { SimpleId::parse(s) } +fn simple_id(s: &str) -> IResult<'_, SimpleId<'_>> { SimpleId::parse(s) } // 144 simple_string_literal = \q { ( \q \q ) | not_quote | \s | \x9 | \xA | \xD } \q . fn simple_string_literal(s: &str) -> IResult<'_, String> { @@ -511,7 +522,7 @@ fn binary_type(s: &str) -> IResult<'_, BinaryType<'_>> { } // 182 boolean_type = BOOLEAN . -fn boolean_type(s: &str) -> IResult<()> { +fn boolean_type(s: &str) -> IResult<'_, ()> { map(kw("boolean"), |_| ())(s) } @@ -929,7 +940,7 @@ pub struct ExplicitAttr<'a> { pub optional: bool, pub parameter_type: ParameterType<'a>, } -fn explicit_attr(s: &str) -> IResult { +fn explicit_attr(s: &str) -> IResult<'_, ExplicitAttr<'_>> { map(tuple(( list1(',', attribute_decl), char(':'), @@ -947,7 +958,7 @@ fn explicit_attr(s: &str) -> IResult { #[derive(Debug)] pub struct Expression<'a>(SimpleExpression<'a>, Option<(RelOpExtended, SimpleExpression<'a>)>); impl<'a> Expression<'a> { - fn parse(s: &'a str) -> IResult { + fn parse(s: &'a str) -> IResult<'a, Self> { let (s, a) = simple_expression(s)?; let (s, b) = opt(pair(rel_op_extended, simple_expression))(s)?; Ok((s, Self(a, b))) @@ -1045,7 +1056,7 @@ pub enum GeneralizedTypes<'a> { GenericEntity(GenericEntityType<'a>), Generic(GenericType<'a>), } -fn generalized_types(s: &str) -> IResult { +fn generalized_types(s: &str) -> IResult<'_, GeneralizedTypes<'_>> { use GeneralizedTypes::*; alt(( map(aggregate_type, Aggregate), @@ -1064,7 +1075,7 @@ pub enum GeneralAggregationTypes<'a> { List(GeneralListType<'a>), Set(GeneralSetType<'a>), } -fn general_aggregation_types(s: &str) -> IResult { +fn general_aggregation_types(s: &str) -> IResult<'_, GeneralAggregationTypes<'_>> { use GeneralAggregationTypes::*; alt(( map(general_array_type, Array), @@ -1083,7 +1094,7 @@ pub struct GeneralArrayType<'a> { pub unique: bool, pub parameter_type: Box>, } -fn general_array_type(s: &str) -> IResult { +fn general_array_type(s: &str) -> IResult<'_, GeneralArrayType<'_>> { map(tuple(( kw("array"), bound_spec, @@ -1104,7 +1115,7 @@ fn general_array_type(s: &str) -> IResult { #[derive(Debug)] pub struct GeneralBagType<'a>(pub Option>, pub Box>); -fn general_bag_type(s: &str) -> IResult { +fn general_bag_type(s: &str) -> IResult<'_, GeneralBagType<'_>> { map(tuple(( kw("bag"), opt(bound_spec), @@ -1121,7 +1132,7 @@ pub struct GeneralListType<'a> { pub unique: bool, pub parameter_type: Box>, } -fn general_list_type(s: &str) -> IResult { +fn general_list_type(s: &str) -> IResult<'_, GeneralListType<'_>> { map(tuple(( kw("list"), opt(bound_spec), @@ -1143,7 +1154,7 @@ pub enum GeneralRef<'a> { Variable(VariableRef<'a>), _SimpleId(SimpleId<'a>), } -fn general_ref(s: &str) -> IResult { +fn general_ref(s: &str) -> IResult<'_, GeneralRef<'_>> { map(simple_id, GeneralRef::_SimpleId)(s) } @@ -1153,7 +1164,7 @@ pub struct GeneralSetType<'a> { pub bounds: Option>, pub parameter_type: Box>, } -fn general_set_type(s: &str) -> IResult { +fn general_set_type(s: &str) -> IResult<'_, GeneralSetType<'_>> { map(tuple(( kw("set"), opt(bound_spec), @@ -1169,7 +1180,7 @@ fn general_set_type(s: &str) -> IResult { // 230 generic_entity_type = GENERIC_ENTITY [ ’:’ type_label ] . #[derive(Debug)] pub struct GenericEntityType<'a>(Option>); -fn generic_entity_type(s: &str) -> IResult { +fn generic_entity_type(s: &str) -> IResult<'_, GenericEntityType<'_>> { map(preceded(kw("generic_entity"), opt(preceded(char(':'), type_label))), GenericEntityType)(s) @@ -1178,7 +1189,7 @@ fn generic_entity_type(s: &str) -> IResult { // 231 generic_type = GENERIC [ ’:’ type_label ] . #[derive(Debug)] pub struct GenericType<'a>(Option>); -fn generic_type(s: &str) -> IResult { +fn generic_type(s: &str) -> IResult<'_, GenericType<'_>> { map(preceded(kw("generic"), opt(preceded(char(':'), type_label))), GenericType)(s) @@ -1187,7 +1198,7 @@ fn generic_type(s: &str) -> IResult { // 232 group_qualifier = ’\’ entity_ref . #[derive(Debug)] pub struct GroupQualifier<'a>(pub EntityRef<'a>); -fn group_qualifier(s: &str) -> IResult { +fn group_qualifier(s: &str) -> IResult<'_, GroupQualifier<'_>> { map(preceded(char('\\'), entity_ref), GroupQualifier)(s) } @@ -1195,7 +1206,7 @@ fn group_qualifier(s: &str) -> IResult { // END_IF ’;’ . #[derive(Debug)] pub struct IfStmt<'a>(LogicalExpression<'a>, Vec>, Option>>); -fn if_stmt(s: &str) -> IResult { +fn if_stmt(s: &str) -> IResult<'_, IfStmt<'_>> { map(tuple(( kw("if"), logical_expression, @@ -1218,7 +1229,7 @@ pub struct IncrementControl<'a> { pub bound2: Bound2<'a>, pub increment: Option>, } -fn increment_control(s: &str) -> IResult { +fn increment_control(s: &str) -> IResult<'_, IncrementControl<'_>> { map(tuple(( variable_id, tag(":="), @@ -1246,7 +1257,7 @@ alias!(Index2<'a>, Index, index_2); // 239 index_qualifier = ’[’ index_1 [ ’:’ index_2 ] ’]’ . #[derive(Debug)] pub struct IndexQualifier<'a>(Index1<'a>, Option>); -fn index_qualifier(s: &str) -> IResult { +fn index_qualifier(s: &str) -> IResult<'_, IndexQualifier<'_>> { let (s, _) = char('[')(s)?; let (s, index1) = index_1(s)?; let (s, index2) = opt(preceded(char(';'), index_2))(s)?; @@ -1260,7 +1271,7 @@ pub enum InstantiableType<'a> { Concrete(ConcreteTypes<'a>), EntityRef(EntityRef<'a>), } -fn instantiable_type(s: &str) -> IResult { +fn instantiable_type(s: &str) -> IResult<'_, InstantiableType<'_>> { use InstantiableType::*; alt(( map(concrete_types, Concrete), @@ -1269,7 +1280,7 @@ fn instantiable_type(s: &str) -> IResult { } // 241 integer_type = INTEGER . -fn integer_type(s: &str) -> IResult<()> { +fn integer_type(s: &str) -> IResult<'_, ()> { map(kw("integer"), |_| ())(s) } @@ -1279,7 +1290,7 @@ pub enum InterfaceSpecification<'a> { ReferenceClause(ReferenceClause<'a>), UseClause(UseClause<'a>), } -fn interface_specification(s: &str) -> IResult { +fn interface_specification(s: &str) -> IResult<'_, InterfaceSpecification<'_>> { use InterfaceSpecification::*; alt((map(reference_clause, ReferenceClause), map(use_clause, UseClause)))(s) @@ -1294,7 +1305,7 @@ pub struct Interval<'a> { pub op2: IntervalOp, pub high: IntervalHigh<'a>, } -fn interval(s: &str) -> IResult { +fn interval(s: &str) -> IResult<'_, Interval<'_>> { map(delimited( char('{'), tuple(( @@ -1321,7 +1332,7 @@ alias!(IntervalLow<'a>, SimpleExpression, interval_low); // 247 #[derive(Debug)] pub enum IntervalOp { LessThan, LessThanOrEqual } -fn interval_op(s: &str) -> IResult { +fn interval_op(s: &str) -> IResult<'_, IntervalOp> { alt(( // Sort by length to pick the best match map(tag("<="), |_| IntervalOp::LessThanOrEqual), @@ -1341,7 +1352,7 @@ pub struct InverseAttr<'a> { pub entity_for: Option>, pub attribute_ref: AttributeRef<'a>, } -fn inverse_attr(s: &str) -> IResult { +fn inverse_attr(s: &str) -> IResult<'_, InverseAttr<'_>> { map(tuple(( attribute_decl, char(':'), @@ -1368,7 +1379,7 @@ fn inverse_attr(s: &str) -> IResult { // 249 inverse_clause = INVERSE inverse_attr { inverse_attr } . #[derive(Debug)] pub struct InverseClause<'a>(Vec>); -fn inverse_clause(s: &str) -> IResult { +fn inverse_clause(s: &str) -> IResult<'_, InverseClause<'_>> { map(preceded(kw("inverse"), many1(inverse_attr)), InverseClause)(s) } @@ -1379,7 +1390,7 @@ pub struct ListType<'a> { pub unique: bool, pub instantiable_type: Box>, } -fn list_type(s: &str) -> IResult { +fn list_type(s: &str) -> IResult<'_, ListType<'_>> { map(tuple(( kw("list"), opt(bound_spec), @@ -1402,7 +1413,7 @@ pub enum Literal { Logical(LogicalLiteral), Real(f64), } -fn literal(s: &str) -> IResult { +fn literal(s: &str) -> IResult<'_, Literal> { use Literal::*; alt(( map(binary_literal, Binary), @@ -1414,7 +1425,7 @@ fn literal(s: &str) -> IResult { // 252 local_decl = LOCAL local_variable { local_variable } END_LOCAL ’;’ #[derive(Debug)] pub struct LocalDecl<'a>(Vec>); -fn local_decl(s: &str) -> IResult { +fn local_decl(s: &str) -> IResult<'_, LocalDecl<'_>> { map(tuple(( kw("local"), many1(local_variable), @@ -1430,7 +1441,7 @@ pub struct LocalVariable<'a> { pub parameter_type: ParameterType<'a>, pub expression: Option>, } -fn local_variable(s: &str) -> IResult { +fn local_variable(s: &str) -> IResult<'_, LocalVariable<'_>> { map(tuple(( list1(',', variable_id), char(':'), @@ -1452,21 +1463,21 @@ alias!(LogicalExpression<'a>, Expression, logical_expression); pub enum LogicalLiteral { True, False, Unknown } -fn logical_literal(s: &str) -> IResult { +fn logical_literal(s: &str) -> IResult<'_, LogicalLiteral> { alt((map(kw("false"), |_| LogicalLiteral::False), map(kw("true"), |_| LogicalLiteral::True), map(kw("unknown"), |_| LogicalLiteral::Unknown)))(s) } // 256 logical_type = LOGICAL . -fn logical_type(s: &str) -> IResult<()> { +fn logical_type(s: &str) -> IResult<'_, ()> { map(kw("logical"), |_| ())(s) } // 257 #[derive(Debug)] pub enum MultiplicationLikeOp {Mul, Div, IntegerDiv, Mod, And, ComplexEntity } -fn multiplication_like_op(s: &str) -> IResult { +fn multiplication_like_op(s: &str) -> IResult<'_, MultiplicationLikeOp> { use MultiplicationLikeOp::*; alt(( map(char('*'), |_| Mul), @@ -1485,7 +1496,7 @@ pub enum NamedTypes<'a> { Type(TypeRef<'a>), _Ambiguous(SimpleId<'a>), } -fn named_types(s: &str) -> IResult { +fn named_types(s: &str) -> IResult<'_, NamedTypes<'_>> { map(simple_id, NamedTypes::_Ambiguous)(s) } @@ -1501,7 +1512,7 @@ pub struct NamedTypeOrRename<'a> { pub named_types: NamedTypes<'a>, pub rename: Option>, } -fn named_type_or_rename(s: &str) -> IResult { +fn named_type_or_rename(s: &str) -> IResult<'_, NamedTypeOrRename<'_>> { map(pair( named_types, opt(preceded(kw("as"), @@ -1510,12 +1521,12 @@ fn named_type_or_rename(s: &str) -> IResult { } // 260 null_stmt = ’;’ . -fn null_stmt(s: &str) -> IResult<()> { +fn null_stmt(s: &str) -> IResult<'_, ()> { map(char(';'), |_| ())(s) } // 261 number_type = NUMBER . -fn number_type(s: &str) -> IResult<()> { +fn number_type(s: &str) -> IResult<'_, ()> { map(kw("number"), |_| ())(s) } @@ -1525,7 +1536,7 @@ alias!(NumericExpression<'a>, SimpleExpression); // 263 one_of = ONEOF ’(’ supertype_expression { ’,’ supertype_expression } ’)’ #[derive(Debug)] pub struct OneOf<'a>(Vec>); -fn one_of(s: &str) -> IResult { +fn one_of(s: &str) -> IResult<'_, OneOf<'_>> { map(preceded( kw("oneof"), parens(list1(',', supertype_expression)), @@ -1545,7 +1556,7 @@ pub enum ParameterType<'a> { Named(NamedTypes<'a>), Simple(SimpleTypes<'a>), } -fn parameter_type(s: &str) -> IResult { +fn parameter_type(s: &str) -> IResult<'_, ParameterType<'_>> { use ParameterType::*; alt(( map(generalized_types, Generalized), @@ -1567,7 +1578,7 @@ pub enum Primary<'a> { Literal(Literal), Qualifiable(QualifiableFactor<'a>, Vec>), } -fn primary(s: &str) -> IResult { +fn primary(s: &str) -> IResult<'_, Primary<'_>> { use Primary::*; alt(( // Order so that the longest parser runs first @@ -1589,7 +1600,7 @@ pub struct ProcedureCallStmt<'a> { pub proc: BuiltInOrProcedureRef<'a>, pub params: Option>, } -fn procedure_call_stmt(s: &str) -> IResult { +fn procedure_call_stmt(s: &str) -> IResult<'_, ProcedureCallStmt<'_>> { map(tuple(( alt((map(built_in_procedure, BuiltInOrProcedureRef::BuiltIn), map(procedure_ref, BuiltInOrProcedureRef::ProcedureRef), @@ -1604,7 +1615,7 @@ fn procedure_call_stmt(s: &str) -> IResult { // 271 procedure_decl = procedure_head algorithm_head { stmt } END_PROCEDURE ’;’ . #[derive(Debug)] pub struct ProcedureDecl<'a>(ProcedureHead<'a>, AlgorithmHead<'a>, Vec>); -fn procedure_decl(s: &str) -> IResult { +fn procedure_decl(s: &str) -> IResult<'_, ProcedureDecl<'_>> { map(tuple(( procedure_head, algorithm_head, @@ -1621,7 +1632,7 @@ pub struct ProcedureHead<'a> { pub procedure_id: ProcedureId<'a>, pub args: Option)>>, } -fn procedure_head(s: &str) -> IResult { +fn procedure_head(s: &str) -> IResult<'_, ProcedureHead<'_>> { map(tuple(( kw("procedure"), procedure_id, @@ -1655,7 +1666,7 @@ pub enum QualifiableFactor<'a> { // catch-all for attribute, constant, general, population _Ambiguous(&'a str), } -fn qualifiable_factor(s: &str) -> IResult { +fn qualifiable_factor(s: &str) -> IResult<'_, QualifiableFactor<'_>> { alt(( // Try parsing the function call first. One valid parse is just a // function_ref, so we convert that case to _Ambiguous, since it may @@ -1686,7 +1697,7 @@ fn qualifiable_factor(s: &str) -> IResult { #[derive(Debug)] pub struct QualifiedAttribute<'a>(pub GroupQualifier<'a>, pub AttributeQualifier<'a>); -fn qualified_attribute(s: &str) -> IResult { +fn qualified_attribute(s: &str) -> IResult<'_, QualifiedAttribute<'_>> { map(tuple(( kw("self"), group_qualifier, @@ -1701,7 +1712,7 @@ pub enum Qualifier<'a> { Group(GroupQualifier<'a>), Index(IndexQualifier<'a>), } -fn qualifier(s: &str) -> IResult { +fn qualifier(s: &str) -> IResult<'_, Qualifier<'_>> { use Qualifier::*; alt(( map(attribute_qualifier, Attribute), @@ -1718,7 +1729,7 @@ pub struct QueryExpression<'a> { pub aggregate: AggregateSource<'a>, pub logical_expression: LogicalExpression<'a>, } -fn query_expression(s: &str) -> IResult { +fn query_expression(s: &str) -> IResult<'_, QueryExpression<'_>> { map(tuple(( kw("query"), char('('), @@ -1738,7 +1749,7 @@ fn query_expression(s: &str) -> IResult { // 278 real_type = REAL [ ’(’ precision_spec ’)’ ] . #[derive(Debug)] pub struct RealType<'a>(Option>); -fn real_type(s: &str) -> IResult { +fn real_type(s: &str) -> IResult<'_, RealType<'_>> { map(preceded(kw("real"), opt(parens(precision_spec))), RealType)(s) @@ -1748,7 +1759,7 @@ fn real_type(s: &str) -> IResult { #[derive(Debug)] pub struct RedeclaredAttribute<'a>(pub QualifiedAttribute<'a>, pub Option>); -fn redeclared_attribute(s: &str) -> IResult { +fn redeclared_attribute(s: &str) -> IResult<'_, RedeclaredAttribute<'_>> { map(pair(qualified_attribute, opt(preceded(kw("renamed"), attribute_id))), |(a, b)| RedeclaredAttribute(a, b))(s) @@ -1760,7 +1771,7 @@ pub enum ReferencedAttribute<'a> { Ref(AttributeRef<'a>), Qualified(QualifiedAttribute<'a>), } -fn referenced_attribute(s: &str) -> IResult { +fn referenced_attribute(s: &str) -> IResult<'_, ReferencedAttribute<'_>> { use ReferencedAttribute::*; alt(( map(attribute_ref, Ref), @@ -2078,7 +2089,7 @@ fn set_type(s: &str) -> IResult<'_, SetType<'_>> { #[derive(Debug)] pub struct SimpleExpression<'a>(pub Box>, pub Vec<(AddLikeOp, Term<'a>)>); impl<'a> SimpleExpression<'a> { - fn parse(s: &'a str) -> IResult { + fn parse(s: &'a str) -> IResult<'a, Self> { let (s, a) = term(s)?; let (s, b) = many0(pair(add_like_op, term))(s)?; Ok((s, SimpleExpression(Box::new(a), b))) From 461a00d2cc9534f7cc223512bd34e27ed70bf1f3 Mon Sep 17 00:00:00 2001 From: Evan Jones Date: Thu, 11 Dec 2025 19:56:17 -0600 Subject: [PATCH 04/11] Upgrade nom from 6.0 to 7 - Update nom dependency in express/Cargo.toml and step/Cargo.toml - Fix fold_many0/fold_many1 API changes: init parameter changed from value to closure in nom 7 - binary_literal: 0 -> || 0 - encoded_string_literal: String::new() -> String::new - simple_string_literal: String::new() -> String::new All 49 tests pass. --- express/Cargo.toml | 2 +- express/src/parse.rs | 6 +++--- step/Cargo.toml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/express/Cargo.toml b/express/Cargo.toml index 94b19bf..7d71636 100644 --- a/express/Cargo.toml +++ b/express/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" [dependencies] fast-float = "0.2" memchr = "2.4.0" -nom = "6.0" +nom = "7" [dev-dependencies] clap = "2.33" diff --git a/express/src/parse.rs b/express/src/parse.rs index 62dc621..11ffe99 100644 --- a/express/src/parse.rs +++ b/express/src/parse.rs @@ -199,7 +199,7 @@ fn octet(s: &str) -> IResult<'_, &str> { // 139 fn binary_literal(s: &str) -> IResult<'_, usize> { - let bits = fold_many1(alt((char('0'), char('1'))), 0, + let bits = fold_many1(alt((char('0'), char('1'))), || 0, |acc, item| acc * 2 + item.to_digit(10).unwrap() as usize); preceded(char('%'), bits)(s) } @@ -208,7 +208,7 @@ fn binary_literal(s: &str) -> IResult<'_, usize> { fn encoded_string_literal(s: &str) -> IResult<'_, String> { delimited( char('"'), - fold_many0(encoded_character, String::new(), + fold_many0(encoded_character, String::new, |mut s: String, c: char| { s.push(c); s }), char('"'))(s) } @@ -275,7 +275,7 @@ fn simple_string_literal(s: &str) -> IResult<'_, String> { )); delimited( char('\''), - fold_many0(f, String::new(), |mut s, c| { s.push(c); s }), + fold_many0(f, String::new, |mut s: String, c| { s.push(c); s }), char('\''))(s) } diff --git a/step/Cargo.toml b/step/Cargo.toml index 02cd5fc..3be2956 100644 --- a/step/Cargo.toml +++ b/step/Cargo.toml @@ -9,7 +9,7 @@ arrayvec = "0.7.1" fast-float = "0.2" log = "0.4.14" memchr = "2.4.0" -nom = "6.0" +nom = "7" rayon = {version = "1.5", optional = true } [features] From 7d1ebf163e6586c50ed4102d5a56c4bead1b4e99 Mon Sep 17 00:00:00 2001 From: Evan Jones Date: Thu, 11 Dec 2025 20:00:49 -0600 Subject: [PATCH 05/11] Upgrade nalgebra-glm from 0.13 to 0.20 - Update nalgebra-glm in gui/Cargo.toml, nurbs/Cargo.toml, triangulate/Cargo.toml - This also upgrades nalgebra from 0.27.1 to 0.34.1 - Resolves the future-incompatibility warning about trailing semicolons in macros No code changes required - the API is backwards compatible for our usage. All 49 tests pass. --- gui/Cargo.toml | 2 +- nurbs/Cargo.toml | 2 +- triangulate/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gui/Cargo.toml b/gui/Cargo.toml index 49c030e..20d80ff 100644 --- a/gui/Cargo.toml +++ b/gui/Cargo.toml @@ -15,7 +15,7 @@ bytemuck = { version = "1.5.1", features = ["derive"] } clap = "2.33" env_logger = "0.8.3" itertools = "0.10.0" -nalgebra-glm = "0.13.0" +nalgebra-glm = "0.20" pollster = "0.2.4" wgpu = "0.8.1" winit = "0.24.0" diff --git a/nurbs/Cargo.toml b/nurbs/Cargo.toml index 57437e4..7ddcb8b 100644 --- a/nurbs/Cargo.toml +++ b/nurbs/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" [dependencies] log = "0.4.14" -nalgebra-glm = "0.13.0" +nalgebra-glm = "0.20" num-integer = "0.1" ordered-float = "2.0" smallvec = "1.6" diff --git a/triangulate/Cargo.toml b/triangulate/Cargo.toml index 4574d38..5605168 100644 --- a/triangulate/Cargo.toml +++ b/triangulate/Cargo.toml @@ -10,7 +10,7 @@ nurbs = { path = "../nurbs" } step = { path = "../step" } log = "0.4.14" -nalgebra-glm = "0.13.0" +nalgebra-glm = "0.20" rayon = { version = "1.5", optional = true } thiserror = "1.0" From 2025658162452acdac00a58975991c9b407089e9 Mon Sep 17 00:00:00 2001 From: Evan Jones Date: Thu, 11 Dec 2025 20:49:06 -0600 Subject: [PATCH 06/11] Add LinearExtrusion surface type with projection and normal computation - Add ExtrusionCurve enum supporting BSpline, NURBS, and Line variants - Implement curve sampling and perpendicular projection for closest point finding - Add Newton-Raphson iteration for closest_u_perp on curved extrusions - Implement Surface::LinearExtrusion with project() and normal() methods - Add support for SURFACE_OF_LINEAR_EXTRUSION in triangulation pipeline - Extract extrusion direction and curve from STEP entities --- triangulate/src/surface.rs | 183 +++++++++- triangulate/src/triangulate.rs | 643 +++++++++++++++++++++------------ 2 files changed, 596 insertions(+), 230 deletions(-) diff --git a/triangulate/src/surface.rs b/triangulate/src/surface.rs index 7e00a09..c98b1e6 100644 --- a/triangulate/src/surface.rs +++ b/triangulate/src/surface.rs @@ -3,9 +3,165 @@ use std::f64::{EPSILON, consts::PI}; use nalgebra_glm as glm; use glm::{DVec2, DVec3, DVec4, DMat4}; -use nurbs::{AbstractSurface, NDBSplineSurface, SampledSurface}; +use nurbs::{AbstractCurve, AbstractSurface, NDBSplineCurve, NDBSplineSurface, SampledSurface}; use crate::{Error, mesh::Vertex}; +#[derive(Debug, Clone)] +pub enum ExtrusionCurve { + BSpline { + curve: nurbs::BSplineCurve, + samples: Vec<(f64, DVec3)>, + }, + NURBS { + curve: nurbs::NURBSCurve, + samples: Vec<(f64, DVec3)>, + }, + Line { + origin: DVec3, + dir_unit: DVec3, + }, +} + +fn curve_samples(curve: &NDBSplineCurve) -> Vec<(f64, DVec3)> +where + NDBSplineCurve: AbstractCurve, +{ + const NUM_SAMPLES_PER_KNOT: usize = 8; + let mut samples = Vec::new(); + for i in 0..curve.knots.len() - 1 { + if curve.knots[i] == curve.knots[i + 1] { + continue; + } + for j in 0..NUM_SAMPLES_PER_KNOT { + let frac = (j as f64) / (NUM_SAMPLES_PER_KNOT as f64 - 1.0); + let u = curve.knots[i] * (1.0 - frac) + curve.knots[i + 1] * frac; + samples.push((u, curve.point(u))); + } + } + samples +} + +fn project_perp(p: DVec3, dir_unit: DVec3) -> DVec3 { + p - dir_unit * p.dot(&dir_unit) +} + +fn clamp_open_interval(u: f64, u_min: f64, u_max: f64) -> f64 { + if u < u_min { + u_min + } else if u > u_max { + u_max + } else { + u + } +} + +fn curve_min_u(curve: &NDBSplineCurve) -> f64 { + curve.min_u() +} + +fn curve_max_u(curve: &NDBSplineCurve) -> f64 { + curve.max_u() +} + +fn closest_u_initial_guess(samples: &[(f64, DVec3)], p_perp: DVec3, dir_unit: DVec3) -> f64 { + let mut best_u = 0.0; + let mut best_dist = std::f64::INFINITY; + for (u, pos) in samples { + let d = (project_perp(*pos, dir_unit) - p_perp).norm(); + if d < best_dist { + best_dist = d; + best_u = *u; + } + } + best_u +} + +fn closest_u_newton(curve: &NDBSplineCurve, p_perp: DVec3, dir_unit: DVec3, u0: f64) -> f64 +where + NDBSplineCurve: AbstractCurve, +{ + let u_min = curve_min_u(curve); + let u_max = curve_max_u(curve); + let mut u_i = clamp_open_interval(u0, u_min, u_max); + + // A coarse convergence threshold is fine for visualization + let eps1 = 0.01; + for _ in 0..64 { + let derivs = curve.derivs::<2>(u_i); + let c = project_perp(derivs[0], dir_unit); + let c_p = project_perp(derivs[1], dir_unit); + let c_pp = project_perp(derivs[2], dir_unit); + let r = c - p_perp; + + if r.norm() <= eps1 { + return u_i; + } + + let denom = c_pp.dot(&r) + c_p.norm_squared(); + if denom.abs() <= std::f64::EPSILON { + return u_i; + } + let delta = -c_p.dot(&r) / denom; + let u_next = clamp_open_interval(u_i + delta, u_min, u_max); + + if ((u_next - u_i) * c_p.norm()).abs() <= eps1 { + return u_next; + } + u_i = u_next; + } + u_i +} + +impl ExtrusionCurve { + pub fn new_bspline(curve: nurbs::BSplineCurve) -> Self { + let samples = curve_samples(&curve); + Self::BSpline { curve, samples } + } + + pub fn new_nurbs(curve: nurbs::NURBSCurve) -> Self { + let samples = curve_samples(&curve); + Self::NURBS { curve, samples } + } + + pub fn new_line(origin: DVec3, dir_unit: DVec3) -> Self { + Self::Line { origin, dir_unit } + } + + pub fn point(&self, u: f64) -> DVec3 { + match self { + Self::BSpline { curve, .. } => curve.point(u), + Self::NURBS { curve, .. } => curve.point(u), + Self::Line { origin, dir_unit } => *origin + *dir_unit * u, + } + } + + pub fn deriv1(&self, u: f64) -> DVec3 { + match self { + Self::BSpline { curve, .. } => curve.derivs::<1>(u)[1], + Self::NURBS { curve, .. } => curve.derivs::<1>(u)[1], + Self::Line { dir_unit, .. } => *dir_unit, + } + } + + pub fn closest_u_perp(&self, p: DVec3, dir_unit: DVec3) -> f64 { + let p_perp = project_perp(p, dir_unit); + match self { + Self::BSpline { curve, samples } => { + let u0 = closest_u_initial_guess(samples, p_perp, dir_unit); + closest_u_newton(curve, p_perp, dir_unit, u0) + } + Self::NURBS { curve, samples } => { + let u0 = closest_u_initial_guess(samples, p_perp, dir_unit); + closest_u_newton(curve, p_perp, dir_unit, u0) + } + Self::Line { origin, dir_unit: line_dir } => { + // Choose u as signed distance along the line direction. + (p - *origin).dot(line_dir) + } + } + } +} + // Represents a surface in 3D space, with a function to project a 3D point // on the surface down to a 2D space. #[derive(Debug, Clone)] @@ -44,6 +200,10 @@ pub enum Surface { major_radius: f64, minor_radius: f64, }, + LinearExtrusion { + curve: ExtrusionCurve, + dir_unit: DVec3, + }, } impl Surface { @@ -54,6 +214,11 @@ impl Surface { mat_i: DMat4::identity(), location, radius, } + + } + + pub fn new_linear_extrusion(curve: ExtrusionCurve, dir_unit: DVec3) -> Self { + Surface::LinearExtrusion { curve, dir_unit } } pub fn new_cylinder(axis: DVec3, ref_direction: DVec3, location: DVec3, radius: f64) -> Self { let mat = Self::make_rigid_transform(axis, ref_direction, location); @@ -203,6 +368,12 @@ impl Surface { yz * angle / yz.norm() }) }, + Surface::LinearExtrusion { curve, dir_unit } => { + let u = curve.closest_u_perp(p, *dir_unit); + let c = curve.point(u); + let v = (p - c).dot(dir_unit); + Ok(DVec2::new(u, v)) + }, } } @@ -410,6 +581,16 @@ impl Surface { (mat * norm.to_homogeneous()).xyz() }, + Surface::LinearExtrusion { curve, dir_unit } => { + let du = curve.deriv1(uv.x); + let n = du.cross(dir_unit); + if n.norm() <= std::f64::EPSILON { + // Degenerate tangent; fall back to something stable-ish + (p - (p.dot(dir_unit) * *dir_unit)).normalize() + } else { + n.normalize() + } + }, } } } diff --git a/triangulate/src/triangulate.rs b/triangulate/src/triangulate.rs index ad4ba6e..9c65004 100644 --- a/triangulate/src/triangulate.rs +++ b/triangulate/src/triangulate.rs @@ -1,37 +1,41 @@ use std::collections::{HashMap, HashSet}; use std::convert::TryInto; +use glm::{DMat4, DVec3, DVec4, U32Vec3}; +use log::{error, info, warn}; use nalgebra_glm as glm; -use glm::{DVec3, DVec4, DMat4, U32Vec3}; -use log::{info, warn, error}; #[cfg(feature = "rayon")] use rayon::prelude::*; -use step::{ - ap214, ap214::*, step_file::{FromEntity, StepFile}, id::Id, ap214::Entity, -}; use crate::{ - Error, curve::Curve, - mesh, mesh::{Mesh, Triangle}, + mesh, + mesh::{Mesh, Triangle}, stats::Stats, - surface::Surface + surface::{ExtrusionCurve, Surface}, + Error, +}; +use nurbs::{BSplineSurface, KnotVector, NURBSSurface, SampledCurve, SampledSurface}; +use step::{ + ap214, + ap214::Entity, + ap214::*, + id::Id, + step_file::{FromEntity, StepFile}, }; -use nurbs::{BSplineSurface, SampledCurve, SampledSurface, NURBSSurface, KnotVector}; const SAVE_DEBUG_SVGS: bool = false; const SAVE_PANIC_SVGS: bool = false; /// `TransformStack` is a mapping of representations to transformed children. -type TransformStack<'a> = - HashMap, Vec<(Representation<'a>, DMat4)>>; +type TransformStack<'a> = HashMap, Vec<(Representation<'a>, DMat4)>>; fn build_transform_stack<'a>(s: &'a StepFile, flip: bool) -> TransformStack<'a> { // Store a map of parent -> (child, transform) let mut transform_stack: HashMap<_, Vec<_>> = HashMap::new(); - for r in s.0.iter() - .filter_map(|e| - RepresentationRelationshipWithTransformation_::try_from_entity(e)) + for r in + s.0.iter() + .filter_map(|e| RepresentationRelationshipWithTransformation_::try_from_entity(e)) { let (a, b) = if flip { (r.rep_2, r.rep_1) @@ -40,12 +44,12 @@ fn build_transform_stack<'a>(s: &'a StepFile, flip: bool) -> TransformStack<'a> }; let mut mat = item_defined_transformation(s, r.transformation_operator.cast()); if flip { - mat = mat.try_inverse().expect("Could not invert transform matrix"); + mat = mat + .try_inverse() + .expect("Could not invert transform matrix"); } - transform_stack.entry(b) - .or_default() - .push((a, mat)); + transform_stack.entry(b).or_default().push((a, mat)); } transform_stack } @@ -64,19 +68,22 @@ fn transform_stack_roots<'a>(transform_stack: &TransformStack<'a>) -> Vec (Mesh, Stats) { - let styled_items: Vec<_> = s.0.iter() + let styled_items: Vec<_> = s + .0 + .iter() .filter_map(|e| MechanicalDesignGeometricPresentationRepresentation_::try_from_entity(e)) .flat_map(|m| m.items.iter()) .filter_map(|item| s.entity(item.cast::())) .collect(); - let brep_colors: HashMap<_, DVec3> = styled_items.iter() - .filter_map(|styled| + let brep_colors: HashMap<_, DVec3> = styled_items + .iter() + .filter_map(|styled| { if styled.styles.len() != 1 { None } else { - presentation_style_color(s, styled.styles[0]) - .map(|c| (styled.item, c)) - }) + presentation_style_color(s, styled.styles[0]).map(|c| (styled.item, c)) + } + }) .collect(); // Store a map of parent -> (child, transform) @@ -91,9 +98,7 @@ pub fn triangulate(s: &StepFile) -> (Mesh, Stats) { transform_stack = build_transform_stack(s, true); roots = transform_stack_roots(&transform_stack); } - let mut todo: Vec<_> = roots.into_iter() - .map(|v| (v, DMat4::identity())) - .collect(); + let mut todo: Vec<_> = roots.into_iter().map(|v| (v, DMat4::identity())).collect(); if todo.len() > 1 { warn!("Transformation stack has more than one root!"); } @@ -101,9 +106,10 @@ pub fn triangulate(s: &StepFile) -> (Mesh, Stats) { // Store a map of ShapeRepresentationRelationships, which some models // use to map from axes to specific instances let mut shape_rep_relationship: HashMap, Vec>> = HashMap::new(); - for (r1, r2) in s.0.iter() - .filter_map(|e| ShapeRepresentationRelationship_::try_from_entity(e)) - .map(|e| (e.rep_1, e.rep_2)) + for (r1, r2) in + s.0.iter() + .filter_map(|e| ShapeRepresentationRelationship_::try_from_entity(e)) + .map(|e| (e.rep_1, e.rep_2)) { shape_rep_relationship.entry(r1).or_default().push(r2); } @@ -131,8 +137,7 @@ pub fn triangulate(s: &StepFile) -> (Mesh, Stats) { match &s[*m] { Entity::ManifoldSolidBrep(_) | Entity::BrepWithVoids(_) - | Entity::ShellBasedSurfaceModel(_) => - to_mesh.entry(*m).or_default().push(mat), + | Entity::ShellBasedSurfaceModel(_) => to_mesh.entry(*m).or_default().push(mat), Entity::Axis2Placement3d(_) => (), e => warn!("Skipping {:?}", e), } @@ -144,97 +149,104 @@ pub fn triangulate(s: &StepFile) -> (Mesh, Stats) { if to_mesh.is_empty() { s.0.iter() .enumerate() - .filter(|(_i, e)| - match e { - Entity::ManifoldSolidBrep(_) - | Entity::BrepWithVoids(_) - | Entity::ShellBasedSurfaceModel(_) => true, - _ => false, - } - ) + .filter(|(_i, e)| match e { + Entity::ManifoldSolidBrep(_) + | Entity::BrepWithVoids(_) + | Entity::ShellBasedSurfaceModel(_) => true, + _ => false, + }) .map(|(i, _e)| Id::new(i)) .for_each(|i| to_mesh.entry(i).or_default().push(DMat4::identity())); } let (to_mesh_iter, empty) = { #[cfg(feature = "rayon")] - { (to_mesh.par_iter(), || (Mesh::default(), Stats::default())) } + { + (to_mesh.par_iter(), || (Mesh::default(), Stats::default())) + } #[cfg(not(feature = "rayon"))] - { (to_mesh.iter(), (Mesh::default(), Stats::default())) } + { + (to_mesh.iter(), (Mesh::default(), Stats::default())) + } }; - let mesh_fold = to_mesh_iter - .fold( - // Empty constructor - empty, - - // Fold operation - |(mut mesh, mut stats), (id, mats)| { - let v_start = mesh.verts.len(); - let t_start = mesh.triangles.len(); - match &s[*id] { - Entity::ManifoldSolidBrep(b) => - closed_shell(s, b.outer, &mut mesh, &mut stats), - Entity::ShellBasedSurfaceModel(b) => - for v in &b.sbsm_boundary { - shell(s, *v, &mut mesh, &mut stats); - }, - Entity::BrepWithVoids(b) => - // TODO: handle voids - closed_shell(s, b.outer, &mut mesh, &mut stats), - _ => { - warn!("Skipping {:?} (not a known solid)", s[*id]); - return (mesh, stats); - }, - }; - - // Pick out a color from the color map and apply it to each - // newly-created vertex - let color = brep_colors.get(id) - .map(|c| *c) - .unwrap_or(DVec3::new(0.5, 0.5, 0.5)); - - // Build copies of the mesh by copying and applying transforms - let v_end = mesh.verts.len(); - let t_end = mesh.triangles.len(); - for mat in &mats[1..] { - for v in v_start..v_end { - let p = mesh.verts[v].pos; - let p_h = DVec4::new(p.x, p.y, p.z, 1.0); - let pos = (mat * p_h).xyz(); - - let n = mesh.verts[v].norm; - let norm = (mat * glm::vec3_to_vec4(&n)).xyz(); - - mesh.verts.push(mesh::Vertex { pos, norm, color }); - } - let offset = mesh.verts.len() - v_end; - for t in t_start..t_end { - let mut tri = mesh.triangles[t]; - tri.verts.add_scalar_mut(offset as u32); - mesh.triangles.push(tri); + let mesh_fold = to_mesh_iter.fold( + // Empty constructor + empty, + // Fold operation + |(mut mesh, mut stats), (id, mats)| { + let v_start = mesh.verts.len(); + let t_start = mesh.triangles.len(); + match &s[*id] { + Entity::ManifoldSolidBrep(b) => closed_shell(s, b.outer, &mut mesh, &mut stats), + Entity::ShellBasedSurfaceModel(b) => { + for v in &b.sbsm_boundary { + shell(s, *v, &mut mesh, &mut stats); } } + Entity::BrepWithVoids(b) => + // TODO: handle voids + { + closed_shell(s, b.outer, &mut mesh, &mut stats) + } + _ => { + warn!("Skipping {:?} (not a known solid)", s[*id]); + return (mesh, stats); + } + }; - // Now that we've built all of the other copies of the mesh, - // re-use the original mesh and apply the first transform - let mat = mats[0]; + // Pick out a color from the color map and apply it to each + // newly-created vertex + let color = brep_colors + .get(id) + .map(|c| *c) + .unwrap_or(DVec3::new(0.5, 0.5, 0.5)); + + // Build copies of the mesh by copying and applying transforms + let v_end = mesh.verts.len(); + let t_end = mesh.triangles.len(); + for mat in &mats[1..] { for v in v_start..v_end { let p = mesh.verts[v].pos; let p_h = DVec4::new(p.x, p.y, p.z, 1.0); - mesh.verts[v].pos = (mat * p_h).xyz(); + let pos = (mat * p_h).xyz(); let n = mesh.verts[v].norm; - mesh.verts[v].norm = (mat * glm::vec3_to_vec4(&n)).xyz(); + let norm = (mat * glm::vec3_to_vec4(&n)).xyz(); - mesh.verts[v].color = color; + mesh.verts.push(mesh::Vertex { pos, norm, color }); } - (mesh, stats) - }); + let offset = mesh.verts.len() - v_end; + for t in t_start..t_end { + let mut tri = mesh.triangles[t]; + tri.verts.add_scalar_mut(offset as u32); + mesh.triangles.push(tri); + } + } + + // Now that we've built all of the other copies of the mesh, + // re-use the original mesh and apply the first transform + let mat = mats[0]; + for v in v_start..v_end { + let p = mesh.verts[v].pos; + let p_h = DVec4::new(p.x, p.y, p.z, 1.0); + mesh.verts[v].pos = (mat * p_h).xyz(); + + let n = mesh.verts[v].norm; + mesh.verts[v].norm = (mat * glm::vec3_to_vec4(&n)).xyz(); + + mesh.verts[v].color = color; + } + (mesh, stats) + }, + ); let (mesh, stats) = { #[cfg(feature = "rayon")] - { mesh_fold.reduce(empty, - |a, b| (Mesh::combine(a.0, b.0), Stats::combine(a.1, b.1))) } + { + mesh_fold.reduce(empty, |a, b| { + (Mesh::combine(a.0, b.0), Stats::combine(a.1, b.1)) + }) + } #[cfg(not(feature = "rayon"))] { mesh_fold @@ -251,57 +263,53 @@ pub fn triangulate(s: &StepFile) -> (Mesh, Stats) { fn item_defined_transformation(s: &StepFile, t: Id) -> DMat4 { let i = s.entity(t).expect("Could not get ItemDefinedTransform"); - let (location, axis, ref_direction) = axis2_placement_3d(s, - i.transform_item_1.cast()); - let t1 = Surface::make_affine_transform(axis, - ref_direction, - axis.cross(&ref_direction), - location); + let (location, axis, ref_direction) = axis2_placement_3d(s, i.transform_item_1.cast()); + let t1 = + Surface::make_affine_transform(axis, ref_direction, axis.cross(&ref_direction), location); - let (location, axis, ref_direction) = axis2_placement_3d(s, - i.transform_item_2.cast()); - let t2 = Surface::make_affine_transform(axis, - ref_direction, - axis.cross(&ref_direction), - location); + let (location, axis, ref_direction) = axis2_placement_3d(s, i.transform_item_2.cast()); + let t2 = + Surface::make_affine_transform(axis, ref_direction, axis.cross(&ref_direction), location); t2 * t1.try_inverse().expect("Could not invert transform matrix") } -fn presentation_style_color(s: &StepFile, p: PresentationStyleAssignment) - -> Option -{ +fn presentation_style_color(s: &StepFile, p: PresentationStyleAssignment) -> Option { // AAAAAHHHHH s.entity(p) .and_then(|p: &PresentationStyleAssignment_| { - let mut surf = p.styles.iter().filter_map(|y| { - // This is an ambiguous parse, so we hard-code the first - // Entity item in the enum - use PresentationStyleSelect::PreDefinedPresentationStyle; - if let PreDefinedPresentationStyle(u) = y { - s.entity(u.cast::()) - } else { - None - }}); - let out = surf.next(); - out - }) - .and_then(|surf: &SurfaceStyleUsage_| - s.entity(surf.style.cast::())) - .and_then(|surf: &SurfaceSideStyle_| if surf.styles.len() != 1 { + let mut surf = p.styles.iter().filter_map(|y| { + // This is an ambiguous parse, so we hard-code the first + // Entity item in the enum + use PresentationStyleSelect::PreDefinedPresentationStyle; + if let PreDefinedPresentationStyle(u) = y { + s.entity(u.cast::()) + } else { + None + } + }); + let out = surf.next(); + out + }) + .and_then(|surf: &SurfaceStyleUsage_| s.entity(surf.style.cast::())) + .and_then(|surf: &SurfaceSideStyle_| { + if surf.styles.len() != 1 { None } else { s.entity(surf.styles[0].cast::()) - }) - .map(|surf: &SurfaceStyleFillArea_| - s.entity(surf.fill_area).expect("Could not get fill_area")) - .and_then(|fill: &FillAreaStyle_| if fill.fill_styles.len() != 1 { + } + }) + .map(|surf: &SurfaceStyleFillArea_| { + s.entity(surf.fill_area).expect("Could not get fill_area") + }) + .and_then(|fill: &FillAreaStyle_| { + if fill.fill_styles.len() != 1 { None } else { s.entity(fill.fill_styles[0].cast::()) - }) - .and_then(|f: &FillAreaStyleColour_| - s.entity(f.fill_colour.cast::())) + } + }) + .and_then(|f: &FillAreaStyleColour_| s.entity(f.fill_colour.cast::())) .map(|c| DVec3::new(c.red, c.green, c.blue)) } @@ -312,9 +320,11 @@ fn cartesian_point(s: &StepFile, a: Id) -> DVec3 { fn direction(s: &StepFile, a: Direction) -> DVec3 { let p = s.entity(a).expect("Could not get cartesian point"); - DVec3::new(p.direction_ratios[0], - p.direction_ratios[1], - p.direction_ratios[2]) + DVec3::new( + p.direction_ratios[0], + p.direction_ratios[1], + p.direction_ratios[2], + ) } fn axis2_placement_3d(s: &StepFile, t: Id) -> (DVec3, DVec3, DVec3) { @@ -357,9 +367,12 @@ fn closed_shell(s: &StepFile, c: ClosedShell, mesh: &mut Mesh, stats: &mut Stats stats.num_shells += 1; } -fn advanced_face(s: &StepFile, f: AdvancedFace, mesh: &mut Mesh, - stats: &mut Stats) -> Result<(), Error> -{ +fn advanced_face( + s: &StepFile, + f: AdvancedFace, + mesh: &mut Mesh, + stats: &mut Stats, +) -> Result<(), Error> { let face = s.entity(f).expect("Could not get AdvancedFace"); stats.num_faces += 1; @@ -391,7 +404,7 @@ fn advanced_face(s: &StepFile, f: AdvancedFace, mesh: &mut Mesh, norm: DVec3::zeros(), color: DVec3::new(0.0, 0.0, 0.0), }); - }, + } // Default for lists of contour points _ => { @@ -448,15 +461,15 @@ fn advanced_face(s: &StepFile, f: AdvancedFace, mesh: &mut Mesh, Err(cdt::Error::PointOnFixedEdge(p)) if p >= bonus_points => { pts[p] = pts[0]; continue; - }, + } Err(e) => { if SAVE_DEBUG_SVGS { let filename = format!("err{}.svg", face.face_geometry.0); t.save_debug_svg(&filename) .expect("Could not save debug SVG"); } - break Err(e) - }, + break Err(e); + } } } }); @@ -466,27 +479,30 @@ fn advanced_face(s: &StepFile, f: AdvancedFace, mesh: &mut Mesh, let a = (a + offset) as u32; let b = (b + offset) as u32; let c = (c + offset) as u32; - mesh.triangles.push(Triangle { verts: - if face.same_sense { + mesh.triangles.push(Triangle { + verts: if face.same_sense { U32Vec3::new(a, b, c) } else { U32Vec3::new(a, c, b) - } + }, }); } - }, + } Ok(Err(e)) => { - error!("Got error while triangulating {}: {:?}", - face.face_geometry.0, e); + error!( + "Got error while triangulating {}: {:?}", + face.face_geometry.0, e + ); stats.num_errors += 1; - }, + } Err(e) => { - error!("Got panic while triangulating {}: {:?}", - face.face_geometry.0, e); + error!( + "Got panic while triangulating {}: {:?}", + face.face_geometry.0, e + ); if SAVE_PANIC_SVGS { let filename = format!("panic{}.svg", face.face_geometry.0); - cdt::save_debug_panic(&pts, &edges, &filename) - .expect("Could not save debug SVG"); + cdt::save_debug_panic(&pts, &edges, &filename).expect("Could not save debug SVG"); } stats.num_panics += 1; } @@ -504,47 +520,82 @@ fn get_surface(s: &StepFile, surf: ap214::Surface) -> Result { match &s[surf] { Entity::CylindricalSurface(c) => { let (location, axis, ref_direction) = axis2_placement_3d(s, c.position); - Ok(Surface::new_cylinder(axis, ref_direction, location, c.radius.0.0.0)) - }, + Ok(Surface::new_cylinder( + axis, + ref_direction, + location, + c.radius.0 .0 .0, + )) + } Entity::ToroidalSurface(c) => { let (location, axis, _ref_direction) = axis2_placement_3d(s, c.position); - Ok(Surface::new_torus(location, axis, c.major_radius.0.0.0, c.minor_radius.0.0.0)) - }, + Ok(Surface::new_torus( + location, + axis, + c.major_radius.0 .0 .0, + c.minor_radius.0 .0 .0, + )) + } Entity::Plane(p) => { // We'll ignore axis and ref_direction in favor of building an // orthonormal basis later on let (location, axis, ref_direction) = axis2_placement_3d(s, p.position); Ok(Surface::new_plane(axis, ref_direction, location)) - }, + } // We treat cones like planes, since that's a valid mapping into 2D Entity::ConicalSurface(c) => { let (location, axis, ref_direction) = axis2_placement_3d(s, c.position); - Ok(Surface::new_cone(axis, ref_direction, location, c.semi_angle.0)) - }, + Ok(Surface::new_cone( + axis, + ref_direction, + location, + c.semi_angle.0, + )) + } Entity::SphericalSurface(c) => { // We'll ignore axis and ref_direction in favor of building an // orthonormal basis later on let (location, _axis, _ref_direction) = axis2_placement_3d(s, c.position); - Ok(Surface::new_sphere(location, c.radius.0.0.0)) - }, - Entity::BSplineSurfaceWithKnots(b) => - { + Ok(Surface::new_sphere(location, c.radius.0 .0 .0)) + } + Entity::SurfaceOfLinearExtrusion(e) => { + let v = s + .entity(e.extrusion_axis) + .expect("Could not get SurfaceOfLinearExtrusion extrusion_axis"); + let dir = direction(s, v.orientation) * v.magnitude.0; + if dir.norm() <= std::f64::EPSILON { + return Err(Error::CouldNotLower); + } + let dir_unit = dir.normalize(); + + let curve = extrusion_curve(s, e.swept_curve)?; + Ok(Surface::new_linear_extrusion(curve, dir_unit)) + } + Entity::BSplineSurfaceWithKnots(b) => { // TODO: make KnotVector::from_multiplicies accept iterators? let u_knots: Vec = b.u_knots.iter().map(|k| k.0).collect(); - let u_multiplicities: Vec = b.u_multiplicities.iter() + let u_multiplicities: Vec = b + .u_multiplicities + .iter() .map(|&k| k.try_into().expect("Got negative multiplicity")) .collect(); let u_knot_vec = KnotVector::from_multiplicities( b.u_degree.try_into().expect("Got negative degree"), - &u_knots, &u_multiplicities); + &u_knots, + &u_multiplicities, + ); let v_knots: Vec = b.v_knots.iter().map(|k| k.0).collect(); - let v_multiplicities: Vec = b.v_multiplicities.iter() + let v_multiplicities: Vec = b + .v_multiplicities + .iter() .map(|&k| k.try_into().expect("Got negative multiplicity")) .collect(); let v_knot_vec = KnotVector::from_multiplicities( b.v_degree.try_into().expect("Got negative degree"), - &v_knots, &v_multiplicities); + &v_knots, + &v_multiplicities, + ); let control_points_list = control_points_2d(s, &b.control_points_list); @@ -556,47 +607,55 @@ fn get_surface(s: &StepFile, surf: ap214::Surface) -> Result { control_points_list, ); Ok(Surface::BSpline(SampledSurface::new(surf))) - }, + } Entity::ComplexEntity(v) if v.len() == 2 => { let bspline = if let Entity::BSplineSurfaceWithKnots(b) = &v[0] { b } else { warn!("Could not get BSplineCurveWithKnots from {:?}", v[0]); - return Err(Error::UnknownCurveType) + return Err(Error::UnknownCurveType); }; let rational = if let Entity::RationalBSplineSurface(b) = &v[1] { b } else { warn!("Could not get RationalBSplineCurve from {:?}", v[1]); - return Err(Error::UnknownCurveType) + return Err(Error::UnknownCurveType); }; // TODO: make KnotVector::from_multiplicies accept iterators? let u_knots: Vec = bspline.u_knots.iter().map(|k| k.0).collect(); - let u_multiplicities: Vec = bspline.u_multiplicities.iter() + let u_multiplicities: Vec = bspline + .u_multiplicities + .iter() .map(|&k| k.try_into().expect("Got negative multiplicity")) .collect(); let u_knot_vec = KnotVector::from_multiplicities( bspline.u_degree.try_into().expect("Got negative degree"), - &u_knots, &u_multiplicities); + &u_knots, + &u_multiplicities, + ); let v_knots: Vec = bspline.v_knots.iter().map(|k| k.0).collect(); - let v_multiplicities: Vec = bspline.v_multiplicities.iter() + let v_multiplicities: Vec = bspline + .v_multiplicities + .iter() .map(|&k| k.try_into().expect("Got negative multiplicity")) .collect(); let v_knot_vec = KnotVector::from_multiplicities( bspline.v_degree.try_into().expect("Got negative degree"), - &v_knots, &v_multiplicities); + &v_knots, + &v_multiplicities, + ); - let control_points_list = control_points_2d( - s, &bspline.control_points_list) + let control_points_list = control_points_2d(s, &bspline.control_points_list) .into_iter() .zip(rational.weights_data.iter()) - .map(|(ctrl, weight)| + .map(|(ctrl, weight)| { ctrl.into_iter() .zip(weight.into_iter()) .map(|(p, w)| DVec4::new(p.x * w, p.y * w, p.z * w, *w)) - .collect()) + .collect() + }) .collect(); let surf = NURBSSurface::new( @@ -607,23 +666,112 @@ fn get_surface(s: &StepFile, surf: ap214::Surface) -> Result { control_points_list, ); Ok(Surface::NURBS(SampledSurface::new(surf))) - - }, + } e => { warn!("Could not get surface from {:?}", e); Err(Error::UnknownSurfaceType) - }, + } } } +fn extrusion_curve(s: &StepFile, curve_id: ap214::Curve) -> Result { + Ok(match &s[curve_id] { + Entity::BSplineCurveWithKnots(c) => { + if c.closed_curve.0 != Some(false) { + return Err(Error::ClosedCurve); + } else if c.self_intersect.0 != Some(false) { + return Err(Error::SelfIntersectingCurve); + } + + let control_points_list = control_points_1d(s, &c.control_points_list); + let knots: Vec = c.knots.iter().map(|k| k.0).collect(); + let multiplicities: Vec = c + .knot_multiplicities + .iter() + .map(|&k| k.try_into().expect("Got negative multiplicity")) + .collect(); + let knot_vec = KnotVector::from_multiplicities( + c.degree.try_into().expect("Got negative degree"), + &knots, + &multiplicities, + ); + + let curve = nurbs::BSplineCurve::new( + c.closed_curve.0.unwrap() == false, + knot_vec, + control_points_list, + ); + ExtrusionCurve::new_bspline(curve) + } + Entity::ComplexEntity(v) if v.len() == 2 => { + let bspline = if let Entity::BSplineCurveWithKnots(b) = &v[0] { + b + } else { + warn!("Could not get BSplineCurveWithKnots from {:?}", v[0]); + return Err(Error::UnknownCurveType); + }; + let rational = if let Entity::RationalBSplineCurve(b) = &v[1] { + b + } else { + warn!("Could not get RationalBSplineCurve from {:?}", v[1]); + return Err(Error::UnknownCurveType); + }; + + if bspline.closed_curve.0 != Some(false) { + return Err(Error::ClosedCurve); + } else if bspline.self_intersect.0 != Some(false) { + return Err(Error::SelfIntersectingCurve); + } + + let knots: Vec = bspline.knots.iter().map(|k| k.0).collect(); + let multiplicities: Vec = bspline + .knot_multiplicities + .iter() + .map(|&k| k.try_into().expect("Got negative multiplicity")) + .collect(); + let knot_vec = KnotVector::from_multiplicities( + bspline.degree.try_into().expect("Got negative degree"), + &knots, + &multiplicities, + ); + + let control_points_list = control_points_1d(s, &bspline.control_points_list) + .into_iter() + .zip(rational.weights_data.iter()) + .map(|(p, w)| DVec4::new(p.x * w, p.y * w, p.z * w, *w)) + .collect(); + + let curve = nurbs::NURBSCurve::new( + bspline.closed_curve.0.unwrap() == false, + knot_vec, + control_points_list, + ); + ExtrusionCurve::new_nurbs(curve) + } + Entity::SurfaceCurve(v) => extrusion_curve(s, v.curve_3d)?, + Entity::SeamCurve(v) => extrusion_curve(s, v.curve_3d)?, + Entity::Line(l) => { + let origin = cartesian_point(s, l.pnt); + let v = s.entity(l.dir).expect("Could not get Line dir"); + let dir = direction(s, v.orientation) * v.magnitude.0; + if dir.norm() <= std::f64::EPSILON { + return Err(Error::CouldNotLower); + } + ExtrusionCurve::new_line(origin, dir.normalize()) + } + e => { + warn!("Could not get swept curve from {:?}", e); + return Err(Error::UnknownCurveType); + } + }) +} + fn control_points_1d(s: &StepFile, row: &Vec) -> Vec { row.iter().map(|p| cartesian_point(s, *p)).collect() } fn control_points_2d(s: &StepFile, rows: &Vec>) -> Vec> { - rows.iter() - .map(|row| control_points_1d(s, row)) - .collect() + rows.iter().map(|row| control_points_1d(s, row)).collect() } fn face_bound(s: &StepFile, b: FaceBound) -> Result, Error> { @@ -639,7 +787,7 @@ fn face_bound(s: &StepFile, b: FaceBound) -> Result, Error> { d.reverse() } Ok(d) - }, + } Entity::VertexLoop(v) => { // This is an "edge loop" with a single vertex, which is // used for cones and not really anything else. @@ -649,9 +797,7 @@ fn face_bound(s: &StepFile, b: FaceBound) -> Result, Error> { } } -fn edge_loop(s: &StepFile, edge_list: &[OrientedEdge]) - -> Result, Error> -{ +fn edge_loop(s: &StepFile, edge_list: &[OrientedEdge]) -> Result, Error> { let mut out = Vec::new(); for (i, e) in edge_list.iter().enumerate() { // Remove the last item from the list, since it's the beginning @@ -680,23 +826,36 @@ fn edge_curve(s: &StepFile, e: EdgeCurve, orientation: bool) -> Result Result -{ +fn curve( + s: &StepFile, + edge_curve: &ap214::EdgeCurve_, + curve_id: ap214::Curve, + orientation: bool, +) -> Result { Ok(match &s[curve_id] { Entity::Circle(c) => { let (location, axis, ref_direction) = axis2_placement_3d(s, c.position.cast()); - Curve::new_circle(location, axis, ref_direction, c.radius.0.0.0, - edge_curve.edge_start == edge_curve.edge_end, - edge_curve.same_sense ^ !orientation) - }, + Curve::new_circle( + location, + axis, + ref_direction, + c.radius.0 .0 .0, + edge_curve.edge_start == edge_curve.edge_end, + edge_curve.same_sense ^ !orientation, + ) + } Entity::Ellipse(c) => { let (location, axis, ref_direction) = axis2_placement_3d(s, c.position.cast()); - Curve::new_ellipse(location, axis, ref_direction, - c.semi_axis_1.0.0.0, c.semi_axis_2.0.0.0, - edge_curve.edge_start == edge_curve.edge_end, - edge_curve.same_sense ^ !orientation) - }, + Curve::new_ellipse( + location, + axis, + ref_direction, + c.semi_axis_1.0 .0 .0, + c.semi_axis_2.0 .0 .0, + edge_curve.edge_start == edge_curve.edge_end, + edge_curve.same_sense ^ !orientation, + ) + } Entity::BSplineCurveWithKnots(c) => { if c.closed_curve.0 != Some(false) { return Err(Error::ClosedCurve); @@ -704,16 +863,19 @@ fn curve(s: &StepFile, edge_curve: &ap214::EdgeCurve_, return Err(Error::SelfIntersectingCurve); } - let control_points_list = control_points_1d( - s, &c.control_points_list); + let control_points_list = control_points_1d(s, &c.control_points_list); let knots: Vec = c.knots.iter().map(|k| k.0).collect(); - let multiplicities: Vec = c.knot_multiplicities.iter() + let multiplicities: Vec = c + .knot_multiplicities + .iter() .map(|&k| k.try_into().expect("Got negative multiplicity")) .collect(); let knot_vec = KnotVector::from_multiplicities( c.degree.try_into().expect("Got negative degree"), - &knots, &multiplicities); + &knots, + &multiplicities, + ); let curve = nurbs::BSplineCurve::new( c.closed_curve.0.unwrap() == false, @@ -721,30 +883,33 @@ fn curve(s: &StepFile, edge_curve: &ap214::EdgeCurve_, control_points_list, ); Curve::BSplineCurveWithKnots(SampledCurve::new(curve)) - }, + } Entity::ComplexEntity(v) if v.len() == 2 => { let bspline = if let Entity::BSplineCurveWithKnots(b) = &v[0] { b } else { warn!("Could not get BSplineCurveWithKnots from {:?}", v[0]); - return Err(Error::UnknownCurveType) + return Err(Error::UnknownCurveType); }; let rational = if let Entity::RationalBSplineCurve(b) = &v[1] { b } else { warn!("Could not get RationalBSplineCurve from {:?}", v[1]); - return Err(Error::UnknownCurveType) + return Err(Error::UnknownCurveType); }; let knots: Vec = bspline.knots.iter().map(|k| k.0).collect(); - let multiplicities: Vec = bspline.knot_multiplicities.iter() + let multiplicities: Vec = bspline + .knot_multiplicities + .iter() .map(|&k| k.try_into().expect("Got negative multiplicity")) .collect(); let knot_vec = KnotVector::from_multiplicities( bspline.degree.try_into().expect("Got negative degree"), - &knots, &multiplicities); + &knots, + &multiplicities, + ); - let control_points_list = control_points_1d( - s, &bspline.control_points_list) + let control_points_list = control_points_1d(s, &bspline.control_points_list) .into_iter() .zip(rational.weights_data.iter()) .map(|(p, w)| DVec4::new(p.x * w, p.y * w, p.z * w, *w)) @@ -756,26 +921,46 @@ fn curve(s: &StepFile, edge_curve: &ap214::EdgeCurve_, control_points_list, ); Curve::NURBSCurve(SampledCurve::new(curve)) - }, - Entity::SurfaceCurve(v) => { - curve(s, edge_curve, v.curve_3d, orientation)? - }, - Entity::SeamCurve(v) => { - curve(s, edge_curve, v.curve_3d, orientation)? - }, + } + Entity::SurfaceCurve(v) => curve(s, edge_curve, v.curve_3d, orientation)?, + Entity::SeamCurve(v) => curve(s, edge_curve, v.curve_3d, orientation)?, // The Line type ignores pnt / dir and just uses u and v Entity::Line(_) => Curve::new_line(), e => { warn!("Could not get edge from {:?}", e); return Err(Error::UnknownCurveType); - }, + } }) } fn vertex_point(s: &StepFile, v: Vertex) -> DVec3 { - cartesian_point(s, + cartesian_point( + s, s.entity(v.cast::()) .expect("Could not get VertexPoint") .vertex_geometry - .cast()) + .cast(), + ) +} + +#[cfg(test)] +mod tests { + use super::triangulate; + use step::step_file::StepFile; + + #[test] + fn triangulates_surface_of_linear_extrusion_example() { + let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("..") + .join("examples") + .join("test_spline_extrude_step.step"); + let data = + std::fs::read(&path).unwrap_or_else(|e| panic!("Could not read {:?}: {:?}", path, e)); + let flat = StepFile::strip_flatten(&data); + let step = StepFile::parse(&flat); + + let (mesh, _stats) = triangulate(&step); + assert!(mesh.verts.len() > 0); + assert!(mesh.triangles.len() > 0); + } } From 5ef4448d7d1b83ec185027242c1a9c9c5197e3ec Mon Sep 17 00:00:00 2001 From: Evan Jones Date: Thu, 11 Dec 2025 20:51:06 -0600 Subject: [PATCH 07/11] Added an example file with linear and BRep sections of a curve, extruded. --- examples/spline_linear_extrusion.step | 352 ++++++++++++++++++++++++++ 1 file changed, 352 insertions(+) create mode 100644 examples/spline_linear_extrusion.step diff --git a/examples/spline_linear_extrusion.step b/examples/spline_linear_extrusion.step new file mode 100644 index 0000000..1038c8d --- /dev/null +++ b/examples/spline_linear_extrusion.step @@ -0,0 +1,352 @@ +ISO-10303-21; +HEADER; +FILE_DESCRIPTION(('Open CASCADE Model'),'2;1'); +FILE_NAME('Open CASCADE Shape Model','2025-12-11T16:15:12',('Author'),( + 'Open CASCADE'),'Open CASCADE STEP processor 7.8','build123d', + 'Unknown'); +FILE_SCHEMA(('AUTOMOTIVE_DESIGN { 1 0 10303 214 1 1 1 1 }')); +ENDSEC; +DATA; +#1 = APPLICATION_PROTOCOL_DEFINITION('international standard', + 'automotive_design',2000,#2); +#2 = APPLICATION_CONTEXT( + 'core data for automotive mechanical design processes'); +#3 = SHAPE_DEFINITION_REPRESENTATION(#4,#10); +#4 = PRODUCT_DEFINITION_SHAPE('','',#5); +#5 = PRODUCT_DEFINITION('design','',#6,#9); +#6 = PRODUCT_DEFINITION_FORMATION('','',#7); +#7 = PRODUCT('COMPOUND','COMPOUND','',(#8)); +#8 = PRODUCT_CONTEXT('',#2,'mechanical'); +#9 = PRODUCT_DEFINITION_CONTEXT('part definition',#2,'design'); +#10 = ADVANCED_BREP_SHAPE_REPRESENTATION('',(#11,#15),#284); +#11 = AXIS2_PLACEMENT_3D('',#12,#13,#14); +#12 = CARTESIAN_POINT('',(0.,0.,0.)); +#13 = DIRECTION('',(0.,0.,1.)); +#14 = DIRECTION('',(1.,0.,-0.)); +#15 = MANIFOLD_SOLID_BREP('',#16); +#16 = CLOSED_SHELL('',(#17,#142,#225,#272,#278)); +#17 = ADVANCED_FACE('',(#18),#32,.F.); +#18 = FACE_BOUND('',#19,.F.); +#19 = EDGE_LOOP('',(#20,#55,#83,#116)); +#20 = ORIENTED_EDGE('',*,*,#21,.T.); +#21 = EDGE_CURVE('',#22,#24,#26,.T.); +#22 = VERTEX_POINT('',#23); +#23 = CARTESIAN_POINT('',(5.,0.,0.)); +#24 = VERTEX_POINT('',#25); +#25 = CARTESIAN_POINT('',(5.,0.,10.)); +#26 = SURFACE_CURVE('',#27,(#31,#43),.PCURVE_S1.); +#27 = LINE('',#28,#29); +#28 = CARTESIAN_POINT('',(5.,0.,0.)); +#29 = VECTOR('',#30,1.); +#30 = DIRECTION('',(0.,0.,1.)); +#31 = PCURVE('',#32,#37); +#32 = PLANE('',#33); +#33 = AXIS2_PLACEMENT_3D('',#34,#35,#36); +#34 = CARTESIAN_POINT('',(5.,0.,0.)); +#35 = DIRECTION('',(0.,1.,0.)); +#36 = DIRECTION('',(1.,0.,0.)); +#37 = DEFINITIONAL_REPRESENTATION('',(#38),#42); +#38 = LINE('',#39,#40); +#39 = CARTESIAN_POINT('',(0.,0.)); +#40 = VECTOR('',#41,1.); +#41 = DIRECTION('',(0.,-1.)); +#42 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#43 = PCURVE('',#44,#49); +#44 = PLANE('',#45); +#45 = AXIS2_PLACEMENT_3D('',#46,#47,#48); +#46 = CARTESIAN_POINT('',(3.,18.,0.)); +#47 = DIRECTION('',(0.993883734674,0.110431526075,-0.)); +#48 = DIRECTION('',(0.110431526075,-0.993883734674,0.)); +#49 = DEFINITIONAL_REPRESENTATION('',(#50),#54); +#50 = LINE('',#51,#52); +#51 = CARTESIAN_POINT('',(18.110770276275,0.)); +#52 = VECTOR('',#53,1.); +#53 = DIRECTION('',(0.,-1.)); +#54 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#55 = ORIENTED_EDGE('',*,*,#56,.T.); +#56 = EDGE_CURVE('',#24,#57,#59,.T.); +#57 = VERTEX_POINT('',#58); +#58 = CARTESIAN_POINT('',(10.,0.,10.)); +#59 = SURFACE_CURVE('',#60,(#64,#71),.PCURVE_S1.); +#60 = LINE('',#61,#62); +#61 = CARTESIAN_POINT('',(5.,0.,10.)); +#62 = VECTOR('',#63,1.); +#63 = DIRECTION('',(1.,0.,0.)); +#64 = PCURVE('',#32,#65); +#65 = DEFINITIONAL_REPRESENTATION('',(#66),#70); +#66 = LINE('',#67,#68); +#67 = CARTESIAN_POINT('',(0.,-10.)); +#68 = VECTOR('',#69,1.); +#69 = DIRECTION('',(1.,0.)); +#70 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#71 = PCURVE('',#72,#77); +#72 = PLANE('',#73); +#73 = AXIS2_PLACEMENT_3D('',#74,#75,#76); +#74 = CARTESIAN_POINT('',(6.654209077302,8.197480348355,10.)); +#75 = DIRECTION('',(0.,0.,1.)); +#76 = DIRECTION('',(1.,0.,-0.)); +#77 = DEFINITIONAL_REPRESENTATION('',(#78),#82); +#78 = LINE('',#79,#80); +#79 = CARTESIAN_POINT('',(-1.654209077302,-8.197480348355)); +#80 = VECTOR('',#81,1.); +#81 = DIRECTION('',(1.,0.)); +#82 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#83 = ORIENTED_EDGE('',*,*,#84,.F.); +#84 = EDGE_CURVE('',#85,#57,#87,.T.); +#85 = VERTEX_POINT('',#86); +#86 = CARTESIAN_POINT('',(10.,0.,0.)); +#87 = SURFACE_CURVE('',#88,(#92,#99),.PCURVE_S1.); +#88 = LINE('',#89,#90); +#89 = CARTESIAN_POINT('',(10.,0.,0.)); +#90 = VECTOR('',#91,1.); +#91 = DIRECTION('',(0.,0.,1.)); +#92 = PCURVE('',#32,#93); +#93 = DEFINITIONAL_REPRESENTATION('',(#94),#98); +#94 = LINE('',#95,#96); +#95 = CARTESIAN_POINT('',(5.,0.)); +#96 = VECTOR('',#97,1.); +#97 = DIRECTION('',(0.,-1.)); +#98 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#99 = PCURVE('',#100,#110); +#100 = SURFACE_OF_LINEAR_EXTRUSION('',#101,#108); +#101 = B_SPLINE_CURVE_WITH_KNOTS('',3,(#102,#103,#104,#105,#106,#107), + .UNSPECIFIED.,.F.,.F.,(4,1,1,4),(0.,10.,17.071067811865, + 20.676619087329),.UNSPECIFIED.); +#102 = CARTESIAN_POINT('',(10.,0.,0.)); +#103 = CARTESIAN_POINT('',(12.913510553271,4.699486232701,0.)); +#104 = CARTESIAN_POINT('',(11.380711874577,9.595598854801,0.)); +#105 = CARTESIAN_POINT('',(5.578916774563,13.988127661751,0.)); +#106 = CARTESIAN_POINT('',(3.480185251676,16.88471318541,-0.)); +#107 = CARTESIAN_POINT('',(3.,18.,0.)); +#108 = VECTOR('',#109,1.); +#109 = DIRECTION('',(-0.,-0.,-1.)); +#110 = DEFINITIONAL_REPRESENTATION('',(#111),#115); +#111 = LINE('',#112,#113); +#112 = CARTESIAN_POINT('',(0.,0.)); +#113 = VECTOR('',#114,1.); +#114 = DIRECTION('',(0.,-1.)); +#115 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#116 = ORIENTED_EDGE('',*,*,#117,.F.); +#117 = EDGE_CURVE('',#22,#85,#118,.T.); +#118 = SURFACE_CURVE('',#119,(#123,#130),.PCURVE_S1.); +#119 = LINE('',#120,#121); +#120 = CARTESIAN_POINT('',(5.,0.,0.)); +#121 = VECTOR('',#122,1.); +#122 = DIRECTION('',(1.,0.,0.)); +#123 = PCURVE('',#32,#124); +#124 = DEFINITIONAL_REPRESENTATION('',(#125),#129); +#125 = LINE('',#126,#127); +#126 = CARTESIAN_POINT('',(0.,0.)); +#127 = VECTOR('',#128,1.); +#128 = DIRECTION('',(1.,0.)); +#129 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#130 = PCURVE('',#131,#136); +#131 = PLANE('',#132); +#132 = AXIS2_PLACEMENT_3D('',#133,#134,#135); +#133 = CARTESIAN_POINT('',(6.654209077302,8.197480348355,0.)); +#134 = DIRECTION('',(0.,0.,1.)); +#135 = DIRECTION('',(1.,0.,-0.)); +#136 = DEFINITIONAL_REPRESENTATION('',(#137),#141); +#137 = LINE('',#138,#139); +#138 = CARTESIAN_POINT('',(-1.654209077302,-8.197480348355)); +#139 = VECTOR('',#140,1.); +#140 = DIRECTION('',(1.,0.)); +#141 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#142 = ADVANCED_FACE('',(#143),#100,.F.); +#143 = FACE_BOUND('',#144,.F.); +#144 = EDGE_LOOP('',(#145,#146,#175,#198)); +#145 = ORIENTED_EDGE('',*,*,#84,.T.); +#146 = ORIENTED_EDGE('',*,*,#147,.T.); +#147 = EDGE_CURVE('',#57,#148,#150,.T.); +#148 = VERTEX_POINT('',#149); +#149 = CARTESIAN_POINT('',(3.,18.,10.)); +#150 = SURFACE_CURVE('',#151,(#158,#165),.PCURVE_S1.); +#151 = B_SPLINE_CURVE_WITH_KNOTS('',3,(#152,#153,#154,#155,#156,#157), + .UNSPECIFIED.,.F.,.F.,(4,1,1,4),(0.,10.,17.071067811865, + 20.676619087329),.UNSPECIFIED.); +#152 = CARTESIAN_POINT('',(10.,0.,10.)); +#153 = CARTESIAN_POINT('',(12.913510553271,4.699486232701,10.)); +#154 = CARTESIAN_POINT('',(11.380711874577,9.595598854801,10.)); +#155 = CARTESIAN_POINT('',(5.578916774563,13.988127661751,10.)); +#156 = CARTESIAN_POINT('',(3.480185251676,16.88471318541,10.)); +#157 = CARTESIAN_POINT('',(3.,18.,10.)); +#158 = PCURVE('',#100,#159); +#159 = DEFINITIONAL_REPRESENTATION('',(#160),#164); +#160 = LINE('',#161,#162); +#161 = CARTESIAN_POINT('',(0.,-10.)); +#162 = VECTOR('',#163,1.); +#163 = DIRECTION('',(1.,0.)); +#164 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#165 = PCURVE('',#72,#166); +#166 = DEFINITIONAL_REPRESENTATION('',(#167),#174); +#167 = B_SPLINE_CURVE_WITH_KNOTS('',3,(#168,#169,#170,#171,#172,#173), + .UNSPECIFIED.,.F.,.F.,(4,1,1,4),(0.,10.,17.071067811865, + 20.676619087329),.UNSPECIFIED.); +#168 = CARTESIAN_POINT('',(3.345790922698,-8.197480348355)); +#169 = CARTESIAN_POINT('',(6.259301475969,-3.497994115654)); +#170 = CARTESIAN_POINT('',(4.726502797275,1.398118506446)); +#171 = CARTESIAN_POINT('',(-1.075292302739,5.790647313396)); +#172 = CARTESIAN_POINT('',(-3.174023825626,8.687232837055)); +#173 = CARTESIAN_POINT('',(-3.654209077302,9.802519651645)); +#174 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#175 = ORIENTED_EDGE('',*,*,#176,.F.); +#176 = EDGE_CURVE('',#177,#148,#179,.T.); +#177 = VERTEX_POINT('',#178); +#178 = CARTESIAN_POINT('',(3.,18.,0.)); +#179 = SURFACE_CURVE('',#180,(#184,#191),.PCURVE_S1.); +#180 = LINE('',#181,#182); +#181 = CARTESIAN_POINT('',(3.,18.,0.)); +#182 = VECTOR('',#183,1.); +#183 = DIRECTION('',(0.,0.,1.)); +#184 = PCURVE('',#100,#185); +#185 = DEFINITIONAL_REPRESENTATION('',(#186),#190); +#186 = LINE('',#187,#188); +#187 = CARTESIAN_POINT('',(20.676619087329,0.)); +#188 = VECTOR('',#189,1.); +#189 = DIRECTION('',(0.,-1.)); +#190 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#191 = PCURVE('',#44,#192); +#192 = DEFINITIONAL_REPRESENTATION('',(#193),#197); +#193 = LINE('',#194,#195); +#194 = CARTESIAN_POINT('',(0.,0.)); +#195 = VECTOR('',#196,1.); +#196 = DIRECTION('',(0.,-1.)); +#197 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#198 = ORIENTED_EDGE('',*,*,#199,.F.); +#199 = EDGE_CURVE('',#85,#177,#200,.T.); +#200 = SURFACE_CURVE('',#201,(#208,#215),.PCURVE_S1.); +#201 = B_SPLINE_CURVE_WITH_KNOTS('',3,(#202,#203,#204,#205,#206,#207), + .UNSPECIFIED.,.F.,.F.,(4,1,1,4),(0.,10.,17.071067811865, + 20.676619087329),.UNSPECIFIED.); +#202 = CARTESIAN_POINT('',(10.,0.,0.)); +#203 = CARTESIAN_POINT('',(12.913510553271,4.699486232701,0.)); +#204 = CARTESIAN_POINT('',(11.380711874577,9.595598854801,0.)); +#205 = CARTESIAN_POINT('',(5.578916774563,13.988127661751,0.)); +#206 = CARTESIAN_POINT('',(3.480185251676,16.88471318541,-0.)); +#207 = CARTESIAN_POINT('',(3.,18.,0.)); +#208 = PCURVE('',#100,#209); +#209 = DEFINITIONAL_REPRESENTATION('',(#210),#214); +#210 = LINE('',#211,#212); +#211 = CARTESIAN_POINT('',(0.,0.)); +#212 = VECTOR('',#213,1.); +#213 = DIRECTION('',(1.,0.)); +#214 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#215 = PCURVE('',#131,#216); +#216 = DEFINITIONAL_REPRESENTATION('',(#217),#224); +#217 = B_SPLINE_CURVE_WITH_KNOTS('',3,(#218,#219,#220,#221,#222,#223), + .UNSPECIFIED.,.F.,.F.,(4,1,1,4),(0.,10.,17.071067811865, + 20.676619087329),.UNSPECIFIED.); +#218 = CARTESIAN_POINT('',(3.345790922698,-8.197480348355)); +#219 = CARTESIAN_POINT('',(6.259301475969,-3.497994115654)); +#220 = CARTESIAN_POINT('',(4.726502797275,1.398118506446)); +#221 = CARTESIAN_POINT('',(-1.075292302739,5.790647313396)); +#222 = CARTESIAN_POINT('',(-3.174023825626,8.687232837055)); +#223 = CARTESIAN_POINT('',(-3.654209077302,9.802519651645)); +#224 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#225 = ADVANCED_FACE('',(#226),#44,.F.); +#226 = FACE_BOUND('',#227,.F.); +#227 = EDGE_LOOP('',(#228,#229,#250,#251)); +#228 = ORIENTED_EDGE('',*,*,#176,.T.); +#229 = ORIENTED_EDGE('',*,*,#230,.T.); +#230 = EDGE_CURVE('',#148,#24,#231,.T.); +#231 = SURFACE_CURVE('',#232,(#236,#243),.PCURVE_S1.); +#232 = LINE('',#233,#234); +#233 = CARTESIAN_POINT('',(3.,18.,10.)); +#234 = VECTOR('',#235,1.); +#235 = DIRECTION('',(0.110431526075,-0.993883734674,0.)); +#236 = PCURVE('',#44,#237); +#237 = DEFINITIONAL_REPRESENTATION('',(#238),#242); +#238 = LINE('',#239,#240); +#239 = CARTESIAN_POINT('',(0.,-10.)); +#240 = VECTOR('',#241,1.); +#241 = DIRECTION('',(1.,0.)); +#242 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#243 = PCURVE('',#72,#244); +#244 = DEFINITIONAL_REPRESENTATION('',(#245),#249); +#245 = LINE('',#246,#247); +#246 = CARTESIAN_POINT('',(-3.654209077302,9.802519651645)); +#247 = VECTOR('',#248,1.); +#248 = DIRECTION('',(0.110431526075,-0.993883734674)); +#249 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#250 = ORIENTED_EDGE('',*,*,#21,.F.); +#251 = ORIENTED_EDGE('',*,*,#252,.F.); +#252 = EDGE_CURVE('',#177,#22,#253,.T.); +#253 = SURFACE_CURVE('',#254,(#258,#265),.PCURVE_S1.); +#254 = LINE('',#255,#256); +#255 = CARTESIAN_POINT('',(3.,18.,0.)); +#256 = VECTOR('',#257,1.); +#257 = DIRECTION('',(0.110431526075,-0.993883734674,0.)); +#258 = PCURVE('',#44,#259); +#259 = DEFINITIONAL_REPRESENTATION('',(#260),#264); +#260 = LINE('',#261,#262); +#261 = CARTESIAN_POINT('',(0.,0.)); +#262 = VECTOR('',#263,1.); +#263 = DIRECTION('',(1.,0.)); +#264 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#265 = PCURVE('',#131,#266); +#266 = DEFINITIONAL_REPRESENTATION('',(#267),#271); +#267 = LINE('',#268,#269); +#268 = CARTESIAN_POINT('',(-3.654209077302,9.802519651645)); +#269 = VECTOR('',#270,1.); +#270 = DIRECTION('',(0.110431526075,-0.993883734674)); +#271 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#272 = ADVANCED_FACE('',(#273),#131,.F.); +#273 = FACE_BOUND('',#274,.F.); +#274 = EDGE_LOOP('',(#275,#276,#277)); +#275 = ORIENTED_EDGE('',*,*,#117,.T.); +#276 = ORIENTED_EDGE('',*,*,#199,.T.); +#277 = ORIENTED_EDGE('',*,*,#252,.T.); +#278 = ADVANCED_FACE('',(#279),#72,.T.); +#279 = FACE_BOUND('',#280,.T.); +#280 = EDGE_LOOP('',(#281,#282,#283)); +#281 = ORIENTED_EDGE('',*,*,#56,.T.); +#282 = ORIENTED_EDGE('',*,*,#147,.T.); +#283 = ORIENTED_EDGE('',*,*,#230,.T.); +#284 = ( GEOMETRIC_REPRESENTATION_CONTEXT(3) +GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#288)) GLOBAL_UNIT_ASSIGNED_CONTEXT +((#285,#286,#287)) REPRESENTATION_CONTEXT('Context #1', + '3D Context with UNIT and UNCERTAINTY') ); +#285 = ( LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT(.MILLI.,.METRE.) ); +#286 = ( NAMED_UNIT(*) PLANE_ANGLE_UNIT() SI_UNIT($,.RADIAN.) ); +#287 = ( NAMED_UNIT(*) SI_UNIT($,.STERADIAN.) SOLID_ANGLE_UNIT() ); +#288 = UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-07),#285, + 'distance_accuracy_value','confusion accuracy'); +#289 = PRODUCT_RELATED_PRODUCT_CATEGORY('part',$,(#7)); +ENDSEC; +END-ISO-10303-21; From 405539e9ca7c08045278ab7ec0a543a9f191ef2e Mon Sep 17 00:00:00 2001 From: Evan Jones Date: Mon, 15 Dec 2025 15:18:11 -0600 Subject: [PATCH 08/11] Add example file with B-spline curve revolved into surface - Add spline_revolve.step example demonstrating SURFACE_OF_REVOLUTION with B-spline curve - Implement Surface::Revolution variant with axis, curve, and transformation matrices - Add ExtrusionCurve::closest_u() for 3D point projection (non-perpendicular) - Implement closest_u_newton_3d() with Newton-Raphson iteration for curved surfaces - Add unwrap_theta_in_place() helper to handle angle wrapping in revolution surfaces - Implement project --- examples/spline_revolve.step | 209 ++++++++++++++++ triangulate/src/curve.rs | 64 +++-- triangulate/src/surface.rs | 445 +++++++++++++++++++++++++++------ triangulate/src/triangulate.rs | 222 +++++++++++++++- 4 files changed, 822 insertions(+), 118 deletions(-) create mode 100644 examples/spline_revolve.step diff --git a/examples/spline_revolve.step b/examples/spline_revolve.step new file mode 100644 index 0000000..a485bf2 --- /dev/null +++ b/examples/spline_revolve.step @@ -0,0 +1,209 @@ +ISO-10303-21; +HEADER; +FILE_DESCRIPTION(('Open CASCADE Model'),'2;1'); +FILE_NAME('Open CASCADE Shape Model','2025-12-12T08:55:01',('Author'),( + 'Open CASCADE'),'Open CASCADE STEP processor 7.8','build123d', + 'Unknown'); +FILE_SCHEMA(('AUTOMOTIVE_DESIGN { 1 0 10303 214 1 1 1 1 }')); +ENDSEC; +DATA; +#1 = APPLICATION_PROTOCOL_DEFINITION('international standard', + 'automotive_design',2000,#2); +#2 = APPLICATION_CONTEXT( + 'core data for automotive mechanical design processes'); +#3 = SHAPE_DEFINITION_REPRESENTATION(#4,#10); +#4 = PRODUCT_DEFINITION_SHAPE('','',#5); +#5 = PRODUCT_DEFINITION('design','',#6,#9); +#6 = PRODUCT_DEFINITION_FORMATION('','',#7); +#7 = PRODUCT('COMPOUND','COMPOUND','',(#8)); +#8 = PRODUCT_CONTEXT('',#2,'mechanical'); +#9 = PRODUCT_DEFINITION_CONTEXT('part definition',#2,'design'); +#10 = ADVANCED_BREP_SHAPE_REPRESENTATION('',(#11,#15),#165); +#11 = AXIS2_PLACEMENT_3D('',#12,#13,#14); +#12 = CARTESIAN_POINT('',(0.,0.,0.)); +#13 = DIRECTION('',(0.,0.,1.)); +#14 = DIRECTION('',(1.,0.,-0.)); +#15 = MANIFOLD_SOLID_BREP('',#16); +#16 = CLOSED_SHELL('',(#17,#88,#138)); +#17 = ADVANCED_FACE('',(#18,#54),#31,.F.); +#18 = FACE_BOUND('',#19,.T.); +#19 = EDGE_LOOP('',(#20)); +#20 = ORIENTED_EDGE('',*,*,#21,.T.); +#21 = EDGE_CURVE('',#22,#22,#24,.T.); +#22 = VERTEX_POINT('',#23); +#23 = CARTESIAN_POINT('',(5.,0.,0.)); +#24 = SURFACE_CURVE('',#25,(#30,#42),.PCURVE_S1.); +#25 = CIRCLE('',#26,5.); +#26 = AXIS2_PLACEMENT_3D('',#27,#28,#29); +#27 = CARTESIAN_POINT('',(0.,0.,0.)); +#28 = DIRECTION('',(-0.,1.,0.)); +#29 = DIRECTION('',(1.,0.,0.)); +#30 = PCURVE('',#31,#36); +#31 = PLANE('',#32); +#32 = AXIS2_PLACEMENT_3D('',#33,#34,#35); +#33 = CARTESIAN_POINT('',(0.,0.,0.)); +#34 = DIRECTION('',(0.,1.,0.)); +#35 = DIRECTION('',(1.,0.,0.)); +#36 = DEFINITIONAL_REPRESENTATION('',(#37),#41); +#37 = CIRCLE('',#38,5.); +#38 = AXIS2_PLACEMENT_2D('',#39,#40); +#39 = CARTESIAN_POINT('',(0.,0.)); +#40 = DIRECTION('',(1.,-0.)); +#41 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#42 = PCURVE('',#43,#48); +#43 = CYLINDRICAL_SURFACE('',#44,5.); +#44 = AXIS2_PLACEMENT_3D('',#45,#46,#47); +#45 = CARTESIAN_POINT('',(0.,5.,0.)); +#46 = DIRECTION('',(-0.,-1.,-0.)); +#47 = DIRECTION('',(1.,-0.,0.)); +#48 = DEFINITIONAL_REPRESENTATION('',(#49),#53); +#49 = LINE('',#50,#51); +#50 = CARTESIAN_POINT('',(-0.,5.)); +#51 = VECTOR('',#52,1.); +#52 = DIRECTION('',(-1.,0.)); +#53 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#54 = FACE_BOUND('',#55,.T.); +#55 = EDGE_LOOP('',(#56)); +#56 = ORIENTED_EDGE('',*,*,#57,.F.); +#57 = EDGE_CURVE('',#58,#58,#60,.T.); +#58 = VERTEX_POINT('',#59); +#59 = CARTESIAN_POINT('',(10.,0.,0.)); +#60 = SURFACE_CURVE('',#61,(#66,#73),.PCURVE_S1.); +#61 = CIRCLE('',#62,10.); +#62 = AXIS2_PLACEMENT_3D('',#63,#64,#65); +#63 = CARTESIAN_POINT('',(0.,0.,0.)); +#64 = DIRECTION('',(-0.,1.,0.)); +#65 = DIRECTION('',(1.,0.,0.)); +#66 = PCURVE('',#31,#67); +#67 = DEFINITIONAL_REPRESENTATION('',(#68),#72); +#68 = CIRCLE('',#69,10.); +#69 = AXIS2_PLACEMENT_2D('',#70,#71); +#70 = CARTESIAN_POINT('',(0.,0.)); +#71 = DIRECTION('',(1.,-0.)); +#72 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#73 = PCURVE('',#74,#82); +#74 = SURFACE_OF_REVOLUTION('',#75,#79); +#75 = B_SPLINE_CURVE_WITH_KNOTS('',2,(#76,#77,#78),.UNSPECIFIED.,.F.,.F. + ,(3,3),(0.,10.),.PIECEWISE_BEZIER_KNOTS.); +#76 = CARTESIAN_POINT('',(10.,0.,0.)); +#77 = CARTESIAN_POINT('',(12.5,7.5,0.)); +#78 = CARTESIAN_POINT('',(5.,5.,0.)); +#79 = AXIS1_PLACEMENT('',#80,#81); +#80 = CARTESIAN_POINT('',(0.,0.,0.)); +#81 = DIRECTION('',(0.,1.,0.)); +#82 = DEFINITIONAL_REPRESENTATION('',(#83),#87); +#83 = LINE('',#84,#85); +#84 = CARTESIAN_POINT('',(0.,0.)); +#85 = VECTOR('',#86,1.); +#86 = DIRECTION('',(1.,0.)); +#87 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#88 = ADVANCED_FACE('',(#89),#74,.T.); +#89 = FACE_BOUND('',#90,.T.); +#90 = EDGE_LOOP('',(#91,#92,#115,#137)); +#91 = ORIENTED_EDGE('',*,*,#57,.T.); +#92 = ORIENTED_EDGE('',*,*,#93,.T.); +#93 = EDGE_CURVE('',#58,#94,#96,.T.); +#94 = VERTEX_POINT('',#95); +#95 = CARTESIAN_POINT('',(5.,5.,0.)); +#96 = SEAM_CURVE('',#97,(#101,#108),.PCURVE_S1.); +#97 = B_SPLINE_CURVE_WITH_KNOTS('',2,(#98,#99,#100),.UNSPECIFIED.,.F., + .F.,(3,3),(0.,10.),.PIECEWISE_BEZIER_KNOTS.); +#98 = CARTESIAN_POINT('',(10.,0.,0.)); +#99 = CARTESIAN_POINT('',(12.5,7.5,0.)); +#100 = CARTESIAN_POINT('',(5.,5.,0.)); +#101 = PCURVE('',#74,#102); +#102 = DEFINITIONAL_REPRESENTATION('',(#103),#107); +#103 = LINE('',#104,#105); +#104 = CARTESIAN_POINT('',(0.,0.)); +#105 = VECTOR('',#106,1.); +#106 = DIRECTION('',(0.,1.)); +#107 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#108 = PCURVE('',#74,#109); +#109 = DEFINITIONAL_REPRESENTATION('',(#110),#114); +#110 = LINE('',#111,#112); +#111 = CARTESIAN_POINT('',(6.28318530718,0.)); +#112 = VECTOR('',#113,1.); +#113 = DIRECTION('',(0.,1.)); +#114 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#115 = ORIENTED_EDGE('',*,*,#116,.F.); +#116 = EDGE_CURVE('',#94,#94,#117,.T.); +#117 = SURFACE_CURVE('',#118,(#123,#130),.PCURVE_S1.); +#118 = CIRCLE('',#119,5.); +#119 = AXIS2_PLACEMENT_3D('',#120,#121,#122); +#120 = CARTESIAN_POINT('',(0.,5.,0.)); +#121 = DIRECTION('',(-0.,1.,0.)); +#122 = DIRECTION('',(1.,0.,0.)); +#123 = PCURVE('',#74,#124); +#124 = DEFINITIONAL_REPRESENTATION('',(#125),#129); +#125 = LINE('',#126,#127); +#126 = CARTESIAN_POINT('',(0.,10.)); +#127 = VECTOR('',#128,1.); +#128 = DIRECTION('',(1.,0.)); +#129 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#130 = PCURVE('',#43,#131); +#131 = DEFINITIONAL_REPRESENTATION('',(#132),#136); +#132 = LINE('',#133,#134); +#133 = CARTESIAN_POINT('',(-0.,0.)); +#134 = VECTOR('',#135,1.); +#135 = DIRECTION('',(-1.,0.)); +#136 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#137 = ORIENTED_EDGE('',*,*,#93,.F.); +#138 = ADVANCED_FACE('',(#139),#43,.F.); +#139 = FACE_BOUND('',#140,.T.); +#140 = EDGE_LOOP('',(#141,#142,#163,#164)); +#141 = ORIENTED_EDGE('',*,*,#116,.T.); +#142 = ORIENTED_EDGE('',*,*,#143,.T.); +#143 = EDGE_CURVE('',#94,#22,#144,.T.); +#144 = SEAM_CURVE('',#145,(#149,#156),.PCURVE_S1.); +#145 = LINE('',#146,#147); +#146 = CARTESIAN_POINT('',(5.,5.,0.)); +#147 = VECTOR('',#148,1.); +#148 = DIRECTION('',(0.,-1.,0.)); +#149 = PCURVE('',#43,#150); +#150 = DEFINITIONAL_REPRESENTATION('',(#151),#155); +#151 = LINE('',#152,#153); +#152 = CARTESIAN_POINT('',(-0.,0.)); +#153 = VECTOR('',#154,1.); +#154 = DIRECTION('',(-0.,1.)); +#155 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#156 = PCURVE('',#43,#157); +#157 = DEFINITIONAL_REPRESENTATION('',(#158),#162); +#158 = LINE('',#159,#160); +#159 = CARTESIAN_POINT('',(-6.28318530718,0.)); +#160 = VECTOR('',#161,1.); +#161 = DIRECTION('',(-0.,1.)); +#162 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#163 = ORIENTED_EDGE('',*,*,#21,.F.); +#164 = ORIENTED_EDGE('',*,*,#143,.F.); +#165 = ( GEOMETRIC_REPRESENTATION_CONTEXT(3) +GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#169)) GLOBAL_UNIT_ASSIGNED_CONTEXT +((#166,#167,#168)) REPRESENTATION_CONTEXT('Context #1', + '3D Context with UNIT and UNCERTAINTY') ); +#166 = ( LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT(.MILLI.,.METRE.) ); +#167 = ( NAMED_UNIT(*) PLANE_ANGLE_UNIT() SI_UNIT($,.RADIAN.) ); +#168 = ( NAMED_UNIT(*) SI_UNIT($,.STERADIAN.) SOLID_ANGLE_UNIT() ); +#169 = UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-07),#166, + 'distance_accuracy_value','confusion accuracy'); +#170 = PRODUCT_RELATED_PRODUCT_CATEGORY('part',$,(#7)); +ENDSEC; +END-ISO-10303-21; diff --git a/triangulate/src/curve.rs b/triangulate/src/curve.rs index c98f807..06c8be4 100644 --- a/triangulate/src/curve.rs +++ b/triangulate/src/curve.rs @@ -1,8 +1,8 @@ +use glm::{DMat4, DVec3, DVec4}; use nalgebra_glm as glm; -use glm::{DVec3, DVec4, DMat4}; -use nurbs::{AbstractCurve, NDBSplineCurve, SampledCurve}; use crate::surface::Surface; +use nurbs::{AbstractCurve, NDBSplineCurve, SampledCurve}; #[derive(Debug)] pub enum Curve { @@ -11,7 +11,7 @@ pub enum Curve { eplane_from_world: DMat4, world_from_eplane: DMat4, closed: bool, - dir: bool + dir: bool, }, Line, BSplineCurveWithKnots(SampledCurve<3>), @@ -19,29 +19,40 @@ pub enum Curve { } impl Curve { - pub fn new_ellipse(location: DVec3, axis: DVec3, ref_direction: DVec3, - radius1: f64, radius2: f64, closed: bool, dir: bool) - -> Self - { + pub fn new_ellipse( + location: DVec3, + axis: DVec3, + ref_direction: DVec3, + radius1: f64, + radius2: f64, + closed: bool, + dir: bool, + ) -> Self { // Build a rotation matrix to go from flat (XY) to 3D space - let world_from_eplane = Surface::make_affine_transform(axis, + let world_from_eplane = Surface::make_affine_transform( + axis, radius1 * ref_direction, radius2 * axis.cross(&ref_direction), - location); - let eplane_from_world = world_from_eplane - .try_inverse() - .expect("Could not invert"); + location, + ); + let eplane_from_world = world_from_eplane.try_inverse().expect("Could not invert"); Self::Ellipse { world_from_eplane, eplane_from_world, - closed, dir + closed, + dir, } } - pub fn new_circle(location: DVec3, axis: DVec3, ref_direction: DVec3, - radius: f64, closed: bool, dir: bool) -> Self { - Self::new_ellipse(location, axis, ref_direction, - radius, radius, closed, dir) + pub fn new_circle( + location: DVec3, + axis: DVec3, + ref_direction: DVec3, + radius: f64, + closed: bool, + dir: bool, + ) -> Self { + Self::new_ellipse(location, axis, ref_direction, radius, radius, closed, dir) } pub fn new_line() -> Self { @@ -49,7 +60,8 @@ impl Curve { } fn curve_points(u: DVec3, v: DVec3, curve: &SampledCurve) -> Vec - where NDBSplineCurve: AbstractCurve + where + NDBSplineCurve: AbstractCurve, { let t_start = curve.u_from_point(u); let t_end = curve.u_from_point(v); @@ -65,14 +77,15 @@ impl Curve { Self::BSplineCurveWithKnots(curve) => Self::curve_points(u, v, curve), Self::NURBSCurve(curve) => Self::curve_points(u, v, curve), Self::Ellipse { - eplane_from_world, world_from_eplane, closed, dir + eplane_from_world, + world_from_eplane, + closed, + dir, } => { // Project from 3D into the "ellipse plane". In the "eplane", // the ellipse lies on the unit circle. - let u_eplane = eplane_from_world * - DVec4::new(u.x, u.y, u.z, 1.0); - let v_eplane = eplane_from_world * - DVec4::new(v.x, v.y, v.z, 1.0); + let u_eplane = eplane_from_world * DVec4::new(u.x, u.y, u.z, 1.0); + let v_eplane = eplane_from_world * DVec4::new(v.x, v.y, v.z, 1.0); // Pick the starting angle in the circle's flat plane let u_ang = u_eplane.y.atan2(u_eplane.x); @@ -92,8 +105,9 @@ impl Curve { const N: usize = 64; let count = 4.max( - (N as f64 * (u_ang - v_ang).abs() / - (2.0 * std::f64::consts::PI)).round() as usize); + (N as f64 * (u_ang - v_ang).abs() / (2.0 * std::f64::consts::PI)).round() + as usize, + ); let mut out_world = vec![u]; // Walk around the circle, using the true positions for start diff --git a/triangulate/src/surface.rs b/triangulate/src/surface.rs index c98b1e6..c6d830d 100644 --- a/triangulate/src/surface.rs +++ b/triangulate/src/surface.rs @@ -1,10 +1,10 @@ -use std::f64::{EPSILON, consts::PI}; +use std::f64::{consts::PI, EPSILON}; +use glm::{DMat4, DVec2, DVec3, DVec4}; use nalgebra_glm as glm; -use glm::{DVec2, DVec3, DVec4, DMat4}; +use crate::{mesh::Vertex, Error}; use nurbs::{AbstractCurve, AbstractSurface, NDBSplineCurve, NDBSplineSurface, SampledSurface}; -use crate::{Error, mesh::Vertex}; #[derive(Debug, Clone)] pub enum ExtrusionCurve { @@ -22,11 +22,44 @@ pub enum ExtrusionCurve { }, } +fn unwrap_theta_in_place(pts: &mut [(f64, f64)]) { + if pts.len() < 2 { + return; + } + let two_pi = 2.0 * PI; + let mut prev = pts[0].0; + for p in &mut pts[1..] { + let mut t = p.0; + while t - prev > PI { + t -= two_pi; + } + while t - prev < -PI { + t += two_pi; + } + p.0 = t; + prev = t; + } +} + +fn closest_u_initial_guess_3d(samples: &[(f64, DVec3)], p: DVec3) -> f64 { + let mut best_u = 0.0; + let mut best_dist = std::f64::INFINITY; + for (u, pos) in samples { + let d = (*pos - p).norm(); + if d < best_dist { + best_dist = d; + best_u = *u; + } + } + best_u +} + fn curve_samples(curve: &NDBSplineCurve) -> Vec<(f64, DVec3)> where NDBSplineCurve: AbstractCurve, { - const NUM_SAMPLES_PER_KNOT: usize = 8; + // const NUM_SAMPLES_PER_KNOT: usize = 8; + const NUM_SAMPLES_PER_KNOT: usize = 3; let mut samples = Vec::new(); for i in 0..curve.knots.len() - 1 { if curve.knots[i] == curve.knots[i + 1] { @@ -76,7 +109,12 @@ fn closest_u_initial_guess(samples: &[(f64, DVec3)], p_perp: DVec3, dir_unit: DV best_u } -fn closest_u_newton(curve: &NDBSplineCurve, p_perp: DVec3, dir_unit: DVec3, u0: f64) -> f64 +fn closest_u_newton( + curve: &NDBSplineCurve, + p_perp: DVec3, + dir_unit: DVec3, + u0: f64, +) -> f64 where NDBSplineCurve: AbstractCurve, { @@ -112,6 +150,41 @@ where u_i } +fn closest_u_newton_3d(curve: &NDBSplineCurve, p: DVec3, u0: f64) -> f64 +where + NDBSplineCurve: AbstractCurve, +{ + let u_min = curve_min_u(curve); + let u_max = curve_max_u(curve); + let mut u_i = clamp_open_interval(u0, u_min, u_max); + + let eps1 = 0.01; + for _ in 0..64 { + let derivs = curve.derivs::<2>(u_i); + let c = derivs[0]; + let c_p = derivs[1]; + let c_pp = derivs[2]; + let r = c - p; + + if r.norm() <= eps1 { + return u_i; + } + + let denom = c_pp.dot(&r) + c_p.norm_squared(); + if denom.abs() <= std::f64::EPSILON { + return u_i; + } + + let delta = -c_p.dot(&r) / denom; + let u_next = clamp_open_interval(u_i + delta, u_min, u_max); + if ((u_next - u_i) * c_p.norm()).abs() <= eps1 { + return u_next; + } + u_i = u_next; + } + u_i +} + impl ExtrusionCurve { pub fn new_bspline(curve: nurbs::BSplineCurve) -> Self { let samples = curve_samples(&curve); @@ -154,12 +227,29 @@ impl ExtrusionCurve { let u0 = closest_u_initial_guess(samples, p_perp, dir_unit); closest_u_newton(curve, p_perp, dir_unit, u0) } - Self::Line { origin, dir_unit: line_dir } => { + Self::Line { + origin, + dir_unit: line_dir, + } => { // Choose u as signed distance along the line direction. (p - *origin).dot(line_dir) } } } + + pub fn closest_u(&self, p: DVec3) -> f64 { + match self { + Self::BSpline { curve, samples } => { + let u0 = closest_u_initial_guess_3d(samples, p); + closest_u_newton_3d(curve, p, u0) + } + Self::NURBS { curve, samples } => { + let u0 = closest_u_initial_guess_3d(samples, p); + closest_u_newton_3d(curve, p, u0) + } + Self::Line { origin, dir_unit } => (p - *origin).dot(dir_unit), + } + } } // Represents a surface in 3D space, with a function to project a 3D point @@ -188,8 +278,8 @@ pub enum Surface { NURBS(SampledSurface<4>), Sphere { location: DVec3, - mat: DMat4, // uv to world - mat_i: DMat4, // world to uv + mat: DMat4, // uv to world + mat_i: DMat4, // world to uv radius: f64, }, Torus { @@ -204,6 +294,13 @@ pub enum Surface { curve: ExtrusionCurve, dir_unit: DVec3, }, + Revolution { + curve: ExtrusionCurve, + axis_origin: DVec3, + axis_unit: DVec3, + x_ref: DVec3, + y_ref: DVec3, + }, } impl Surface { @@ -212,33 +309,46 @@ impl Surface { // mat and mat_i are built in prepare() mat: DMat4::identity(), mat_i: DMat4::identity(), - location, radius, + location, + radius, } - } pub fn new_linear_extrusion(curve: ExtrusionCurve, dir_unit: DVec3) -> Self { Surface::LinearExtrusion { curve, dir_unit } } + + pub fn new_revolution(curve: ExtrusionCurve, axis_origin: DVec3, axis_unit: DVec3) -> Self { + Surface::Revolution { + curve, + axis_origin, + axis_unit, + x_ref: DVec3::new(1.0, 0.0, 0.0), + y_ref: DVec3::new(0.0, 1.0, 0.0), + } + } pub fn new_cylinder(axis: DVec3, ref_direction: DVec3, location: DVec3, radius: f64) -> Self { let mat = Self::make_rigid_transform(axis, ref_direction, location); Surface::Cylinder { mat, mat_i: mat.try_inverse().expect("Could not invert"), - axis, radius, location, + axis, + radius, + location, z_min: 0.0, z_max: 0.0, } } - pub fn new_torus(location: DVec3, axis: DVec3, - major_radius: f64, minor_radius: f64) -> Self - { + pub fn new_torus(location: DVec3, axis: DVec3, major_radius: f64, minor_radius: f64) -> Self { Surface::Torus { // mat and mat_i are built in prepare() mat: DMat4::identity(), mat_i: DMat4::identity(), - location, axis, major_radius, minor_radius + location, + axis, + major_radius, + minor_radius, } } @@ -260,13 +370,18 @@ impl Surface { } } - pub fn make_affine_transform(z_world: DVec3, x_world: DVec3, y_world: DVec3, origin_world: DVec3) -> DMat4 { + pub fn make_affine_transform( + z_world: DVec3, + x_world: DVec3, + y_world: DVec3, + origin_world: DVec3, + ) -> DMat4 { let mut mat = DMat4::identity(); mat.set_column(0, &glm::vec3_to_vec4(&x_world)); mat.set_column(1, &glm::vec3_to_vec4(&y_world)); mat.set_column(2, &glm::vec3_to_vec4(&z_world)); mat.set_column(3, &glm::vec3_to_vec4(&origin_world)); - *mat.get_mut((3, 3)).unwrap() = 1.0; + *mat.get_mut((3, 3)).unwrap() = 1.0; mat } @@ -276,12 +391,13 @@ impl Surface { mat.set_column(1, &glm::vec3_to_vec4(&z_world.cross(&x_world))); mat.set_column(2, &glm::vec3_to_vec4(&z_world)); mat.set_column(3, &glm::vec3_to_vec4(&origin_world)); - *mat.get_mut((3, 3)).unwrap() = 1.0; + *mat.get_mut((3, 3)).unwrap() = 1.0; mat } fn surf_lower(p: DVec3, surf: &SampledSurface) -> Result - where NDBSplineSurface: AbstractSurface + where + NDBSplineSurface: AbstractSurface, { surf.uv_from_point(p).ok_or(Error::CouldNotLower) } @@ -292,15 +408,18 @@ impl Surface { fn lower(&self, p: DVec3) -> Result { let p_ = DVec4::new(p.x, p.y, p.z, 1.0); match self { - Surface::Plane { mat_i, .. } => { - Ok(glm::vec4_to_vec2(&(mat_i * p_))) - }, + Surface::Plane { mat_i, .. } => Ok(glm::vec4_to_vec2(&(mat_i * p_))), Surface::Cone { mat_i, .. } => { let xy = glm::vec4_to_vec2(&(mat_i * p_)); Ok(DVec2::new(-xy.x, xy.y)) - }, + } - Surface::Cylinder { mat_i, z_min, z_max, .. } => { + Surface::Cylinder { + mat_i, + z_min, + z_max, + .. + } => { let p = mat_i * p_; // We convert the Z coordinates to either add or subtract from // the radius, so that we maintain the right topology (instead @@ -311,8 +430,13 @@ impl Surface { let z = (p.z - z_min) / (z_max - z_min); let scale = 1.0 / (1.0 + z); Ok(DVec2::new(p.x * scale, p.y * scale)) - }, - Surface::Torus { mat_i, major_radius, minor_radius, .. } => { + } + Surface::Torus { + mat_i, + major_radius, + minor_radius, + .. + } => { let p = mat_i * p_; /* ^ Y @@ -332,18 +456,16 @@ impl Surface { // Rotate the point so that it's got Y = 0, so we can calculate // the minor angle let z = DVec3::new(0.0, major_angle.sin(), major_angle.cos()); - let new_mat = Self::make_rigid_transform( - z, DVec3::new(1.0, 0.0, 0.0), z * *major_radius); - let new_mat_i = new_mat.try_inverse() - .expect("Could not invert"); + let new_mat = + Self::make_rigid_transform(z, DVec3::new(1.0, 0.0, 0.0), z * *major_radius); + let new_mat_i = new_mat.try_inverse().expect("Could not invert"); let new_p = new_mat_i * DVec4::new(p.x, p.y, p.z, 1.0); let minor_angle = new_p.x.atan2(new_p.z); // Construct nested circles with a scale based on the ratio // of radiuses (to make an _attempt_ to match 3D distance) - let scale = 1.0 + (major_radius / minor_radius) * - (major_angle + PI) / (2.0 * PI); + let scale = 1.0 + (major_radius / minor_radius) * (major_angle + PI) / (2.0 * PI); let x = if *major_radius > 0.0 { -minor_angle.cos() @@ -351,7 +473,7 @@ impl Surface { minor_angle.cos() }; Ok(scale * DVec2::new(x, minor_angle.sin())) - }, + } Surface::BSpline(surf) => Self::surf_lower(p, surf), Surface::NURBS(surf) => Self::surf_lower(p, surf), Surface::Sphere { mat_i, radius, .. } => { @@ -367,19 +489,65 @@ impl Surface { } else { yz * angle / yz.norm() }) - }, + } Surface::LinearExtrusion { curve, dir_unit } => { let u = curve.closest_u_perp(p, *dir_unit); let c = curve.point(u); let v = (p - c).dot(dir_unit); Ok(DVec2::new(u, v)) - }, + } + Surface::Revolution { + curve, + axis_origin, + axis_unit, + x_ref, + y_ref, + } => { + // Compute theta in the plane perpendicular to the axis + let delta = p - *axis_origin; + let radial = delta - *axis_unit * delta.dot(axis_unit); + if radial.norm() <= EPSILON { + return Err(Error::CouldNotLower); + } + let x = radial.dot(x_ref); + let y = radial.dot(y_ref); + let theta = y.atan2(x); + + // Unrotate into the swept curve's plane, then solve for u + let delta0 = Self::rotate_about_axis(delta, *axis_unit, -theta); + let p0 = *axis_origin + delta0; + let u = curve.closest_u(p0); + Ok(DVec2::new(theta, u)) + } } } + fn pick_perp_basis(axis_unit: DVec3) -> (DVec3, DVec3) { + let helper = if axis_unit.x.abs() < 0.9 { + DVec3::new(1.0, 0.0, 0.0) + } else { + DVec3::new(0.0, 1.0, 0.0) + }; + let x_ref = axis_unit.cross(&helper).normalize(); + let y_ref = axis_unit.cross(&x_ref).normalize(); + (x_ref, y_ref) + } + + fn rotate_about_axis(v: DVec3, axis_unit: DVec3, angle: f64) -> DVec3 { + // Rodrigues' rotation formula + let c = angle.cos(); + let s = angle.sin(); + v * c + axis_unit.cross(&v) * s + axis_unit * axis_unit.dot(&v) * (1.0 - c) + } + fn prepare(&mut self, verts: &[Vertex]) { match self { - Surface::Cylinder { mat_i, z_min, z_max, .. } => { + Surface::Cylinder { + mat_i, + z_min, + z_max, + .. + } => { *z_min = std::f64::INFINITY; *z_max = -std::f64::INFINITY; for v in verts { @@ -391,37 +559,64 @@ impl Surface { *z_max = p.z; } } - }, - Surface::Sphere { mat, mat_i, location, .. } => { + } + Surface::Sphere { + mat, + mat_i, + location, + .. + } => { let ref_direction = (verts[0].pos - *location).normalize(); let d1 = (verts.last().unwrap().pos - *location).normalize(); let axis = ref_direction.cross(&d1).normalize(); - *mat = Self::make_rigid_transform( - axis, ref_direction, *location); - *mat_i = mat - .try_inverse() - .expect("Could not invert"); - }, - Surface::Torus { axis, mat, mat_i, location, .. } => { - let mean_dir = verts.iter() + *mat = Self::make_rigid_transform(axis, ref_direction, *location); + *mat_i = mat.try_inverse().expect("Could not invert"); + } + Surface::Torus { + axis, + mat, + mat_i, + location, + .. + } => { + let mean_dir = verts + .iter() .map(|v| v.pos - *location) .sum::() .normalize(); let mean_perp_dir = (mean_dir - *axis * mean_dir.dot(axis)).normalize(); - *mat = Self::make_rigid_transform( - mean_perp_dir, *axis, *location); - *mat_i = mat - .try_inverse() - .expect("Could not invert"); - }, + *mat = Self::make_rigid_transform(mean_perp_dir, *axis, *location); + *mat_i = mat.try_inverse().expect("Could not invert"); + } + Surface::Revolution { + axis_origin, + axis_unit, + x_ref, + y_ref, + .. + } => { + // Pick a reference direction from the average radial direction + let mut mean = DVec3::zeros(); + for v in verts { + let delta = v.pos - *axis_origin; + let radial = delta - *axis_unit * delta.dot(axis_unit); + mean += radial; + } + if mean.norm() > EPSILON { + *x_ref = mean.normalize(); + *y_ref = axis_unit.cross(x_ref).normalize(); + } else { + let (x, y) = Self::pick_perp_basis(*axis_unit); + *x_ref = x; + *y_ref = y; + } + } _ => (), } } - pub fn lower_verts(&mut self, verts: &mut [Vertex]) - -> Result, Error> - { + pub fn lower_verts(&mut self, verts: &mut [Vertex]) -> Result, Error> { self.prepare(verts); let mut pts = Vec::with_capacity(verts.len()); for v in verts { @@ -448,6 +643,42 @@ impl Surface { Ok(pts) } + pub fn lower_verts_with_contours( + &mut self, + verts: &mut [Vertex], + contour_starts: &[usize], + ) -> Result, Error> { + // Default path for non-revolution surfaces + if !matches!(self, Surface::Revolution { .. }) { + return self.lower_verts(verts); + } + + self.prepare(verts); + let mut pts = Vec::with_capacity(verts.len()); + for v in verts.iter_mut() { + let proj = self.lower(v.pos)?; + v.norm = self.normal(v.pos, proj); + pts.push((proj.x, proj.y)); + } + + // Unwrap theta per contour to avoid seam collapse. + let mut starts = contour_starts.to_vec(); + if starts.first().copied() != Some(0) { + starts.insert(0, 0); + } + starts.retain(|&s| s < pts.len()); + if starts.is_empty() { + starts.push(0); + } + starts.push(pts.len()); + + for w in starts.windows(2) { + let (start, end) = (w[0], w[1]); + unwrap_theta_in_place(&mut pts[start..end]); + } + Ok(pts) + } + pub fn raise(&self, uv: DVec2) -> Option { match self { Surface::Sphere { mat, radius, .. } => { @@ -458,36 +689,53 @@ impl Surface { let x = angle.cos(); // Calculate pre-transformed position - let pos = (*radius) * if uv.norm() < EPSILON { - DVec3::new(x, 0.0, 0.0) - } else { - let yz = uv.normalize() * angle.sin(); - DVec3::new(x, yz.x, yz.y) - }; + let pos = (*radius) + * if uv.norm() < EPSILON { + DVec3::new(x, 0.0, 0.0) + } else { + let yz = uv.normalize() * angle.sin(); + DVec3::new(x, yz.x, yz.y) + }; // Transform into world space - let pos = (mat * DVec4::new(pos.x, pos.y, pos.z, 1.0)) - .xyz(); + let pos = (mat * DVec4::new(pos.x, pos.y, pos.z, 1.0)).xyz(); Some(pos) - }, + } Surface::BSpline(s) => Some(s.surf.point(uv)), Surface::NURBS(s) => Some(s.surf.point(uv)), - Surface::Torus { mat, minor_radius, major_radius, .. } => { + Surface::Torus { + mat, + minor_radius, + major_radius, + .. + } => { let mut uv = uv; if *major_radius > 0.0 { uv.x *= -1.0; } let minor_angle = uv.y.atan2(uv.x); - let major_angle = (uv.norm() - 1.0) / - (major_radius / minor_radius) * 2.0 * PI - PI; + let major_angle = (uv.norm() - 1.0) / (major_radius / minor_radius) * 2.0 * PI - PI; let new_p = DVec3::new(minor_angle.sin(), 0.0, minor_angle.cos()) * *minor_radius; let z = DVec3::new(0.0, major_angle.sin(), major_angle.cos()); - let new_mat = Self::make_rigid_transform( - z, DVec3::new(1.0, 0.0, 0.0), z * *major_radius); + let new_mat = + Self::make_rigid_transform(z, DVec3::new(1.0, 0.0, 0.0), z * *major_radius); let p = new_mat * DVec4::new(new_p.x, new_p.y, new_p.z, 1.0); Some((mat * p).xyz()) - }, + } + Surface::Revolution { + curve, + axis_origin, + axis_unit, + .. + } => { + let theta = uv.x; + let u = uv.y; + let c0 = curve.point(u); + let delta0 = c0 - *axis_origin; + let delta = Self::rotate_about_axis(delta0, *axis_unit, theta); + Some(*axis_origin + delta) + } _ => unimplemented!(), } } @@ -504,13 +752,12 @@ impl Surface { (xmin, xmax, ymin, ymax) } - pub fn add_steiner_points(&self, pts: &mut Vec<(f64, f64)>, - verts: &mut Vec) - { + pub fn add_steiner_points(&self, pts: &mut Vec<(f64, f64)>, verts: &mut Vec) { let (xmin, xmax, ymin, ymax) = Self::bbox(&pts); let num_pts = match self { - Surface::Sphere { .. } => 6, + Surface::Sphere { .. } => 6, Surface::Torus { .. } => 32, + Surface::Revolution { .. } => 16, _ => 0, }; @@ -535,7 +782,8 @@ impl Surface { } fn surf_normal(uv: DVec2, surf: &SampledSurface) -> DVec3 - where NDBSplineSurface: AbstractSurface + where + NDBSplineSurface: AbstractSurface, { // Calculate first order derivs, then cross them to get normal let derivs = surf.surf.derivs::<1>(uv); @@ -547,7 +795,9 @@ impl Surface { pub fn normal(&self, p: DVec3, uv: DVec2) -> DVec3 { match self { Surface::Plane { normal, .. } => *normal, - Surface::Cone { mat, mat_i, angle, .. } => { + Surface::Cone { + mat, mat_i, angle, .. + } => { // Project into CONE SPACE let pos = mat_i * DVec4::new(p.x, p.y, p.z, 1.0); let xy = if pos.xy().norm() > std::f64::EPSILON { @@ -555,8 +805,7 @@ impl Surface { } else { return DVec3::zeros(); }; - let normal = DVec4::new(xy.x * angle.cos(), - xy.y * angle.cos(), -angle.sin(), 0.0); + let normal = DVec4::new(xy.x * angle.cos(), xy.y * angle.cos(), -angle.sin(), 0.0); // Deproject back into world space (mat * normal).xyz() } @@ -569,10 +818,15 @@ impl Surface { // (same hack as below) let norm = DVec3::new(proj.x, proj.y, 0.0).normalize(); (mat * norm.to_homogeneous()).xyz() - }, + } Surface::BSpline(surf) => Self::surf_normal(uv, surf), Surface::NURBS(surf) => Self::surf_normal(uv, surf), - Surface::Torus { mat, mat_i, major_radius, .. } => { + Surface::Torus { + mat, + mat_i, + major_radius, + .. + } => { let p = (*mat_i * DVec4::new(p.x, p.y, p.z, 1.0)).xyz(); let major_angle = p.y.atan2(p.z); @@ -580,7 +834,7 @@ impl Surface { let norm = (p - z).normalize(); (mat * norm.to_homogeneous()).xyz() - }, + } Surface::LinearExtrusion { curve, dir_unit } => { let du = curve.deriv1(uv.x); let n = du.cross(dir_unit); @@ -590,7 +844,34 @@ impl Surface { } else { n.normalize() } - }, + } + Surface::Revolution { + curve, + axis_origin, + axis_unit, + .. + } => { + let theta = uv.x; + let u = uv.y; + + // Tangent in u direction is swept tangent rotated by theta + let du0 = curve.deriv1(u); + let du = Self::rotate_about_axis(du0, *axis_unit, theta); + + // Radial direction from axis to point determines theta tangent + let delta = p - *axis_origin; + let radial = delta - *axis_unit * delta.dot(axis_unit); + if radial.norm() <= EPSILON { + return DVec3::zeros(); + } + let dtheta = axis_unit.cross(&radial); + let n = du.cross(&dtheta); + if n.norm() <= EPSILON { + DVec3::zeros() + } else { + n.normalize() + } + } } } } diff --git a/triangulate/src/triangulate.rs b/triangulate/src/triangulate.rs index 9c65004..a1f462b 100644 --- a/triangulate/src/triangulate.rs +++ b/triangulate/src/triangulate.rs @@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet}; use std::convert::TryInto; use glm::{DMat4, DVec3, DVec4, U32Vec3}; -use log::{error, info, warn}; +use log::{debug, error, info, warn}; use nalgebra_glm as glm; #[cfg(feature = "rayon")] @@ -257,9 +257,38 @@ pub fn triangulate(s: &StepFile) -> (Mesh, Stats) { info!("num_faces: {}", stats.num_faces); info!("num_errors: {}", stats.num_errors); info!("num_panics: {}", stats.num_panics); + log_mesh_vertices_and_triangles(&mesh); (mesh, stats) } +fn log_mesh_vertices_and_triangles(mesh: &Mesh) { + if !log::log_enabled!(log::Level::Info) { + return; + } + + info!("mesh.verts (index: [x, y, z]):"); + for (i, v) in mesh.verts.iter().enumerate() { + info!( + "{:03}: [{:7.2}, {:7.2}, {:7.2}]", + i + 1, + v.pos.x, + v.pos.y, + v.pos.z + ); + } + + info!("mesh.triangles (tri_index: [v0, v1, v2]):"); + for (i, t) in mesh.triangles.iter().enumerate() { + info!( + "{:03}: [{}, {}, {}]", + i + 1, + t.verts.x, + t.verts.y, + t.verts.z + ); + } +} + fn item_defined_transformation(s: &StepFile, t: Id) -> DMat4 { let i = s.entity(t).expect("Could not get ItemDefinedTransform"); @@ -339,6 +368,40 @@ fn axis2_placement_3d(s: &StepFile, t: Id) -> (DVec3, DVec3, (location, axis, ref_direction) } +fn axis1_placement(s: &StepFile, t: Id) -> (DVec3, DVec3) { + let a = s.entity(t).expect("Could not get Axis1Placement"); + let location = cartesian_point(s, a.location); + let axis = a + .axis + .map(|d| direction(s, d)) + .unwrap_or(DVec3::new(0.0, 0.0, 1.0)); + debug!( + "Axis1Placement id={:?} location={:?} axis(raw)={:?}", + t, location, axis + ); + (location, axis) +} + +fn debug_print_vec3(prefix: &str, pts: &[DVec3]) { + const MAX: usize = 6; + let n = pts.len(); + if n == 0 { + debug!("{}: []", prefix); + return; + } + if n <= MAX { + debug!("{} (n={}): {:?}", prefix, n, pts); + return; + } + debug!( + "{} (n={}): head={:?} tail={:?}", + prefix, + n, + &pts[..3], + &pts[n - 3..] + ); +} + fn shell(s: &StepFile, c: Shell, mesh: &mut Mesh, stats: &mut Stats) { match &s[c] { Entity::ClosedShell(_) => closed_shell(s, c.cast(), mesh, stats), @@ -379,6 +442,14 @@ fn advanced_face( // Grab the surface, returning early if it's unimplemented let mut surf = get_surface(s, face.face_geometry)?; + let is_revolution = matches!(surf, Surface::Revolution { .. }); + if is_revolution { + debug!( + "Triangulating AdvancedFace={:?} face_geometry={:?} (Revolution)", + f, face.face_geometry + ); + } + // This is the starting point at which we insert new vertices let offset = mesh.verts.len(); @@ -387,6 +458,14 @@ fn advanced_face( let mut edges = Vec::new(); let v_start = mesh.verts.len(); let mut num_pts = 0; + let mut contour_starts: Vec = Vec::new(); + + let mut bbox_min = DVec3::new(std::f64::INFINITY, std::f64::INFINITY, std::f64::INFINITY); + let mut bbox_max = DVec3::new( + -std::f64::INFINITY, + -std::f64::INFINITY, + -std::f64::INFINITY, + ); for b in &face.bounds { let bound_contours = face_bound(s, *b)?; @@ -408,9 +487,20 @@ fn advanced_face( // Default for lists of contour points _ => { + contour_starts.push(num_pts); // Record the initial point to close the loop let start = num_pts; + for pt in bound_contours { + if is_revolution { + bbox_min.x = bbox_min.x.min(pt.x); + bbox_min.y = bbox_min.y.min(pt.y); + bbox_min.z = bbox_min.z.min(pt.z); + bbox_max.x = bbox_max.x.max(pt.x); + bbox_max.y = bbox_max.y.max(pt.y); + bbox_max.z = bbox_max.z.max(pt.z); + } + // The contour marches forward! edges.push((num_pts, num_pts + 1)); @@ -422,6 +512,7 @@ fn advanced_face( }); num_pts += 1; } + // The last point is a duplicate, because it closes the // contours, so we skip it here and reattach the contour to // the start. @@ -435,12 +526,23 @@ fn advanced_face( } } + if is_revolution { + debug!( + "Revolution contour bbox: min={:?} max={:?} num_pts={} num_edges={} num_contours={}", + bbox_min, + bbox_max, + num_pts, + edges.len(), + contour_starts.len() + ); + } + // We inject Stiner points based on the surface type to improve curvature, // e.g. for spherical sections. However, we don't want triagulation to // _fail_ due to these points, so if that happens, we nuke the point (by // assigning it to the first point in the list, which causes it to get // deduplicated), then retry. - let mut pts = surf.lower_verts(&mut mesh.verts[v_start..])?; + let mut pts = surf.lower_verts_with_contours(&mut mesh.verts[v_start..], &contour_starts)?; let bonus_points = pts.len(); surf.add_steiner_points(&mut pts, &mut mesh.verts); let result = std::panic::catch_unwind(|| { @@ -571,6 +673,16 @@ fn get_surface(s: &StepFile, surf: ap214::Surface) -> Result { let curve = extrusion_curve(s, e.swept_curve)?; Ok(Surface::new_linear_extrusion(curve, dir_unit)) } + Entity::SurfaceOfRevolution(r) => { + let (axis_origin, axis) = axis1_placement(s, r.axis_position); + if axis.norm() <= std::f64::EPSILON { + return Err(Error::CouldNotLower); + } + let axis_unit = axis.normalize(); + + let curve = extrusion_curve(s, r.swept_curve)?; + Ok(Surface::new_revolution(curve, axis_origin, axis_unit)) + } Entity::BSplineSurfaceWithKnots(b) => { // TODO: make KnotVector::from_multiplicies accept iterators? let u_knots: Vec = b.u_knots.iter().map(|k| k.0).collect(); @@ -684,6 +796,25 @@ fn extrusion_curve(s: &StepFile, curve_id: ap214::Curve) -> Result>() + ); + debug!( + " multiplicities={:?}", + c.knot_multiplicities + .iter() + .map(|&k| k as i64) + .collect::>() + ); + debug_print_vec3(" control_points", &control_points_list); let knots: Vec = c.knots.iter().map(|k| k.0).collect(); let multiplicities: Vec = c .knot_multiplicities @@ -729,17 +860,37 @@ fn extrusion_curve(s: &StepFile, curve_id: ap214::Curve) -> Result = + control_points_1d(s, &bspline.control_points_list) + .into_iter() + .zip(rational.weights_data.iter()) + .map(|(p, w)| DVec4::new(p.x * w, p.y * w, p.z * w, *w)) + .collect(); + debug!( + " weighted_control_points(n={}) head={:?}", + control_points_list.len(), + &control_points_list[..control_points_list.len().min(6)] + ); let curve = nurbs::NURBSCurve::new( bspline.closed_curve.0.unwrap() == false, @@ -757,6 +908,13 @@ fn extrusion_curve(s: &StepFile, curve_id: ap214::Curve) -> Result { @@ -780,8 +938,13 @@ fn face_bound(s: &StepFile, b: FaceBound) -> Result, Error> { Entity::FaceOuterBound(b) => (b.bound, b.orientation), e => panic!("Could not get bound from {:?} at {:?}", e, b), }; + debug!( + "face_bound={:?} bound={:?} orientation={}", + b, bound, orientation + ); match &s[bound] { Entity::EdgeLoop(e) => { + debug!(" EdgeLoop id={:?} edges={} ", bound, e.edge_list.len()); let mut d = edge_loop(s, &e.edge_list)?; if !orientation { d.reverse() @@ -948,19 +1111,56 @@ mod tests { use super::triangulate; use step::step_file::StepFile; + fn assert_mesh_is_sane(mesh: &crate::mesh::Mesh) { + assert!(!mesh.verts.is_empty()); + assert!(!mesh.triangles.is_empty()); + + for v in &mesh.verts { + assert!(v.pos.x.is_finite()); + assert!(v.pos.y.is_finite()); + assert!(v.pos.z.is_finite()); + assert!(v.norm.x.is_finite()); + assert!(v.norm.y.is_finite()); + assert!(v.norm.z.is_finite()); + } + for t in &mesh.triangles { + for idx in t.verts.iter() { + assert!((*idx as usize) < mesh.verts.len()); + } + } + } + #[test] fn triangulates_surface_of_linear_extrusion_example() { let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("..") .join("examples") - .join("test_spline_extrude_step.step"); + .join("spline_linear_extrusion.step"); + let data = + std::fs::read(&path).unwrap_or_else(|e| panic!("Could not read {:?}: {:?}", path, e)); + let flat = StepFile::strip_flatten(&data); + let step = StepFile::parse(&flat); + + let (mesh, stats) = triangulate(&step); + assert_eq!(stats.num_errors, 0); + assert_eq!(stats.num_panics, 0); + assert_mesh_is_sane(&mesh); + } + + #[test] + fn triangulates_surface_of_revolution_example() { + let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("..") + .join("examples") + .join("spline_revolution.step"); let data = std::fs::read(&path).unwrap_or_else(|e| panic!("Could not read {:?}: {:?}", path, e)); let flat = StepFile::strip_flatten(&data); let step = StepFile::parse(&flat); - let (mesh, _stats) = triangulate(&step); - assert!(mesh.verts.len() > 0); - assert!(mesh.triangles.len() > 0); + let (mesh, stats) = triangulate(&step); + assert_eq!(stats.num_errors, 0); + assert_eq!(stats.num_panics, 0); + assert_mesh_is_sane(&mesh); } } From 2ec329815d729e2b4b94c72dc85c1bfc562eed50 Mon Sep 17 00:00:00 2001 From: Evan Jones Date: Mon, 15 Dec 2025 15:36:43 -0600 Subject: [PATCH 09/11] example file is "spline_revolve.step", not "spline_revolution.step" --- triangulate/src/triangulate.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/triangulate/src/triangulate.rs b/triangulate/src/triangulate.rs index a1f462b..1a57525 100644 --- a/triangulate/src/triangulate.rs +++ b/triangulate/src/triangulate.rs @@ -1152,7 +1152,7 @@ mod tests { let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("..") .join("examples") - .join("spline_revolution.step"); + .join("spline_revolve.step"); let data = std::fs::read(&path).unwrap_or_else(|e| panic!("Could not read {:?}: {:?}", path, e)); let flat = StepFile::strip_flatten(&data); From 6ce56a2ca0811ad0bb8bc2ff932b2453ec1f5bf9 Mon Sep 17 00:00:00 2001 From: Evan Jones Date: Mon, 15 Dec 2025 15:37:52 -0600 Subject: [PATCH 10/11] commented out unused "use" statement --- nurbs/src/knot_vector.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nurbs/src/knot_vector.rs b/nurbs/src/knot_vector.rs index 650a6d6..88ad7d8 100644 --- a/nurbs/src/knot_vector.rs +++ b/nurbs/src/knot_vector.rs @@ -18,7 +18,9 @@ impl KnotVector { /// Constructs a new knot vector of over pub fn from_multiplicities(p: usize, knots: &[f64], multiplicities: &[usize]) -> Self { assert!(knots.len() == multiplicities.len()); - let U = knots.iter().zip(multiplicities.iter()) + let U = knots + .iter() + .zip(multiplicities.iter()) .flat_map(|(k, m)| std::iter::repeat(*k).take(*m)) .collect(); Self { U, p } @@ -135,9 +137,7 @@ impl KnotVector { let mut s2 = 1; a[0][0] = 1.0; for k in 1..=n { - let aus = |i: i32| -> usize { - i.try_into().expect("Could not convert to usize") - }; + let aus = |i: i32| -> usize { i.try_into().expect("Could not convert to usize") }; let mut d = 0.0; let rk = (r as i32) - (k as i32); let pk = (self.p as i32) - (k as i32); @@ -187,8 +187,8 @@ impl std::ops::Index for KnotVector { #[cfg(test)] mod tests { - use super::*; /* + use super::*; #[test] fn test_find_span() { let k = KnotVector { From a2dc96b338f588189a3dc186c816c78c2f91684d Mon Sep 17 00:00:00 2001 From: Evan Jones Date: Mon, 15 Dec 2025 16:11:43 -0600 Subject: [PATCH 11/11] Added CI build/test workflow --- .github/workflows/ci.yml | 28 ++++++++++++++++++++++++++++ rust-toolchain.toml | 3 +++ 2 files changed, 31 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 rust-toolchain.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c5863d4 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,28 @@ +name: CI + +on: + push: + pull_request: + +jobs: + test: + name: Build and test (ubuntu-latest) + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + toolchain: 1.92.0 + + - name: Rust cache + uses: Swatinem/rust-cache@v2 + + - name: Build + run: cargo build --workspace --all-features --exclude gui + + - name: Test + run: cargo test --workspace --all-features --exclude gui diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..4124759 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "1.92.0" +profile = "minimal"