Compare commits
378 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d75a61d775 | |||
| 24db4c4ae4 | |||
| edf63d4a1b | |||
| b5b0922e7f | |||
| ff390f772d | |||
| d69f0bba2a | |||
| 33c14fc14c | |||
| 536e58aacc | |||
| 377829adae | |||
| cc9639b179 | |||
| b733ce5f29 | |||
| 1c8b2b82d7 | |||
| 11cd2dc1cb | |||
| 3789898ea2 | |||
| 0b3318f9e1 | |||
| 7589ad5f05 | |||
| 6df32262bd | |||
| 001ca1c287 | |||
| 59e459559b | |||
| d305fe59cb | |||
| f163b2822e | |||
| 630bb38b8b | |||
| bacff2da34 | |||
| 129d5285ed | |||
| 5d07fe0ea0 | |||
| 062e3c2838 | |||
| f4cc4bc77e | |||
| d395f03219 | |||
| 56ed399d70 | |||
| 7e0766fc7f | |||
| 0df343ebe1 | |||
| 3cad57ceb3 | |||
| 11937ad7c9 | |||
| 8210fde69a | |||
| fa6f90621e | |||
| f6f1852664 | |||
| b3f4190614 | |||
| 2fc0024cd2 | |||
| 300a5627fd | |||
| 22f029fe33 | |||
| c728266c88 | |||
| 7f2d57cdad | |||
| 81a22bd4cc | |||
| f64da099ab | |||
| f4ba200640 | |||
| 4e84766be9 | |||
| 73a95aded0 | |||
| 404600a839 | |||
| 93d517eab7 | |||
| 4110d90107 | |||
| 2d134bf7e1 | |||
| 686d0fd31c | |||
| b06878bbe1 | |||
| 64114167a8 | |||
| 0c7cd75353 | |||
| 3c5679ad2a | |||
| b043623021 | |||
| bd0997913a | |||
| ac0ab3904c | |||
| 5c9d672a2b | |||
| a79f0fd035 | |||
| 18d6653282 | |||
| d58a656c8a | |||
| 720d049145 | |||
| 129688a646 | |||
| fa66258b23 | |||
| 9f0c572993 | |||
| 158baff5b0 | |||
| 7a29d27e46 | |||
| 82e3d37dd1 | |||
| 9695ec8c51 | |||
| 15fa319433 | |||
| 793bbb9cd3 | |||
| 3291010d43 | |||
| 14732ce174 | |||
| ba655988b0 | |||
| 96822c4e66 | |||
| 08356de1ad | |||
| 375f5071ae | |||
| 5900a3c178 | |||
| d7bf324029 | |||
| c784fbf462 | |||
| 26f1802787 | |||
| a96e0f8c8e | |||
| 9f36c9cfe5 | |||
| 4959f277e8 | |||
| ea08b83f7a | |||
| b24dc685fa | |||
| 7cad14fe25 | |||
| 4d552b6834 | |||
| 378a91995e | |||
| 53b66dd26b | |||
| 9dd04c0881 | |||
| 474c18c29f | |||
| b676da4ac4 | |||
| fe964f4c8e | |||
| 1fea4cc01b | |||
| c3d716f6fd | |||
| 236cca2663 | |||
| f20064c9f0 | |||
| 6f1b22d13a | |||
| 23f95dd38d | |||
| 9eb767a4f6 | |||
| 358680cb9a | |||
| 87ae033f61 | |||
| e5a09155a2 | |||
| 988599fbb5 | |||
| baa5f54edb | |||
| d580fd0bc1 | |||
| 2352306269 | |||
| f0bafee076 | |||
| ef19bfa69d | |||
| 45601f1a2f | |||
| adf3298baf | |||
| 8f5e5f675b | |||
| 9217e67a9d | |||
| fe68607a52 | |||
| 88d15a5c9c | |||
| 3072047129 | |||
| 316379b04c | |||
| b056d98645 | |||
| 7329155d7a | |||
| c9a5103da7 | |||
| dbdf4892db | |||
| 3aa993cecb | |||
| 327f05adca | |||
| ec0d4bf1e3 | |||
| 73afa28026 | |||
| f0c7169be8 | |||
| ee10e9d3fc | |||
| 93912d6712 | |||
| f81fc97a91 | |||
| cc23c147be | |||
| 02892a96fe | |||
| ba143fe44b | |||
| 3a71a10cf1 | |||
| bafd130ece | |||
| f79ae7e058 | |||
| 77170bf9f3 | |||
| 69167dc206 | |||
| 19a5ccfb74 | |||
| b3c6a75387 | |||
| c6556fa2ef | |||
| 3bc40fb0bb | |||
| 4370af5376 | |||
| 912971d8de | |||
| 9318d85cc8 | |||
| deddb216e5 | |||
| 465d69ba00 | |||
| 14d959834f | |||
| 465f635142 | |||
| ed5bdc91ed | |||
| 0023d2ca2a | |||
| ab487991b6 | |||
| 142eefdf39 | |||
| ebccf6ff79 | |||
| 11fe8e8cce | |||
| 1fe1a54dcf | |||
| a1b9030dba | |||
| eec400d0cf | |||
| 991b31265f | |||
| 675d896e0d | |||
| defcee165a | |||
| 722df50d34 | |||
| 431bb459fc | |||
| a393f7ca8b | |||
| 650fad3b22 | |||
| 81007e0ecf | |||
| d29450f0a9 | |||
| 1e5bf903a2 | |||
| 878edf7c8f | |||
| ab0ff7b792 | |||
| b5999e3361 | |||
| d904c68c10 | |||
| daec64cf8e | |||
| 1844386235 | |||
| 05f90eae5c | |||
| 7bbc568022 | |||
| 7a1edbbf99 | |||
| 058e3cb946 | |||
| 08dd45cc32 | |||
| d4173c1449 | |||
| 4b7c6e4641 | |||
| 2477423ba7 | |||
| 71ec9a6d11 | |||
| 140b39f605 | |||
| 1a0522cef3 | |||
| b343d9bd0d | |||
| e32ec52071 | |||
| 2bbe2a8b93 | |||
| 604e141468 | |||
| 39dcc5da0b | |||
| af35edf0a2 | |||
| fbc7400524 | |||
| 8a71b19f78 | |||
| 105395e9fb | |||
| 715bb7274a | |||
| 43445bc4ff | |||
| 3bff2d5d57 | |||
| 4784e7d432 | |||
| 378149a0af | |||
| 0240e3b49e | |||
| d65993389b | |||
| 4cc78c4bc9 | |||
| 8a148b892f | |||
| fba0c46ed8 | |||
| 91c4090037 | |||
| c2962a668b | |||
| 27d3028880 | |||
| 93b3c37584 | |||
| f5a8d1a738 | |||
| e10cc38ca7 | |||
| b6d2d01068 | |||
| cca8828565 | |||
| 3c783b3cac | |||
| 8f8be7482a | |||
| 3b859ba398 | |||
| 4c52072e0a | |||
| 1241589d18 | |||
| 1a9d28e660 | |||
| e14913b8c3 | |||
| 0d7805d060 | |||
| 14359333b4 | |||
| 75d88c5f39 | |||
| 9a3eddf6d5 | |||
| f541525a80 | |||
| c3e8a95bf1 | |||
| c2d0e1d0d4 | |||
| f0d83d8d43 | |||
| ba14a8c87f | |||
| 065a2318d3 | |||
| 40d025bb57 | |||
| ddeac27f04 | |||
| d73bf365c2 | |||
| 198f1a2564 | |||
| 694e9e1915 | |||
| 0f29aacf24 | |||
| a28cf6d908 | |||
| 2cb5d6e410 | |||
| 59628d5241 | |||
| bbe23f6996 | |||
| df5b151c90 | |||
| ccb4f54550 | |||
| 30a3533956 | |||
| e07bfc18d5 | |||
| 2ad14ca00b | |||
| 450dbcce81 | |||
| 296a00e2eb | |||
| f085a3fb5a | |||
| 56c4109210 | |||
| 92bcb51bf2 | |||
| ce3d610ac5 | |||
| 1be044e442 | |||
| cc7d274f43 | |||
| 0b1de566df | |||
| 5e0ea324c3 | |||
| b9180027c5 | |||
| 77a40a3af5 | |||
| 22f4945d71 | |||
| c0f8949705 | |||
| 7883cd9d32 | |||
| b7be9b6fb1 | |||
| 2b46792a82 | |||
| 4c3e572bfe | |||
| 0f116ad847 | |||
| 16b1b904af | |||
| 9916048921 | |||
| 9bea79b3b2 | |||
| af1d7dfaf3 | |||
| 03a5706680 | |||
| 42882cb71b | |||
| b14c8db8d3 | |||
| 2fec0ea791 | |||
| dc4da49078 | |||
| ea5ed874a3 | |||
| 70729d5ddd | |||
| e443840acf | |||
| d269ac73dd | |||
| 148a11f83e | |||
| 66b3c5e60c | |||
| cfc4018bd3 | |||
| 4b74ca8607 | |||
| 1c64a975c0 | |||
| 78faa71592 | |||
| 6f5f044095 | |||
| 0b240f0752 | |||
| 6d54be55db | |||
| 7148aa0a6f | |||
| f8e4682670 | |||
| 9da1d791cc | |||
| 77ffcead69 | |||
| a9a9b6b525 | |||
| adcf81b5c8 | |||
| fc46610b5b | |||
| 69fceb4d09 | |||
| 9671745fdb | |||
| 787ad92ff2 | |||
| e79716c5f0 | |||
| 1f92335d6d | |||
| 02f75c717c | |||
| ce3e591a47 | |||
| 059253ff39 | |||
| 957a284332 | |||
| 5c7a96b960 | |||
| 58911632e3 | |||
| 9e2f5d57de | |||
| 64267d7697 | |||
| ee94ac2bf9 | |||
| 5f8379806d | |||
| 580521f3b0 | |||
| 18ad5f5c11 | |||
| fcf4e223f7 | |||
| a16bc4eae0 | |||
| a15390ab0d | |||
| a2f0270a6e | |||
| 04446b9e79 | |||
| f408980a98 | |||
| b70a0c6fe3 | |||
| 7f50e9a09a | |||
| b52d8e0023 | |||
| e97597596a | |||
| 4536ecee87 | |||
| 72b7123f7b | |||
| 72fe3d1384 | |||
| 481a095ed3 | |||
| e3a5af037e | |||
| fe27951d96 | |||
| baf5ab0b14 | |||
| 057aef10c0 | |||
| c35ce07337 | |||
| e088e139af | |||
| dc62aa22b9 | |||
| cd67efdfff | |||
| f0124a76c1 | |||
| 5d4e14e802 | |||
| a88f860c5e | |||
| 1112ec39ab | |||
| 3ec1007b7b | |||
| 82d51af262 | |||
| e83c0125ba | |||
| b23e91651c | |||
| db88dbe6d0 | |||
| da792173ff | |||
| 51f0c26838 | |||
| cb14c43381 | |||
| ea68a245b8 | |||
| c5df445483 | |||
| adadaa4d91 | |||
| 6e46fd2f8a | |||
| 5df0c4401e | |||
| 6778b7086c | |||
| 2dcf89f6fe | |||
| 3249265560 | |||
| 200b84f49a | |||
| 1268a08baf | |||
| 46c3f9d1ac | |||
| 29e7e84f4e | |||
| b2883c91e4 | |||
| 82b7ca1a26 | |||
| b56eac2f50 | |||
| f26f79c3c8 | |||
| a0b723b2cb | |||
| 1b472be6b7 | |||
| 662984268b | |||
| 1d681f9166 | |||
| cfe5f330c9 | |||
| 17e6780357 | |||
| 98132d2811 | |||
| d9d1865504 | |||
| 405f3f4ee4 | |||
| ca350002ba | |||
| 4ea9e4b9aa | |||
| 2269182922 | |||
| d7dbd3349f | |||
| ebec2c5bd2 | |||
| a74ead9bd7 | |||
| f46aa4f50b | |||
| afde379cda |
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"csharpier": {
|
||||
"version": "0.30.6",
|
||||
"commands": [
|
||||
"dotnet-csharpier"
|
||||
],
|
||||
"rollForward": false
|
||||
},
|
||||
"gitversion.tool": {
|
||||
"version": "6.1.0",
|
||||
"commands": [
|
||||
"dotnet-gitversion"
|
||||
],
|
||||
"rollForward": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
printWidth: 120
|
||||
useTabs: false
|
||||
tabWidth: 2
|
||||
preprocessorSymbolSets:
|
||||
- ""
|
||||
- "DEBUG"
|
||||
- "DEBUG,CODE_STYLE"
|
||||
+327
@@ -0,0 +1,327 @@
|
||||
root = true
|
||||
# Don't use tabs for indentation.
|
||||
[*]
|
||||
indent_style = space
|
||||
|
||||
# Microsoft .NET properties
|
||||
csharp_using_directive_placement = outside_namespace:silent
|
||||
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none
|
||||
dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:none
|
||||
dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none
|
||||
|
||||
|
||||
# Standard properties
|
||||
insert_final_newline = true
|
||||
|
||||
# (Please don't specify an indent_size here; that has too many unintended consequences.)
|
||||
|
||||
# Code files
|
||||
[*.{cs,csx,vb,vbx}]
|
||||
indent_size = 2
|
||||
charset = utf-8
|
||||
|
||||
# Xml project files
|
||||
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
|
||||
indent_size = 2
|
||||
space_after_last_pi_attribute = false
|
||||
# Xml config files
|
||||
[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
|
||||
indent_size = 2
|
||||
space_after_last_pi_attribute = false
|
||||
|
||||
# JSON files
|
||||
[*.json]
|
||||
indent_size = 2
|
||||
|
||||
# Dotnet code style settings:
|
||||
[*.{cs,vb}]
|
||||
# Sort using and Import directives with System.* appearing first
|
||||
dotnet_sort_system_directives_first = true
|
||||
dotnet_separate_import_directive_groups = false
|
||||
|
||||
# Avoid "this." and "Me." if not necessary
|
||||
dotnet_style_qualification_for_field = false:suggestion
|
||||
dotnet_style_qualification_for_property = false:suggestion
|
||||
dotnet_style_qualification_for_method = false:suggestion
|
||||
dotnet_style_qualification_for_event = false:suggestion
|
||||
|
||||
# Use language keywords instead of framework type names for type references
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||
# Parentheses preferences
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:silent
|
||||
dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:silent
|
||||
dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:silent
|
||||
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
|
||||
|
||||
# Modifier preferences
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
|
||||
dotnet_style_readonly_field = true:suggestion
|
||||
|
||||
# Expression-level preferences
|
||||
dotnet_style_object_initializer = true:suggestion
|
||||
dotnet_style_collection_initializer = true:suggestion
|
||||
dotnet_style_coalesce_expression = true:suggestion
|
||||
dotnet_style_null_propagation = true:suggestion
|
||||
dotnet_style_explicit_tuple_names = true:suggestion
|
||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
|
||||
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||
dotnet_style_prefer_auto_properties = true:warning
|
||||
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
||||
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
||||
|
||||
|
||||
# CSharp code style settings:
|
||||
[*.cs]
|
||||
# Prefer "var" everywhere
|
||||
csharp_style_var_elsewhere = false:none
|
||||
csharp_style_var_for_built_in_types = false:none
|
||||
csharp_style_var_when_type_is_apparent = false:none
|
||||
|
||||
# Prefer method-like constructs to have a block body
|
||||
csharp_style_expression_bodied_methods = true:suggestion
|
||||
csharp_style_expression_bodied_constructors = false:suggestion
|
||||
csharp_style_expression_bodied_operators = true:suggestion
|
||||
|
||||
# Prefer property-like constructs to have an expression-body
|
||||
csharp_style_expression_bodied_properties = true:suggestion
|
||||
csharp_style_expression_bodied_indexers = true:suggestion
|
||||
csharp_style_expression_bodied_accessors = true:suggestion
|
||||
|
||||
# Suggest more modern language features when available
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||
csharp_style_inlined_variable_declaration = true:suggestion
|
||||
csharp_style_throw_expression = true:suggestion
|
||||
csharp_style_conditional_delegate_call = true:suggestion
|
||||
csharp_style_namespace_declarations = file_scoped
|
||||
|
||||
# Newline settings
|
||||
csharp_new_line_before_open_brace = all
|
||||
csharp_new_line_before_else = true
|
||||
csharp_new_line_before_catch = true
|
||||
csharp_new_line_before_finally = true
|
||||
csharp_new_line_before_members_in_object_initializers = true
|
||||
csharp_new_line_before_members_in_anonymous_types = true
|
||||
|
||||
# Space preferences
|
||||
csharp_space_after_cast = false
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||
csharp_space_between_parentheses = false
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
csharp_space_after_colon_in_inheritance_clause = true
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||
|
||||
# Wrapping preferences
|
||||
csharp_preserve_single_line_statements = true
|
||||
csharp_preserve_single_line_blocks = true
|
||||
|
||||
|
||||
|
||||
# SYMBOL NAMING RULES
|
||||
# Copied from https://github.com/dotnet/roslyn/blob/main/.editorconfig
|
||||
# Adapted rules:
|
||||
# - Constants are ALL_UPPER
|
||||
# - Non-private fields are PascalCase
|
||||
|
||||
# Non-private fields are PascalCase
|
||||
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.severity = warning
|
||||
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.symbols = non_private_readonly_fields
|
||||
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.style = non_private_readonly_field_style
|
||||
|
||||
dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected
|
||||
|
||||
dotnet_naming_style.non_private_readonly_field_style.capitalization = pascal_case
|
||||
|
||||
# Constants are ALL_UPPER
|
||||
dotnet_naming_rule.constants_should_be_all_upper.severity = warning
|
||||
dotnet_naming_rule.constants_should_be_all_upper.symbols = constants
|
||||
dotnet_naming_rule.constants_should_be_all_upper.style = constant_style
|
||||
|
||||
dotnet_naming_symbols.constants.applicable_kinds = field, local
|
||||
dotnet_naming_symbols.constants.required_modifiers = const
|
||||
|
||||
dotnet_naming_style.constant_style.capitalization = all_upper
|
||||
|
||||
# Private static fields are camelCase and start with s_
|
||||
dotnet_naming_rule.static_fields_should_be_camel_case.severity = warning
|
||||
dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields
|
||||
dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style
|
||||
|
||||
dotnet_naming_symbols.static_fields.applicable_accessibilities = private
|
||||
dotnet_naming_symbols.static_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.static_fields.required_modifiers = static
|
||||
|
||||
dotnet_naming_style.static_field_style.capitalization = camel_case
|
||||
dotnet_naming_style.static_field_style.required_prefix = s_
|
||||
|
||||
|
||||
# Instance fields are camelCase and start with _
|
||||
dotnet_naming_rule.instance_fields_should_be_camel_case.severity = warning
|
||||
dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields
|
||||
dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style
|
||||
|
||||
dotnet_naming_symbols.instance_fields.applicable_kinds = field
|
||||
|
||||
dotnet_naming_style.instance_field_style.capitalization = camel_case
|
||||
dotnet_naming_style.instance_field_style.required_prefix = _
|
||||
|
||||
# Locals and parameters are camelCase
|
||||
dotnet_naming_rule.locals_should_be_camel_case.severity = warning
|
||||
dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters
|
||||
dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style
|
||||
|
||||
dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local
|
||||
|
||||
dotnet_naming_style.camel_case_style.capitalization = camel_case
|
||||
|
||||
# Local functions are PascalCase
|
||||
dotnet_naming_rule.local_functions_should_be_pascal_case.severity = warning
|
||||
dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions
|
||||
dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style
|
||||
|
||||
dotnet_naming_symbols.local_functions.applicable_kinds = local_function
|
||||
|
||||
dotnet_naming_style.local_function_style.capitalization = pascal_case
|
||||
|
||||
# By default, name items with PascalCase
|
||||
dotnet_naming_rule.members_should_be_pascal_case.severity = warning
|
||||
dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members
|
||||
dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style
|
||||
|
||||
dotnet_naming_symbols.all_members.applicable_kinds = *
|
||||
|
||||
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
|
||||
|
||||
|
||||
# Analyzer settings
|
||||
dotnet_analyzer_diagnostic.category-Style.severity = warning # All rules will use this severity unless overriden
|
||||
dotnet_diagnostic.ide0055.severity = none # Formatting rule: Incompatible with CSharpier
|
||||
dotnet_diagnostic.ide0007.severity = none # Use var instead of explicit type: Preference
|
||||
dotnet_diagnostic.ide0009.severity = none # Add this or Me qualification: Preference
|
||||
dotnet_diagnostic.ide0200.severity = none # Remove unnecessary lambda expression: may be performance reasons not to
|
||||
dotnet_diagnostic.ide0058.severity = none # Remove unnecessary expression value: Subjective
|
||||
dotnet_diagnostic.ide0010.severity = none # Add missing cases to switch statement: Too verbose
|
||||
dotnet_diagnostic.ide0200.severity = none # Remove unnecessary lambda expression: may be performance reasons not to
|
||||
dotnet_diagnostic.ide0058.severity = none # Remove unnecessary expression value: Subjective
|
||||
dotnet_diagnostic.ide0305.severity = none # Use collection expression for fluent: Can obfuscate intent
|
||||
dotnet_diagnostic.ide0001.severity = suggestion # Name can be simplified: Non enforceable in build
|
||||
dotnet_diagnostic.ide0046.severity = suggestion # Use conditional expression for return: Subjective
|
||||
dotnet_diagnostic.ide0045.severity = suggestion # Use conditional expression for assignment: Subjective
|
||||
dotnet_diagnostic.ide0078.severity = suggestion # Use pattern matching: Subjective
|
||||
dotnet_diagnostic.ide0260.severity = suggestion # Use pattern matching: Subjective
|
||||
dotnet_diagnostic.ide0022.severity = suggestion # Use expression body for method: Subjective
|
||||
dotnet_diagnostic.ide0061.severity = suggestion # Use expression body for local functions: Subjective
|
||||
dotnet_diagnostic.ide0063.severity = suggestion # Using directive can be simplified
|
||||
dotnet_diagnostic.ide0066.severity = suggestion # Use switch expression: Subjective
|
||||
dotnet_diagnostic.ide0029.severity = suggestion # Null check can be simplified: Subjective
|
||||
dotnet_diagnostic.ide0030.severity = suggestion # Null check can be simplified: Subjective
|
||||
dotnet_diagnostic.ide0270.severity = suggestion # Null check can be simplified: Subjective
|
||||
dotnet_diagnostic.ide0042.severity = suggestion # Deconstruct variable declaration: Subjective
|
||||
dotnet_diagnostic.ide0039.severity = suggestion # Use local function instead of lambda: Subjective
|
||||
dotnet_diagnostic.ide0029.severity = suggestion # Null check can be simplified: Subjective
|
||||
dotnet_diagnostic.ide0030.severity = suggestion # Null check can be simplified: Subjective
|
||||
dotnet_diagnostic.ide0270.severity = suggestion # Null check can be simplified: Subjective
|
||||
dotnet_diagnostic.ide0042.severity = suggestion # Deconstruct variable declaration: Subjective
|
||||
dotnet_diagnostic.ide0028.severity = suggestion # Use collection initializers: Subjective
|
||||
dotnet_diagnostic.ide0072.severity = suggestion # Populate switch statement: Subjective
|
||||
dotnet_diagnostic.ide0074.severity = suggestion # Use compound assignment: Subjective
|
||||
dotnet_diagnostic.ide0300.severity = suggestion # Use collection expression for array: Subjective, maybe aspirational
|
||||
dotnet_diagnostic.ide0290.severity = suggestion # primary constructors: subjective, and readonly properties are not a thing
|
||||
dotnet_diagnostic.ide0290.severity = suggestion # Use primary constructor: Subjective
|
||||
dotnet_diagnostic.ide0037.severity = suggestion # Use inferred member names: Sometimes its nice to be explicit
|
||||
dotnet_diagnostic.ide0301.severity = suggestion # Use collection expression for empty: Subjective, intent
|
||||
dotnet_diagnostic.ide0021.severity = suggestion # Use expression body for constructors : Subjective
|
||||
dotnet_diagnostic.ide0090.severity = suggestion # Simplify new expression : Subjective
|
||||
|
||||
dotnet_diagnostic.ide0047.severity = suggestion # Parentheses preferences: IDEs don't properly pick it up
|
||||
dotnet_diagnostic.ide0130.severity = suggestion # Namespace does not match folder structure : Aspirational
|
||||
dotnet_diagnostic.ide1006.severity = suggestion # Naming rule violation : Aspirational
|
||||
|
||||
# Maintainability rules
|
||||
dotnet_diagnostic.ca1501.severity = warning # Avoid excessive inheritance
|
||||
dotnet_diagnostic.ca1502.severity = warning # Avoid excessive complexity
|
||||
dotnet_diagnostic.ca1505.severity = warning # Avoid unmaintainable code
|
||||
dotnet_diagnostic.ca1506.severity = warning # Avoid excessive class coupling
|
||||
dotnet_diagnostic.ca1507.severity = warning # Use nameof in place of string
|
||||
dotnet_diagnostic.ca1508.severity = warning # Avoid dead conditional code
|
||||
dotnet_diagnostic.ca1509.severity = warning # Invalid entry in code metrics configuration file
|
||||
dotnet_diagnostic.ca1861.severity = suggestion # Prefer 'static readonly' fields over constant array arguments if the called method is called repeatedly and is not mutating the passed array (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861)
|
||||
|
||||
|
||||
# Performance rules
|
||||
dotnet_diagnostic.ca1849.severity = suggestion # Call async methods when in an async method: May decrease performance
|
||||
dotnet_diagnostic.ca1822.severity = suggestion # Mark member as static
|
||||
dotnet_diagnostic.ca1859.severity = suggestion # Use concrete types when possible for improved performance
|
||||
|
||||
# Design rule
|
||||
dotnet_diagnostic.ca1002.severity = suggestion # Do not expose generic lists
|
||||
dotnet_diagnostic.ca1051.severity = warning # Do not declare visible instance fields
|
||||
dotnet_diagnostic.ca1056.severity = suggestion # URI properties should not be strings
|
||||
dotnet_diagnostic.ca1062.severity = none # Public method must check all parameters for null
|
||||
|
||||
# Naming
|
||||
dotnet_diagnostic.ca1707.severity = none # Remove underscores in names
|
||||
|
||||
# Usage
|
||||
dotnet_diagnostic.ca2227.severity = suggestion # Collection props should be read-only
|
||||
|
||||
dotnet_code_quality.ca1051.exclude_structs = true # CA1051 is excluded in structs
|
||||
dotnet_code_quality.dispose_ownership_transfer_at_constructor = true # CA2000 has a lot of false positives without this
|
||||
dotnet_code_quality.dispose_ownership_transfer_at_method_call = true # CA2000 has a lot of false positives without this
|
||||
dotnet_code_quality.dispose_analysis_kind = NonExceptionPathsOnlyNotDisposed # CA2000 has a lot of false positives without this
|
||||
|
||||
# NUnit
|
||||
dotnet_diagnostic.NUnit2001.severity = warning # Consider using Assert.That(expr, Is.False) instead of Assert.False(expr)
|
||||
dotnet_diagnostic.NUnit2002.severity = warning # Consider using Assert.That(expr, Is.False) instead of Assert.IsFalse(expr)
|
||||
dotnet_diagnostic.NUnit2003.severity = warning # Consider using Assert.That(expr, Is.True) instead of Assert.IsTrue(expr)
|
||||
dotnet_diagnostic.NUnit2004.severity = warning # Consider using Assert.That(expr, Is.True) instead of Assert.True(expr)
|
||||
dotnet_diagnostic.NUnit2005.severity = warning # Consider using Assert.That(actual, Is.EqualTo(expected)) instead of Assert.AreEqual(expected, actual)
|
||||
dotnet_diagnostic.NUnit2006.severity = warning # Consider using Assert.That(actual, Is.Not.EqualTo(expected)) instead of Assert.AreNotEqual(expected, actual)
|
||||
|
||||
dotnet_diagnostic.NUnit2010.severity = warning # Use EqualConstraint for better assertion messages in case of failure
|
||||
dotnet_diagnostic.NUnit2011.severity = warning # Use ContainsConstraint for better assertion messages in case of failure
|
||||
dotnet_diagnostic.NUnit2011.severity = warning # Use StartsWithConstraint for better assertion messages in case of failure
|
||||
dotnet_diagnostic.NUnit2011.severity = warning # Use EndsWithConstraint for better assertion messages in case of failure
|
||||
dotnet_diagnostic.NUnit2014.severity = warning # Use SomeItemsConstraint for better assertion messages in case of failure
|
||||
|
||||
dotnet_diagnostic.NUnit2015.severity = warning # Consider using Assert.That(actual, Is.SameAs(expected)) instead of Assert.AreSame(expected, actual)
|
||||
dotnet_diagnostic.NUnit2016.severity = warning # Consider using Assert.That(expr, Is.Null) instead of Assert.Null(expr)
|
||||
dotnet_diagnostic.NUnit2017.severity = warning # Consider using Assert.That(expr, Is.Null) instead of Assert.IsNull(expr)
|
||||
dotnet_diagnostic.NUnit2018.severity = warning # Consider using Assert.That(expr, Is.Not.Null) instead of Assert.NotNull(expr)
|
||||
dotnet_diagnostic.NUnit2028.severity = warning # Consider using Assert.That(actual, Is.GreaterThanOrEqualTo(expected)) instead of Assert.GreaterOrEqual(actual, expected)
|
||||
dotnet_diagnostic.NUnit2027.severity = warning # Consider using Assert.That(actual, Is.GreaterThan(expected)) instead of Assert.Greater(actual, expected)
|
||||
dotnet_diagnostic.NUnit2029.severity = warning # Consider using Assert.That(actual, Is.LessThan(expected)) instead of Assert.Less(actual, expected)
|
||||
dotnet_diagnostic.NUnit2030.severity = warning # Consider using Assert.That(actual, Is.LessThanOrEqualTo(expected)) instead of Assert.LessOrEqual(actual, expected)
|
||||
dotnet_diagnostic.NUnit2031.severity = warning # Consider using Assert.That(actual, Is.Not.SameAs(expected)) instead of Assert.AreNotSame(expected, actual)
|
||||
dotnet_diagnostic.NUnit2032.severity = warning # Consider using Assert.That(expr, Is.Zero) instead of Assert.Zero(expr)
|
||||
dotnet_diagnostic.NUnit2033.severity = warning # Consider using Assert.That(expr, Is.Not.Zero) instead of Assert.NotZero(expr)
|
||||
dotnet_diagnostic.NUnit2034.severity = warning # Consider using Assert.That(expr, Is.NaN) instead of Assert.IsNaN(expr)
|
||||
dotnet_diagnostic.NUnit2035.severity = warning # Consider using Assert.That(collection, Is.Empty) instead of Assert.IsEmpty(collection)
|
||||
dotnet_diagnostic.NUnit2036.severity = warning # Consider using Assert.That(collection, Is.Not.Empty) instead of Assert.IsNotEmpty(collection)
|
||||
dotnet_diagnostic.NUnit2037.severity = warning # Consider using Assert.That(collection, Does.Contain(instance)) instead of Assert.Contains(instance, collection)
|
||||
dotnet_diagnostic.NUnit2038.severity = warning # Consider using Assert.That(actual, Is.InstanceOf(expected)) instead of Assert.IsInstanceOf(expected, actual)
|
||||
dotnet_diagnostic.NUnit2039.severity = warning # Consider using Assert.That(actual, Is.Not.InstanceOf(expected)) instead of Assert.IsNotInstanceOf(expected, actual)
|
||||
|
||||
[*.{appxmanifest,asax,ascx,aspx,axaml,build,c,c++,cc,cginc,compute,cp,cpp,cs,cshtml,cu,cuh,cxx,dtd,fs,fsi,fsscript,fsx,fx,fxh,h,hh,hlsl,hlsli,hlslinc,hpp,hxx,inc,inl,ino,ipp,ixx,master,ml,mli,mpp,mq4,mq5,mqh,nuspec,paml,razor,resw,resx,shader,skin,tpp,usf,ush,vb,xaml,xamlx,xoml,xsd}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
tab_width = 2
|
||||
|
||||
# Verify
|
||||
[*.{received,verified}.{json}]
|
||||
charset = utf-8-bom
|
||||
end_of_line = lf
|
||||
indent_size = unset
|
||||
indent_style = unset
|
||||
insert_final_newline = false
|
||||
tab_width = unset
|
||||
trim_trailing_whitespace = false
|
||||
@@ -0,0 +1,8 @@
|
||||
# Set the default behavior, in case people don't have core.autocrlf set.
|
||||
* text=auto
|
||||
|
||||
# need original files to be windows
|
||||
*.txt text eol=crlf
|
||||
|
||||
# Verify
|
||||
*.verified.json text eol=lf working-tree-encoding=UTF-8
|
||||
@@ -0,0 +1,76 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
- Using welcoming and inclusive language
|
||||
- Being respectful of differing viewpoints and experiences
|
||||
- Gracefully accepting constructive criticism
|
||||
- Focusing on what is best for the community
|
||||
- Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
- The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at hello@speckle.systems. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
@@ -0,0 +1,58 @@
|
||||
# Speckle Contribution Guidelines
|
||||
|
||||
## Introduction
|
||||
|
||||
Thank you for reading this! Speckle's a rather wide network of parts that depend on each other, either directly, indirectly or even just cosmetically.
|
||||
|
||||
> **Speckle** is a quite large ecosystem of moving parts. Any changes may have unintended effects, that can cause problems quickly for many people (and processes) that rely on Speckle.
|
||||
|
||||
This means that what might look like a simple quick change in one repo may have a big hidden cost that propagates around other parts of the project. We're all here to help each other, and this guide is meant to help you get started and promote a framework that can untangle all these dependecies through discussion!
|
||||
|
||||
## Bugs & Issues 🐞
|
||||
|
||||
### Found a new bug?
|
||||
|
||||
- First step is to check whether this is a new bug! We encourage you to search through the issues of the project in question **and** associated repos!
|
||||
|
||||
- If you come up with nothing, **open a new issue with a clear title and description**, as much relevant information as possible: system configuration, code samples & steps to reproduce the problem.
|
||||
|
||||
- Can't mention this often enough: tells us how to reproduce the problem! We will ignore or flag as such issues without reproduction steps.
|
||||
|
||||
- Try to reference & note all potentially affected projects.
|
||||
|
||||
### Sending a PR for Bug Fixes
|
||||
|
||||
You fixed something! Great! We hope you logged it first :) Make sure though that you've covered the lateral thinking needed for a bug report, as described above, also in your implementation! If there any tests, make sure they all pass. If there are none, it means they're missing - so add them!
|
||||
|
||||
### Code Style
|
||||
|
||||
When collaborating on a project in GitHub, it's important to follow coding conventions and style guidelines to ensure consistency and readability of the codebase. One commonly used convention is to use two spaces for indentation.
|
||||
|
||||
To use two spaces for indentation in GitHub, you can configure your text editor or IDE to use this indentation style. Once you've written your code, you can commit and push your changes to GitHub.
|
||||
|
||||
When collaborating with others on GitHub, it's important to communicate any changes to coding conventions or style guidelines, and to work together to maintain a consistent coding style throughout the project. Code reviews can also help ensure that code is well-formatted and easy to read.
|
||||
|
||||
## New Features 🎉
|
||||
|
||||
The golden rule is to Discuss First!
|
||||
|
||||
- Before embarking on adding a new feature, suggest it first as an issue with the `enhancement` label and/or title - this will allow relevant people to pitch in
|
||||
- We'll now discuss your requirements and see how and if they fit within the Speckle ecosystem.
|
||||
- The last step is to actually start writing code & submit a PR so we can follow along!
|
||||
- All new features should, if and where possible, come with tests. We won't merge without!
|
||||
|
||||
> Many clients may potentially have overlapping scopes, some features might already be in dev somewhere else, or might have been postponed to the next major release due to api instability in that area. For example, adding a delete stream button in the accounts panel in rhino: this feature was planned for speckle admin, and the whole functionality of the accounts panel in rhino is to be greatly reduced!
|
||||
|
||||
## Cosmetic Patches ✨
|
||||
|
||||
Changes that are cosmetic in nature and do not add anything substantial to the stability or functionality of Speckle **will generally not be accepted**.
|
||||
|
||||
Why? However trivial the changes might seem, there might be subtle reasons for the original code to be as it is. Furthermore, there are a lot of potential hidden costs (that even maintainers themselves are not aware of fully!) and they eat up review time unncessarily.
|
||||
|
||||
> **Examples**: modifying the colour of an UI element in one client may have a big hidden cost and need propagation in several other clients that implement a similar ui element. Changing the default port or specifiying `localhost` instead of `0.0.0.0` breaks cross-vm debugging and developing.
|
||||
|
||||
## Wrap up
|
||||
|
||||
Don't worry if you get things wrong. We all do, including project owners: this document should've been here a long time ago. There's plenty of room for discussion on our community [forum](https://discourse.speckle.works).
|
||||
|
||||
🙌❤️💙💚💜🙌
|
||||
@@ -0,0 +1,6 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions" # search for actions - there are other options available
|
||||
directory: "/" # search in .github/workflows under root `/`
|
||||
schedule:
|
||||
interval: "weekly" # check for action update every week
|
||||
@@ -0,0 +1,32 @@
|
||||
name: .NET CI Build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.x.x
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.nuget/packages
|
||||
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
|
||||
|
||||
- name: 🔫 Build All
|
||||
run: ./build.sh
|
||||
|
||||
- name: Upload coverage reports to Codecov with GitHub Action
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
files: tests/**/coverage.xml
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
@@ -0,0 +1,37 @@
|
||||
name: .NET Build and Publish
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main", "dev"]
|
||||
tags: ["3.*"]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.x.x
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.nuget/packages
|
||||
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
|
||||
|
||||
- name: 🔫 Build and Pack
|
||||
run: ./build.sh pack
|
||||
|
||||
- name: Upload coverage reports to Codecov with GitHub Action
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
files: tests/**/coverage.xml
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
- name: Push to nuget.org
|
||||
run: dotnet nuget push output/*.nupkg --source "https://api.nuget.org/v3/index.json" --api-key ${{secrets.CONNECTORS_NUGET_TOKEN }} --skip-duplicate
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
**/bin/*
|
||||
**/obj/*
|
||||
_ReSharper.SharpCompress/
|
||||
bin/
|
||||
*.suo
|
||||
*.user
|
||||
TestArchives/Scratch/
|
||||
TestArchives/Scratch2/
|
||||
TestResults/
|
||||
*.nupkg
|
||||
packages/*/
|
||||
project.lock.json
|
||||
tests/TestArchives/Scratch
|
||||
.vs
|
||||
tools
|
||||
.vscode
|
||||
.idea/
|
||||
|
||||
.DS_Store
|
||||
*.snupkg
|
||||
coverage.xml
|
||||
|
||||
*.received.*
|
||||
@@ -0,0 +1,50 @@
|
||||
# Speckle Contribution Guidelines
|
||||
|
||||
## Introduction
|
||||
|
||||
Thank you for reading this! Speckle's a rather wide network of parts that depend on each other, either directly, indirectly or even just cosmetically.
|
||||
|
||||
> **Speckle** is a quite large ecosystem of moving parts. Any changes may have unintended effects, that can cause problems quickly for many people (and processes) that rely on Speckle.
|
||||
|
||||
This means that what might look like a simple quick change in one repo may have a big hidden cost that propagates around other parts of the project. We're all here to help each other, and this guide is meant to help you get started and promote a framework that can untangle all these dependecies through discussion!
|
||||
|
||||
## Bugs & Issues 🐞
|
||||
|
||||
### Found a new bug?
|
||||
|
||||
- First step is to check whether this is a new bug! We encourage you to search through the issues of the project in question **and** associated repos!
|
||||
|
||||
- If you come up with nothing, **open a new issue with a clear title and description**, as much relevant information as possible: system configuration, code samples & steps to reproduce the problem.
|
||||
|
||||
- Can't mention this often enough: tells us how to reproduce the problem! We will ignore or flag as such issues without reproduction steps.
|
||||
|
||||
- Try to reference & note all potentially affected projects.
|
||||
|
||||
### Sending a PR for Bug Fixes
|
||||
|
||||
You fixed something! Great! We hope you logged it first :) Make sure though that you've covered the lateral thinking needed for a bug report, as described above, also in your implementation! If there any tests, make sure they all pass. If there are none, it means they're missing - so add them!
|
||||
|
||||
## New Features 🎉
|
||||
|
||||
The golden rule is to Discuss First!
|
||||
|
||||
- Before embarking on adding a new feature, suggest it first as an issue with the `enhancement` label and/or title - this will allow relevant people to pitch in
|
||||
- We'll now discuss your requirements and see how and if they fit within the Speckle ecosystem.
|
||||
- The last step is to actually start writing code & submit a PR so we can follow along!
|
||||
- All new features should, if and where possible, come with tests. We won't merge without!
|
||||
|
||||
> Many clients may potentially have overlapping scopes, some features might already be in dev somewhere else, or might have been postponed to the next major release due to api instability in that area. For example, adding a delete stream button in the accounts panel in rhino: this feature was planned for speckle admin, and the whole functionality of the accounts panel in rhino is to be greatly reduced!
|
||||
|
||||
## Cosmetic Patches ✨
|
||||
|
||||
Changes that are cosmetic in nature and do not add anything substantial to the stability or functionality of Speckle **will generally not be accepted**.
|
||||
|
||||
Why? However trivial the changes might seem, there might be subtle reasons for the original code to be as it is. Furthermore, there are a lot of potential hidden costs (that even maintainers themselves are not aware of fully!) and they eat up review time unncessarily.
|
||||
|
||||
> **Examples**: modifying the colour of an UI element in one client may have a big hidden cost and need propagation in several other clients that implement a similar ui element. Changing the default port or specifiying `localhost` instead of `0.0.0.0` breaks cross-vm debugging and developing.
|
||||
|
||||
## Wrap up
|
||||
|
||||
Don't worry if you get things wrong. We all do, including project owners: this document should've been here a long time ago. There's plenty of room for discussion on our community [forum](https://discourse.speckle.works).
|
||||
|
||||
🙌❤️💙💚💜🙌
|
||||
@@ -0,0 +1,4 @@
|
||||
CA1502: 25
|
||||
CA1501: 5
|
||||
CA1506(Method): 50
|
||||
CA1506(Type): 95
|
||||
@@ -0,0 +1,86 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup Label="Compiler Properties">
|
||||
<LangVersion>12</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Nugetspec Package Properties">
|
||||
<!-- Defines common Nugetspec properties -->
|
||||
<!-- Inheriting packable projects should define the rest of the nugetspec properties (PackageId, Description) -->
|
||||
<!-- and may, if needed, override/extend any of these (e.g. PackageTags) -->
|
||||
<Authors>Speckle</Authors>
|
||||
<Copyright>Copyright (c) AEC Systems Ltd</Copyright>
|
||||
<PackageProjectUrl>https://speckle.systems/</PackageProjectUrl>
|
||||
<PackageIcon>logo.png</PackageIcon>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<RepositoryUrl>https://github.com/specklesystems/speckle-sharp-sdk</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageTags>speckle</PackageTags>
|
||||
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Nuget Package Properties">
|
||||
<IsPackable>false</IsPackable> <!--Can be set to true in inheriting .props/.csproj files for projects that should be packed-->
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
|
||||
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Analyers">
|
||||
<EnableNetAnalyzers>true</EnableNetAnalyzers>
|
||||
<AnalysisLevel>latest-AllEnabledByDefault</AnalysisLevel>
|
||||
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
|
||||
<!-- Ingored warnings, some aspirational but too noisy for now, some by design. -->
|
||||
<NoWarn>
|
||||
<!--Disabled by design-->
|
||||
CA5399;CA1812;
|
||||
<!--XML comment-->
|
||||
CS1591;CS1573;
|
||||
<!-- Globalization rules -->
|
||||
CA1303;CA1304;CA1305;CA1307;CA1308;CA1309;CA1310;CA1311;
|
||||
<!-- Logging -->
|
||||
CA1848;CA1727;
|
||||
<!-- Others we don't want -->
|
||||
CA1815;CA1725;
|
||||
<!-- Naming things is hard enough -->
|
||||
CA1710;CA1711;CA1720;CA1724;
|
||||
<!-- Aspirational -->
|
||||
CA1502;CA1716;NETSDK1206;
|
||||
$(NoWarn)
|
||||
</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Expose the repository root to all projects -->
|
||||
<RepositoryRoot>$(MSBuildThisFileDirectory)</RepositoryRoot>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\..\README.md" Pack="true" PackagePath="\"/>
|
||||
<None
|
||||
Condition="'$(IsPackable)' == 'true'"
|
||||
Include="..\..\logo.png"
|
||||
Pack="true"
|
||||
PackagePath="\"
|
||||
Visible="false"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- This file contains the configuration for some analyzer warnings, such as cyclomatic
|
||||
complexity threshold -->
|
||||
<AdditionalFiles Include="$(RepositoryRoot)CodeMetricsConfig.txt"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project>
|
||||
<PropertyGroup Condition="'$(IsTestProject)' == 'true'">
|
||||
<NoWarn>
|
||||
$(NoWarn);
|
||||
<!-- Things we need to test -->
|
||||
CS0618;CA1034;CA2201;CA1051;CA1040;CA1724;
|
||||
IDE0044;IDE0130;CA1508;
|
||||
<!-- Analysers that provide no tangeable value to a test project -->
|
||||
CA5394;CA2007;CA1852;CA1819;CA1711;CA1063;CA1816;CA2234;CS8618;CA1054;CA1810;CA2208;CA1019;CA1831;
|
||||
</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="DeepClean">
|
||||
<Message Text="Deep clean of $(MSBuildProjectName).csproj" Importance="high"/>
|
||||
<RemoveDir Directories="$(BaseIntermediateOutputPath)"/>
|
||||
<RemoveDir Directories="$(BaseOutputPath)"/>
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -0,0 +1,39 @@
|
||||
<Project>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="altcover" Version="9.0.1" />
|
||||
<PackageVersion Include="AwesomeAssertions" Version="8.1.0" />
|
||||
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
|
||||
<PackageVersion Include="Bullseye" Version="6.0.0" />
|
||||
<PackageVersion Include="GraphQL.Client" Version="6.0.0" />
|
||||
<PackageVersion Include="Glob" Version="1.1.9" />
|
||||
<PackageVersion Include="HttpMultipartParser" Version="9.0.0" />
|
||||
<PackageVersion Include="ILRepack.FullAuto" Version="1.6.0" />
|
||||
<PackageVersion Include="Microsoft.CSharp" Version="4.7.0" />
|
||||
<!-- Keep at exactly 7.0.5 for side by side with V2 -->
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="[7.0.5,)" />
|
||||
<PackageVersion Include="Microsoft.Extensions.ObjectPool" Version="9.0.3" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="[2.2.0,)" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="[2.2.0,)" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="[2.2.0,)" />
|
||||
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="[5.0.0,)" />
|
||||
<PackageVersion Include="Moq" Version="4.20.72" />
|
||||
<PackageVersion Include="Open.ChannelExtensions" Version="9.0.0" />
|
||||
<PackageVersion Include="Polly" Version="7.2.3" />
|
||||
<PackageVersion Include="Polly.Contrib.WaitAndRetry" Version="1.1.1" />
|
||||
<PackageVersion Include="Polly.Extensions.Http" Version="3.0.0" />
|
||||
<PackageVersion Include="RichardSzalay.MockHttp" Version="7.0.0" />
|
||||
<PackageVersion Include="Speckle.Newtonsoft.Json" Version="13.0.2" />
|
||||
<PackageVersion Include="Speckle.DoubleNumerics" Version="4.1.0" />
|
||||
<PackageVersion Include="SimpleExec" Version="12.0.0" />
|
||||
<PackageVersion Include="System.Threading.Channels" Version="9.0.2" />
|
||||
<PackageVersion Include="Verify.Quibble" Version="2.1.1" />
|
||||
<PackageVersion Include="Verify.Xunit" Version="29.2.0" />
|
||||
<PackageVersion Include="xunit" Version="2.9.3" />
|
||||
<PackageVersion Include="xunit.assert" Version="2.9.3" />
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="3.0.2" />
|
||||
<GlobalPackageReference Include="PolySharp" Version="1.15.0" />
|
||||
<GlobalPackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
|
||||
<GlobalPackageReference Include="Speckle.InterfaceGenerator" Version="0.9.6" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,6 @@
|
||||
workflow: GitFlow/v1
|
||||
next-version: 3.0.0
|
||||
branches:
|
||||
main:
|
||||
prevent-increment:
|
||||
when-current-commit-tagged: true
|
||||
@@ -0,0 +1,17 @@
|
||||
If it's your first time here - or you forgot about them - make sure you read the [contribution guidelines](CONTRIBUTING.md), and then feel free to delete this line!
|
||||
|
||||
### Expected vs. Actual Behavior
|
||||
|
||||
Describe the problem here.
|
||||
|
||||
### Reproduction Steps & System Config (win, osx, web, etc.)
|
||||
|
||||
Let us know how we can reproduce this, and attach relevant files (if any).
|
||||
|
||||
### Proposed Solution (if any)
|
||||
|
||||
Let us know what how you would solve this.
|
||||
|
||||
#### Optional: Affected Projects
|
||||
|
||||
Does this issue propagate to other dependencies or dependents? If so, list them here!
|
||||
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2020 AEC Systems
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,2 +1,76 @@
|
||||
# speckle-sharp-sdk
|
||||
The speckle core
|
||||

|
||||
Speckle | Sharp | SDK
|
||||
=================================================================================================================================
|
||||
|
||||
[](https://twitter.com/SpeckleSystems) [](https://speckle.community) [](https://speckle.systems) [](https://speckle.guide/dev/)
|
||||
|
||||
> Speckle is the first AEC data hub that connects with your favorite AEC tools. Speckle exists to overcome the challenges of working in a fragmented industry where communication, creative workflows, and the exchange of data are often hindered by siloed software and processes. It is here to make the industry better.
|
||||
|
||||
### .NET SDK, Tests, and Objects
|
||||
|
||||
[](https://codecov.io/gh/specklesystems/speckle-sharp-sdk)
|
||||
|
||||
> [!WARNING]
|
||||
> This is an early beta release, not meant for use in production! We're working to stabilise the 3.0 API, and until then there will be breaking changes. You have been warned!
|
||||
|
||||
# Repo structure
|
||||
|
||||
This repo is the home of our next-generation Speckle .NET SDK. It uses .NET Standard 2.0 and has been tested on Windows and MacOS.
|
||||
|
||||
- **SDK**
|
||||
- [`Speckle.Sdk`](https://github.com/specklesystems/speckle-sharp-sdk/tree/dev/src/Speckle.Sdk): Transports, serialization, API wrappers, and logging.
|
||||
- [`Speckle.Sdk.Dependencies`](https://github.com/specklesystems/speckle-sharp-sdk/tree/dev/src/Speckle.Sdk.Dependencies): Dependencies and code that shouldn't cause conflicts in Host Apps. This uses [IL Repack](https://github.com/gluck/il-repack) to merge together and interalized only to be used by Speckle.
|
||||
- **Speckle Objects**
|
||||
- [`Speckle.Objects`](https://github.com/specklesystems/speckle-sharp-sdk/tree/dev/src/Speckle.Objects): The Speckle Objects classes used for conversions.
|
||||
- **Tests**
|
||||
- [`Tests`](https://github.com/specklesystems/speckle-sharp-sdk/tree/dev/tests): Unit, serialization, integration, and performance tests.
|
||||
|
||||
### Other repos
|
||||
|
||||
Make sure to also check and ⭐️ these other Speckle next generation repositories:
|
||||
|
||||
- [`speckle-sharp-connectors`](https://github.com/specklesystems/speckle-sharp-connectors): our csharp repo of next gen connectors
|
||||
- [`speckle-sketchup`](https://github.com/specklesystems/speckle-sketchup): Sketchup connector
|
||||
- [`speckle-powerbi`](https://github.com/specklesystems/speckle-powerbi): PowerBi connector
|
||||
- and more [connectors & tooling](https://github.com/specklesystems/)!
|
||||
|
||||
## Documentation
|
||||
|
||||
Comprehensive developer and user documentation can be found in our:
|
||||
|
||||
### 📚 [Speckle Docs website](https://speckle.guide/dev/)
|
||||
|
||||
# Developing and Debugging
|
||||
|
||||
### Building
|
||||
|
||||
Make sure you clone this repository together with its submodules: `git clone https://github.com/specklesystems/speckle-sharp-sdk.git -recursive`.
|
||||
Afterwards, just restore all the NuGet packages and hit Build!
|
||||
|
||||
### Developing
|
||||
|
||||
This project is evolving fast, to better understand how to use Core we suggest checking out the Unit and Integration tests. Running the integration tests locally requires a local server running on your computer.
|
||||
|
||||
We'll be also adding [preliminary documentation on our forum](https://discourse.speckle.works/c/speckle-insider/10).
|
||||
|
||||
### Tests
|
||||
|
||||
There are two test projects, one for unit tests and one for integration tests. The latter needs a server running locally in order to run.
|
||||
|
||||
## Contributing
|
||||
|
||||
Before embarking on submitting a patch, please make sure you read:
|
||||
|
||||
- [Contribution Guidelines](CONTRIBUTING.md)
|
||||
- [Code of Conduct](CODE_OF_CONDUCT.md)
|
||||
|
||||
# Security and Licensing
|
||||
|
||||
### Security
|
||||
|
||||
For any security vulnerabilities or concerns, please contact us directly at security[at]speckle.systems.
|
||||
|
||||
### License
|
||||
|
||||
Unless otherwise described, the code in this repository is licensed under the Apache-2.0 License. Please note that some modules, extensions or code herein might be otherwise licensed. This is indicated either in the root of the containing folder under a different license file, or in the respective file's header. If you have any questions, don't hesitate to get in touch with us via [email](mailto:hello@speckle.systems).
|
||||
|
||||
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Sdk", "src\Speckle.Sdk\Speckle.Sdk.csproj", "{A413E196-3696-4F48-B635-04B5F76BF9C9}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Sdk.Tests.Unit", "tests\Speckle.Sdk.Tests.Unit\Speckle.Sdk.Tests.Unit.csproj", "{99AE2273-12C5-4A9D-9FDD-19F8B394B5E2}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Objects", "src\Speckle.Objects\Speckle.Objects.csproj", "{181F50AA-DD2A-4541-98EF-B868E2D06B9A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Objects.Tests.Unit", "tests\Speckle.Objects.Tests.Unit\Speckle.Objects.Tests.Unit.csproj", "{A0338FC0-3011-498F-AD09-01230FABD3ED}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5CB96C27-FC5B-4A41-86B6-951AF99B8116}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{35047EE7-AD1D-4741-80A7-8F0E874718E9}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "config", "config", "{DA2AED52-58F9-471E-8AD8-102FD36129E3}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.csharpierrc.yaml = .csharpierrc.yaml
|
||||
.editorconfig = .editorconfig
|
||||
Directory.Build.props = Directory.Build.props
|
||||
Directory.Packages.props = Directory.Packages.props
|
||||
global.json = global.json
|
||||
README.md = README.md
|
||||
GitVersion.yml = GitVersion.yml
|
||||
docker-compose.yml = docker-compose.yml
|
||||
CodeMetricsConfig.txt = CodeMetricsConfig.txt
|
||||
Directory.Build.Targets = Directory.Build.Targets
|
||||
.config\dotnet-tools.json = .config\dotnet-tools.json
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{58D37DA9-F948-48CA-9A73-F5BBBD533DBF}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "build", "build\build.csproj", "{9B8DDEB5-37C7-49B5-984D-C65DE5FCB7B7}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Sdk.Serialization.Tests", "tests\Speckle.Sdk.Serialization.Tests\Speckle.Sdk.Serialization.Tests.csproj", "{AA1E1E51-49AE-4F71-84B1-938E19695BE0}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Sdk.Tests.Integration", "tests\Speckle.Sdk.Tests.Integration\Speckle.Sdk.Tests.Integration.csproj", "{4FB41A6D-D139-4111-8115-E3F9F6BEAF24}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{B623BD21-5CAA-43F9-A539-1835276C220E}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.github\workflows\pr.yml = .github\workflows\pr.yml
|
||||
.github\workflows\release.yml = .github\workflows\release.yml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Sdk.Tests.Performance", "tests\Speckle.Sdk.Tests.Performance\Speckle.Sdk.Tests.Performance.csproj", "{870E3396-E6F7-43AE-B120-E651FA4F46BD}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Sdk.Serialization.Testing", "tests\Speckle.Sdk.Serialization.Testing\Speckle.Sdk.Serialization.Testing.csproj", "{FF922B6D-D416-4348-8CB8-0C8B28691070}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Sdk.Dependencies", "src\Speckle.Sdk.Dependencies\Speckle.Sdk.Dependencies.csproj", "{27584AB4-8ACD-4850-8CC2-7E5BC739FB78}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Sdk.Testing", "tests\Speckle.Sdk.Testing\Speckle.Sdk.Testing.csproj", "{7B617C0D-2354-415C-993C-5071D4113E27}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "performance", "performance", "{FFB07238-87E8-463A-AA39-3B38AAAA94C1}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{8781B61F-0308-488A-BEB2-1939E7CEEBE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8781B61F-0308-488A-BEB2-1939E7CEEBE9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8781B61F-0308-488A-BEB2-1939E7CEEBE9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8781B61F-0308-488A-BEB2-1939E7CEEBE9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A413E196-3696-4F48-B635-04B5F76BF9C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A413E196-3696-4F48-B635-04B5F76BF9C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A413E196-3696-4F48-B635-04B5F76BF9C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A413E196-3696-4F48-B635-04B5F76BF9C9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{99AE2273-12C5-4A9D-9FDD-19F8B394B5E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{99AE2273-12C5-4A9D-9FDD-19F8B394B5E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{99AE2273-12C5-4A9D-9FDD-19F8B394B5E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{99AE2273-12C5-4A9D-9FDD-19F8B394B5E2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{181F50AA-DD2A-4541-98EF-B868E2D06B9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{181F50AA-DD2A-4541-98EF-B868E2D06B9A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{181F50AA-DD2A-4541-98EF-B868E2D06B9A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{181F50AA-DD2A-4541-98EF-B868E2D06B9A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A0338FC0-3011-498F-AD09-01230FABD3ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A0338FC0-3011-498F-AD09-01230FABD3ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A0338FC0-3011-498F-AD09-01230FABD3ED}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A0338FC0-3011-498F-AD09-01230FABD3ED}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{9B8DDEB5-37C7-49B5-984D-C65DE5FCB7B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9B8DDEB5-37C7-49B5-984D-C65DE5FCB7B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9B8DDEB5-37C7-49B5-984D-C65DE5FCB7B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9B8DDEB5-37C7-49B5-984D-C65DE5FCB7B7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{AA1E1E51-49AE-4F71-84B1-938E19695BE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{AA1E1E51-49AE-4F71-84B1-938E19695BE0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{AA1E1E51-49AE-4F71-84B1-938E19695BE0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{AA1E1E51-49AE-4F71-84B1-938E19695BE0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4FB41A6D-D139-4111-8115-E3F9F6BEAF24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4FB41A6D-D139-4111-8115-E3F9F6BEAF24}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4FB41A6D-D139-4111-8115-E3F9F6BEAF24}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4FB41A6D-D139-4111-8115-E3F9F6BEAF24}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{870E3396-E6F7-43AE-B120-E651FA4F46BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{870E3396-E6F7-43AE-B120-E651FA4F46BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{870E3396-E6F7-43AE-B120-E651FA4F46BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{870E3396-E6F7-43AE-B120-E651FA4F46BD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FF922B6D-D416-4348-8CB8-0C8B28691070}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FF922B6D-D416-4348-8CB8-0C8B28691070}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FF922B6D-D416-4348-8CB8-0C8B28691070}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FF922B6D-D416-4348-8CB8-0C8B28691070}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{27584AB4-8ACD-4850-8CC2-7E5BC739FB78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{27584AB4-8ACD-4850-8CC2-7E5BC739FB78}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{27584AB4-8ACD-4850-8CC2-7E5BC739FB78}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{27584AB4-8ACD-4850-8CC2-7E5BC739FB78}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7B617C0D-2354-415C-993C-5071D4113E27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7B617C0D-2354-415C-993C-5071D4113E27}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7B617C0D-2354-415C-993C-5071D4113E27}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7B617C0D-2354-415C-993C-5071D4113E27}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{A413E196-3696-4F48-B635-04B5F76BF9C9} = {5CB96C27-FC5B-4A41-86B6-951AF99B8116}
|
||||
{181F50AA-DD2A-4541-98EF-B868E2D06B9A} = {5CB96C27-FC5B-4A41-86B6-951AF99B8116}
|
||||
{99AE2273-12C5-4A9D-9FDD-19F8B394B5E2} = {35047EE7-AD1D-4741-80A7-8F0E874718E9}
|
||||
{A0338FC0-3011-498F-AD09-01230FABD3ED} = {35047EE7-AD1D-4741-80A7-8F0E874718E9}
|
||||
{9B8DDEB5-37C7-49B5-984D-C65DE5FCB7B7} = {58D37DA9-F948-48CA-9A73-F5BBBD533DBF}
|
||||
{AA1E1E51-49AE-4F71-84B1-938E19695BE0} = {35047EE7-AD1D-4741-80A7-8F0E874718E9}
|
||||
{4FB41A6D-D139-4111-8115-E3F9F6BEAF24} = {35047EE7-AD1D-4741-80A7-8F0E874718E9}
|
||||
{B623BD21-5CAA-43F9-A539-1835276C220E} = {DA2AED52-58F9-471E-8AD8-102FD36129E3}
|
||||
{27584AB4-8ACD-4850-8CC2-7E5BC739FB78} = {5CB96C27-FC5B-4A41-86B6-951AF99B8116}
|
||||
{7B617C0D-2354-415C-993C-5071D4113E27} = {35047EE7-AD1D-4741-80A7-8F0E874718E9}
|
||||
{FF922B6D-D416-4348-8CB8-0C8B28691070} = {FFB07238-87E8-463A-AA39-3B38AAAA94C1}
|
||||
{870E3396-E6F7-43AE-B120-E651FA4F46BD} = {FFB07238-87E8-463A-AA39-3B38AAAA94C1}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,3 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=QL/@EntryIndexedValue">QL</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=XYZ/@EntryIndexedValue">XYZ</s:String></wpf:ResourceDictionary>
|
||||
@@ -0,0 +1,3 @@
|
||||
$ErrorActionPreference = "Stop";
|
||||
|
||||
dotnet run --project build/build.csproj -- $args
|
||||
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
dotnet run --project build/build.csproj -- "$@"
|
||||
@@ -0,0 +1,187 @@
|
||||
using System.Text.Json;
|
||||
using GlobExpressions;
|
||||
using static Bullseye.Targets;
|
||||
using static SimpleExec.Command;
|
||||
|
||||
const string CLEAN = "clean";
|
||||
const string FORMAT = "format";
|
||||
const string RESTORE_TOOLS = "restore-tools";
|
||||
|
||||
const string RESTORE = "restore";
|
||||
const string BUILD = "build";
|
||||
const string TEST = "test";
|
||||
const string INTEGRATION = "integration";
|
||||
const string PACK = "pack";
|
||||
const string CLEAN_LOCKS = "clean-locks";
|
||||
const string PERF = "perf";
|
||||
const string DEEP_CLEAN = "deep-clean";
|
||||
|
||||
static async Task<(string, string)> GetVersions()
|
||||
{
|
||||
var (output, _) = await ReadAsync("dotnet", "dotnet-gitversion /output json").ConfigureAwait(false);
|
||||
output = output.Trim();
|
||||
var jDoc = JsonDocument.Parse(output);
|
||||
var version = jDoc.RootElement.GetProperty("FullSemVer").GetString() ?? "3.0.0-localBuild";
|
||||
var fileVersion = jDoc.RootElement.GetProperty("AssemblySemFileVer").GetString() ?? "3.0.0.0";
|
||||
return (version, fileVersion);
|
||||
}
|
||||
|
||||
Target(
|
||||
CLEAN_LOCKS,
|
||||
() =>
|
||||
{
|
||||
foreach (var f in Glob.Files(".", "**/*.lock.json"))
|
||||
{
|
||||
Console.WriteLine("Found and will delete: " + f);
|
||||
File.Delete(f);
|
||||
}
|
||||
Console.WriteLine("Running restore now.");
|
||||
Run("dotnet", "restore .\\Speckle.Sdk.sln");
|
||||
}
|
||||
);
|
||||
|
||||
Target(
|
||||
CLEAN,
|
||||
forEach: ["**/output"],
|
||||
dir =>
|
||||
{
|
||||
IEnumerable<string> GetDirectories(string d)
|
||||
{
|
||||
return Glob.Directories(".", d);
|
||||
}
|
||||
|
||||
void RemoveDirectory(string d)
|
||||
{
|
||||
if (Directory.Exists(d))
|
||||
{
|
||||
Console.WriteLine(d);
|
||||
Directory.Delete(d, true);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var d in GetDirectories(dir))
|
||||
{
|
||||
RemoveDirectory(d);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
Target(RESTORE_TOOLS, () => RunAsync("dotnet", "tool restore"));
|
||||
|
||||
Target(FORMAT, dependsOn: [RESTORE_TOOLS], () => RunAsync("dotnet", "csharpier --check ."));
|
||||
|
||||
Target(RESTORE, dependsOn: [FORMAT], () => RunAsync("dotnet", "restore Speckle.Sdk.sln --locked-mode"));
|
||||
|
||||
Target(
|
||||
BUILD,
|
||||
dependsOn: [RESTORE],
|
||||
async () =>
|
||||
{
|
||||
var (version, fileVersion) = await GetVersions().ConfigureAwait(false);
|
||||
Console.WriteLine($"Version: {version} & {fileVersion}");
|
||||
await RunAsync(
|
||||
"dotnet",
|
||||
$"build Speckle.Sdk.sln -c Release --no-restore -warnaserror -p:Version={version} -p:FileVersion={fileVersion}"
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
);
|
||||
|
||||
Target(
|
||||
TEST,
|
||||
dependsOn: [BUILD],
|
||||
Glob.Files(".", "**/*.Tests.Unit.csproj").Concat(Glob.Files(".", "**/*.Tests.csproj")),
|
||||
async file =>
|
||||
{
|
||||
await RunAsync(
|
||||
"dotnet",
|
||||
$"test {file} -c Release --no-build --no-restore --verbosity=normal /p:AltCover=true /p:AltCoverAttributeFilter=ExcludeFromCodeCoverage /p:AltCoverVerbosity=Warning"
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
);
|
||||
|
||||
Target(
|
||||
INTEGRATION,
|
||||
dependsOn: [BUILD],
|
||||
async () =>
|
||||
{
|
||||
await RunAsync("docker", "compose -f docker-compose.yml up --wait").ConfigureAwait(false);
|
||||
foreach (var test in Glob.Files(".", "**/*.Tests.Integration.csproj"))
|
||||
{
|
||||
await RunAsync(
|
||||
"dotnet",
|
||||
$"test {test} -c Release --no-build --no-restore --verbosity=normal /p:AltCover=true /p:AltCoverAttributeFilter=ExcludeFromCodeCoverage"
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
await RunAsync("docker", "compose down").ConfigureAwait(false);
|
||||
}
|
||||
);
|
||||
|
||||
Target(
|
||||
PERF,
|
||||
Glob.Files(".", "**/*.Tests.Performance.csproj"),
|
||||
async file =>
|
||||
{
|
||||
void CheckBuildDirectory(string dir, string build)
|
||||
{
|
||||
var binDir = Path.Combine(dir, "bin", build);
|
||||
Console.WriteLine($"Checking: {binDir}");
|
||||
if (Directory.Exists(binDir))
|
||||
{
|
||||
Directory.Delete(binDir, true);
|
||||
Console.WriteLine($"Deleted: {binDir}");
|
||||
}
|
||||
}
|
||||
var dir = Path.GetDirectoryName(file) ?? throw new InvalidOperationException();
|
||||
CheckBuildDirectory(dir, "Release");
|
||||
CheckBuildDirectory(dir, "Debug");
|
||||
await RunAsync("dotnet", $"run --project {file} -c Release").ConfigureAwait(false);
|
||||
}
|
||||
);
|
||||
|
||||
Target(
|
||||
DEEP_CLEAN,
|
||||
() =>
|
||||
{
|
||||
foreach (var f in Glob.Directories(".", "**/bin"))
|
||||
{
|
||||
if (f.StartsWith("build"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Console.WriteLine("Found and will delete: " + f);
|
||||
Directory.Delete(f, true);
|
||||
}
|
||||
foreach (var f in Glob.Directories(".", "**/obj"))
|
||||
{
|
||||
if (f.StartsWith("Build"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Console.WriteLine("Found and will delete: " + f);
|
||||
Directory.Delete(f, true);
|
||||
}
|
||||
Console.WriteLine("Running restore now.");
|
||||
Run("dotnet", "restore .\\Speckle.Sdk.sln --no-cache");
|
||||
}
|
||||
);
|
||||
|
||||
Target(
|
||||
PACK,
|
||||
dependsOn: [TEST],
|
||||
async () =>
|
||||
{
|
||||
{
|
||||
var (version, fileVersion) = await GetVersions().ConfigureAwait(false);
|
||||
Console.WriteLine($"Version: {version} & {fileVersion}");
|
||||
await RunAsync("dotnet", $"pack Speckle.Sdk.sln -c Release -o output --no-build -p:Version={version}")
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
Target("default", dependsOn: [FORMAT, TEST, INTEGRATION], () => Console.WriteLine("Done!"));
|
||||
|
||||
await RunTargetsAndExitAsync(args).ConfigureAwait(true);
|
||||
@@ -0,0 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Bullseye" />
|
||||
<PackageReference Include="Glob" />
|
||||
<PackageReference Include="SimpleExec" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dependencies": {
|
||||
"net8.0": {
|
||||
"Bullseye": {
|
||||
"type": "Direct",
|
||||
"requested": "[6.0.0, )",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "vgwwXfzs7jJrskWH7saHRMgPzziq/e86QZNWY1MnMxd7e+De7E7EX4K3C7yrvaK9y02SJoLxNxcLG/q5qUAghw=="
|
||||
},
|
||||
"Glob": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.1.9, )",
|
||||
"resolved": "1.1.9",
|
||||
"contentHash": "AfK5+ECWYTP7G3AAdnU8IfVj+QpGjrh9GC2mpdcJzCvtQ4pnerAGwHsxJ9D4/RnhDUz2DSzd951O/lQjQby2Sw=="
|
||||
},
|
||||
"Microsoft.SourceLink.GitHub": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.0.0, )",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Build.Tasks.Git": "8.0.0",
|
||||
"Microsoft.SourceLink.Common": "8.0.0"
|
||||
}
|
||||
},
|
||||
"PolySharp": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.15.0, )",
|
||||
"resolved": "1.15.0",
|
||||
"contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g=="
|
||||
},
|
||||
"SimpleExec": {
|
||||
"type": "Direct",
|
||||
"requested": "[12.0.0, )",
|
||||
"resolved": "12.0.0",
|
||||
"contentHash": "ptxlWtxC8vM6Y6e3h9ZTxBBkOWnWrm/Sa1HT+2i1xcXY3Hx2hmKDZP5RShPf8Xr9D+ivlrXNy57ktzyH8kyt+Q=="
|
||||
},
|
||||
"Speckle.InterfaceGenerator": {
|
||||
"type": "Direct",
|
||||
"requested": "[0.9.6, )",
|
||||
"resolved": "0.9.6",
|
||||
"contentHash": "HKH7tYrYYlCK1ct483hgxERAdVdMtl7gUKW9ijWXxA1UsYR4Z+TrRHYmzZ9qmpu1NnTycSrp005NYM78GDKV1w=="
|
||||
},
|
||||
"Microsoft.Build.Tasks.Git": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ=="
|
||||
},
|
||||
"Microsoft.SourceLink.Common": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
informational: true
|
||||
target: auto
|
||||
threshold: 1%
|
||||
base: auto
|
||||
patch:
|
||||
default:
|
||||
informational: true
|
||||
github_checks:
|
||||
annotations: false
|
||||
@@ -0,0 +1,111 @@
|
||||
version: "3.9"
|
||||
name: "speckle-server"
|
||||
|
||||
services:
|
||||
####
|
||||
# Speckle Server dependencies
|
||||
#######
|
||||
postgres:
|
||||
image: "postgres:16.4-alpine3.20@sha256:d898b0b78a2627cb4ee63464a14efc9d296884f1b28c841b0ab7d7c42f1fffdf"
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_DB: speckle
|
||||
POSTGRES_USER: speckle
|
||||
POSTGRES_PASSWORD: speckle
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data/
|
||||
healthcheck:
|
||||
# the -U user has to match the POSTGRES_USER value
|
||||
test: ["CMD-SHELL", "pg_isready -U speckle"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
|
||||
redis:
|
||||
image: "redis:6.0-alpine"
|
||||
restart: always
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
|
||||
minio:
|
||||
image: "minio/minio:RELEASE.2023-10-25T06-33-25Z"
|
||||
command: server /data --console-address ":9001"
|
||||
restart: always
|
||||
volumes:
|
||||
- minio-data:/data
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
"curl -s -o /dev/null http://127.0.0.1:9000/minio/index.html",
|
||||
]
|
||||
interval: 5s
|
||||
timeout: 30s
|
||||
retries: 30
|
||||
start_period: 10s
|
||||
|
||||
speckle-server:
|
||||
image: speckle/speckle-server:latest
|
||||
restart: always
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- /nodejs/bin/node
|
||||
- -e
|
||||
- "try { require('node:http').request({headers: {'Content-Type': 'application/json'}, port:3000, hostname:'127.0.0.1', path:'/readiness', method: 'GET', timeout: 2000 }, (res) => { body = ''; res.on('data', (chunk) => {body += chunk;}); res.on('end', () => {process.exit(res.statusCode != 200 || body.toLowerCase().includes('error'));}); }).end(); } catch { process.exit(1); }"
|
||||
interval: 10s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 90s
|
||||
ports:
|
||||
- "0.0.0.0:3000:3000"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
minio:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
# TODO: Change this to the URL of the speckle server, as accessed from the network
|
||||
CANONICAL_URL: "http://127.0.0.1:8080"
|
||||
SPECKLE_AUTOMATE_URL: "http://127.0.0.1:3030"
|
||||
FRONTEND_ORIGIN: "http://127.0.0.1:8081"
|
||||
|
||||
# TODO: Change thvolumes:
|
||||
REDIS_URL: "redis://redis"
|
||||
|
||||
S3_ENDPOINT: "http://minio:9000"
|
||||
S3_ACCESS_KEY: "minioadmin"
|
||||
S3_SECRET_KEY: "minioadmin"
|
||||
S3_BUCKET: "speckle-server"
|
||||
S3_CREATE_BUCKET: "true"
|
||||
|
||||
FILE_SIZE_LIMIT_MB: 100
|
||||
MAX_PROJECT_MODELS_PER_PAGE: 500
|
||||
|
||||
# TODO: Change this to a unique secret for this server
|
||||
SESSION_SECRET: "TODO:ReplaceWithLongString"
|
||||
|
||||
STRATEGY_LOCAL: "true"
|
||||
DEBUG: "speckle:*"
|
||||
|
||||
POSTGRES_URL: "postgres"
|
||||
POSTGRES_USER: "speckle"
|
||||
POSTGRES_PASSWORD: "speckle"
|
||||
POSTGRES_DB: "speckle"
|
||||
ENABLE_MP: "false"
|
||||
|
||||
networks:
|
||||
default:
|
||||
name: speckle-server
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
redis-data:
|
||||
minio-data:
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "8.0.400",
|
||||
"rollForward": "latestMinor"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
|
||||
|
||||
## Storage Size
|
||||
1 million objects => 540mb ( based on ~= 4.2 million objects => 2.3GB not gzipped)
|
||||
|
||||
1 million objects => 127mb gzipped
|
||||
|
||||
4x reduction in space
|
||||
|
||||
## Local storage takeaways:
|
||||
|
||||
SQLite optimisations make a difference in insertion speed. Insertion speed does slow down on large tables (+1m rows).
|
||||
|
||||
Partioned tables (by, for example, the first two decimals of the hash) have slower but predictable insertion speed. Not sure if compromise is worth it?
|
||||
|
||||
## Even More Optimised single object table
|
||||
Optimisations are:
|
||||
- `PRAGMA journal_mode = MEMORY;`
|
||||
- `PRAGMA synchronous = OFF;`
|
||||
- `PRAGMA count_changes=OFF;`
|
||||
- `PRAGMA temp_store=MEMORY;`
|
||||
|
||||
|
||||
### Test 1
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 2286 ms -> 50000 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 1426 ms -> 100000 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
### Test 2
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 3052 ms -> 33333.333333333336 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 2244 ms -> 50000 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
### Test 3
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 4941 ms -> 25000 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 2555 ms -> 50000 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
### Test 4
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 8022 ms -> 12500 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 3350 ms -> 33333.333333333336 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
|
||||
### Test 5
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 6602 ms -> 16666.666666666668 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 3445 ms -> 33333.333333333336 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
### Test 5+: A couple of more rounds, pushing objs to 2.000k
|
||||
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 7332 ms -> 14285.714285714286 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 7625 ms -> 14285.714285714286 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 7539 ms -> 14285.714285714286 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 4249 ms -> 25000 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 8300 ms -> 12500 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 7289 ms -> 14285.714285714286 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 8668 ms -> 12500 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 8060 ms -> 12500 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
|
||||
|
||||
Starting to save 100000 of objects
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 10228 ms -> 10000 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 11475 ms -> 9090.90909090909 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
|
||||
Starting to save 100000 of objects
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 12540 ms -> 8333.333333333334 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 7113 ms -> 14285.714285714286 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
|
||||
Starting to save 100000 of objects
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 17153 ms -> 5882.35294117647 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 5997 ms -> 20000 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
|
||||
Starting to save 100000 of objects
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 20841 ms -> 5000 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 9195 ms -> 11111.111111111111 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
Starting to save 100000 of objects
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 13404 ms -> 7692.307692307692 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 7529 ms -> 14285.714285714286 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
|
||||
Starting to save 100000 of objects
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 19806 ms -> 5263.1578947368425 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 7318 ms -> 14285.714285714286 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
|
||||
Starting to save 100000 of objects
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 24612 ms -> 4166.666666666667 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 7410 ms -> 14285.714285714286 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
|
||||
Starting to save 100000 of objects
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 22257 ms -> 4545.454545454545 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 18699 ms -> 5555.555555555556 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
|
||||
Starting to save 100000 of objects
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 20947 ms -> 5000 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 14089 ms -> 7142.857142857143 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
|
||||
## Optimised single object table
|
||||
Optimisations are: `PRAGMA journal_mode = MEMORY;` and `PRAGMA synchronous = OFF;`
|
||||
|
||||
### Test 1:
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 2267 ms -> 50000 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 1327 ms -> 100000 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
### Test 2:
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 4532 ms -> 25000 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 2243 ms -> 50000 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
### Test 3:
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 3768 ms -> 33333.333333333336 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 5295 ms -> 20000 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
### Test 4:
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 4033 ms -> 25000 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 3126 ms -> 33333.333333333336 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
### Test 5:
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 4432 ms -> 25000 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 3527 ms -> 33333.333333333336 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
|
||||
## Single object table
|
||||
|
||||
### Test 1:
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 11964 ms -> 9090.90909090909 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 6875 ms -> 16666.666666666668 objects per second
|
||||
-------------------------------------------------
|
||||
200k total in db
|
||||
|
||||
### Test 2:
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 21956 ms -> 4761.9047619047615 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 8904 ms -> 12500 objects per second
|
||||
-------------------------------------------------
|
||||
400k total in db
|
||||
|
||||
### Test 3:
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 25532 ms -> 4000 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 10124 ms -> 10000 objects per second
|
||||
-------------------------------------------------
|
||||
600k total in db
|
||||
|
||||
### Test 4:
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 26629 ms -> 3846.153846153846 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 10610 ms -> 10000 objects per second
|
||||
-------------------------------------------------
|
||||
800k total in db
|
||||
|
||||
### Test 5:
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 26956 ms -> 3846.153846153846 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 11007 ms -> 9090.90909090909 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
1000k total in db
|
||||
|
||||
|
||||
## Bucketed Object Table (256 individual tables for objects):
|
||||
Pre-generate 256 tables, of form `objs${prefix}`, where prefix is the cartesian product of all the valid hex decimals (`0-9, a-f`).
|
||||
|
||||
### Test 1:
|
||||
Forgot to copy paste.
|
||||
200k total in db
|
||||
|
||||
### Test 2:
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 19096 ms -> 5263.1578947368425 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 7401 ms -> 14285.714285714286 objects per second
|
||||
-------------------------------------------------
|
||||
400k total in db
|
||||
|
||||
### Test 3:
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 22477 ms -> 4545.454545454545 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 8668 ms -> 12500 objects per second
|
||||
-------------------------------------------------
|
||||
600k total in db
|
||||
|
||||
### Test 4:
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 23438 ms -> 4347.826086956522 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 9288 ms -> 11111.111111111111 objects per second
|
||||
-------------------------------------------------
|
||||
800k total in db
|
||||
|
||||
### Test 5:
|
||||
-------------------------------------------------
|
||||
BufferedWriteTest: Wrote 100000 in 23735 ms -> 4347.826086956522 objects per second
|
||||
-------------------------------------------------
|
||||
|
||||
-------------------------------------------------
|
||||
BulkWriteMany: Wrote 100000 in 9944 ms -> 11111.111111111111 objects per second
|
||||
-------------------------------------------------
|
||||
1mil total in db
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
[*.{cs,vb}]
|
||||
|
||||
# Name properties with camelCase
|
||||
dotnet_naming_rule.properties_should_be_camel_case.severity = none
|
||||
dotnet_naming_rule.properties_should_be_camel_case.symbols = properties
|
||||
dotnet_naming_rule.properties_should_be_camel_case.style = property_style
|
||||
|
||||
dotnet_naming_symbols.properties.applicable_kinds = property
|
||||
dotnet_naming_style.property_style.capitalization = camel_case
|
||||
@@ -0,0 +1,69 @@
|
||||
using Speckle.Objects.Geometry;
|
||||
using Speckle.Sdk.Models;
|
||||
using Point = Speckle.Objects.Geometry.Point;
|
||||
|
||||
namespace Speckle.Objects.Annotation;
|
||||
|
||||
/// <summary>
|
||||
/// Text class for representation in the viewer
|
||||
/// </summary>
|
||||
[SpeckleType("Objects.Annotation.Text")]
|
||||
public class Text : Base
|
||||
{
|
||||
/// <summary>
|
||||
/// Plain text, without formatting
|
||||
/// </summary>
|
||||
public required string value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Origin point, relation to the text is defined by AlignmentHorizontal and AlignmentVertical
|
||||
/// </summary>
|
||||
public required Point origin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Height in linear units or pixels (if Units.None)
|
||||
/// </summary>
|
||||
public required double height { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Units will be 'Units.None' if the text size is defined in pixels (stays the same size
|
||||
/// independently of zooming the model). Default height in pixels is 17px (used for Viewer measurements)
|
||||
/// </summary>
|
||||
public required string units { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Horizontal alignment: Left, Center or Right
|
||||
/// </summary>
|
||||
public AlignmentHorizontal alignmentH { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Vertical alignment: Top, Center or Bottom
|
||||
/// </summary>
|
||||
public AlignmentVertical alignmentV { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Plane will be null if the text object orientation follows camera view
|
||||
/// </summary>
|
||||
public Plane? plane { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum width of the text field (in 'units').
|
||||
/// Text will be split into lines (wrapped) to fit into the width.
|
||||
/// null, if text should not be wrapped.
|
||||
/// </summary>
|
||||
public double? maxWidth { get; set; }
|
||||
}
|
||||
|
||||
public enum AlignmentHorizontal
|
||||
{
|
||||
Left,
|
||||
Center,
|
||||
Right,
|
||||
}
|
||||
|
||||
public enum AlignmentVertical
|
||||
{
|
||||
Top,
|
||||
Center,
|
||||
Bottom,
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a ArcGIS.Core.CoreObjectsBase object in ArcGIS
|
||||
/// </summary>
|
||||
[SpeckleType("Objects.Data.ArcgisObject")]
|
||||
public class ArcgisObject : DataObject, IGisObject
|
||||
{
|
||||
public required string type { get; set; }
|
||||
|
||||
public required string units { get; set; }
|
||||
|
||||
IReadOnlyList<Base> IDisplayValue<IReadOnlyList<Base>>.displayValue => displayValue;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a base class object in Archicad
|
||||
/// </summary>
|
||||
[SpeckleType("Objects.Data.ArchicadObject")]
|
||||
public class ArchicadObject : DataObject, IArchicadObject
|
||||
{
|
||||
public required string type { get; set; }
|
||||
|
||||
public required string level { get; set; }
|
||||
|
||||
[DetachProperty]
|
||||
public required List<ArchicadObject> elements { get; set; }
|
||||
|
||||
IReadOnlyList<IArchicadObject> IArchicadObject.elements => elements;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Autodesk.Civil.DatabaseServices.Entity object in Civil3d
|
||||
/// </summary>
|
||||
[SpeckleType("Objects.Data.Civil3dObject")]
|
||||
public class Civil3dObject : DataObject, ICivilObject
|
||||
{
|
||||
public required string type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Curves representing the base curve of an entity
|
||||
/// </summary>
|
||||
public required List<ICurve>? baseCurves { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Children objects, eg profiles, this civil entity may contain.
|
||||
/// </summary>
|
||||
[DetachProperty]
|
||||
public required List<Base> elements { get; set; }
|
||||
|
||||
public required string units { get; set; }
|
||||
|
||||
IReadOnlyList<Base> ICivilObject.elements => elements;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Data;
|
||||
|
||||
[SpeckleType("Objects.Data.DataObject")]
|
||||
public class DataObject : Base, IDataObject
|
||||
{
|
||||
public required string name { get; set; }
|
||||
|
||||
[DetachProperty]
|
||||
public required List<Base> displayValue { get; set; }
|
||||
|
||||
public required Dictionary<string, object?> properties { get; set; }
|
||||
|
||||
IReadOnlyList<Base> IDisplayValue<IReadOnlyList<Base>>.displayValue => displayValue;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a wrapper object in ETABS
|
||||
/// </summary>
|
||||
[SpeckleType("Objects.Data.EtabsObject")]
|
||||
public class EtabsObject : DataObject, ICsiObject
|
||||
{
|
||||
public required string type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Children objects, eg joints, this etabs object may contain.
|
||||
/// </summary>
|
||||
[DetachProperty]
|
||||
public required List<EtabsObject> elements { get; set; }
|
||||
|
||||
public required string units { get; set; }
|
||||
|
||||
IReadOnlyList<ICsiObject> ICsiObject.elements => elements;
|
||||
|
||||
IReadOnlyList<Base> IDisplayValue<IReadOnlyList<Base>>.displayValue => displayValue;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a "first selectable ancestor" Navisworks.ModelItem object in Navisworks
|
||||
/// </summary>
|
||||
[SpeckleType("Objects.Data.NavisworksObject")]
|
||||
public class NavisworksObject : DataObject, INavisworksObject
|
||||
{
|
||||
public required string units { get; set; }
|
||||
|
||||
IReadOnlyList<Base> IDisplayValue<IReadOnlyList<Base>>.displayValue => displayValue;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Autodesk.Revit.DB.Element object in Revit
|
||||
/// </summary>
|
||||
[SpeckleType("Objects.Data.RevitObject")]
|
||||
public class RevitObject : DataObject, IRevitObject
|
||||
{
|
||||
public required string type { get; set; }
|
||||
public required string family { get; set; }
|
||||
public required string category { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The level constraint of the object.
|
||||
/// For objects constrained by multiple levels, this represents the base constraint.
|
||||
/// For objects with no level constraint, this should be null.
|
||||
/// </summary>
|
||||
public required string? level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A Curve or Point object representing the location of a Revit element.
|
||||
/// </summary>
|
||||
public required Base? location { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Children objects, eg hosted elements, this RevitObject may contain.
|
||||
/// </summary>
|
||||
[DetachProperty]
|
||||
public required List<RevitObject> elements { get; set; }
|
||||
|
||||
public required string units { get; set; }
|
||||
|
||||
IReadOnlyList<IRevitObject> IRevitObject.elements => elements;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Tekla.Structures.Model.ModelObject object in Tekla Structures
|
||||
/// </summary>
|
||||
[SpeckleType("Objects.Data.TeklaObject")]
|
||||
public class TeklaObject : DataObject, ITeklaObject
|
||||
{
|
||||
public required string type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Children objects, eg profiles, this tekla modelobject may contain.
|
||||
/// </summary>
|
||||
[DetachProperty]
|
||||
public required List<TeklaObject> elements { get; set; }
|
||||
|
||||
public required string units { get; set; }
|
||||
|
||||
IReadOnlyList<ITeklaObject> ITeklaObject.elements => elements;
|
||||
|
||||
IReadOnlyList<Base> IDisplayValue<IReadOnlyList<Base>>.displayValue => displayValue;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Deprecated;
|
||||
|
||||
[SpeckleType("Objects.Deprecated.LegacyV2")]
|
||||
[DeprecatedSpeckleType("Objects.Other.BlockInstance")]
|
||||
[DeprecatedSpeckleType("Objects.Other.Revit.RevitInstance")]
|
||||
[DeprecatedSpeckleType("Objects.BuiltElements.View")]
|
||||
[DeprecatedSpeckleType("Objects.BuiltElements.GridLine")]
|
||||
[DeprecatedSpeckleType("Objects.Other.BlockDefinition")]
|
||||
[DeprecatedSpeckleType("Objects.Other.DisplayStyle")]
|
||||
[DeprecatedSpeckleType("Objects.Other.Material")]
|
||||
[DeprecatedSpeckleType("Objects.Other.MaterialQuantity")]
|
||||
[DeprecatedSpeckleType("Objects.Other.Revit.RevitMaterial")]
|
||||
[DeprecatedSpeckleType("Objects.BuiltElements.Revit.Parameter")]
|
||||
[DeprecatedSpeckleType("Objects.BuiltElements.Revit.Curve.ModelCurve")]
|
||||
[DeprecatedSpeckleType("Objects.BuiltElements.Revit.DirectShape")]
|
||||
public class LegacyV2 : Base { }
|
||||
@@ -0,0 +1,103 @@
|
||||
using Speckle.Objects.Geometry;
|
||||
|
||||
namespace Speckle.Objects;
|
||||
|
||||
public static class CurveTypeEncoding
|
||||
{
|
||||
public const double Arc = 0;
|
||||
public const double Circle = 1;
|
||||
public const double Curve = 2;
|
||||
public const double Ellipse = 3;
|
||||
public const double Line = 4;
|
||||
public const double Polyline = 5;
|
||||
public const double PolyCurve = 6;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class is a helper class for Brep curve encoding!!
|
||||
/// </summary>
|
||||
public static class CurveArrayEncodingExtensions
|
||||
{
|
||||
public static List<double> ToArray(IReadOnlyCollection<ICurve> curves)
|
||||
{
|
||||
var list = new List<double>();
|
||||
foreach (var curve in curves)
|
||||
{
|
||||
switch (curve)
|
||||
{
|
||||
case Arc a:
|
||||
list.AddRange(a.ToList());
|
||||
break;
|
||||
case Circle c:
|
||||
list.AddRange(c.ToList());
|
||||
break;
|
||||
case Curve c:
|
||||
list.AddRange(c.ToList());
|
||||
break;
|
||||
case Ellipse e:
|
||||
list.AddRange(e.ToList());
|
||||
break;
|
||||
case Line l:
|
||||
list.AddRange(l.ToList());
|
||||
break;
|
||||
case Polycurve p:
|
||||
list.AddRange(p.ToList());
|
||||
break;
|
||||
case Polyline p:
|
||||
list.AddRange(p.ToList());
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(curves), $"Unkown curve type: {curve.GetType()}.");
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public static List<ICurve> FromArray(List<double> list)
|
||||
{
|
||||
var curves = new List<ICurve>();
|
||||
if (list.Count == 0)
|
||||
{
|
||||
return curves;
|
||||
}
|
||||
|
||||
var done = false;
|
||||
var currentIndex = 0;
|
||||
|
||||
while (!done)
|
||||
{
|
||||
var itemLength = (int)list[currentIndex];
|
||||
var item = list.GetRange(currentIndex, itemLength + 1);
|
||||
|
||||
switch (item[1])
|
||||
{
|
||||
case CurveTypeEncoding.Arc:
|
||||
curves.Add(Arc.FromList(item));
|
||||
break;
|
||||
case CurveTypeEncoding.Circle:
|
||||
curves.Add(Circle.FromList(item));
|
||||
break;
|
||||
case CurveTypeEncoding.Curve:
|
||||
curves.Add(Curve.FromList(item));
|
||||
break;
|
||||
case CurveTypeEncoding.Ellipse:
|
||||
curves.Add(Ellipse.FromList(item));
|
||||
break;
|
||||
case CurveTypeEncoding.Line:
|
||||
curves.Add(Line.FromList(item));
|
||||
break;
|
||||
case CurveTypeEncoding.Polyline:
|
||||
curves.Add(Polyline.FromList(item));
|
||||
break;
|
||||
case CurveTypeEncoding.PolyCurve:
|
||||
curves.Add(Polycurve.FromList(item));
|
||||
break;
|
||||
}
|
||||
|
||||
currentIndex += itemLength + 1;
|
||||
done = currentIndex >= list.Count;
|
||||
}
|
||||
return curves;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
using Speckle.Newtonsoft.Json;
|
||||
using Speckle.Objects.Other;
|
||||
using Speckle.Objects.Primitive;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a sub-curve of a three-dimensional circle.
|
||||
/// </summary>
|
||||
[SpeckleType("Objects.Geometry.Arc")]
|
||||
public class Arc : Base, IHasBoundingBox, ICurve, ITransformable<Arc>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the plane of the <see cref="Arc"/>.
|
||||
/// The plane origin is the <see cref="Arc"/> center.
|
||||
/// The plane normal indicates the handedness of the <see cref="Arc"/> such that direction from <see cref="startPoint"/> to <see cref="endPoint"/> is counterclockwise.
|
||||
/// </summary>
|
||||
public required Plane plane { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The start <see cref="Point"/> of the <see cref="Arc"/>
|
||||
/// </summary>
|
||||
public required Point startPoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the point at 0.5 length.
|
||||
/// </summary>
|
||||
public required Point midPoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The end <see cref="Point"/> of the <see cref="Arc"/>
|
||||
/// </summary>
|
||||
public required Point endPoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The radius of the <see cref="Arc"/>
|
||||
/// </summary>
|
||||
public double radius => Point.Distance(plane.origin, startPoint);
|
||||
|
||||
/// <summary>
|
||||
/// OBSOLETE - This is just here for backwards compatibility.
|
||||
/// </summary>
|
||||
[JsonIgnore, Obsolete("start angle should be calculated from arc startpoint and plane if needed", true)]
|
||||
public double? startAngle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// OBSOLETE - This is just here for backwards compatibility.
|
||||
/// </summary>
|
||||
[JsonIgnore, Obsolete("end angle should be calculated from arc endpoint and plane if needed", true)]
|
||||
public double? endAngle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// OBSOLETE - This is just here for backwards compatibility.
|
||||
/// </summary>
|
||||
[JsonIgnore, Obsolete("Refer to measure instead", true)]
|
||||
public double angleRadians { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The measure of the <see cref="Arc"/> in radians.
|
||||
/// Calculated using the arc addition postulate using the <see cref="midPoint"/>.
|
||||
/// </summary>
|
||||
public double measure =>
|
||||
(2 * Math.Asin(Point.Distance(startPoint, midPoint) / (2 * radius)))
|
||||
+ (2 * Math.Asin(Point.Distance(midPoint, endPoint) / (2 * radius)));
|
||||
|
||||
/// <summary>
|
||||
/// The units this object was specified in.
|
||||
/// </summary>
|
||||
public required string units { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Interval domain { get; set; } = new() { start = 0, end = 0 };
|
||||
|
||||
/// <summary>
|
||||
/// The length of the <see cref="Arc"/>
|
||||
/// </summary>
|
||||
public double length => radius * measure;
|
||||
|
||||
/// <summary>
|
||||
/// OBSOLETE - This is just here for backwards compatibility.
|
||||
/// </summary>
|
||||
[JsonIgnore, Obsolete("Area property does not belong on an arc", true)]
|
||||
public double area { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Box? bbox { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TransformTo(Transform transform, out Arc transformed)
|
||||
{
|
||||
startPoint.TransformTo(transform, out Point transformedStartPoint);
|
||||
midPoint.TransformTo(transform, out Point transformedMidpoint);
|
||||
endPoint.TransformTo(transform, out Point transformedEndPoint);
|
||||
plane.TransformTo(transform, out Plane pln);
|
||||
Arc arc = new()
|
||||
{
|
||||
startPoint = transformedStartPoint,
|
||||
endPoint = transformedEndPoint,
|
||||
midPoint = transformedMidpoint,
|
||||
plane = pln,
|
||||
domain = domain,
|
||||
units = units,
|
||||
};
|
||||
transformed = arc;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TransformTo(Transform transform, out ITransformable transformed)
|
||||
{
|
||||
var res = TransformTo(transform, out Arc arc);
|
||||
transformed = arc;
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a flat list with the values of the <see cref="Arc"/>
|
||||
/// This is only used for serialisation purposes.
|
||||
/// </summary>
|
||||
/// <returns>A list of numbers representing the <see cref="Arc"/>'s value</returns>
|
||||
public List<double> ToList()
|
||||
{
|
||||
var list = new List<double>();
|
||||
list.Add(radius);
|
||||
list.Add(0); // Backwards compatibility: start angle
|
||||
list.Add(0); // Backwards compatibility: end angle
|
||||
list.Add(measure);
|
||||
list.Add(domain?.start ?? 0);
|
||||
list.Add(domain?.end ?? 0);
|
||||
list.AddRange(plane.ToList());
|
||||
list.AddRange(startPoint.ToList());
|
||||
list.AddRange(midPoint.ToList());
|
||||
list.AddRange(endPoint.ToList());
|
||||
list.Add(Units.GetEncodingFromUnit(units));
|
||||
list.Insert(0, CurveTypeEncoding.Arc);
|
||||
list.Insert(0, list.Count);
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Arc"/> instance based on a flat list of numerical values.
|
||||
/// This is only used for deserialisation purposes.
|
||||
/// </summary>
|
||||
/// <remarks>The input list should be the result of having called <see cref="Arc.ToList"/></remarks>
|
||||
/// <param name="list">A list of numbers</param>
|
||||
/// <returns>A new <see cref="Arc"/> with the values assigned from the list.</returns>
|
||||
public static Arc FromList(List<double> list)
|
||||
{
|
||||
string units = Units.GetUnitFromEncoding(list[^1]);
|
||||
Arc arc = new()
|
||||
{
|
||||
domain = new Interval { start = list[6], end = list[7] },
|
||||
units = units,
|
||||
plane = Plane.FromList(list.GetRange(8, 13)),
|
||||
startPoint = Point.FromList(list.GetRange(21, 3), units),
|
||||
midPoint = Point.FromList(list.GetRange(24, 3), units),
|
||||
endPoint = Point.FromList(list.GetRange(27, 3), units),
|
||||
};
|
||||
|
||||
arc.plane.units = arc.units;
|
||||
return arc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Geometry.Autocad;
|
||||
|
||||
/// <summary>
|
||||
/// A curve that is comprised of line, arc and/or curve segments, representing the Autocad Polyline, Polyline2d, and Polyline3d classes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="AutocadPolyType.Light"/> and <see cref="AutocadPolyType.Simple2d"/> types will have only <see cref="Line"/>s and <see cref="Arc"/>s in <see cref="Polycurve.segments"/>.
|
||||
/// <see cref="AutocadPolyType.Simple3d"/> type will have only <see cref="Line"/>s in <see cref="Polycurve.segments"/>.
|
||||
/// <see cref="AutocadPolyType.FitCurve2d"/> type will only have <see cref="Arc"/>s in <see cref="Polycurve.segments"/>.
|
||||
/// <see cref="AutocadPolyType.CubicSpline2d"/>, <see cref="AutocadPolyType.CubicSpline3d"/>, <see cref="AutocadPolyType.QuadSpline2d"/>, and <see cref="AutocadPolyType.QuadSpline3d"/> types will have only a single <see cref="Curve"/>s in <see cref="Polycurve.segments"/>.
|
||||
/// </remarks>
|
||||
[SpeckleType("Objects.Geometry.Autocad.AutocadPolycurve")]
|
||||
public class AutocadPolycurve : Polycurve
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the raw coordinates of the vertices.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For <see cref="AutocadPolyType.Light"/> Polylines, these are xy coordinates in the Object Coordinate System (OCS)/>.
|
||||
/// For Polyline2d and Polyline3d types, these are xyz coordinates in the Global Coordinate System. fml.
|
||||
/// </remarks>
|
||||
[DetachProperty, Chunkable(31250)]
|
||||
public required List<double> value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The bulge factor at each vertex. Should be null for Polyline3d.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The bulge factor is used to indicate how much of an arc segment is present at this vertex.
|
||||
/// The bulge factor is the tangent of one fourth the included angle for an arc segment,
|
||||
/// made negative if the arc goes clockwise from the start point to the endpoint.
|
||||
/// A bulge of 0 indicates a straight segment, and a bulge of 1 is a semicircle.
|
||||
/// </remarks>
|
||||
public required List<double>? bulges { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The tangent in radians at each vertex. Should be null for Polyline and Polyline3d.
|
||||
/// </summary>
|
||||
public required List<double?>? tangents { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The normal of the plane of the Autocad Polyline or Polyline2d. Should be null for Polyline3d.
|
||||
/// </summary>
|
||||
public required Vector? normal { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The distance from the plane to the origin of the Autocad Polyline or Polyline2d. Should be null for Polyline3d.
|
||||
/// </summary>
|
||||
public double? elevation { get; set; }
|
||||
|
||||
public required AutocadPolyType polyType { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the type of a Autocad Polyline.
|
||||
/// </summary>
|
||||
public enum AutocadPolyType
|
||||
{
|
||||
/// Polyline type is not known
|
||||
Unknown,
|
||||
|
||||
/// Polyline type is the Autocad Polyline class
|
||||
Light,
|
||||
|
||||
Simple2d,
|
||||
|
||||
Simple3d,
|
||||
|
||||
/// The Autocad Polyline2d fit curve poly type. Constructed with pairs of arcs with continuous tangents.
|
||||
FitCurve2d,
|
||||
|
||||
CubicSpline2d,
|
||||
|
||||
CubicSpline3d,
|
||||
|
||||
QuadSpline2d,
|
||||
|
||||
QuadSpline3d,
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using Speckle.Newtonsoft.Json;
|
||||
using Speckle.Objects.Primitive;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a 3-dimensional box oriented on a plane.
|
||||
/// </summary>
|
||||
[SpeckleType("Objects.Geometry.Box")]
|
||||
public class Box : Base, IHasVolume, IHasArea, IHasBoundingBox
|
||||
{
|
||||
[JsonIgnore, Obsolete("Use plane property instead", true)]
|
||||
public Plane basePlane
|
||||
{
|
||||
get => plane;
|
||||
set => plane = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the plane that defines the orientation of the <see cref="Box"/>
|
||||
/// </summary>
|
||||
public required Plane plane { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Interval"/> that defines the min and max coordinate in the X direction
|
||||
/// </summary>
|
||||
public required Interval xSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Interval"/> that defines the min and max coordinate in the Y direction
|
||||
/// </summary>
|
||||
public required Interval ySize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Interval"/> that defines the min and max coordinate in the Y direction
|
||||
/// </summary>
|
||||
public required Interval zSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The units this object's coordinates are in.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This should be one of <see cref="Units"/>
|
||||
/// </remarks>
|
||||
public required string units { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double area => 2 * (xSize.Length * ySize.Length + xSize.Length * zSize.Length + ySize.Length * zSize.Length);
|
||||
|
||||
[JsonIgnore, Obsolete("Boxs should not have a bounding box", true)]
|
||||
public Box? bbox { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double volume => xSize.Length * ySize.Length * zSize.Length;
|
||||
}
|
||||
@@ -0,0 +1,740 @@
|
||||
using System.Runtime.Serialization;
|
||||
using Speckle.Newtonsoft.Json;
|
||||
using Speckle.Objects.Other;
|
||||
using Speckle.Objects.Primitive;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a "Boundary Representation" Solid
|
||||
/// </summary>
|
||||
[SpeckleType("Objects.Geometry.Brep")]
|
||||
public class Brep : Base, IHasArea, IHasVolume, IHasBoundingBox, ITransformable<Brep>, IDisplayValue<List<Mesh>>
|
||||
{
|
||||
/// <summary>
|
||||
/// The unit's this object's coordinates are in.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This should be one of <see cref="Units"/>
|
||||
/// </remarks>
|
||||
public required string units { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of surfaces in this <see cref="Brep"/> instance.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public required List<Surface> Surfaces { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the flat list of numbers representing the <see cref="Brep"/>'s surfaces.
|
||||
/// </summary>
|
||||
[DetachProperty, Chunkable(31250)]
|
||||
public List<double> SurfacesValue
|
||||
{
|
||||
get
|
||||
{
|
||||
var list = new List<double>();
|
||||
foreach (var srf in Surfaces)
|
||||
{
|
||||
list.AddRange(srf.ToList());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var list = new List<Surface>();
|
||||
var done = false;
|
||||
var currentIndex = 0;
|
||||
while (!done)
|
||||
{
|
||||
var len = (int)value[currentIndex];
|
||||
list.Add(Surface.FromList(value.GetRange(currentIndex + 1, len)));
|
||||
currentIndex += len + 1;
|
||||
done = currentIndex >= value.Count;
|
||||
}
|
||||
|
||||
Surfaces = list;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of 3-dimensional curves in this <see cref="Brep"/> instance.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public required List<ICurve> Curve3D { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the flat list of numbers representing the <see cref="Brep"/>'s 3D curves.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is only used for the <see cref="Brep"/> class serialisation/deserialisation. You should use <see cref="Brep.Curve3D"/> instead.
|
||||
/// </remarks>
|
||||
[DetachProperty, Chunkable(31250)]
|
||||
public List<double> Curve3DValues
|
||||
{
|
||||
get => CurveArrayEncodingExtensions.ToArray(Curve3D);
|
||||
set
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
Curve3D = CurveArrayEncodingExtensions.FromArray(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of 2-dimensional UV curves in this <see cref="Brep"/> instance.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public required List<ICurve> Curve2D { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the flat list of numbers representing the <see cref="Brep"/>'s 2D curves.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is only used for the <see cref="Brep"/> class serialisation/deserialisation. You should use <see cref="Brep.Curve2D"/> instead.
|
||||
/// </remarks>
|
||||
[DetachProperty, Chunkable(31250)]
|
||||
public List<double> Curve2DValues
|
||||
{
|
||||
get => CurveArrayEncodingExtensions.ToArray(Curve2D);
|
||||
set
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
Curve2D = CurveArrayEncodingExtensions.FromArray(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of vertices in this <see cref="Brep"/> instance.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public required List<Point> Vertices { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the flat list of numbers representing the <see cref="Brep"/>'s vertices.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is only used for the <see cref="Brep"/> class serialisation/deserialisation. You should use <see cref="Brep.Vertices"/> instead.
|
||||
/// </remarks>
|
||||
[DetachProperty, Chunkable(31250)]
|
||||
public List<double> VerticesValue
|
||||
{
|
||||
get
|
||||
{
|
||||
var list = new List<double>((Vertices.Count * 3) + 1);
|
||||
list.Add(Units.GetEncodingFromUnit(units));
|
||||
foreach (var vertex in Vertices)
|
||||
{
|
||||
list.AddRange(vertex.ToList());
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var units = value.Count % 3 == 0 ? Units.None : Units.GetUnitFromEncoding(value[0]);
|
||||
Vertices = new(value.Count / 3);
|
||||
for (int i = value.Count % 3 == 0 ? 0 : 1; i < value.Count; i += 3)
|
||||
{
|
||||
Vertices.Add(new Point(value[i], value[i + 1], value[i + 2], units));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of edges in this <see cref="Brep"/> instance.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public required List<BrepEdge> Edges { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the flat list of numbers representing the <see cref="Brep"/>'s edges.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is only used for the <see cref="Brep"/> class serialisation/deserialisation. You should use <see cref="Brep.Edges"/> instead.
|
||||
/// </remarks>
|
||||
[DetachProperty, Chunkable(62500)]
|
||||
public List<double?> EdgesValue
|
||||
{
|
||||
get =>
|
||||
Edges
|
||||
.SelectMany(e =>
|
||||
{
|
||||
var ints = new List<double?>();
|
||||
ints.Add(e.Curve3dIndex);
|
||||
ints.Add(e.StartIndex);
|
||||
ints.Add(e.EndIndex);
|
||||
ints.Add(Convert.ToInt32(e.ProxyCurveIsReversed));
|
||||
ints.Add(e.Domain.start);
|
||||
ints.Add(e.Domain.end);
|
||||
ints.AddRange(e.TrimIndices.Select(Convert.ToDouble).Cast<double?>());
|
||||
return ints.Prepend(ints.Count);
|
||||
})
|
||||
.ToList();
|
||||
set
|
||||
{
|
||||
Edges = new List<BrepEdge>();
|
||||
if (value == null || value.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
while (i < value.Count)
|
||||
{
|
||||
int n = Convert.ToInt32(value[i]);
|
||||
|
||||
var loopValues = value.GetRange(i + 1, n);
|
||||
var curve3dIndex = Convert.ToInt32(loopValues[0]);
|
||||
var startIndex = Convert.ToInt32(loopValues[1]);
|
||||
var endIndex = Convert.ToInt32(loopValues[2]);
|
||||
var proxyReversed = Convert.ToBoolean(loopValues[3]);
|
||||
var domainStart = loopValues[4];
|
||||
var domainEnd = loopValues[5];
|
||||
Interval domain =
|
||||
domainStart.HasValue && domainEnd.HasValue
|
||||
? new() { start = domainStart.Value, end = domainEnd.Value }
|
||||
: Interval.UnitInterval;
|
||||
|
||||
var trimIndices = loopValues.GetRange(6, loopValues.Count - 6).Select(d => Convert.ToInt32(d)).ToArray();
|
||||
|
||||
var edge = new BrepEdge
|
||||
{
|
||||
Brep = this,
|
||||
Curve3dIndex = curve3dIndex,
|
||||
TrimIndices = trimIndices,
|
||||
StartIndex = startIndex,
|
||||
EndIndex = endIndex,
|
||||
ProxyCurveIsReversed = proxyReversed,
|
||||
Domain = domain,
|
||||
};
|
||||
|
||||
Edges.Add(edge);
|
||||
i += n + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of closed UV loops in this <see cref="Brep"/> instance.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public required List<BrepLoop> Loops { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the flat list of numbers representing the <see cref="Brep"/>'s loops.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is only used for the <see cref="Brep"/> class serialisation/deserialisation. You should use <see cref="Brep.Loops"/> instead.
|
||||
/// </remarks>
|
||||
[DetachProperty, Chunkable(62500)]
|
||||
public List<int> LoopsValue
|
||||
{
|
||||
get =>
|
||||
Loops
|
||||
.SelectMany(l =>
|
||||
{
|
||||
var ints = new List<int>();
|
||||
ints.Add(l.FaceIndex);
|
||||
ints.Add((int)l.Type);
|
||||
ints.AddRange(l.TrimIndices);
|
||||
return ints.Prepend(ints.Count);
|
||||
})
|
||||
.ToList();
|
||||
set
|
||||
{
|
||||
Loops = new List<BrepLoop>();
|
||||
if (value == null || value.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
while (i < value.Count)
|
||||
{
|
||||
int n = value[i];
|
||||
|
||||
var loopValues = value.GetRange(i + 1, n);
|
||||
var faceIndex = loopValues[0];
|
||||
var type = (BrepLoopType)loopValues[1];
|
||||
var trimIndices = loopValues.GetRange(2, loopValues.Count - 2);
|
||||
var loop = new BrepLoop
|
||||
{
|
||||
Brep = this,
|
||||
FaceIndex = faceIndex,
|
||||
TrimIndices = trimIndices,
|
||||
Type = type,
|
||||
};
|
||||
Loops.Add(loop);
|
||||
i += n + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of UV trim segments for each surface in this <see cref="Brep"/> instance.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public required List<BrepTrim> Trims { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the flat list of numbers representing the <see cref="Brep"/>'s trims.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is only used for the <see cref="Brep"/> class serialisation/deserialisation. You should use <see cref="Brep.Trims"/> instead.
|
||||
/// </remarks>
|
||||
[DetachProperty, Chunkable(62500)]
|
||||
public List<int> TrimsValue
|
||||
{
|
||||
get
|
||||
{
|
||||
List<int> list = new(Trims.Count * TRIMS_ENCODING_LENGTH);
|
||||
foreach (var trim in Trims)
|
||||
{
|
||||
list.Add(trim.EdgeIndex);
|
||||
list.Add(trim.StartIndex);
|
||||
list.Add(trim.EndIndex);
|
||||
list.Add(trim.FaceIndex);
|
||||
list.Add(trim.LoopIndex);
|
||||
list.Add(trim.CurveIndex);
|
||||
list.Add(trim.IsoStatus);
|
||||
list.Add((int)trim.TrimType);
|
||||
list.Add(trim.IsReversed ? 1 : 0);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var list = new List<BrepTrim>(value.Count / TRIMS_ENCODING_LENGTH);
|
||||
for (int i = 0; i < value.Count; i += TRIMS_ENCODING_LENGTH)
|
||||
{
|
||||
var trim = new BrepTrim
|
||||
{
|
||||
Brep = this,
|
||||
EdgeIndex = value[i],
|
||||
StartIndex = value[i + 1],
|
||||
EndIndex = value[i + 2],
|
||||
FaceIndex = value[i + 3],
|
||||
LoopIndex = value[i + 4],
|
||||
CurveIndex = value[i + 5],
|
||||
IsoStatus = value[i + 6],
|
||||
TrimType = (BrepTrimType)value[i + 7],
|
||||
IsReversed = value[i + 8] == 1,
|
||||
Domain = Interval.UnitInterval, //TODO: This is a problem, see CXPLA-28
|
||||
};
|
||||
list.Add(trim);
|
||||
}
|
||||
|
||||
Trims = list;
|
||||
}
|
||||
}
|
||||
|
||||
private const int TRIMS_ENCODING_LENGTH = 9;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of faces in this <see cref="Brep"/> instance.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public required List<BrepFace> Faces { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the flat list of numbers representing the <see cref="Brep"/>'s faces.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is only used for the <see cref="Brep"/> class serialisation/deserialisation. You should use <see cref="Brep.Faces"/> instead.
|
||||
/// </remarks>
|
||||
[DetachProperty, Chunkable(62500)]
|
||||
public List<int> FacesValue
|
||||
{
|
||||
get =>
|
||||
Faces
|
||||
.SelectMany(f =>
|
||||
{
|
||||
var ints = new List<int>();
|
||||
ints.Add(f.SurfaceIndex);
|
||||
ints.Add(f.OuterLoopIndex);
|
||||
ints.Add(f.OrientationReversed ? 1 : 0);
|
||||
ints.AddRange(f.LoopIndices);
|
||||
return ints.Prepend(ints.Count);
|
||||
})
|
||||
.ToList();
|
||||
set
|
||||
{
|
||||
if (value == null || value.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Faces = new List<BrepFace>();
|
||||
|
||||
var i = 0;
|
||||
while (i < value.Count)
|
||||
{
|
||||
int n = value[i];
|
||||
|
||||
var faceValues = value.GetRange(i + 1, n);
|
||||
var surfIndex = faceValues[0];
|
||||
var outerLoopIndex = faceValues[1];
|
||||
var orientationIsReversed = faceValues[2] == 1;
|
||||
var loopIndices = faceValues.GetRange(3, faceValues.Count - 3);
|
||||
var face = new BrepFace
|
||||
{
|
||||
Brep = this,
|
||||
SurfaceIndex = surfIndex,
|
||||
LoopIndices = loopIndices,
|
||||
OuterLoopIndex = outerLoopIndex,
|
||||
OrientationReversed = orientationIsReversed,
|
||||
};
|
||||
Faces.Add(face);
|
||||
i += n + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets if this <see cref="Brep"/> instance is closed or not.
|
||||
/// </summary>
|
||||
public required bool IsClosed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of surfaces in this <see cref="Brep"/> instance.
|
||||
/// </summary>
|
||||
public required BrepOrientation Orientation { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
[DetachProperty]
|
||||
public required List<Mesh> displayValue { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double area { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Box? bbox { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double volume { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TransformTo(Transform transform, out Brep transformed)
|
||||
{
|
||||
// transform display values
|
||||
var displayValues = new List<Mesh>(displayValue.Count);
|
||||
foreach (Mesh v in displayValue)
|
||||
{
|
||||
v.TransformTo(transform, out Mesh mesh);
|
||||
displayValues.Add(mesh);
|
||||
}
|
||||
|
||||
// transform surfaces
|
||||
var surfaces = new List<Surface>(Surfaces.Count);
|
||||
foreach (var srf in Surfaces)
|
||||
{
|
||||
srf.TransformTo(transform, out Surface surface);
|
||||
surfaces.Add(surface);
|
||||
}
|
||||
|
||||
// transform curve3d
|
||||
var success3D = true;
|
||||
var transformedCurve3D = new List<ICurve>();
|
||||
foreach (var curve in Curve3D)
|
||||
{
|
||||
if (curve is ITransformable c)
|
||||
{
|
||||
c.TransformTo(transform, out ITransformable tc);
|
||||
transformedCurve3D.Add((ICurve)tc);
|
||||
}
|
||||
else
|
||||
{
|
||||
success3D = false;
|
||||
}
|
||||
}
|
||||
|
||||
// transform vertices
|
||||
var transformedVertices = new List<Point>(Vertices.Count);
|
||||
foreach (var vertex in Vertices)
|
||||
{
|
||||
vertex.TransformTo(transform, out Point transformedVertex);
|
||||
transformedVertices.Add(transformedVertex);
|
||||
}
|
||||
|
||||
transformed = new Brep
|
||||
{
|
||||
units = units,
|
||||
displayValue = displayValues,
|
||||
Surfaces = surfaces,
|
||||
Curve3D = transformedCurve3D,
|
||||
Curve2D = new List<ICurve>(Curve2D),
|
||||
Vertices = transformedVertices,
|
||||
Edges = new List<BrepEdge>(Edges.Count),
|
||||
Loops = new List<BrepLoop>(Loops.Count),
|
||||
Trims = new List<BrepTrim>(Trims.Count),
|
||||
Faces = new List<BrepFace>(Faces.Count),
|
||||
IsClosed = IsClosed,
|
||||
Orientation = Orientation,
|
||||
applicationId = applicationId ?? id,
|
||||
};
|
||||
|
||||
foreach (var e in Edges)
|
||||
{
|
||||
transformed.Edges.Add(
|
||||
new BrepEdge
|
||||
{
|
||||
Brep = transformed,
|
||||
Curve3dIndex = e.Curve3dIndex,
|
||||
TrimIndices = e.TrimIndices,
|
||||
StartIndex = e.StartIndex,
|
||||
EndIndex = e.EndIndex,
|
||||
ProxyCurveIsReversed = e.ProxyCurveIsReversed,
|
||||
Domain = e.Domain,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
foreach (var l in Loops)
|
||||
{
|
||||
transformed.Loops.Add(
|
||||
new BrepLoop
|
||||
{
|
||||
Brep = transformed,
|
||||
FaceIndex = l.FaceIndex,
|
||||
TrimIndices = l.TrimIndices,
|
||||
Type = l.Type,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
foreach (var t in Trims)
|
||||
{
|
||||
transformed.Trims.Add(
|
||||
new BrepTrim
|
||||
{
|
||||
Brep = transformed,
|
||||
EdgeIndex = t.EdgeIndex,
|
||||
FaceIndex = t.FaceIndex,
|
||||
LoopIndex = t.LoopIndex,
|
||||
CurveIndex = t.CurveIndex,
|
||||
IsoStatus = t.IsoStatus,
|
||||
TrimType = t.TrimType,
|
||||
IsReversed = t.IsReversed,
|
||||
StartIndex = t.StartIndex,
|
||||
EndIndex = t.EndIndex,
|
||||
Domain = null!,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
foreach (var f in Faces)
|
||||
{
|
||||
transformed.Faces.Add(
|
||||
new BrepFace
|
||||
{
|
||||
Brep = transformed,
|
||||
SurfaceIndex = f.SurfaceIndex,
|
||||
LoopIndices = f.LoopIndices,
|
||||
OuterLoopIndex = f.OuterLoopIndex,
|
||||
OrientationReversed = f.OrientationReversed,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return success3D;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TransformTo(Transform transform, out ITransformable transformed)
|
||||
{
|
||||
var res = TransformTo(transform, out Brep brep);
|
||||
transformed = brep;
|
||||
return res;
|
||||
}
|
||||
|
||||
[OnDeserialized]
|
||||
internal void OnDeserialized(StreamingContext context)
|
||||
{
|
||||
Surfaces.ForEach(s => s.units = units);
|
||||
|
||||
for (var i = 0; i < Edges.Count; i++)
|
||||
{
|
||||
var e = Edges[i];
|
||||
var existing = e;
|
||||
lock (existing)
|
||||
{
|
||||
if (e.Brep != null)
|
||||
{
|
||||
e = new BrepEdge
|
||||
{
|
||||
Brep = this,
|
||||
Curve3dIndex = e.Curve3dIndex,
|
||||
TrimIndices = e.TrimIndices,
|
||||
StartIndex = e.StartIndex,
|
||||
EndIndex = e.EndIndex,
|
||||
ProxyCurveIsReversed = e.ProxyCurveIsReversed,
|
||||
Domain = e.Domain,
|
||||
};
|
||||
|
||||
Edges[i] = e;
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Brep = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < Loops.Count; i++)
|
||||
{
|
||||
var l = Loops[i];
|
||||
var existingLoop = l;
|
||||
lock (existingLoop)
|
||||
{
|
||||
if (l.Brep != null)
|
||||
{
|
||||
l = new BrepLoop
|
||||
{
|
||||
Brep = this,
|
||||
FaceIndex = l.FaceIndex,
|
||||
TrimIndices = l.TrimIndices,
|
||||
Type = l.Type,
|
||||
};
|
||||
|
||||
Loops[i] = l;
|
||||
}
|
||||
else
|
||||
{
|
||||
l.Brep = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < Trims.Count; i++)
|
||||
{
|
||||
var t = Trims[i];
|
||||
var existingTrim = t;
|
||||
lock (existingTrim)
|
||||
{
|
||||
if (t.Brep != null)
|
||||
{
|
||||
t = new BrepTrim
|
||||
{
|
||||
Brep = this,
|
||||
EdgeIndex = t.EdgeIndex,
|
||||
LoopIndex = t.LoopIndex,
|
||||
CurveIndex = t.CurveIndex,
|
||||
IsoStatus = t.IsoStatus,
|
||||
TrimType = t.TrimType,
|
||||
IsReversed = t.IsReversed,
|
||||
StartIndex = t.StartIndex,
|
||||
EndIndex = t.EndIndex,
|
||||
FaceIndex = t.FaceIndex,
|
||||
Domain = Interval.UnitInterval, //TODO: This is a problem, see CXPLA-28
|
||||
};
|
||||
Trims[i] = t;
|
||||
}
|
||||
else
|
||||
{
|
||||
t.Brep = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < Faces.Count; i++)
|
||||
{
|
||||
var f = Faces[i];
|
||||
var existingFace = f;
|
||||
lock (existingFace)
|
||||
{
|
||||
if (f.Brep != null)
|
||||
{
|
||||
f = new BrepFace
|
||||
{
|
||||
Brep = this,
|
||||
SurfaceIndex = f.SurfaceIndex,
|
||||
LoopIndices = f.LoopIndices,
|
||||
OuterLoopIndex = f.OuterLoopIndex,
|
||||
OrientationReversed = f.OrientationReversed,
|
||||
};
|
||||
Faces[i] = f;
|
||||
}
|
||||
else
|
||||
{
|
||||
f.Brep = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the orientation of a <see cref="Brep"/>
|
||||
/// </summary>
|
||||
public enum BrepOrientation
|
||||
{
|
||||
/// Brep has no specific orientation
|
||||
None = 0,
|
||||
|
||||
/// Brep faces inward
|
||||
Inward = -1,
|
||||
|
||||
/// Brep faces outward
|
||||
Outward = 1,
|
||||
|
||||
/// Orientation is not known
|
||||
Unknown = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the type of a loop in a <see cref="Brep"/>'s face.
|
||||
/// </summary>
|
||||
public enum BrepLoopType
|
||||
{
|
||||
/// Loop type is not known
|
||||
Unknown,
|
||||
|
||||
/// Loop is the outer loop of a face
|
||||
Outer,
|
||||
|
||||
/// Loop is an inner loop of a face
|
||||
Inner,
|
||||
|
||||
/// Loop is a closed curve with no area.
|
||||
Slit,
|
||||
|
||||
/// Loop represents a curve on a surface
|
||||
CurveOnSurface,
|
||||
|
||||
/// Loop is collapsed to a point.
|
||||
PointOnSurface,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the type of a trim in a <see cref="Brep"/>'s loop.
|
||||
/// </summary>
|
||||
public enum BrepTrimType
|
||||
{
|
||||
Unknown,
|
||||
Boundary,
|
||||
Mated,
|
||||
Seam,
|
||||
Singular,
|
||||
CurveOnSurface,
|
||||
PointOnSurface,
|
||||
Slit,
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using Speckle.Newtonsoft.Json;
|
||||
using Speckle.Objects.Primitive;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an edge of the <see cref="Brep"/>.
|
||||
/// </summary>
|
||||
[SpeckleType("Objects.Geometry.BrepEdge")]
|
||||
public class BrepEdge : Base
|
||||
{
|
||||
[JsonIgnore]
|
||||
public required Brep Brep { get; set; }
|
||||
|
||||
public required int Curve3dIndex { get; set; }
|
||||
public required int[] TrimIndices { get; set; }
|
||||
public required int StartIndex { get; set; }
|
||||
public required int EndIndex { get; set; }
|
||||
|
||||
public required bool ProxyCurveIsReversed { get; set; }
|
||||
|
||||
public required Interval Domain { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public Point StartVertex => Brep.Vertices[StartIndex];
|
||||
|
||||
[JsonIgnore]
|
||||
public Point EndVertex => Brep.Vertices[EndIndex];
|
||||
|
||||
[JsonIgnore]
|
||||
public IEnumerable<BrepTrim> Trims => TrimIndices.Select(i => Brep.Trims[i]);
|
||||
|
||||
[JsonIgnore]
|
||||
public ICurve Curve => Brep.Curve3D[Curve3dIndex];
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using Speckle.Newtonsoft.Json;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a face on a <see cref="Brep"/>
|
||||
/// </summary>
|
||||
[SpeckleType("Objects.Geometry.BrepFace")]
|
||||
public class BrepFace : Base
|
||||
{
|
||||
[JsonIgnore]
|
||||
public required Brep Brep { get; set; }
|
||||
|
||||
public required int SurfaceIndex { get; set; }
|
||||
public required List<int> LoopIndices { get; set; }
|
||||
public required int OuterLoopIndex { get; set; }
|
||||
public required bool OrientationReversed { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public BrepLoop OuterLoop => Brep.Loops[OuterLoopIndex];
|
||||
|
||||
[JsonIgnore]
|
||||
public Surface Surface => Brep.Surfaces[SurfaceIndex];
|
||||
|
||||
[JsonIgnore]
|
||||
public List<BrepLoop> Loops => LoopIndices.Select(i => Brep.Loops[i]).ToList();
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Speckle.Newtonsoft.Json;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a UV Trim Closed Loop on one of the <see cref="Brep"/>'s surfaces.
|
||||
/// </summary>
|
||||
[SpeckleType("Objects.Geometry.BrepLoop")]
|
||||
public class BrepLoop : Base
|
||||
{
|
||||
[JsonIgnore]
|
||||
public required Brep Brep { get; set; }
|
||||
|
||||
public required int FaceIndex { get; set; }
|
||||
public required List<int> TrimIndices { get; set; }
|
||||
public required BrepLoopType Type { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public BrepFace Face => Brep.Faces[FaceIndex];
|
||||
|
||||
[JsonIgnore]
|
||||
public List<BrepTrim> Trims => TrimIndices.Select(i => Brep.Trims[i]).ToList();
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using Speckle.Newtonsoft.Json;
|
||||
using Speckle.Objects.Primitive;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a UV Trim curve for one of the <see cref="Brep"/>'s surfaces.
|
||||
/// </summary>
|
||||
[SpeckleType("Objects.Geometry.BrepTrim")]
|
||||
public class BrepTrim : Base
|
||||
{
|
||||
[JsonIgnore]
|
||||
public required Brep Brep { get; set; }
|
||||
public required int EdgeIndex { get; set; }
|
||||
public required int StartIndex { get; set; }
|
||||
public required int EndIndex { get; set; }
|
||||
public required int FaceIndex { get; set; }
|
||||
public required int LoopIndex { get; set; }
|
||||
public required int CurveIndex { get; set; }
|
||||
public required int IsoStatus { get; set; }
|
||||
public required BrepTrimType TrimType { get; set; }
|
||||
public required bool IsReversed { get; set; }
|
||||
|
||||
public required Interval Domain { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public BrepFace Face => Brep.Faces[FaceIndex];
|
||||
|
||||
[JsonIgnore]
|
||||
public BrepLoop Loop => Brep.Loops[LoopIndex];
|
||||
|
||||
[JsonIgnore]
|
||||
public BrepEdge? Edge => EdgeIndex != -1 ? Brep.Edges[EdgeIndex] : null;
|
||||
|
||||
[JsonIgnore]
|
||||
public ICurve Curve2d => Brep.Curve2D[CurveIndex];
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Speckle.Objects.Other;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Geometry;
|
||||
|
||||
public interface IRawEncodedObject
|
||||
{
|
||||
public RawEncoding encodedValue { get; set; }
|
||||
}
|
||||
|
||||
public abstract class RawEncodedObject : Base, IDisplayValue<List<Mesh>>, IRawEncodedObject, IHasArea, IHasVolume
|
||||
{
|
||||
[DetachProperty]
|
||||
public required List<Mesh> displayValue { get; set; }
|
||||
|
||||
[DetachProperty]
|
||||
public required RawEncoding encodedValue { get; set; }
|
||||
|
||||
public required string units { get; set; }
|
||||
|
||||
public double area { get; set; }
|
||||
|
||||
public double volume { get; set; }
|
||||
}
|
||||
|
||||
[SpeckleType("Objects.Geometry.BrepX")]
|
||||
public class BrepX : RawEncodedObject;
|
||||
|
||||
[SpeckleType("Objects.Geometry.ExtrusionX")]
|
||||
public class ExtrusionX : RawEncodedObject;
|
||||
|
||||
[SpeckleType("Objects.Geometry.SubDX")]
|
||||
public class SubDX : RawEncodedObject;
|
||||
@@ -0,0 +1,82 @@
|
||||
using Speckle.Objects.Primitive;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a circular curve based on a base <see cref="Plane"/> and a <see cref="double"/> as radius.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// These circles are expected to be full (untrimmed) circles.
|
||||
/// For trimmed circles, convert them as <see cref="Arc"/>s instead
|
||||
/// </remarks>
|
||||
[SpeckleType("Objects.Geometry.Circle")]
|
||||
public class Circle : Base, ICurve, IHasArea, IHasBoundingBox
|
||||
{
|
||||
/// <summary>
|
||||
/// The radius of the circle
|
||||
/// </summary>
|
||||
public required double radius { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Plane"/> the circle lies in.
|
||||
/// </summary>
|
||||
public required Plane plane { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The units this object was modeled in.
|
||||
/// </summary>
|
||||
public required string units { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Interval domain { get; set; } = Interval.UnitInterval;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double length => 2 * Math.PI * radius;
|
||||
|
||||
//public Point center { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double area => Math.PI * radius * radius;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Box? bbox { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the coordinates of this <see cref="Circle"/> as a list of numbers
|
||||
/// </summary>
|
||||
/// <returns>A list of values representing the <see cref="Circle"/></returns>
|
||||
public List<double> ToList()
|
||||
{
|
||||
var list = new List<double>();
|
||||
|
||||
list.Add(radius);
|
||||
list.Add(domain.start);
|
||||
list.Add(domain.end);
|
||||
list.AddRange(plane.ToList());
|
||||
|
||||
list.Add(Units.GetEncodingFromUnit(units));
|
||||
list.Insert(0, CurveTypeEncoding.Circle);
|
||||
list.Insert(0, list.Count);
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Circle"/> based on a list of coordinates and the unit they're drawn in.
|
||||
/// </summary>
|
||||
/// <param name="list">The list of values representing this <see cref="Circle"/></param>
|
||||
/// <returns>A new <see cref="Circle"/> with the provided values.</returns>
|
||||
public static Circle FromList(List<double> list)
|
||||
{
|
||||
var circle = new Circle
|
||||
{
|
||||
radius = list[2],
|
||||
domain = new Interval { start = list[3], end = list[4] },
|
||||
plane = Plane.FromList(list.GetRange(5, 13)),
|
||||
units = Units.GetUnitFromEncoding(list[^1]),
|
||||
};
|
||||
|
||||
return circle;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Speckle.Newtonsoft.Json;
|
||||
using Speckle.Objects.Other;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Geometry;
|
||||
|
||||
[SpeckleType("Objects.Geometry.ControlPoint")]
|
||||
public class ControlPoint : Point, ITransformable<ControlPoint>
|
||||
{
|
||||
public ControlPoint() { }
|
||||
|
||||
[SetsRequiredMembers]
|
||||
public ControlPoint(double x, double y, double z, double w, string units, string? applicationId = null)
|
||||
: base(x, y, z, units, applicationId)
|
||||
{
|
||||
weight = w;
|
||||
}
|
||||
|
||||
public required double weight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// OBSOLETE - This is just here for backwards compatibility.
|
||||
/// </summary>
|
||||
[
|
||||
JsonProperty(NullValueHandling = NullValueHandling.Ignore),
|
||||
Obsolete("Access coordinates using XYZ and weight fields", true)
|
||||
]
|
||||
private new List<double> value
|
||||
{
|
||||
#pragma warning disable CS8603 // Possible null reference return. Reason: obsolete.
|
||||
get => null;
|
||||
#pragma warning restore CS8603 // Possible null reference return. Reason: obsolete.
|
||||
set
|
||||
{
|
||||
x = value[0];
|
||||
y = value[1];
|
||||
z = value[2];
|
||||
weight = value.Count > 3 ? value[3] : 1;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TransformTo(Transform transform, out ControlPoint transformed)
|
||||
{
|
||||
TransformTo(transform, out Point transformedPoint);
|
||||
transformed = new ControlPoint(
|
||||
transformedPoint.x,
|
||||
transformedPoint.y,
|
||||
transformedPoint.z,
|
||||
weight,
|
||||
units,
|
||||
applicationId
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{{{x},{y},{z},{weight}}}";
|
||||
}
|
||||
|
||||
public void Deconstruct(out double x, out double y, out double z, out double weight)
|
||||
{
|
||||
Deconstruct(out x, out y, out z, out weight, out _);
|
||||
}
|
||||
|
||||
public void Deconstruct(out double x, out double y, out double z, out double weight, out string? units)
|
||||
{
|
||||
Deconstruct(out x, out y, out z, out units);
|
||||
weight = this.weight;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
using Speckle.Objects.Other;
|
||||
using Speckle.Objects.Primitive;
|
||||
using Speckle.Sdk;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Geometry;
|
||||
|
||||
[SpeckleType("Objects.Geometry.Curve")]
|
||||
public class Curve : Base, ICurve, IHasBoundingBox, IHasArea, ITransformable<Curve>, IDisplayValue<Polyline>
|
||||
{
|
||||
public required int degree { get; set; }
|
||||
|
||||
public required bool periodic { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// "True" if weights differ, "False" if weights are the same.
|
||||
/// </summary>
|
||||
public required bool rational { get; set; }
|
||||
|
||||
[DetachProperty, Chunkable(31250)]
|
||||
public required List<double> points { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the weights for this <see cref="Curve"/>. Use a default value of 1 for unweighted points.
|
||||
/// </summary>
|
||||
[DetachProperty, Chunkable(31250)]
|
||||
public required List<double> weights { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the knots for this <see cref="Curve"/>. Count should be equal to <see cref="points"/> count + <see cref="degree"/> + 1.
|
||||
/// </summary>
|
||||
[DetachProperty, Chunkable(31250)]
|
||||
public required List<double> knots { get; set; }
|
||||
|
||||
public required bool closed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The units this object was specified in.
|
||||
/// </summary>
|
||||
public required string units { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Interval domain { get; set; } = Interval.UnitInterval;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double length { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
[DetachProperty]
|
||||
public required Polyline displayValue { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double area { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Box? bbox { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TransformTo(Transform transform, out Curve transformed)
|
||||
{
|
||||
// transform points
|
||||
var transformedPoints = new List<Point>();
|
||||
foreach (var point in GetPoints())
|
||||
{
|
||||
point.TransformTo(transform, out Point transformedPoint);
|
||||
transformedPoints.Add(transformedPoint);
|
||||
}
|
||||
|
||||
var result = displayValue.TransformTo(transform, out ITransformable polyline);
|
||||
transformed = new Curve
|
||||
{
|
||||
degree = degree,
|
||||
periodic = periodic,
|
||||
rational = rational,
|
||||
points = transformedPoints.SelectMany(o => o.ToList()).ToList(),
|
||||
weights = weights,
|
||||
knots = knots,
|
||||
displayValue = (Polyline)polyline,
|
||||
closed = closed,
|
||||
units = units,
|
||||
applicationId = applicationId,
|
||||
domain = domain != null ? new Interval { start = domain.start, end = domain.end } : Interval.UnitInterval,
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TransformTo(Transform transform, out ITransformable transformed)
|
||||
{
|
||||
var res = TransformTo(transform, out Curve curve);
|
||||
transformed = curve;
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <returns><see cref="points"/> as list of <see cref="Point"/>s</returns>
|
||||
/// <exception cref="SpeckleException">when list is malformed</exception>
|
||||
public List<Point> GetPoints()
|
||||
{
|
||||
if (points.Count % 3 != 0)
|
||||
{
|
||||
throw new SpeckleException(
|
||||
$"{nameof(Curve)}.{nameof(points)} list is malformed: expected length to be multiple of 3"
|
||||
);
|
||||
}
|
||||
|
||||
var pts = new List<Point>(points.Count / 3);
|
||||
for (int i = 2; i < points.Count; i += 3)
|
||||
{
|
||||
pts.Add(new Point(points[i - 2], points[i - 1], points[i], units));
|
||||
}
|
||||
|
||||
return pts;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the values of this <see cref="Curve"/> as a list of numbers.
|
||||
/// </summary>
|
||||
/// <returns>A list of values representing the <see cref="Curve"/></returns>
|
||||
/// <remarks>
|
||||
/// This is currently only used for encoding optimization in curves in breps!
|
||||
/// </remarks>
|
||||
public List<double> ToList()
|
||||
{
|
||||
var list = new List<double>();
|
||||
var curve = this;
|
||||
list.Add(curve.degree); // 0
|
||||
list.Add(curve.periodic ? 1 : 0); // 1
|
||||
list.Add(curve.rational ? 1 : 0); // 2
|
||||
list.Add(curve.closed ? 1 : 0); // 3
|
||||
list.Add(curve.domain?.start ?? 0); // 4
|
||||
list.Add(curve.domain?.end ?? 1); // 5
|
||||
|
||||
list.Add(curve.points.Count); // 6
|
||||
list.Add(curve.weights.Count); // 7
|
||||
list.Add(curve.knots.Count); // 8
|
||||
|
||||
list.AddRange(curve.points); // 9 onwards
|
||||
list.AddRange(curve.weights);
|
||||
list.AddRange(curve.knots);
|
||||
|
||||
list.Add(Units.GetEncodingFromUnit(units));
|
||||
list.Insert(0, CurveTypeEncoding.Curve);
|
||||
list.Insert(0, list.Count);
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Curve"/> based on a list of coordinates and the unit they're drawn in.
|
||||
/// </summary>
|
||||
/// <param name="list">The list of values representing this <see cref="Curve"/></param>
|
||||
/// <returns>A new <see cref="Curve"/> with the provided values.</returns>
|
||||
/// <remarks>
|
||||
/// This is currently being used only for deserialization of Brep curves!
|
||||
/// </remarks>
|
||||
public static Curve FromList(List<double> list)
|
||||
{
|
||||
if ((int)list[0] != list.Count - 1)
|
||||
{
|
||||
throw new ArgumentException($"Incorrect length. Expected {list[0]}, got {list.Count}", nameof(list));
|
||||
}
|
||||
|
||||
if (list[1] != CurveTypeEncoding.Curve)
|
||||
{
|
||||
throw new ArgumentException($"Wrong curve type. Expected {CurveTypeEncoding.Curve}, got {list[1]}", nameof(list));
|
||||
}
|
||||
|
||||
var pointsCount = (int)list[8];
|
||||
var weightsCount = (int)list[9];
|
||||
var knotsCount = (int)list[10];
|
||||
|
||||
string units = Units.GetUnitFromEncoding(list[^1]);
|
||||
var curve = new Curve
|
||||
{
|
||||
degree = (int)list[2],
|
||||
periodic = (int)list[3] == 1,
|
||||
rational = (int)list[4] == 1,
|
||||
closed = (int)list[5] == 1,
|
||||
domain = new Interval { start = list[6], end = list[7] },
|
||||
displayValue = new Polyline { value = new(), units = units }, // this is unique to breps, so we do not create curves with null displayValues
|
||||
points = list.GetRange(11, pointsCount),
|
||||
weights = list.GetRange(11 + pointsCount, weightsCount),
|
||||
knots = list.GetRange(11 + pointsCount + weightsCount, knotsCount),
|
||||
units = units,
|
||||
};
|
||||
|
||||
return curve;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using Speckle.Objects.Primitive;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Geometry;
|
||||
|
||||
[SpeckleType("Objects.Geometry.Ellipse")]
|
||||
public class Ellipse : Base, ICurve, IHasArea
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the first radius of the <see cref="Ellipse"/>. This is usually the major radius.
|
||||
/// </summary>
|
||||
public required double firstRadius { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the second radius of the <see cref="Ellipse"/>. This is usually the minor radius.
|
||||
/// </summary>
|
||||
public required double secondRadius { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the plane to draw this ellipse in.
|
||||
/// </summary>
|
||||
public required Plane plane { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or set the domain interval to trim this <see cref="Ellipse"/> with.
|
||||
/// </summary>
|
||||
public Interval? trimDomain { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Box? bbox { get; set; }
|
||||
|
||||
public required string units { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the domain interval for this <see cref="Ellipse"/>.
|
||||
/// </summary>
|
||||
public required Interval domain { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public double length { get; set; }
|
||||
|
||||
//public Point center { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public double area { get; set; }
|
||||
|
||||
public List<double> ToList()
|
||||
{
|
||||
var list = new List<double>();
|
||||
list.Add(firstRadius);
|
||||
list.Add(secondRadius);
|
||||
list.Add(domain.start);
|
||||
list.Add(domain.end);
|
||||
|
||||
list.AddRange(plane.ToList());
|
||||
|
||||
list.Add(Units.GetEncodingFromUnit(units));
|
||||
list.Insert(0, CurveTypeEncoding.Ellipse);
|
||||
list.Insert(0, list.Count);
|
||||
return list;
|
||||
}
|
||||
|
||||
public static Ellipse FromList(List<double> list)
|
||||
{
|
||||
var ellipse = new Ellipse
|
||||
{
|
||||
firstRadius = list[2],
|
||||
secondRadius = list[3],
|
||||
domain = new Interval { start = list[4], end = list[5] },
|
||||
plane = Plane.FromList(list.GetRange(6, 13)),
|
||||
units = Units.GetUnitFromEncoding(list[^1]),
|
||||
};
|
||||
return ellipse;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Speckle.Newtonsoft.Json;
|
||||
using Speckle.Objects.Other;
|
||||
using Speckle.Objects.Primitive;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Geometry;
|
||||
|
||||
[SpeckleType("Objects.Geometry.Line")]
|
||||
public class Line : Base, ICurve, IHasBoundingBox, ITransformable<Line>
|
||||
{
|
||||
public Line() { }
|
||||
|
||||
/// <param name="coordinates"></param>
|
||||
/// <param name="units"></param>
|
||||
/// <param name="applicationId"></param>
|
||||
/// <exception cref="ArgumentException"><paramref name="coordinates"/> must have a length of 6</exception>
|
||||
[SetsRequiredMembers]
|
||||
public Line(IList<double> coordinates, string units, string? applicationId = null)
|
||||
{
|
||||
if (coordinates.Count < 6)
|
||||
{
|
||||
throw new ArgumentException("Line from coordinate array requires 6 coordinates.", nameof(coordinates));
|
||||
}
|
||||
|
||||
start = new Point(coordinates[0], coordinates[1], coordinates[2], units, applicationId);
|
||||
end = new Point(coordinates[3], coordinates[4], coordinates[5], units, applicationId);
|
||||
this.units = units;
|
||||
this.applicationId = applicationId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OBSOLETE - This is just here for backwards compatibility.
|
||||
/// You should not use this for anything.
|
||||
/// </summary>
|
||||
[JsonIgnore, Obsolete("Area should not be on the line class", true)]
|
||||
public double area => 0;
|
||||
|
||||
public required string units { get; set; }
|
||||
|
||||
public required Point start { get; set; }
|
||||
public required Point end { get; set; }
|
||||
|
||||
public Interval domain { get; set; } = Interval.UnitInterval;
|
||||
public double length => Point.Distance(start, end);
|
||||
|
||||
public Box? bbox { get; set; }
|
||||
|
||||
public bool TransformTo(Transform transform, out Line transformed)
|
||||
{
|
||||
start.TransformTo(transform, out Point transformedStart);
|
||||
end.TransformTo(transform, out Point transformedEnd);
|
||||
transformed = new Line
|
||||
{
|
||||
start = transformedStart,
|
||||
end = transformedEnd,
|
||||
applicationId = applicationId,
|
||||
units = units,
|
||||
domain = new() { start = domain.start, end = domain.end },
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TransformTo(Transform transform, out ITransformable transformed)
|
||||
{
|
||||
var res = TransformTo(transform, out Line line);
|
||||
transformed = line;
|
||||
return res;
|
||||
}
|
||||
|
||||
public List<double> ToList()
|
||||
{
|
||||
var list = new List<double>();
|
||||
list.AddRange(start.ToList());
|
||||
list.AddRange(end.ToList());
|
||||
list.Add(domain?.start ?? 0);
|
||||
list.Add(domain?.end ?? 1);
|
||||
list.Add(Units.GetEncodingFromUnit(units));
|
||||
list.Insert(0, CurveTypeEncoding.Line);
|
||||
list.Insert(0, list.Count);
|
||||
return list;
|
||||
}
|
||||
|
||||
public static Line FromList(IReadOnlyList<double> list)
|
||||
{
|
||||
var units = Units.GetUnitFromEncoding(list[^1]);
|
||||
var startPt = new Point(list[2], list[3], list[4], units);
|
||||
var endPt = new Point(list[5], list[6], list[7], units);
|
||||
var line = new Line
|
||||
{
|
||||
start = startPt,
|
||||
end = endPt,
|
||||
units = units,
|
||||
domain = new Interval { start = list[8], end = list[9] },
|
||||
};
|
||||
return line;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OBSOLETE - This is just here for backwards compatibility.
|
||||
/// You should not use this for anything. Access coordinates using start and end point.
|
||||
/// </summary>
|
||||
[
|
||||
JsonProperty(NullValueHandling = NullValueHandling.Ignore),
|
||||
Obsolete("Access coordinates using start and end point", true)
|
||||
]
|
||||
public List<double>? value
|
||||
{
|
||||
get => null;
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
start = new Point(value[0], value[1], value[2], Units.Meters);
|
||||
end = new Point(value[3], value[4], value[5], Units.Meters);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
using System.Diagnostics.Contracts;
|
||||
using Speckle.Newtonsoft.Json;
|
||||
using Speckle.Objects.Other;
|
||||
using Speckle.Objects.Utils;
|
||||
using Speckle.Sdk;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Geometry;
|
||||
|
||||
/// <remarks><a href="https://speckle.notion.site/Objects-Geometry-Mesh-9b0bf5ab92bf42f58bf2fe3922d2efca">More docs on notion</a></remarks>
|
||||
[SpeckleType("Objects.Geometry.Mesh")]
|
||||
public class Mesh : Base, IHasBoundingBox, IHasVolume, IHasArea, ITransformable<Mesh>
|
||||
{
|
||||
/// <summary>
|
||||
/// Flat list of vertex data (flat <c>x,y,z,x,y,z...</c> list)
|
||||
/// </summary>
|
||||
[DetachProperty, Chunkable(31250)]
|
||||
public required List<double> vertices { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Flat list of face data<br/>
|
||||
/// Each face starts with the length of the face (e.g. 3 in the case of triangles), followed by that many indices
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// N-gons are supported, but large values of n (> ~50) tend to cause significant performance problems for consumers (e.g. HostApps and <see cref="MeshTriangulationHelper"/>.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code>[
|
||||
/// 3, 0, 1, 2, //first face, a triangle (3-gon)
|
||||
/// 4, 1, 2, 3, 4, //second face, a quad (4-gon)
|
||||
/// 6, 4, 5, 6, 7, 8, 9, //third face, an n-gon (6-gon)
|
||||
/// ];</code></example>
|
||||
[DetachProperty, Chunkable(62500)]
|
||||
public required List<int> faces { get; set; }
|
||||
|
||||
/// <summary>Vertex colors as ARGB <see cref="int"/>s</summary>
|
||||
/// <remarks>Expected that there are either 1 color per vertex, or an empty <see cref="List{T}"/></remarks>
|
||||
[DetachProperty, Chunkable(62500)]
|
||||
public List<int> colors { get; set; } = new();
|
||||
|
||||
/// <summary>Flat list of texture coordinates (flat <c>u,v,u,v,u,v...</c> list)</summary>
|
||||
/// <remarks>Expected that there are either 1 texture coordinate per vertex, or an empty <see cref="List{T}"/></remarks>
|
||||
[DetachProperty, Chunkable(31250)]
|
||||
public List<double> textureCoordinates { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// <summary>Flat list of vertex normal data (flat <c>x,y,z,x,y,z...</c> list)</summary>
|
||||
/// <remarks>Expected that there are either 1 texture coordinate per vertex, or an empty <see cref="List{T}"/></remarks>
|
||||
/// </summary>
|
||||
[DetachProperty, Chunkable(31250)]
|
||||
public List<double> vertexNormals { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The unit's this <see cref="Mesh"/> is in.
|
||||
/// This should be one of <see cref="Units"/>
|
||||
/// </summary>
|
||||
public required string units { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double area { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Box? bbox { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double volume { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Transform(Transform transform)
|
||||
{
|
||||
// transform vertices
|
||||
vertices = GetPoints()
|
||||
.SelectMany(vertex =>
|
||||
{
|
||||
vertex.TransformTo(transform, out Point transformedVertex);
|
||||
return transformedVertex.ToList();
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TransformTo(Transform transform, out Mesh transformed)
|
||||
{
|
||||
// transform vertices
|
||||
var transformedVertices = new List<Point>();
|
||||
foreach (var vertex in GetPoints())
|
||||
{
|
||||
vertex.TransformTo(transform, out Point transformedVertex);
|
||||
transformedVertices.Add(transformedVertex);
|
||||
}
|
||||
|
||||
transformed = new Mesh
|
||||
{
|
||||
vertices = transformedVertices.SelectMany(o => o.ToList()).ToList(),
|
||||
textureCoordinates = textureCoordinates,
|
||||
applicationId = applicationId ?? id,
|
||||
faces = faces,
|
||||
colors = colors,
|
||||
units = units,
|
||||
};
|
||||
transformed["renderMaterial"] = this["renderMaterial"];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TransformTo(Transform transform, out ITransformable transformed)
|
||||
{
|
||||
var res = TransformTo(transform, out Mesh brep);
|
||||
transformed = brep;
|
||||
return res;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public int VerticesCount => vertices.Count / 3;
|
||||
|
||||
[JsonIgnore]
|
||||
public int TextureCoordinatesCount => textureCoordinates.Count / 2;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a vertex as a <see cref="Point"/> by <paramref name="index"/>
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the vertex</param>
|
||||
/// <returns>Vertex as a <see cref="Point"/></returns>
|
||||
/// <remarks>It is usually recommended to instead consume the <see cref="vertices"/> list manually for better performance</remarks>
|
||||
[Pure]
|
||||
public Point GetPoint(int index)
|
||||
{
|
||||
index *= 3;
|
||||
return new Point(vertices[index], vertices[index + 1], vertices[index + 2], units, applicationId);
|
||||
}
|
||||
|
||||
/// <returns><see cref="vertices"/> as list of <see cref="Point"/>s</returns>
|
||||
/// <exception cref="SpeckleException">when list is malformed</exception>
|
||||
/// <remarks>It is usually recommended to instead consume the <see cref="vertices"/> list manually for better performance</remarks>
|
||||
[Pure]
|
||||
public List<Point> GetPoints()
|
||||
{
|
||||
if (vertices.Count % 3 != 0)
|
||||
{
|
||||
throw new SpeckleException(
|
||||
$"{nameof(Mesh)}.{nameof(vertices)} list is malformed: expected length to be multiple of 3"
|
||||
);
|
||||
}
|
||||
|
||||
var pts = new List<Point>(vertices.Count / 3);
|
||||
for (int i = 2; i < vertices.Count; i += 3)
|
||||
{
|
||||
pts.Add(new Point(vertices[i - 2], vertices[i - 1], vertices[i], units));
|
||||
}
|
||||
|
||||
return pts;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a texture coordinate as a <see cref="ValueTuple{T1, T2}"/> by <paramref name="index"/>
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the texture coordinate</param>
|
||||
/// <returns>Texture coordinate as a <see cref="ValueTuple{T1, T2}"/></returns>
|
||||
[Pure]
|
||||
public (double, double) GetTextureCoordinate(int index)
|
||||
{
|
||||
index *= 2;
|
||||
return (textureCoordinates[index], textureCoordinates[index + 1]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
using Speckle.Objects.Other;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// A 3-dimensional Plane consisting of an origin <see cref="Point"/>, and 3 <see cref="Vector"/> as its X, Y and Z axis.
|
||||
/// </summary>
|
||||
[SpeckleType("Objects.Geometry.Plane")]
|
||||
public class Plane : Base, ITransformable<Plane>
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="Plane"/>s origin point.
|
||||
/// </summary>
|
||||
public required Point origin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Plane"/>s Z axis.
|
||||
/// </summary>
|
||||
public required Vector normal { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Plane"/>s X axis.
|
||||
/// </summary>
|
||||
public required Vector xdir { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Plane"/>s Y axis.
|
||||
/// </summary>
|
||||
public required Vector ydir { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The unit's this <see cref="Plane"/> is in.
|
||||
/// This should be one of <see cref="Units"/>
|
||||
/// </summary>
|
||||
public required string units { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TransformTo(Transform transform, out Plane transformed)
|
||||
{
|
||||
origin.TransformTo(transform, out Point transformedOrigin);
|
||||
normal.TransformTo(transform, out Vector transformedNormal);
|
||||
xdir.TransformTo(transform, out Vector transformedXdir);
|
||||
ydir.TransformTo(transform, out Vector transformedYdir);
|
||||
transformed = new Plane
|
||||
{
|
||||
origin = transformedOrigin,
|
||||
normal = transformedNormal,
|
||||
xdir = transformedXdir,
|
||||
ydir = transformedYdir,
|
||||
applicationId = applicationId,
|
||||
units = units,
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TransformTo(Transform transform, out ITransformable transformed)
|
||||
{
|
||||
var res = TransformTo(transform, out Plane plane);
|
||||
transformed = plane;
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the values of this <see cref="Plane"/> as a list of numbers
|
||||
/// </summary>
|
||||
/// <returns>A list of values representing the Plane.</returns>
|
||||
|
||||
public List<double> ToList()
|
||||
{
|
||||
var list = new List<double>();
|
||||
|
||||
list.AddRange(origin.ToList());
|
||||
list.AddRange(normal.ToList());
|
||||
list.AddRange(xdir.ToList());
|
||||
list.AddRange(ydir.ToList());
|
||||
list.Add(Units.GetEncodingFromUnit(units));
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Plane"/> based on a list of values and the unit they're drawn in.
|
||||
/// </summary>
|
||||
/// <param name="list">The list of values representing this plane</param>
|
||||
/// <returns>A new <see cref="Plane"/> with the provided values.</returns>
|
||||
public static Plane FromList(IReadOnlyList<double> list)
|
||||
{
|
||||
var units = Units.GetUnitFromEncoding(list[^1]);
|
||||
var plane = new Plane
|
||||
{
|
||||
origin = new Point(list[0], list[1], list[2], units),
|
||||
normal = new Vector(list[3], list[4], list[5], units),
|
||||
xdir = new Vector(list[6], list[7], list[8], units),
|
||||
ydir = new Vector(list[9], list[10], list[11], units),
|
||||
units = units,
|
||||
};
|
||||
|
||||
return plane;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Speckle.Newtonsoft.Json;
|
||||
using Speckle.Objects.Other;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// A 3-dimensional point
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// TODO: The Point class does not override the Equality operator, which means that there may be cases where `Equals` is used instead of `==`, as the comparison will be done by reference, not value.
|
||||
/// </remarks>
|
||||
[SpeckleType("Objects.Geometry.Point")]
|
||||
public class Point : Base, ITransformable<Point>, IEquatable<Point>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public Point() { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new <see cref="Point"/> from a set of coordinates and it's units.
|
||||
/// </summary>
|
||||
/// <param name="x">The x coordinate</param>
|
||||
/// <param name="y">The y coordinate</param>
|
||||
/// <param name="z">The z coordinate</param>
|
||||
/// <param name="units">The units of the point's coordinates. Defaults to Meters. </param>
|
||||
/// <param name="applicationId">The object's unique application ID</param>
|
||||
[SetsRequiredMembers]
|
||||
public Point(double x, double y, double z, string units, string? applicationId = null)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
this.applicationId = applicationId;
|
||||
this.units = units;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The x coordinate of the point.
|
||||
/// </summary>
|
||||
public required double x { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The y coordinate of the point.
|
||||
/// </summary>
|
||||
public required double y { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The z coordinate of the point.
|
||||
/// </summary>
|
||||
public required double z { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The units this <see cref="Point"/> is in.
|
||||
/// This should be one of the units specified in <see cref="Units"/>
|
||||
/// </summary>
|
||||
public required string units { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TransformTo(Transform transform, out Point transformed)
|
||||
{
|
||||
var matrix = transform.matrix;
|
||||
|
||||
var unitFactor = Units.GetConversionFactor(transform.units, units); // applied to translation vector
|
||||
var divisor = matrix.M41 + matrix.M42 + matrix.M43 + unitFactor * matrix.M44;
|
||||
var x = (this.x * matrix.M11 + this.y * matrix.M12 + this.z * matrix.M13 + unitFactor * matrix.M14) / divisor;
|
||||
var y = (this.x * matrix.M21 + this.y * matrix.M22 + this.z * matrix.M23 + unitFactor * matrix.M24) / divisor;
|
||||
var z = (this.x * matrix.M31 + this.y * matrix.M32 + this.z * matrix.M33 + unitFactor * matrix.M34) / divisor;
|
||||
|
||||
transformed = new Point(x, y, z, units, applicationId);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TransformTo(Transform transform, out ITransformable transformed)
|
||||
{
|
||||
var res = TransformTo(transform, out Point pt);
|
||||
transformed = pt;
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the coordinates of this <see cref="Point"/> as a list of numbers
|
||||
/// </summary>
|
||||
/// <returns>A list of coordinates {x, y, z} </returns>
|
||||
public List<double> ToList()
|
||||
{
|
||||
return new List<double> { x, y, z };
|
||||
}
|
||||
|
||||
public Vector ToVector()
|
||||
{
|
||||
return new Vector(x, y, z, units, applicationId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Point"/> based on a list of coordinates and the unit they're drawn in.
|
||||
/// </summary>
|
||||
/// <param name="list">The list of coordinates {x, y, z}</param>
|
||||
/// <param name="units">The units the coordinates are in</param>
|
||||
/// <returns>A new <see cref="Point"/> with the provided coordinates.</returns>
|
||||
public static Point FromList(IList<double> list, string units)
|
||||
{
|
||||
return new Point(list[0], list[1], list[2], units);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deconstructs a <see cref="Point"/> into it's coordinates and units
|
||||
/// </summary>
|
||||
/// <param name="x">The x coordinate</param>
|
||||
/// <param name="y">The y coordinate</param>
|
||||
/// <param name="z">The z coordinate</param>
|
||||
/// <param name="units">The units the point's coordinates are in.</param>
|
||||
public void Deconstruct(out double x, out double y, out double z, out string? units)
|
||||
{
|
||||
Deconstruct(out x, out y, out z);
|
||||
units = this.units;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deconstructs a <see cref="Point"/> into it's coordinates and units
|
||||
/// </summary>
|
||||
/// <param name="x">The x coordinate</param>
|
||||
/// <param name="y">The y coordinate</param>
|
||||
/// <param name="z">The z coordinate</param>
|
||||
public void Deconstruct(out double x, out double y, out double z)
|
||||
{
|
||||
x = this.x;
|
||||
y = this.y;
|
||||
z = this.z;
|
||||
}
|
||||
|
||||
public static Point operator +(Point point1, Point point2) =>
|
||||
new(point1.x + point2.x, point1.y + point2.y, point1.z + point2.z, point1.units);
|
||||
|
||||
public static Point operator -(Point point1, Point point2) =>
|
||||
new(point1.x - point2.x, point1.y - point2.y, point1.z - point2.z, point1.units);
|
||||
|
||||
public static Point operator *(Point point1, Point point2) =>
|
||||
new(point1.x * point2.x, point1.y * point2.y, point1.z * point2.z, point1.units);
|
||||
|
||||
public static Point operator *(Point point, double val) =>
|
||||
new(point.x * val, point.y * val, point.z * val, point.units);
|
||||
|
||||
public static Point operator /(Point point, double val) =>
|
||||
new(point.x / val, point.y / val, point.z / val, point.units);
|
||||
|
||||
public static bool operator ==(Point? point1, Point? point2)
|
||||
{
|
||||
if (point1 is null && point2 is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (point1 is null || point2 is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return point1.units == point2.units && point1.x == point2.x && point1.y == point2.y && point1.z == point2.z;
|
||||
}
|
||||
|
||||
public static bool operator !=(Point? point1, Point? point2) => !(point1 == point2);
|
||||
|
||||
/// <summary>
|
||||
/// Computes a point equidistant from two points.
|
||||
/// </summary>
|
||||
/// <param name="point1">First point.</param>
|
||||
/// <param name="point2">Second point.</param>
|
||||
/// <returns>A point at the same distance from <paramref name="point1"/> and <paramref name="point2"/></returns>
|
||||
public static Point Midpoint(Point point1, Point point2)
|
||||
{
|
||||
return new Point(
|
||||
0.5 * (point1.x + point2.x),
|
||||
0.5 * (point1.y + point2.y),
|
||||
0.5 * (point1.z + point2.z),
|
||||
point1.units
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the distance between two points
|
||||
/// </summary>
|
||||
/// <param name="point1">First point.</param>
|
||||
/// <param name="point2">Second point.</param>
|
||||
/// <returns>The distance from <paramref name="point1"/> to <paramref name="point2"/></returns>
|
||||
public static double Distance(Point point1, Point point2)
|
||||
{
|
||||
return Math.Sqrt(
|
||||
Math.Pow(point1.x - point2.x, 2) + Math.Pow(point1.y - point2.y, 2) + Math.Pow(point1.z - point2.z, 2)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the distance between two points.
|
||||
/// </summary>
|
||||
/// <param name="point">point for distance measurement</param>
|
||||
/// <returns>The length of the line between this and the other point</returns>
|
||||
public double DistanceTo(Point point)
|
||||
{
|
||||
return Math.Sqrt(Math.Pow(x - point.x, 2) + Math.Pow(y - point.y, 2) + Math.Pow(z - point.z, 2));
|
||||
}
|
||||
|
||||
public bool Equals(Point? other) => this == other;
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (ReferenceEquals(this, obj))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (obj is Point p)
|
||||
{
|
||||
return this == p;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
#if NETSTANDARD2_0
|
||||
return HashCode.Of(units).And(x).And(y).And(y);
|
||||
#else
|
||||
return HashCode.Combine(units, x, y, z);
|
||||
#endif
|
||||
}
|
||||
|
||||
[Obsolete($"Use {nameof(Vector.ToPoint)}", true)]
|
||||
public Point(Vector _) { }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the coordinates of the <see cref="Point"/>
|
||||
/// </summary>
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore), Obsolete("Use x,y,z properties instead", true)]
|
||||
public List<double> value
|
||||
{
|
||||
get => null!;
|
||||
set
|
||||
{
|
||||
x = value[0];
|
||||
y = value[1];
|
||||
z = value.Count > 2 ? value[2] : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
using Speckle.Objects.Other;
|
||||
using Speckle.Sdk;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// A collection of points, with color and size support.
|
||||
/// </summary>
|
||||
[SpeckleType("Objects.Geometry.Pointcloud")]
|
||||
public class Pointcloud : Base, IHasBoundingBox, ITransformable<Pointcloud>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the list of points of this <see cref="Pointcloud"/>, stored as a flat list of coordinates [x1,y1,z1,x2,y2,...]
|
||||
/// </summary>
|
||||
[DetachProperty, Chunkable(31250)]
|
||||
public required List<double> points { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of colors of this <see cref="Pointcloud"/>'s points., stored as ARGB <see cref="int"/>s.
|
||||
/// </summary>
|
||||
[DetachProperty, Chunkable(62500)]
|
||||
public List<int> colors { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of sizes of this <see cref="Pointcloud"/>'s points.
|
||||
/// </summary>
|
||||
[DetachProperty, Chunkable(62500)]
|
||||
public List<double> sizes { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The unit's this <see cref="Pointcloud"/> is in.
|
||||
/// This should be one of <see cref="Units"/>
|
||||
/// </summary>
|
||||
public required string units { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Box? bbox { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TransformTo(Transform transform, out Pointcloud transformed)
|
||||
{
|
||||
// transform points
|
||||
var transformedPoints = new List<Point>();
|
||||
foreach (var point in GetPoints())
|
||||
{
|
||||
point.TransformTo(transform, out Point transformedPoint);
|
||||
transformedPoints.Add(transformedPoint);
|
||||
}
|
||||
|
||||
transformed = new Pointcloud
|
||||
{
|
||||
units = units,
|
||||
points = transformedPoints.SelectMany(o => o.ToList()).ToList(),
|
||||
colors = colors,
|
||||
sizes = sizes,
|
||||
applicationId = applicationId,
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TransformTo(Transform transform, out ITransformable transformed)
|
||||
{
|
||||
var res = TransformTo(transform, out Pointcloud pc);
|
||||
transformed = pc;
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <returns><see cref="points"/> as list of <see cref="Point"/>s</returns>
|
||||
/// <exception cref="SpeckleException">when list is malformed</exception>
|
||||
public List<Point> GetPoints()
|
||||
{
|
||||
if (points.Count % 3 != 0)
|
||||
{
|
||||
throw new SpeckleException(
|
||||
$"{nameof(Pointcloud)}.{nameof(points)} list is malformed: expected length to be multiple of 3"
|
||||
);
|
||||
}
|
||||
|
||||
var pts = new List<Point>(points.Count / 3);
|
||||
for (int i = 2; i < points.Count; i += 3)
|
||||
{
|
||||
pts.Add(new Point(points[i - 2], points[i - 1], points[i], units));
|
||||
}
|
||||
|
||||
return pts;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
using Speckle.Objects.Other;
|
||||
using Speckle.Objects.Primitive;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// A curve that is comprised of multiple curves connected.
|
||||
/// </summary>
|
||||
[SpeckleType("Objects.Geometry.Polycurve")]
|
||||
public class Polycurve : Base, ICurve, IHasArea, IHasBoundingBox, ITransformable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the list of segments that comprise this <see cref="Polycurve"/>
|
||||
/// </summary>
|
||||
public required List<ICurve> segments { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a Boolean value indicating if the <see cref="Polycurve"/> is closed
|
||||
/// (i.e. The start point of the first segment and the end point of the last segment coincide.)
|
||||
/// </summary>
|
||||
public bool closed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The unit's this <see cref="Polycurve"/> is in.
|
||||
/// This should be one of <see cref="Units"/>
|
||||
/// </summary>
|
||||
public required string units { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The internal domain of this curve.
|
||||
/// </summary>
|
||||
public Interval domain { get; set; } = Interval.UnitInterval;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double length { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double area { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Box? bbox { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TransformTo(Transform transform, out ITransformable polycurve)
|
||||
{
|
||||
// transform segments
|
||||
var success = true;
|
||||
var transformed = new List<ICurve>();
|
||||
foreach (var curve in segments)
|
||||
{
|
||||
if (curve is ITransformable c)
|
||||
{
|
||||
c.TransformTo(transform, out ITransformable tc);
|
||||
transformed.Add((ICurve)tc);
|
||||
}
|
||||
else
|
||||
{
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
polycurve = new Polycurve
|
||||
{
|
||||
segments = transformed,
|
||||
applicationId = applicationId,
|
||||
closed = closed,
|
||||
units = units,
|
||||
};
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new <see cref="Polycurve"/> instance from an existing <see cref="Polyline"/> curve.
|
||||
/// </summary>
|
||||
/// <param name="polyline">The polyline to be used when constructing the <see cref="Polycurve"/></param>
|
||||
/// <returns>A <see cref="Polycurve"/> with the same shape as the provided polyline.</returns>
|
||||
public static implicit operator Polycurve(Polyline polyline)
|
||||
{
|
||||
Polycurve polycurve = new()
|
||||
{
|
||||
segments = new(),
|
||||
units = polyline.units,
|
||||
area = polyline.area,
|
||||
domain = polyline.domain,
|
||||
closed = polyline.closed,
|
||||
bbox = polyline.bbox,
|
||||
length = polyline.length,
|
||||
};
|
||||
|
||||
var points = polyline.GetPoints();
|
||||
for (var i = 0; i < points.Count - 1; i++)
|
||||
{
|
||||
var line = new Line
|
||||
{
|
||||
start = points[i],
|
||||
end = points[i + 1],
|
||||
units = polyline.units,
|
||||
};
|
||||
polycurve.segments.Add(line);
|
||||
}
|
||||
|
||||
if (polyline.closed)
|
||||
{
|
||||
var line = new Line
|
||||
{
|
||||
start = points[^1],
|
||||
end = points[0],
|
||||
units = polyline.units,
|
||||
};
|
||||
polycurve.segments.Add(line);
|
||||
}
|
||||
|
||||
return polycurve;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the values of this <see cref="Polycurve"/> as a list of numbers
|
||||
/// </summary>
|
||||
/// <returns>A list of values representing the polycurve.</returns>
|
||||
public List<double> ToList()
|
||||
{
|
||||
var list = new List<double>();
|
||||
list.Add(closed ? 1 : 0);
|
||||
list.Add(domain.start);
|
||||
list.Add(domain.end);
|
||||
|
||||
var crvs = CurveArrayEncodingExtensions.ToArray(segments);
|
||||
list.Add(crvs.Count);
|
||||
list.AddRange(crvs);
|
||||
|
||||
list.Add(Units.GetEncodingFromUnit(units));
|
||||
list.Insert(0, CurveTypeEncoding.PolyCurve);
|
||||
list.Insert(0, list.Count);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Polycurve"/> based on a list of coordinates and the unit they're drawn in.
|
||||
/// </summary>
|
||||
/// <param name="list">The list of values representing this polycurve</param>
|
||||
/// <returns>A new <see cref="Polycurve"/> with the provided values.</returns>
|
||||
public static Polycurve FromList(List<double> list)
|
||||
{
|
||||
var temp = list.GetRange(6, (int)list[5]);
|
||||
var polycurve = new Polycurve
|
||||
{
|
||||
segments = CurveArrayEncodingExtensions.FromArray(temp),
|
||||
closed = (int)list[2] == 1,
|
||||
domain = new Interval { start = list[3], end = list[4] },
|
||||
units = Units.GetUnitFromEncoding(list[^1]),
|
||||
};
|
||||
|
||||
return polycurve;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
using Speckle.Objects.Other;
|
||||
using Speckle.Objects.Primitive;
|
||||
using Speckle.Sdk;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// A polyline curve, defined by a set of vertices.
|
||||
/// </summary>
|
||||
[SpeckleType("Objects.Geometry.Polyline")]
|
||||
public class Polyline : Base, ICurve, IHasArea, IHasBoundingBox, ITransformable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the raw coordinates that define this polyline. Use GetPoints instead to access this data as <see cref="Point"/> instances instead.
|
||||
/// </summary>
|
||||
[DetachProperty, Chunkable(31250)]
|
||||
public required List<double> value { get; set; }
|
||||
|
||||
/// <remarks>
|
||||
/// If true, do not add the last point to the value list. Polyline first and last points should be unique.
|
||||
/// </remarks>
|
||||
public bool closed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The unit's this <see cref="Polyline"/> is in.
|
||||
/// This should be one of <see cref="Units"/>
|
||||
/// </summary>
|
||||
public required string units { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The internal domain of this curve.
|
||||
/// </summary>
|
||||
public Interval domain { get; set; } = Interval.UnitInterval;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double length { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double area { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Box? bbox { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TransformTo(Transform transform, out ITransformable transformed)
|
||||
{
|
||||
// transform points
|
||||
var transformedPoints = new List<Point>();
|
||||
foreach (var point in GetPoints())
|
||||
{
|
||||
point.TransformTo(transform, out Point transformedPoint);
|
||||
transformedPoints.Add(transformedPoint);
|
||||
}
|
||||
|
||||
transformed = new Polyline
|
||||
{
|
||||
value = transformedPoints.SelectMany(o => o.ToList()).ToList(),
|
||||
closed = closed,
|
||||
applicationId = applicationId,
|
||||
units = units,
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
///<remarks>This function may be suboptimal for performance for polylines with many points</remarks>
|
||||
/// <returns><see cref="value"/> as List of <see cref="Point"/>s</returns>
|
||||
/// <exception cref="SpeckleException">when list is malformed</exception>
|
||||
public List<Point> GetPoints()
|
||||
{
|
||||
if (value.Count % 3 != 0)
|
||||
{
|
||||
throw new SpeckleException(
|
||||
$"{nameof(Polyline)}.{nameof(value)} list is malformed: expected length to be multiple of 3"
|
||||
);
|
||||
}
|
||||
|
||||
var pts = new List<Point>(value.Count / 3);
|
||||
for (int i = 2; i < value.Count; i += 3)
|
||||
{
|
||||
pts.Add(new Point(value[i - 2], value[i - 1], value[i], units));
|
||||
}
|
||||
|
||||
return pts;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the values of this <see cref="Polyline"/> as a list of numbers
|
||||
/// </summary>
|
||||
/// <returns>A list of values representing the polyline.</returns>
|
||||
public List<double> ToList()
|
||||
{
|
||||
var list = new List<double>();
|
||||
list.Add(closed ? 1 : 0); // 2
|
||||
list.Add(domain?.start ?? 0); // 3
|
||||
list.Add(domain?.end ?? 1); // 4
|
||||
list.Add(value.Count); // 5
|
||||
list.AddRange(value); // 6 onwards
|
||||
|
||||
list.Add(Units.GetEncodingFromUnit(units));
|
||||
list.Insert(0, CurveTypeEncoding.Polyline); // 1
|
||||
list.Insert(0, list.Count); // 0
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Polyline"/> based on a list of coordinates and the unit they're drawn in.
|
||||
/// </summary>
|
||||
/// <param name="list">The list of values representing this polyline</param>
|
||||
/// <returns>A new <see cref="Polyline"/> with the provided values.</returns>
|
||||
public static Polyline FromList(List<double> list)
|
||||
{
|
||||
int pointCount = (int)list[5];
|
||||
return new()
|
||||
{
|
||||
closed = (int)list[2] == 1,
|
||||
domain = new Interval { start = list[3], end = list[4] },
|
||||
value = list.GetRange(6, pointCount),
|
||||
units = Units.GetUnitFromEncoding(list[^1]),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace Speckle.Objects.Geometry;
|
||||
|
||||
public static class PolylineExtensions
|
||||
{
|
||||
public static IEnumerable<Line> EnumerateAsLines(this Polyline polyline)
|
||||
{
|
||||
List<Point> points = polyline.GetPoints();
|
||||
if (points.Count == 0)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
Point previousPoint = points[0];
|
||||
for (int i = 1; i < points.Count; i++)
|
||||
{
|
||||
yield return new Line()
|
||||
{
|
||||
start = previousPoint,
|
||||
end = points[i],
|
||||
units = polyline.units,
|
||||
};
|
||||
previousPoint = points[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
using Speckle.Objects.Other;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// Flat polygon, defined by an outer boundary and inner loops.
|
||||
/// </summary>
|
||||
[SpeckleType("Objects.Geometry.Region")]
|
||||
public class Region : Base, IHasArea, IHasBoundingBox, ITransformable, IDisplayValue<List<Mesh>>
|
||||
{
|
||||
/// <summary>
|
||||
/// Boundary of a region.
|
||||
/// Should be a planar, closed, non-self-intersecting ICurve.
|
||||
/// </summary>
|
||||
public required ICurve boundary { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Loops (voids) in the region.
|
||||
/// Each loop should be planar, closed, non-self-intersecting ICurve, located inside the boundary.
|
||||
/// The loops should not intersect or touch each other.
|
||||
/// </summary>
|
||||
public required List<ICurve> innerLoops { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The units of object's coordinates.
|
||||
/// This should be one of <see cref="Units"/>
|
||||
/// </summary>
|
||||
public required string units { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indication whether the region is just a geometry (false) or has a hatch pattern (true).
|
||||
/// It's a distinction for receiving in apps that support both Region and Hatch (aka region with hatch pattern)
|
||||
/// </summary>
|
||||
public required bool hasHatchPattern { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double area { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Box? bbox { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
[DetachProperty]
|
||||
public List<Mesh> displayValue { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TransformTo(Transform transform, out ITransformable transformed)
|
||||
{
|
||||
// assign self to the returned object, in case transformation fails
|
||||
transformed = this;
|
||||
|
||||
// transform boundary
|
||||
if (boundary is ITransformable boundaryTransformable)
|
||||
{
|
||||
boundaryTransformable.TransformTo(transform, out ITransformable transformedBoundaryResult);
|
||||
var transformedBoundary = (ICurve)transformedBoundaryResult;
|
||||
|
||||
// transform inner loops
|
||||
var transformedLoops = new List<ICurve>();
|
||||
foreach (var loop in innerLoops)
|
||||
{
|
||||
if (loop is ITransformable loopTransformable)
|
||||
{
|
||||
loopTransformable.TransformTo(transform, out ITransformable transformedLoop);
|
||||
transformedLoops.Add((ICurve)transformedLoop);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// transform display meshes
|
||||
var transformedMeshes = new List<Mesh>();
|
||||
foreach (var mesh in displayValue)
|
||||
{
|
||||
mesh.TransformTo(transform, out ITransformable transformedMesh);
|
||||
transformedMeshes.Add((Mesh)transformedMesh);
|
||||
}
|
||||
|
||||
// if boundary and loops transformations succeeded
|
||||
transformed = new Region
|
||||
{
|
||||
boundary = transformedBoundary,
|
||||
innerLoops = transformedLoops,
|
||||
hasHatchPattern = hasHatchPattern,
|
||||
displayValue = transformedMeshes,
|
||||
units = units,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using Speckle.Objects.Primitive;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Geometry;
|
||||
|
||||
public enum SpiralType
|
||||
{
|
||||
Biquadratic,
|
||||
BiquadraticParabola,
|
||||
Bloss,
|
||||
Clothoid,
|
||||
Cosine,
|
||||
Cubic,
|
||||
CubicParabola,
|
||||
Radioid,
|
||||
Sinusoid,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
[SpeckleType("Objects.Geometry.Spiral")]
|
||||
public class Spiral : Base, ICurve, IHasBoundingBox, IDisplayValue<Polyline>
|
||||
{
|
||||
public required Point startPoint { get; set; }
|
||||
public required Point endPoint { get; set; }
|
||||
public required Plane plane { get; set; } // plane with origin at spiral center
|
||||
public required double turns { get; set; } // total angle of spiral. positive is counterclockwise, negative is clockwise
|
||||
public required Vector pitchAxis { get; set; } = new(0, 0, 0, Units.None);
|
||||
public required double pitch { get; set; }
|
||||
public required SpiralType spiralType { get; set; }
|
||||
|
||||
public required string units { get; set; }
|
||||
|
||||
public required double length { get; set; }
|
||||
|
||||
public required Interval domain { get; set; }
|
||||
|
||||
[DetachProperty]
|
||||
public required Polyline displayValue { get; set; }
|
||||
|
||||
public Box? bbox { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Speckle.Objects.Other;
|
||||
using Speckle.Objects.Primitive;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// A Surface in NURBS form.
|
||||
/// </summary>
|
||||
[SpeckleType("Objects.Geometry.Surface")]
|
||||
public class Surface : Base, IHasBoundingBox, IHasArea, ITransformable<Surface>
|
||||
{
|
||||
[Obsolete("Constructor should only be used by serializer, use one of the other constructors instead")]
|
||||
public Surface()
|
||||
{
|
||||
pointData = [];
|
||||
}
|
||||
|
||||
public Surface(List<List<ControlPoint>> controlPoints)
|
||||
{
|
||||
SetControlPoints(controlPoints);
|
||||
}
|
||||
|
||||
public Surface(IList<double> pointData, int countU, int countV)
|
||||
{
|
||||
this.pointData = pointData;
|
||||
this.countU = countU;
|
||||
this.countV = countV;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The degree of the surface in the U direction
|
||||
/// </summary>
|
||||
public required int degreeU { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The degree of the surface in the V direction
|
||||
/// </summary>
|
||||
public required int degreeV { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the <see cref="Surface"/> is rational.
|
||||
/// </summary>
|
||||
public required bool rational { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The raw data of the surface's control points. Use <see cref="GetControlPoints"/> or <see cref="SetControlPoints"/> instead of accessing this directly.
|
||||
/// </summary>
|
||||
public IList<double> pointData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of control points in the U direction
|
||||
/// </summary>
|
||||
public int countU { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of control points in the V direction
|
||||
/// </summary>
|
||||
public int countV { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The knot vector in the U direction
|
||||
/// </summary>
|
||||
public required List<double> knotsU { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The knot vector in the V direction
|
||||
/// </summary>
|
||||
public required List<double> knotsV { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The surface's domain in the U direction
|
||||
/// </summary>
|
||||
public required Interval domainU { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The surface's domain in the V direction
|
||||
/// </summary>
|
||||
public required Interval domainV { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a surface is closed around the <see cref="domainU"/>.
|
||||
/// </summary>
|
||||
public required bool closedU { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a surface is closed around the <see cref="domainV"/>
|
||||
/// </summary>
|
||||
public required bool closedV { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The unit's this <see cref="Surface"/> is in.
|
||||
/// This should be one of <see cref="Units"/>
|
||||
/// </summary>
|
||||
public required string units { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double area { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Box? bbox { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TransformTo(Transform transform, out Surface transformed)
|
||||
{
|
||||
var ptMatrix = GetControlPoints();
|
||||
foreach (var ctrlPts in ptMatrix)
|
||||
{
|
||||
for (int i = 0; i < ctrlPts.Count; i++)
|
||||
{
|
||||
ctrlPts[i].TransformTo(transform, out var tPt);
|
||||
ctrlPts[i] = tPt;
|
||||
}
|
||||
}
|
||||
|
||||
transformed = new Surface(ptMatrix)
|
||||
{
|
||||
degreeU = degreeU,
|
||||
degreeV = degreeV,
|
||||
countU = countU,
|
||||
countV = countV,
|
||||
rational = rational,
|
||||
closedU = closedU,
|
||||
closedV = closedV,
|
||||
domainU = domainU,
|
||||
domainV = domainV,
|
||||
knotsU = knotsU,
|
||||
knotsV = knotsV,
|
||||
units = units,
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TransformTo(Transform transform, out ITransformable transformed)
|
||||
{
|
||||
var res = TransformTo(transform, out Surface surface);
|
||||
transformed = surface;
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the control points of this s<see cref="Surface"/>
|
||||
/// </summary>
|
||||
/// <returns>A 2-dimensional array representing this <see cref="Surface"/>s control points.</returns>
|
||||
/// <remarks>The ControlPoints will be ordered following directions "[u][v]"</remarks>
|
||||
|
||||
public List<List<ControlPoint>> GetControlPoints()
|
||||
{
|
||||
var matrix = new List<List<ControlPoint>>();
|
||||
for (var i = 0; i < countU; i++)
|
||||
{
|
||||
matrix.Add(new List<ControlPoint>());
|
||||
}
|
||||
|
||||
for (var i = 0; i < pointData.Count; i += 4)
|
||||
{
|
||||
var uIndex = i / (countV * 4);
|
||||
matrix[uIndex].Add(new ControlPoint(pointData[i], pointData[i + 1], pointData[i + 2], pointData[i + 3], units));
|
||||
}
|
||||
|
||||
return matrix;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the control points of this <see cref="Surface"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">A 2-dimensional array of <see cref="ControlPoint"/> instances.</param>
|
||||
/// <remarks>The <paramref name="value"/> must be ordered following directions "[u][v]"</remarks>
|
||||
[MemberNotNull(nameof(pointData))]
|
||||
[MemberNotNull(nameof(countU))]
|
||||
[MemberNotNull(nameof(countV))]
|
||||
public void SetControlPoints(List<List<ControlPoint>> value)
|
||||
{
|
||||
List<double> data = new();
|
||||
countU = value.Count;
|
||||
countV = value[0].Count;
|
||||
value.ForEach(row =>
|
||||
row.ForEach(pt =>
|
||||
{
|
||||
data.Add(pt.x);
|
||||
data.Add(pt.y);
|
||||
data.Add(pt.z);
|
||||
data.Add(pt.weight);
|
||||
})
|
||||
);
|
||||
pointData = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the coordinates of this <see cref="Surface"/> as a list of numbers
|
||||
/// </summary>
|
||||
/// <returns>A list of values representing the surface</returns>
|
||||
public List<double> ToList()
|
||||
{
|
||||
var list = new List<double>();
|
||||
list.Add(degreeU);
|
||||
list.Add(degreeV);
|
||||
list.Add(countU);
|
||||
list.Add(countV);
|
||||
list.Add(rational ? 1 : 0);
|
||||
list.Add(closedU ? 1 : 0);
|
||||
list.Add(closedV ? 1 : 0);
|
||||
list.Add(domainU.start); // 7
|
||||
list.Add(domainU.end);
|
||||
list.Add(domainV.start);
|
||||
list.Add(domainV.end); // [0] 10
|
||||
|
||||
list.Add(pointData.Count); // 11
|
||||
list.Add(knotsU.Count); // 12
|
||||
list.Add(knotsV.Count); // 13
|
||||
|
||||
list.AddRange(pointData);
|
||||
list.AddRange(knotsU);
|
||||
list.AddRange(knotsV);
|
||||
|
||||
list.Add(Units.GetEncodingFromUnit(units));
|
||||
list.Insert(0, list.Count);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Surface"/> based on a list of coordinates and the unit they're drawn in.
|
||||
/// </summary>
|
||||
/// <param name="list">The list of values representing this surface</param>
|
||||
/// <returns>A new <see cref="Surface"/> with the provided values.</returns>
|
||||
public static Surface FromList(List<double> list)
|
||||
{
|
||||
var pointCount = (int)list[11];
|
||||
var knotsUCount = (int)list[12];
|
||||
var knotsVCount = (int)list[13];
|
||||
var countU = (int)list[2];
|
||||
var countV = (int)list[3];
|
||||
|
||||
var pointData = list.GetRange(14, pointCount);
|
||||
var u = list[^1];
|
||||
|
||||
return new Surface(pointData, countU, countV)
|
||||
{
|
||||
degreeU = (int)list[0],
|
||||
degreeV = (int)list[1],
|
||||
rational = list[4] == 1,
|
||||
closedU = list[5] == 1,
|
||||
closedV = list[6] == 1,
|
||||
domainU = new Interval { start = list[7], end = list[8] },
|
||||
domainV = new Interval { start = list[9], end = list[10] },
|
||||
knotsU = list.GetRange(14 + pointCount, knotsUCount),
|
||||
knotsV = list.GetRange(14 + pointCount + knotsUCount, knotsVCount),
|
||||
units = Units.GetUnitFromEncoding(u),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Speckle.Newtonsoft.Json;
|
||||
using Speckle.Objects.Other;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// A 3-dimensional vector
|
||||
/// </summary>
|
||||
[SpeckleType("Objects.Geometry.Vector")]
|
||||
public class Vector : Base, IHasBoundingBox, ITransformable<Vector>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public Vector() { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new 2D <see cref="Vector"/> from it's X and Y coordinates.
|
||||
/// </summary>
|
||||
/// <param name="x">The x coordinate of the vector</param>
|
||||
/// <param name="y">The y coordinate of the vector</param>
|
||||
/// <param name="units">The units the coordinates are in.</param>
|
||||
/// <param name="applicationId">The unique application ID of the object.</param>
|
||||
[SetsRequiredMembers]
|
||||
public Vector(double x, double y, double z, string units, string? applicationId = null)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
this.applicationId = applicationId;
|
||||
this.units = units;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The unit's this <see cref="Vector"/> is in.
|
||||
/// This should be one of <see cref="Units"/>
|
||||
/// </summary>
|
||||
public required string units { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The x coordinate of the vector.
|
||||
/// </summary>
|
||||
public required double x { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The y coordinate of the vector.
|
||||
/// </summary>
|
||||
public required double y { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The z coordinate of the vector.
|
||||
/// </summary>
|
||||
public required double z { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Euclidean length of this vector.
|
||||
/// </summary>
|
||||
/// <returns>Length of the vector.</returns>
|
||||
[JsonIgnore]
|
||||
public double Length => Math.Sqrt(DotProduct(this, this));
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Box? bbox { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TransformTo(Transform transform, out Vector transformed)
|
||||
{
|
||||
var m = transform.matrix;
|
||||
var tX = x * m.M11 + y * m.M12 + z * m.M13;
|
||||
var tY = x * m.M21 + y * m.M22 + z * m.M23;
|
||||
var tZ = x * m.M31 + y * m.M32 + z * m.M33;
|
||||
transformed = new Vector(tX, tY, tZ, units, applicationId);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TransformTo(Transform transform, out ITransformable transformed)
|
||||
{
|
||||
_ = TransformTo(transform, out Vector vec);
|
||||
transformed = vec;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the coordinates of this <see cref="Vector"/> as a list of numbers
|
||||
/// </summary>
|
||||
/// <returns>A list of coordinates {x, y, z} </returns>
|
||||
public List<double> ToList() => [x, y, z];
|
||||
|
||||
public Point ToPoint() => new(x, y, z, units, applicationId);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new vector based on a list of coordinates and the unit they're drawn in.
|
||||
/// </summary>
|
||||
/// <param name="list">The list of coordinates {x, y, z}</param>
|
||||
/// <param name="units">The units the coordinates are in</param>
|
||||
/// <returns>A new <see cref="Vector"/> with the provided coordinates.</returns>
|
||||
public static Vector FromList(IReadOnlyList<double> list, string units)
|
||||
{
|
||||
return new Vector(list[0], list[1], list[2], units);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Divides a vector by a numerical value. This will divide each coordinate by the provided value.
|
||||
/// </summary>
|
||||
/// <param name="vector">The vector to divide</param>
|
||||
/// <param name="val">The value to divide by</param>
|
||||
/// <returns>The resulting <see cref="Vector"/></returns>
|
||||
public static Vector operator /(Vector vector, double val) =>
|
||||
new(vector.x / val, vector.y / val, vector.z / val, vector.units);
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies a vector by a numerical value. This will multiply each coordinate by the provided value.
|
||||
/// </summary>
|
||||
/// <param name="vector">The vector to multiply</param>
|
||||
/// <param name="val">The value to multiply by</param>
|
||||
/// <returns>The resulting <see cref="Vector"/></returns>
|
||||
public static Vector operator *(Vector vector, double val) =>
|
||||
new(vector.x * val, vector.y * val, vector.z * val, vector.units);
|
||||
|
||||
/// <summary>
|
||||
/// Adds two vectors by adding each of their coordinates.
|
||||
/// </summary>
|
||||
/// <param name="vector1">The first vector</param>
|
||||
/// <param name="vector2">The second vector</param>
|
||||
/// <returns>The resulting <see cref="Vector"/></returns>
|
||||
public static Vector operator +(Vector vector1, Vector vector2) =>
|
||||
new(vector1.x + vector2.x, vector1.y + vector2.y, vector1.z + vector2.z, vector1.units);
|
||||
|
||||
/// <summary>
|
||||
/// Subtracts two vectors by subtracting each of their coordinates.
|
||||
/// </summary>
|
||||
/// <param name="vector1">The first vector</param>
|
||||
/// <param name="vector2">The second vector</param>
|
||||
/// <returns>The resulting <see cref="Vector"/></returns>
|
||||
public static Vector operator -(Vector vector1, Vector vector2) =>
|
||||
new(vector1.x - vector2.x, vector1.y - vector2.y, vector1.z - vector2.z, vector1.units);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the scalar product (dot product) of two given vectors
|
||||
/// Dot product = u1*v1 + u2*v2 + u3*v3.
|
||||
/// </summary>
|
||||
/// <param name="u">First vector.</param>
|
||||
/// <param name="v">Second vector.</param>
|
||||
/// <returns>Numerical value of the dot product.</returns>
|
||||
public static double DotProduct(Vector u, Vector v)
|
||||
{
|
||||
return u.x * v.x + u.y * v.y + u.z * v.z;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the vector product (cross product) of two given vectors
|
||||
/// Cross product = { u2 * v3 - u3 * v2; u3 * v1 - u1 * v3; u1 * v2 - u2 * v1 }.
|
||||
/// </summary>
|
||||
/// <param name="u">First vector.</param>
|
||||
/// <param name="v">Second vector.</param>
|
||||
/// <returns>Vector result of the cross product.</returns>
|
||||
public static Vector CrossProduct(Vector u, Vector v)
|
||||
{
|
||||
if (u.units != v.units && u.units != Units.None && v.units != Units.None)
|
||||
{
|
||||
throw new ArgumentException("Cannot perform cross product on two vectors with different unit systems");
|
||||
}
|
||||
|
||||
var x = u.y * v.z - u.z * v.y;
|
||||
var y = u.z * v.x - u.x * v.z;
|
||||
var z = u.x * v.y - u.y * v.x;
|
||||
|
||||
return new Vector(x, y, z, units: u.units);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute and return a unit vector from this vector
|
||||
/// </summary>
|
||||
/// <returns>a normalized unit vector</returns>
|
||||
public void Normalize()
|
||||
{
|
||||
var length = Length;
|
||||
x /= length;
|
||||
y /= length;
|
||||
z /= length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inverses the direction of the vector, equivalent to multiplying by -1
|
||||
/// </summary>
|
||||
/// <returns>A pointing in the opposite direction</returns>
|
||||
public Vector Negate()
|
||||
{
|
||||
x *= -1;
|
||||
y *= -1;
|
||||
z *= -1;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the coordinates of the vector
|
||||
/// </summary>
|
||||
[
|
||||
JsonProperty(NullValueHandling = NullValueHandling.Ignore),
|
||||
Obsolete("Use X,Y,Z fields to access coordinates instead", true)
|
||||
]
|
||||
public List<double> value
|
||||
{
|
||||
#pragma warning disable CS8603 // Possible null reference return.
|
||||
get => null;
|
||||
#pragma warning restore CS8603 // Possible null reference return.
|
||||
set
|
||||
{
|
||||
x = value[0];
|
||||
y = value[1];
|
||||
z = value.Count > 2 ? value[2] : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
using Speckle.Objects.Geometry;
|
||||
using Speckle.Objects.Other;
|
||||
using Speckle.Objects.Primitive;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects;
|
||||
|
||||
#region Generic interfaces.
|
||||
|
||||
/// <summary>
|
||||
/// Represents an object that has a <see cref="IHasBoundingBox.bbox"/>
|
||||
/// </summary>
|
||||
public interface IHasBoundingBox : ISpeckleObject
|
||||
{
|
||||
/// <summary>
|
||||
/// The bounding box containing the object.
|
||||
/// </summary>
|
||||
Box? bbox { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a <see cref="Base"/> object that has <see cref="IHasArea.area"/>
|
||||
/// </summary>
|
||||
public interface IHasArea : ISpeckleObject
|
||||
{
|
||||
/// <summary>
|
||||
/// The area of the object
|
||||
/// </summary>
|
||||
double area { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an object that has <see cref="IHasVolume.volume"/>
|
||||
/// </summary>
|
||||
public interface IHasVolume : ISpeckleObject
|
||||
{
|
||||
/// <summary>
|
||||
/// The volume of the object
|
||||
/// </summary>
|
||||
double volume { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents
|
||||
/// </summary>
|
||||
public interface ICurve : ISpeckleObject
|
||||
{
|
||||
/// <summary>
|
||||
/// The length of the curve.
|
||||
/// </summary>
|
||||
double length { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The numerical domain driving the curve's internal parametrization.
|
||||
/// </summary>
|
||||
Interval domain { get; }
|
||||
|
||||
string units { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generic Interface for transformable objects.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of object to support transformations.</typeparam>
|
||||
public interface ITransformable<T> : ITransformable
|
||||
where T : ITransformable<T>
|
||||
{
|
||||
/// <inheritdoc cref="ITransformable.TransformTo"/>
|
||||
bool TransformTo(Transform transform, out T transformed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for transformable objects where the type may not be known on convert (eg ICurve implementations)
|
||||
/// </summary>
|
||||
public interface ITransformable : ISpeckleObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a copy of the object with it's coordinates transformed by the provided <paramref name="transform"/>
|
||||
/// </summary>
|
||||
/// <param name="transform">The <see cref="Transform"/> to be applied.</param>
|
||||
/// <param name="transformed">The transformed copy of the object.</param>
|
||||
/// <returns>True if the transform operation was successful, false otherwise.</returns>
|
||||
bool TransformTo(Transform transform, out ITransformable transformed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies displayable <see cref="Base"/> simple geometries to be used as a fallback
|
||||
/// if a displayable form cannot be converted.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <see cref="Base"/> objects that represent conceptual / abstract / mathematically derived geometry
|
||||
/// can use <see cref="displayValue"/> to be used in case the object lacks a natively displayable form.
|
||||
/// (e.g <see cref="Spiral"/>)
|
||||
/// </example>
|
||||
/// <typeparam name="T">
|
||||
/// Type of display value.
|
||||
/// Expected to be either a <see cref="Base"/> type or a <see cref="List{T}"/> of <see cref="Base"/>s,
|
||||
/// Should be constrained to types of <see cref="Point"/>, <see cref="Line"/>, <see cref="Mesh"/> or <see cref="Polyline"/>.
|
||||
/// </typeparam>
|
||||
public interface IDisplayValue<out T> : ISpeckleObject
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="displayValue"/> <see cref="Base"/>(s) will be used to display this <see cref="Base"/>
|
||||
/// if a native displayable object cannot be converted.
|
||||
/// </summary>
|
||||
T displayValue { get; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Data objects
|
||||
|
||||
/// <summary>
|
||||
/// Specifies properties on objects to be used for data-based workflows
|
||||
/// </summary>
|
||||
public interface IProperties : ISpeckleObject
|
||||
{
|
||||
Dictionary<string, object?> properties { get; }
|
||||
}
|
||||
|
||||
public interface IDataObject : IProperties, IDisplayValue<IReadOnlyList<Base>>
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the object, primarily used to decorate the object for consumption in frontend and other apps
|
||||
/// </summary>
|
||||
string name { get; }
|
||||
}
|
||||
|
||||
public interface IRevitObject : IDataObject
|
||||
{
|
||||
string type { get; }
|
||||
|
||||
string family { get; }
|
||||
|
||||
string category { get; }
|
||||
|
||||
Base? location { get; }
|
||||
|
||||
IReadOnlyList<IRevitObject> elements { get; }
|
||||
}
|
||||
|
||||
public interface ICivilObject : IDataObject
|
||||
{
|
||||
string type { get; }
|
||||
|
||||
List<ICurve>? baseCurves { get; }
|
||||
|
||||
IReadOnlyList<Base> elements { get; }
|
||||
}
|
||||
|
||||
public interface ITeklaObject : IDataObject
|
||||
{
|
||||
string type { get; }
|
||||
|
||||
IReadOnlyList<ITeklaObject> elements { get; }
|
||||
}
|
||||
|
||||
public interface ICsiObject : IDataObject
|
||||
{
|
||||
string type { get; }
|
||||
|
||||
IReadOnlyList<ICsiObject> elements { get; }
|
||||
}
|
||||
|
||||
public interface IGisObject : IDataObject
|
||||
{
|
||||
string type { get; }
|
||||
}
|
||||
|
||||
public interface IArchicadObject : IDataObject
|
||||
{
|
||||
string type { get; }
|
||||
|
||||
string level { get; }
|
||||
|
||||
IReadOnlyList<IArchicadObject> elements { get; }
|
||||
}
|
||||
|
||||
public interface INavisworksObject : IDataObject { }
|
||||
|
||||
|
||||
#endregion
|
||||
@@ -0,0 +1,23 @@
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Other;
|
||||
|
||||
/// <summary>
|
||||
/// Keeps track of a raw-encoded object in a native supported format. see <see cref="RawEncodingFormats"/>
|
||||
/// </summary>
|
||||
[SpeckleType("Objects.Other.RawEncoding")]
|
||||
public class RawEncoding : Base // note: at this stage, since we're using this for extrusions and subds the name doesn't make sense anymore
|
||||
{
|
||||
public required string format { get; set; }
|
||||
public required string contents { get; set; }
|
||||
|
||||
public RawEncoding() { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Supported encoding types "strongly" typed strings. This needs to match the extension of the file format.
|
||||
/// </summary>
|
||||
public static class RawEncodingFormats
|
||||
{
|
||||
public const string RHINO_3DM = "3dm";
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using System.Drawing;
|
||||
using Speckle.Newtonsoft.Json;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Models.Proxies;
|
||||
|
||||
namespace Speckle.Objects.Other;
|
||||
|
||||
/// <summary>
|
||||
/// Minimal physically based material DTO class. Based on references from
|
||||
/// https://threejs.org/docs/index.html#api/en/materials/MeshStandardMaterial
|
||||
/// Theoretically has equivalents in Unity and Unreal.
|
||||
///
|
||||
/// See: https://docs.unrealengine.com/en-US/RenderingAndGraphics/Materials/PhysicallyBased/index.html
|
||||
/// And: https://blogs.unity3d.com/2014/10/29/physically-based-shading-in-unity-5-a-primer/
|
||||
/// </summary>
|
||||
[SpeckleType("Objects.Other.RenderMaterial")]
|
||||
public class RenderMaterial : Base
|
||||
{
|
||||
public required string name { get; set; }
|
||||
public double opacity { get; set; } = 1;
|
||||
public double metalness { get; set; }
|
||||
public double roughness { get; set; } = 1;
|
||||
|
||||
public required int diffuse { get; set; } = Color.LightGray.ToArgb();
|
||||
|
||||
public int emissive { get; set; } = Color.Black.ToArgb();
|
||||
|
||||
[JsonIgnore]
|
||||
public Color diffuseColor
|
||||
{
|
||||
get => Color.FromArgb(diffuse);
|
||||
set => diffuse = value.ToArgb();
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public Color emissiveColor
|
||||
{
|
||||
get => Color.FromArgb(emissive);
|
||||
set => diffuse = value.ToArgb();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to store render material to object relationships in root collections
|
||||
/// </summary>
|
||||
[SpeckleType("Objects.Other.RenderMaterialProxy")]
|
||||
public class RenderMaterialProxy : Base, IProxyCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// The list of application ids of objects that use this render material
|
||||
/// </summary>
|
||||
public required List<string> objects { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The render material used by <see cref="objects"/>
|
||||
/// </summary>
|
||||
public required RenderMaterial value { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
using Speckle.DoubleNumerics;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Other;
|
||||
|
||||
/// <summary>
|
||||
/// Generic transform class
|
||||
/// </summary>
|
||||
[SpeckleType("Objects.Other.Transform")]
|
||||
public class Transform : Base
|
||||
{
|
||||
/// <summary>
|
||||
/// The column-based 4x4 transform matrix
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Graphics based apps typically use column-based matrices, where the last column defines translation.
|
||||
/// Modelling apps may use row-based matrices, where the last row defines translation. Transpose if so.
|
||||
/// </remarks>
|
||||
public required Matrix4x4 matrix { get; set; } = Matrix4x4.Identity;
|
||||
|
||||
/// <summary>
|
||||
/// Units for translation
|
||||
/// </summary>
|
||||
public required string units { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Converts this transform to the input units
|
||||
/// </summary>
|
||||
/// <param name="newUnits">The target units</param>
|
||||
/// <returns>A matrix array with the translation scaled by input units</returns>
|
||||
/// <remarks>If either the transform's <see cref="units"/> or the given <paramref name="newUnits"/> is <see langword="null"/>, will return the matrix array data unscaled</remarks>
|
||||
public double[] ConvertToUnits(string newUnits)
|
||||
{
|
||||
if (newUnits == null || units == null)
|
||||
{
|
||||
return ToArray();
|
||||
}
|
||||
|
||||
var sf = Units.GetConversionFactor(units, newUnits);
|
||||
|
||||
return new[]
|
||||
{
|
||||
matrix.M11,
|
||||
matrix.M12,
|
||||
matrix.M13,
|
||||
matrix.M14 * sf,
|
||||
matrix.M21,
|
||||
matrix.M22,
|
||||
matrix.M23,
|
||||
matrix.M24 * sf,
|
||||
matrix.M31,
|
||||
matrix.M32,
|
||||
matrix.M33,
|
||||
matrix.M34 * sf,
|
||||
matrix.M41,
|
||||
matrix.M42,
|
||||
matrix.M43,
|
||||
matrix.M44,
|
||||
};
|
||||
}
|
||||
|
||||
// Creates a matrix4x4 from a double array
|
||||
internal static Matrix4x4 CreateMatrix(double[] value)
|
||||
{
|
||||
return new Matrix4x4(
|
||||
value[0],
|
||||
value[1],
|
||||
value[2],
|
||||
value[3],
|
||||
value[4],
|
||||
value[5],
|
||||
value[6],
|
||||
value[7],
|
||||
value[8],
|
||||
value[9],
|
||||
value[10],
|
||||
value[11],
|
||||
value[12],
|
||||
value[13],
|
||||
value[14],
|
||||
value[15]
|
||||
);
|
||||
}
|
||||
|
||||
// Creates a matrix from a float array
|
||||
internal static Matrix4x4 CreateMatrix(float[] value)
|
||||
{
|
||||
return new Matrix4x4(
|
||||
Convert.ToDouble(value[0]),
|
||||
Convert.ToDouble(value[1]),
|
||||
Convert.ToDouble(value[2]),
|
||||
Convert.ToDouble(value[3]),
|
||||
Convert.ToDouble(value[4]),
|
||||
Convert.ToDouble(value[5]),
|
||||
Convert.ToDouble(value[6]),
|
||||
Convert.ToDouble(value[7]),
|
||||
Convert.ToDouble(value[8]),
|
||||
Convert.ToDouble(value[9]),
|
||||
Convert.ToDouble(value[10]),
|
||||
Convert.ToDouble(value[11]),
|
||||
Convert.ToDouble(value[12]),
|
||||
Convert.ToDouble(value[13]),
|
||||
Convert.ToDouble(value[14]),
|
||||
Convert.ToDouble(value[15])
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the double array of the transform matrix
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public double[] ToArray()
|
||||
{
|
||||
return new double[]
|
||||
{
|
||||
matrix.M11,
|
||||
matrix.M12,
|
||||
matrix.M13,
|
||||
matrix.M14,
|
||||
matrix.M21,
|
||||
matrix.M22,
|
||||
matrix.M23,
|
||||
matrix.M24,
|
||||
matrix.M31,
|
||||
matrix.M32,
|
||||
matrix.M33,
|
||||
matrix.M34,
|
||||
matrix.M41,
|
||||
matrix.M42,
|
||||
matrix.M43,
|
||||
matrix.M44,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Speckle.Newtonsoft.Json;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Objects.Primitive;
|
||||
|
||||
[SpeckleType("Objects.Primitive.Interval")]
|
||||
public class Interval : Base
|
||||
{
|
||||
public required double start { get; set; }
|
||||
public required double end { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public double Length => Math.Abs((end) - (start));
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return base.ToString() + $"[{start}, {end}]";
|
||||
}
|
||||
|
||||
public static Interval UnitInterval => new() { start = 0, end = 1 };
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup Label="Compiler Properties">
|
||||
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
|
||||
<PolySharpExcludeGeneratedTypes>System.Runtime.CompilerServices.RequiresLocationAttribute</PolySharpExcludeGeneratedTypes>
|
||||
<Configurations>Debug;Release;Local</Configurations>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Nugetspec Package Properties">
|
||||
<PackageId>Speckle.Objects</PackageId>
|
||||
<Description>Objects is the default object model for Speckle</Description>
|
||||
<PackageTags>$(PackageTags) objects</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Nuget Package Properties">
|
||||
<IsPackable>true</IsPackable>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Analyers">
|
||||
<NoWarn>
|
||||
$(NoWarn);
|
||||
CA1819;CA1008;CA2225;
|
||||
</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Label="Expose internals to test projects">
|
||||
<InternalsVisibleTo Include="Speckle.Objects.Tests.Unit" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Label="Project References">
|
||||
<ProjectReference Include="..\Speckle.Sdk\Speckle.Sdk.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,271 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using Speckle.Objects.Geometry;
|
||||
|
||||
namespace Speckle.Objects.Utils;
|
||||
|
||||
/// <summary>
|
||||
/// Set of functions to triangulate n-gon faces (i.e. polygon faces with an arbitrary (n) number of vertices) in <see cref="Mesh"/>es.
|
||||
/// </summary>
|
||||
public static class MeshTriangulationHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Triangulates all faces in <paramref name="mesh"/>.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The mesh to triangulate.</param>
|
||||
/// <param name="preserveQuads">If <see langword="true"/>, will not triangulate quad faces.</param>
|
||||
public static void TriangulateMesh(this Mesh mesh, bool preserveQuads = false)
|
||||
{
|
||||
List<int> triangles = new(mesh.faces.Count); //Our new list is going to be at least as big as our old one
|
||||
int i = 0;
|
||||
while (i < mesh.faces.Count)
|
||||
{
|
||||
int n = mesh.faces[i];
|
||||
if (n < 3)
|
||||
{
|
||||
n += 3; // 0 -> 3, 1 -> 4
|
||||
}
|
||||
|
||||
if (n == 3)
|
||||
{
|
||||
triangles.Add(3);
|
||||
triangles.Add(mesh.faces[i + 1]);
|
||||
triangles.Add(mesh.faces[i + 2]);
|
||||
triangles.Add(mesh.faces[i + 3]);
|
||||
}
|
||||
else if (preserveQuads && n == 4)
|
||||
{
|
||||
triangles.Add(4);
|
||||
triangles.Add(mesh.faces[i + 1]);
|
||||
triangles.Add(mesh.faces[i + 2]);
|
||||
triangles.Add(mesh.faces[i + 3]);
|
||||
triangles.Add(mesh.faces[i + 4]);
|
||||
}
|
||||
else
|
||||
{
|
||||
var triangle = TriangulateFace(i, mesh);
|
||||
triangles.AddRange(triangle);
|
||||
}
|
||||
|
||||
i += n + 1;
|
||||
}
|
||||
|
||||
mesh.faces = triangles;
|
||||
}
|
||||
|
||||
/// <overloads>Overload using a <see cref="Mesh"/>, does not mutate <paramref name="mesh"/></overloads>
|
||||
/// <inheritdoc cref="TriangulateFace(int,IReadOnlyList{int},IReadOnlyList{double},bool)"/>
|
||||
public static List<int> TriangulateFace(int faceIndex, Mesh mesh, bool includeIndicators = true)
|
||||
{
|
||||
return TriangulateFace(faceIndex, mesh.faces, mesh.vertices, includeIndicators);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the triangulation of the face at <paramref name="faceIndex"/> in <paramref name="faces"/> list.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This implementation is based the ear clipping method
|
||||
/// Proposed by "Christer Ericson (2005) <i>Real-Time Collision Detection</i>".
|
||||
/// </remarks>
|
||||
/// <param name="faceIndex">The index of the face's cardinality indicator <c>n</c> in <paramref name="faces"/> list</param>.
|
||||
/// <param name="faces"></param>
|
||||
/// <param name="vertices"></param>
|
||||
/// <param name="includeIndicators">if <see langword="true"/>, the returned list will include cardinality indicators for each triangle
|
||||
/// (i.e 4 ints for each tri), otherwise will simply be 3 ints for each tri.</param>
|
||||
/// <returns>List of triangle faces in the specified format.</returns>
|
||||
public static List<int> TriangulateFace(
|
||||
int faceIndex,
|
||||
IReadOnlyList<int> faces,
|
||||
IReadOnlyList<double> vertices,
|
||||
bool includeIndicators = true
|
||||
)
|
||||
{
|
||||
int n = faces[faceIndex];
|
||||
if (n < 3)
|
||||
{
|
||||
n += 3; // 0 -> 3, 1 -> 4
|
||||
}
|
||||
#region Local Funcitions
|
||||
//Converts from relative to absolute index (returns index in mesh.vertices list)
|
||||
int AsIndex(int v) => faceIndex + v + 1;
|
||||
|
||||
//Gets vertex from a relative vert index
|
||||
Vector3 V(int v)
|
||||
{
|
||||
int index = faces[AsIndex(v)] * 3;
|
||||
return new Vector3(vertices[index], vertices[index + 1], vertices[index + 2]);
|
||||
}
|
||||
#endregion
|
||||
|
||||
int intsPerTri = includeIndicators ? 4 : 3;
|
||||
List<int> triangleFaces = new((n - 2) * intsPerTri);
|
||||
|
||||
//Calculate face normal using the Newell Method
|
||||
Vector3 faceNormal = Vector3.Zero;
|
||||
for (int ii = n - 1, jj = 0; jj < n; ii = jj, jj++)
|
||||
{
|
||||
Vector3 iPos = V(ii);
|
||||
Vector3 jPos = V(jj);
|
||||
faceNormal.x += (jPos.y - iPos.y) * (iPos.z + jPos.z); // projection on yz
|
||||
faceNormal.y += (jPos.z - iPos.z) * (iPos.x + jPos.x); // projection on xz
|
||||
faceNormal.z += (jPos.x - iPos.x) * (iPos.y + jPos.y); // projection on xy
|
||||
}
|
||||
faceNormal.Normalize();
|
||||
|
||||
//Set up previous and next links to effectively form a double-linked vertex list
|
||||
int[] prev = new int[n],
|
||||
next = new int[n];
|
||||
for (int j = 0; j < n; j++)
|
||||
{
|
||||
prev[j] = j - 1;
|
||||
next[j] = j + 1;
|
||||
}
|
||||
prev[0] = n - 1;
|
||||
next[n - 1] = 0;
|
||||
|
||||
//Start clipping ears until we are left with a triangle
|
||||
int i = 0;
|
||||
int counter = 0;
|
||||
while (n >= 3)
|
||||
{
|
||||
bool isEar = true;
|
||||
|
||||
//If we are the last triangle or we have exhausted our vertices, the below statement will be false
|
||||
if (n > 3 && counter < n)
|
||||
{
|
||||
Vector3 prevVertex = V(prev[i]);
|
||||
Vector3 earVertex = V(i);
|
||||
Vector3 nextVertex = V(next[i]);
|
||||
|
||||
if (TriangleIsCCW(faceNormal, prevVertex, earVertex, nextVertex))
|
||||
{
|
||||
int k = next[next[i]];
|
||||
|
||||
do
|
||||
{
|
||||
if (TestPointTriangle(V(k), prevVertex, earVertex, nextVertex))
|
||||
{
|
||||
isEar = false;
|
||||
break;
|
||||
}
|
||||
|
||||
k = next[k];
|
||||
} while (k != prev[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
isEar = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isEar)
|
||||
{
|
||||
int a = faces[AsIndex(i)];
|
||||
int b = faces[AsIndex(next[i])];
|
||||
int c = faces[AsIndex(prev[i])];
|
||||
|
||||
if (includeIndicators)
|
||||
{
|
||||
triangleFaces.Add(3);
|
||||
}
|
||||
|
||||
triangleFaces.Add(a);
|
||||
triangleFaces.Add(b);
|
||||
triangleFaces.Add(c);
|
||||
|
||||
next[prev[i]] = next[i];
|
||||
prev[next[i]] = prev[i];
|
||||
n--;
|
||||
i = prev[i];
|
||||
counter = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
i = next[i];
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
return triangleFaces;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests if point <paramref name="v"/> is within triangle <paramref name="a"/><paramref name="b"/><paramref name="c"/>
|
||||
/// </summary>
|
||||
/// <returns>true if <paramref name="v"/> is within triangle</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool TestPointTriangle(Vector3 v, Vector3 a, Vector3 b, Vector3 c)
|
||||
{
|
||||
static bool Test(Vector3 v, Vector3 a, Vector3 b)
|
||||
{
|
||||
Vector3 crossA = v.Cross(a);
|
||||
Vector3 crossB = a.Cross(b);
|
||||
double dotWithEpsilon = double.Epsilon + crossA.Dot(crossB);
|
||||
return Math.Sign(dotWithEpsilon) != -1;
|
||||
}
|
||||
|
||||
return Test(b - a, v - a, c - a) && Test(c - b, v - b, a - b) && Test(a - c, v - c, b - c);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks that triangle <paramref name="a"/><paramref name="b"/><paramref name="c"/> is clockwise with reference to <paramref name="referenceNormal"/>
|
||||
/// </summary>
|
||||
/// <param name="referenceNormal">The normal direction of the face</param>
|
||||
/// <param name="a"></param>
|
||||
/// <param name="b"></param>
|
||||
/// <param name="c"></param>
|
||||
/// <returns>true if triangle is ccw</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool TriangleIsCCW(Vector3 referenceNormal, Vector3 a, Vector3 b, Vector3 c)
|
||||
{
|
||||
Vector3 triangleNormal = (c - a).Cross(b - a);
|
||||
triangleNormal.Normalize();
|
||||
return referenceNormal.Dot(triangleNormal) > 0.0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3-dimension x, Y, Z Vector of <see cref="double"/>s encapsulating necessary vector mathematics
|
||||
/// </summary>
|
||||
private struct Vector3
|
||||
{
|
||||
public double x,
|
||||
y,
|
||||
z;
|
||||
|
||||
public Vector3(double x, double y, double z)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
public static readonly Vector3 Zero = new(0, 0, 0);
|
||||
|
||||
public static Vector3 operator +(Vector3 a, Vector3 b) => new(a.x + b.x, a.y + b.y, a.z + b.z);
|
||||
|
||||
public static Vector3 operator -(Vector3 a, Vector3 b) => new(a.x - b.x, a.y - b.y, a.z - b.z);
|
||||
|
||||
public readonly double Dot(Vector3 v)
|
||||
{
|
||||
return x * v.x + y * v.y + z * v.z;
|
||||
}
|
||||
|
||||
public readonly Vector3 Cross(Vector3 v)
|
||||
{
|
||||
var x = this.y * v.z - this.z * v.y;
|
||||
var y = this.z * v.x - this.x * v.z;
|
||||
var z = this.x * v.y - this.y * v.x;
|
||||
|
||||
return new Vector3(x, y, z);
|
||||
}
|
||||
|
||||
public readonly double SquareSum => x * x + y * y + z * z;
|
||||
|
||||
public void Normalize()
|
||||
{
|
||||
double scale = 1d / Math.Sqrt(SquareSum);
|
||||
x *= scale;
|
||||
y *= scale;
|
||||
z *= scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,546 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dependencies": {
|
||||
".NETStandard,Version=v2.0": {
|
||||
"Microsoft.SourceLink.GitHub": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.0.0, )",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Build.Tasks.Git": "8.0.0",
|
||||
"Microsoft.SourceLink.Common": "8.0.0"
|
||||
}
|
||||
},
|
||||
"NETStandard.Library": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.0.3, )",
|
||||
"resolved": "2.0.3",
|
||||
"contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==",
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.Platforms": "1.1.0"
|
||||
}
|
||||
},
|
||||
"PolySharp": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.15.0, )",
|
||||
"resolved": "1.15.0",
|
||||
"contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g=="
|
||||
},
|
||||
"Speckle.InterfaceGenerator": {
|
||||
"type": "Direct",
|
||||
"requested": "[0.9.6, )",
|
||||
"resolved": "0.9.6",
|
||||
"contentHash": "HKH7tYrYYlCK1ct483hgxERAdVdMtl7gUKW9ijWXxA1UsYR4Z+TrRHYmzZ9qmpu1NnTycSrp005NYM78GDKV1w=="
|
||||
},
|
||||
"GraphQL.Client.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "h7uzWFORHZ+CCjwr/ThAyXMr0DPpzEANDa4Uo54wqCQ+j7qUKwqYTgOrb1W40sqbvNaZm9v/X7It31SUw0maHA==",
|
||||
"dependencies": {
|
||||
"GraphQL.Primitives": "6.0.0"
|
||||
}
|
||||
},
|
||||
"GraphQL.Client.Abstractions.Websocket": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "Nr9bPf8gIOvLuXpqEpqr9z9jslYFJOvd0feHth3/kPqeR3uMbjF5pjiwh4jxyMcxHdr8Pb6QiXkV3hsSyt0v7A==",
|
||||
"dependencies": {
|
||||
"GraphQL.Client.Abstractions": "6.0.0"
|
||||
}
|
||||
},
|
||||
"GraphQL.Primitives": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "yg72rrYDapfsIUrul7aF6wwNnTJBOFvuA9VdDTQpPa8AlAriHbufeXYLBcodKjfUdkCnaiggX1U/nEP08Zb5GA=="
|
||||
},
|
||||
"Microsoft.Build.Tasks.Git": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ=="
|
||||
},
|
||||
"Microsoft.Data.Sqlite.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.5",
|
||||
"contentHash": "FTerRmQPqHrCrnoUzhBu+E+1DNGwyrAMLqHkAqOOOu5pGfyMOj8qQUBxI/gDtWtG11p49UxSfWmBzRNlwZqfUg==",
|
||||
"dependencies": {
|
||||
"SQLitePCLRaw.core": "2.1.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "nOP8R1mVb/6mZtm2qgAJXn/LFm/2kMjHDAg/QJLFG6CuWYJtaD3p1BwQhufBVvRzL9ceJ/xF0SQ0qsI2GkDQAA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "2.2.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "65MrmXCziWaQFrI0UHkQbesrX5wTwf9XPjY5yFm/VkgJKFJ5gqvXRoXjIZcf2wLi5ZlwGz/oMYfyURVCWbM5iw==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Primitives": "2.2.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.Binder": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "vJ9xvOZCnUAIHcGC3SU35r3HKmHTVIeHzo6u/qzlHAqD8m6xv92MLin4oJntTvkpKxVX3vI1GFFkIQtU3AdlsQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration": "2.2.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "B2WqEox8o+4KUOpL7rZPyh6qYjik8tHi2tN8Z9jZkHzED8ElYgZa/h6K+xliB435SqUcWT290Fr2aa8BtZjn8A=="
|
||||
},
|
||||
"Microsoft.Extensions.Options": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "UpZLNLBpIZ0GTebShui7xXYh6DmBHjWM8NxGxZbdQh/bPZ5e6YswqI+bru6BnEL5eWiOdodsXtEz3FROcgi/qg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
|
||||
"Microsoft.Extensions.Primitives": "2.2.0",
|
||||
"System.ComponentModel.Annotations": "4.5.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Primitives": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "azyQtqbm4fSaDzZHD/J+V6oWMFaf2tWP4WEGIYePLCMw3+b2RQdj9ybgbQyjCshcitQKQ4lEDOZjmSlTTrHxUg==",
|
||||
"dependencies": {
|
||||
"System.Memory": "4.5.1",
|
||||
"System.Runtime.CompilerServices.Unsafe": "4.5.1"
|
||||
}
|
||||
},
|
||||
"Microsoft.NETCore.Platforms": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.1.0",
|
||||
"contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A=="
|
||||
},
|
||||
"Microsoft.NETCore.Targets": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.1.0",
|
||||
"contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg=="
|
||||
},
|
||||
"Microsoft.SourceLink.Common": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
|
||||
},
|
||||
"SQLitePCLRaw.bundle_e_sqlite3": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.1.4",
|
||||
"contentHash": "EWI1olKDjFEBMJu0+3wuxwziIAdWDVMYLhuZ3Qs84rrz+DHwD00RzWPZCa+bLnHCf3oJwuFZIRsHT5p236QXww==",
|
||||
"dependencies": {
|
||||
"SQLitePCLRaw.lib.e_sqlite3": "2.1.4",
|
||||
"SQLitePCLRaw.provider.e_sqlite3": "2.1.4"
|
||||
}
|
||||
},
|
||||
"SQLitePCLRaw.core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.1.4",
|
||||
"contentHash": "inBjvSHo9UDKneGNzfUfDjK08JzlcIhn1+SP5Y3m6cgXpCxXKCJDy6Mka7LpgSV+UZmKSnC8rTwB0SQ0xKu5pA==",
|
||||
"dependencies": {
|
||||
"System.Memory": "4.5.3"
|
||||
}
|
||||
},
|
||||
"SQLitePCLRaw.lib.e_sqlite3": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.1.4",
|
||||
"contentHash": "2C9Q9eX7CPLveJA0rIhf9RXAvu+7nWZu1A2MdG6SD/NOu26TakGgL1nsbc0JAspGijFOo3HoN79xrx8a368fBg=="
|
||||
},
|
||||
"SQLitePCLRaw.provider.e_sqlite3": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.1.4",
|
||||
"contentHash": "CSlb5dUp1FMIkez9Iv5EXzpeq7rHryVNqwJMWnpq87j9zWZexaEMdisDktMsnnrzKM6ahNrsTkjqNodTBPBxtQ==",
|
||||
"dependencies": {
|
||||
"SQLitePCLRaw.core": "2.1.4"
|
||||
}
|
||||
},
|
||||
"System.Buffers": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.4.0",
|
||||
"contentHash": "AwarXzzoDwX6BgrhjoJsk6tUezZEozOT5Y9QKF94Gl4JK91I4PIIBkBco9068Y9/Dra8Dkbie99kXB8+1BaYKw=="
|
||||
},
|
||||
"System.ComponentModel.Annotations": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.0",
|
||||
"contentHash": "UxYQ3FGUOtzJ7LfSdnYSFd7+oEv6M8NgUatatIN2HxNtDdlcvFAf+VIq4Of9cDMJEJC0aSRv/x898RYhB4Yppg=="
|
||||
},
|
||||
"System.Memory": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.3",
|
||||
"contentHash": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA==",
|
||||
"dependencies": {
|
||||
"System.Buffers": "4.4.0",
|
||||
"System.Numerics.Vectors": "4.4.0",
|
||||
"System.Runtime.CompilerServices.Unsafe": "4.5.2"
|
||||
}
|
||||
},
|
||||
"System.Numerics.Vectors": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.4.0",
|
||||
"contentHash": "UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ=="
|
||||
},
|
||||
"System.Reactive": {
|
||||
"type": "Transitive",
|
||||
"resolved": "5.0.0",
|
||||
"contentHash": "erBZjkQHWL9jpasCE/0qKAryzVBJFxGHVBAvgRN1bzM0q2s1S4oYREEEL0Vb+1kA/6BKb5FjUZMp5VXmy+gzkQ==",
|
||||
"dependencies": {
|
||||
"System.Runtime.InteropServices.WindowsRuntime": "4.3.0",
|
||||
"System.Threading.Tasks.Extensions": "4.5.4"
|
||||
}
|
||||
},
|
||||
"System.Runtime": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==",
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.Platforms": "1.1.0",
|
||||
"Microsoft.NETCore.Targets": "1.1.0"
|
||||
}
|
||||
},
|
||||
"System.Runtime.CompilerServices.Unsafe": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.3",
|
||||
"contentHash": "3TIsJhD1EiiT0w2CcDMN/iSSwnNnsrnbzeVHSKkaEgV85txMprmuO+Yq2AdSbeVGcg28pdNDTPK87tJhX7VFHw=="
|
||||
},
|
||||
"System.Runtime.InteropServices.WindowsRuntime": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "J4GUi3xZQLUBasNwZnjrffN8i5wpHrBtZoLG+OhRyGo/+YunMRWWtwoMDlUAIdmX0uRfpHIBDSV6zyr3yf00TA==",
|
||||
"dependencies": {
|
||||
"System.Runtime": "4.3.0"
|
||||
}
|
||||
},
|
||||
"System.Threading.Tasks.Extensions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.4",
|
||||
"contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==",
|
||||
"dependencies": {
|
||||
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
|
||||
}
|
||||
},
|
||||
"speckle.sdk": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"GraphQL.Client": "[6.0.0, )",
|
||||
"Microsoft.Bcl.AsyncInterfaces": "[5.0.0, )",
|
||||
"Microsoft.CSharp": "[4.7.0, )",
|
||||
"Microsoft.Data.Sqlite": "[7.0.5, )",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "[2.2.0, )",
|
||||
"Microsoft.Extensions.Logging": "[2.2.0, )",
|
||||
"Speckle.DoubleNumerics": "[4.1.0, )",
|
||||
"Speckle.Newtonsoft.Json": "[13.0.2, )",
|
||||
"Speckle.Sdk.Dependencies": "[1.0.0, )"
|
||||
}
|
||||
},
|
||||
"speckle.sdk.dependencies": {
|
||||
"type": "Project"
|
||||
},
|
||||
"GraphQL.Client": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[6.0.0, )",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "8yPNBbuVBpTptivyAlak4GZvbwbUcjeQTL4vN1HKHRuOykZ4r7l5fcLS6vpyPyLn0x8FsL31xbOIKyxbmR9rbA==",
|
||||
"dependencies": {
|
||||
"GraphQL.Client.Abstractions": "6.0.0",
|
||||
"GraphQL.Client.Abstractions.Websocket": "6.0.0",
|
||||
"System.Reactive": "5.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Bcl.AsyncInterfaces": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[5.0.0, )",
|
||||
"resolved": "5.0.0",
|
||||
"contentHash": "W8DPQjkMScOMTtJbPwmPyj9c3zYSFGawDW3jwlBOOsnY+EzZFLgNQ/UMkK35JmkNOVPdCyPr2Tw7Vv9N+KA3ZQ==",
|
||||
"dependencies": {
|
||||
"System.Threading.Tasks.Extensions": "4.5.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.CSharp": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[4.7.0, )",
|
||||
"resolved": "4.7.0",
|
||||
"contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA=="
|
||||
},
|
||||
"Microsoft.Data.Sqlite": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[7.0.5, )",
|
||||
"resolved": "7.0.5",
|
||||
"contentHash": "KGxbPeWsQMnmQy43DSBxAFtHz3l2JX8EWBSGUCvT3CuZ8KsuzbkqMIJMDOxWtG8eZSoCDI04aiVQjWuuV8HmSw==",
|
||||
"dependencies": {
|
||||
"Microsoft.Data.Sqlite.Core": "7.0.5",
|
||||
"SQLitePCLRaw.bundle_e_sqlite3": "2.1.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.2.0, )",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
|
||||
},
|
||||
"Microsoft.Extensions.Logging": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.2.0, )",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "Nxqhadc9FCmFHzU+fz3oc8sFlE6IadViYg8dfUdGzJZ2JUxnCsRghBhhOWdM4B2zSZqEc+0BjliBh/oNdRZuig==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Binder": "2.2.0",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "2.2.0",
|
||||
"Microsoft.Extensions.Options": "2.2.0"
|
||||
}
|
||||
},
|
||||
"Speckle.DoubleNumerics": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[4.1.0, )",
|
||||
"resolved": "4.1.0",
|
||||
"contentHash": "20DtS+FsDRsOD9+AU3TwNFZ0qrKo5f6f7B5ZR9wStsIHHHC9k7DpjbCvuNtmnSjx54MD+TJC7wV2f5iyGVPj1A=="
|
||||
},
|
||||
"Speckle.Newtonsoft.Json": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[13.0.2, )",
|
||||
"resolved": "13.0.2",
|
||||
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
|
||||
}
|
||||
},
|
||||
"net8.0": {
|
||||
"Microsoft.SourceLink.GitHub": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.0.0, )",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Build.Tasks.Git": "8.0.0",
|
||||
"Microsoft.SourceLink.Common": "8.0.0"
|
||||
}
|
||||
},
|
||||
"PolySharp": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.15.0, )",
|
||||
"resolved": "1.15.0",
|
||||
"contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g=="
|
||||
},
|
||||
"Speckle.InterfaceGenerator": {
|
||||
"type": "Direct",
|
||||
"requested": "[0.9.6, )",
|
||||
"resolved": "0.9.6",
|
||||
"contentHash": "HKH7tYrYYlCK1ct483hgxERAdVdMtl7gUKW9ijWXxA1UsYR4Z+TrRHYmzZ9qmpu1NnTycSrp005NYM78GDKV1w=="
|
||||
},
|
||||
"GraphQL.Client.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "h7uzWFORHZ+CCjwr/ThAyXMr0DPpzEANDa4Uo54wqCQ+j7qUKwqYTgOrb1W40sqbvNaZm9v/X7It31SUw0maHA==",
|
||||
"dependencies": {
|
||||
"GraphQL.Primitives": "6.0.0"
|
||||
}
|
||||
},
|
||||
"GraphQL.Client.Abstractions.Websocket": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "Nr9bPf8gIOvLuXpqEpqr9z9jslYFJOvd0feHth3/kPqeR3uMbjF5pjiwh4jxyMcxHdr8Pb6QiXkV3hsSyt0v7A==",
|
||||
"dependencies": {
|
||||
"GraphQL.Client.Abstractions": "6.0.0"
|
||||
}
|
||||
},
|
||||
"GraphQL.Primitives": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "yg72rrYDapfsIUrul7aF6wwNnTJBOFvuA9VdDTQpPa8AlAriHbufeXYLBcodKjfUdkCnaiggX1U/nEP08Zb5GA=="
|
||||
},
|
||||
"Microsoft.Build.Tasks.Git": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ=="
|
||||
},
|
||||
"Microsoft.Data.Sqlite.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.5",
|
||||
"contentHash": "FTerRmQPqHrCrnoUzhBu+E+1DNGwyrAMLqHkAqOOOu5pGfyMOj8qQUBxI/gDtWtG11p49UxSfWmBzRNlwZqfUg==",
|
||||
"dependencies": {
|
||||
"SQLitePCLRaw.core": "2.1.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "nOP8R1mVb/6mZtm2qgAJXn/LFm/2kMjHDAg/QJLFG6CuWYJtaD3p1BwQhufBVvRzL9ceJ/xF0SQ0qsI2GkDQAA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "2.2.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "65MrmXCziWaQFrI0UHkQbesrX5wTwf9XPjY5yFm/VkgJKFJ5gqvXRoXjIZcf2wLi5ZlwGz/oMYfyURVCWbM5iw==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Primitives": "2.2.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.Binder": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "vJ9xvOZCnUAIHcGC3SU35r3HKmHTVIeHzo6u/qzlHAqD8m6xv92MLin4oJntTvkpKxVX3vI1GFFkIQtU3AdlsQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration": "2.2.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "B2WqEox8o+4KUOpL7rZPyh6qYjik8tHi2tN8Z9jZkHzED8ElYgZa/h6K+xliB435SqUcWT290Fr2aa8BtZjn8A=="
|
||||
},
|
||||
"Microsoft.Extensions.Options": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "UpZLNLBpIZ0GTebShui7xXYh6DmBHjWM8NxGxZbdQh/bPZ5e6YswqI+bru6BnEL5eWiOdodsXtEz3FROcgi/qg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
|
||||
"Microsoft.Extensions.Primitives": "2.2.0",
|
||||
"System.ComponentModel.Annotations": "4.5.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Primitives": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "azyQtqbm4fSaDzZHD/J+V6oWMFaf2tWP4WEGIYePLCMw3+b2RQdj9ybgbQyjCshcitQKQ4lEDOZjmSlTTrHxUg==",
|
||||
"dependencies": {
|
||||
"System.Memory": "4.5.1",
|
||||
"System.Runtime.CompilerServices.Unsafe": "4.5.1"
|
||||
}
|
||||
},
|
||||
"Microsoft.SourceLink.Common": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
|
||||
},
|
||||
"SQLitePCLRaw.bundle_e_sqlite3": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.1.4",
|
||||
"contentHash": "EWI1olKDjFEBMJu0+3wuxwziIAdWDVMYLhuZ3Qs84rrz+DHwD00RzWPZCa+bLnHCf3oJwuFZIRsHT5p236QXww==",
|
||||
"dependencies": {
|
||||
"SQLitePCLRaw.lib.e_sqlite3": "2.1.4",
|
||||
"SQLitePCLRaw.provider.e_sqlite3": "2.1.4"
|
||||
}
|
||||
},
|
||||
"SQLitePCLRaw.core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.1.4",
|
||||
"contentHash": "inBjvSHo9UDKneGNzfUfDjK08JzlcIhn1+SP5Y3m6cgXpCxXKCJDy6Mka7LpgSV+UZmKSnC8rTwB0SQ0xKu5pA==",
|
||||
"dependencies": {
|
||||
"System.Memory": "4.5.3"
|
||||
}
|
||||
},
|
||||
"SQLitePCLRaw.lib.e_sqlite3": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.1.4",
|
||||
"contentHash": "2C9Q9eX7CPLveJA0rIhf9RXAvu+7nWZu1A2MdG6SD/NOu26TakGgL1nsbc0JAspGijFOo3HoN79xrx8a368fBg=="
|
||||
},
|
||||
"SQLitePCLRaw.provider.e_sqlite3": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.1.4",
|
||||
"contentHash": "CSlb5dUp1FMIkez9Iv5EXzpeq7rHryVNqwJMWnpq87j9zWZexaEMdisDktMsnnrzKM6ahNrsTkjqNodTBPBxtQ==",
|
||||
"dependencies": {
|
||||
"SQLitePCLRaw.core": "2.1.4"
|
||||
}
|
||||
},
|
||||
"System.ComponentModel.Annotations": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.0",
|
||||
"contentHash": "UxYQ3FGUOtzJ7LfSdnYSFd7+oEv6M8NgUatatIN2HxNtDdlcvFAf+VIq4Of9cDMJEJC0aSRv/x898RYhB4Yppg=="
|
||||
},
|
||||
"System.Memory": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.3",
|
||||
"contentHash": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA=="
|
||||
},
|
||||
"System.Reactive": {
|
||||
"type": "Transitive",
|
||||
"resolved": "5.0.0",
|
||||
"contentHash": "erBZjkQHWL9jpasCE/0qKAryzVBJFxGHVBAvgRN1bzM0q2s1S4oYREEEL0Vb+1kA/6BKb5FjUZMp5VXmy+gzkQ=="
|
||||
},
|
||||
"System.Runtime.CompilerServices.Unsafe": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.1",
|
||||
"contentHash": "Zh8t8oqolRaFa9vmOZfdQm/qKejdqz0J9kr7o2Fu0vPeoH3BL1EOXipKWwkWtLT1JPzjByrF19fGuFlNbmPpiw=="
|
||||
},
|
||||
"speckle.sdk": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"GraphQL.Client": "[6.0.0, )",
|
||||
"Microsoft.CSharp": "[4.7.0, )",
|
||||
"Microsoft.Data.Sqlite": "[7.0.5, )",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "[2.2.0, )",
|
||||
"Microsoft.Extensions.Logging": "[2.2.0, )",
|
||||
"Speckle.DoubleNumerics": "[4.1.0, )",
|
||||
"Speckle.Newtonsoft.Json": "[13.0.2, )",
|
||||
"Speckle.Sdk.Dependencies": "[1.0.0, )"
|
||||
}
|
||||
},
|
||||
"speckle.sdk.dependencies": {
|
||||
"type": "Project"
|
||||
},
|
||||
"GraphQL.Client": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[6.0.0, )",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "8yPNBbuVBpTptivyAlak4GZvbwbUcjeQTL4vN1HKHRuOykZ4r7l5fcLS6vpyPyLn0x8FsL31xbOIKyxbmR9rbA==",
|
||||
"dependencies": {
|
||||
"GraphQL.Client.Abstractions": "6.0.0",
|
||||
"GraphQL.Client.Abstractions.Websocket": "6.0.0",
|
||||
"System.Reactive": "5.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.CSharp": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[4.7.0, )",
|
||||
"resolved": "4.7.0",
|
||||
"contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA=="
|
||||
},
|
||||
"Microsoft.Data.Sqlite": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[7.0.5, )",
|
||||
"resolved": "7.0.5",
|
||||
"contentHash": "KGxbPeWsQMnmQy43DSBxAFtHz3l2JX8EWBSGUCvT3CuZ8KsuzbkqMIJMDOxWtG8eZSoCDI04aiVQjWuuV8HmSw==",
|
||||
"dependencies": {
|
||||
"Microsoft.Data.Sqlite.Core": "7.0.5",
|
||||
"SQLitePCLRaw.bundle_e_sqlite3": "2.1.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.2.0, )",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
|
||||
},
|
||||
"Microsoft.Extensions.Logging": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.2.0, )",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "Nxqhadc9FCmFHzU+fz3oc8sFlE6IadViYg8dfUdGzJZ2JUxnCsRghBhhOWdM4B2zSZqEc+0BjliBh/oNdRZuig==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Binder": "2.2.0",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "2.2.0",
|
||||
"Microsoft.Extensions.Options": "2.2.0"
|
||||
}
|
||||
},
|
||||
"Speckle.DoubleNumerics": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[4.1.0, )",
|
||||
"resolved": "4.1.0",
|
||||
"contentHash": "20DtS+FsDRsOD9+AU3TwNFZ0qrKo5f6f7B5ZR9wStsIHHHC9k7DpjbCvuNtmnSjx54MD+TJC7wV2f5iyGVPj1A=="
|
||||
},
|
||||
"Speckle.Newtonsoft.Json": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[13.0.2, )",
|
||||
"resolved": "13.0.2",
|
||||
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System.Collections.Frozen;
|
||||
|
||||
namespace Speckle.Sdk.Dependencies;
|
||||
|
||||
public static class Collections
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
public static IReadOnlySet<T> Freeze<T>(this IEnumerable<T> source)
|
||||
#else
|
||||
public static IReadOnlyCollection<T> Freeze<T>(this IEnumerable<T> source)
|
||||
#endif
|
||||
{
|
||||
return source.ToFrozenSet();
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<TKey, TValue> Freeze<TKey, TValue>(
|
||||
this IEnumerable<KeyValuePair<TKey, TValue>> source
|
||||
)
|
||||
where TKey : notnull => source.ToFrozenDictionary();
|
||||
}
|
||||
|
||||
public static class EnumerableExtensions
|
||||
{
|
||||
public static IEnumerable<int> RangeFrom(int from, int to) => Enumerable.Range(from, to - from + 1);
|
||||
|
||||
#if NETSTANDARD2_0
|
||||
public static IEnumerable<TSource> DistinctBy<TSource, TKey>(
|
||||
this IEnumerable<TSource> source,
|
||||
Func<TSource, TKey> keySelector
|
||||
)
|
||||
{
|
||||
var keys = new HashSet<TKey>();
|
||||
foreach (var element in source)
|
||||
{
|
||||
if (keys.Contains(keySelector(element)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
keys.Add(keySelector(element));
|
||||
yield return element;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using Polly;
|
||||
using Polly.Contrib.WaitAndRetry;
|
||||
|
||||
namespace Speckle.Sdk.Dependencies;
|
||||
|
||||
public static class GraphQLRetry
|
||||
{
|
||||
public static async Task<T> ExecuteAsync<T, TInnerException>(
|
||||
Func<Task<T>> func,
|
||||
Action<Exception, TimeSpan>? onRetry = null
|
||||
)
|
||||
where TInnerException : Exception
|
||||
{
|
||||
var delay = Backoff.DecorrelatedJitterBackoffV2(TimeSpan.FromSeconds(1), 5);
|
||||
var graphqlRetry = Policy
|
||||
.HandleInner<TInnerException>()
|
||||
.WaitAndRetryAsync(
|
||||
delay,
|
||||
(ex, timeout, _) =>
|
||||
{
|
||||
onRetry?.Invoke(ex, timeout);
|
||||
}
|
||||
);
|
||||
|
||||
return await graphqlRetry.ExecuteAsync(func).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace Speckle.Sdk.Logging;
|
||||
|
||||
public interface ISdkActivity : IDisposable
|
||||
{
|
||||
void SetTag(string key, object? value);
|
||||
void RecordException(Exception e);
|
||||
string TraceId { get; }
|
||||
void SetStatus(SdkActivityStatusCode code);
|
||||
|
||||
void InjectHeaders(Action<string, string> header);
|
||||
}
|
||||
|
||||
public enum SdkActivityStatusCode
|
||||
{
|
||||
/// <summary>Unset status code is the default value indicating the status code is not initialized.</summary>
|
||||
Unset,
|
||||
|
||||
/// <summary>Status code indicating the operation has been validated and completed successfully.</summary>
|
||||
Ok,
|
||||
|
||||
/// <summary>Status code indicating an error is encountered during the operation.</summary>
|
||||
Error,
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Speckle.Sdk.Logging;
|
||||
|
||||
public interface ISdkActivityFactory : IDisposable
|
||||
{
|
||||
ISdkActivity? Start(string? name = default, [CallerMemberName] string source = "");
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
|
||||
namespace Speckle.Sdk.Dependencies;
|
||||
|
||||
public class Pool<T>
|
||||
where T : class, new()
|
||||
{
|
||||
private readonly ObjectPool<T> _objectPool;
|
||||
|
||||
internal Pool(IPooledObjectPolicy<T> objectPolicy)
|
||||
{
|
||||
_objectPool = ObjectPool.Create(objectPolicy);
|
||||
}
|
||||
|
||||
public T Get() => _objectPool.Get();
|
||||
|
||||
public void Return(T obj) => _objectPool.Return(obj);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
|
||||
namespace Speckle.Sdk.Dependencies;
|
||||
|
||||
public static class Pools
|
||||
{
|
||||
public const int DefaultCapacity = 50;
|
||||
|
||||
public static Pool<Dictionary<string, object?>> ObjectDictionaries { get; } = new(new ObjectDictionaryPolicy());
|
||||
|
||||
private sealed class ObjectDictionaryPolicy : IPooledObjectPolicy<Dictionary<string, object?>>
|
||||
{
|
||||
public Dictionary<string, object?> Create() => new(50, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public bool Return(Dictionary<string, object?> obj)
|
||||
{
|
||||
obj.Clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static Pool<StringBuilder> StringBuilders { get; } =
|
||||
new(new StringBuilderPooledObjectPolicy() { MaximumRetainedCapacity = 100 * 1024 * 1024 });
|
||||
|
||||
private sealed class ObjectDictionaryPolicy<TKey, TValue> : IPooledObjectPolicy<Dictionary<TKey, TValue>>
|
||||
where TKey : notnull
|
||||
{
|
||||
public Dictionary<TKey, TValue> Create() => new(DefaultCapacity);
|
||||
|
||||
public bool Return(Dictionary<TKey, TValue> obj)
|
||||
{
|
||||
obj.Clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ObjectConcurrentDictionaryPolicy<TKey, TValue>
|
||||
: IPooledObjectPolicy<ConcurrentDictionary<TKey, TValue>>
|
||||
where TKey : notnull
|
||||
{
|
||||
public ConcurrentDictionary<TKey, TValue> Create() => new(Environment.ProcessorCount, DefaultCapacity);
|
||||
|
||||
public bool Return(ConcurrentDictionary<TKey, TValue> obj)
|
||||
{
|
||||
obj.Clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ObjectListPolicy<T> : IPooledObjectPolicy<List<T>>
|
||||
{
|
||||
public List<T> Create() => new(DefaultCapacity);
|
||||
|
||||
public bool Return(List<T> obj)
|
||||
{
|
||||
obj.Clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static Pool<List<T>> CreateListPool<T>() => new(new ObjectListPolicy<T>());
|
||||
|
||||
public static Pool<Dictionary<TKey, TValue>> CreateDictionaryPool<TKey, TValue>()
|
||||
where TKey : notnull => new(new ObjectDictionaryPolicy<TKey, TValue>());
|
||||
|
||||
public static Pool<ConcurrentDictionary<TKey, TValue>> CreateConcurrentDictionaryPool<TKey, TValue>()
|
||||
where TKey : notnull => new(new ObjectConcurrentDictionaryPolicy<TKey, TValue>());
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System.Buffers;
|
||||
using Speckle.Sdk.Dependencies;
|
||||
|
||||
namespace Speckle.Sdk.Serialisation.V2.Send;
|
||||
|
||||
public sealed class Batch<T> : IMemoryOwner<T>
|
||||
where T : IHasByteSize
|
||||
{
|
||||
private static readonly Pool<List<T>> _pool = Pools.CreateListPool<T>();
|
||||
#pragma warning disable IDE0032
|
||||
private readonly List<T> _items = _pool.Get();
|
||||
private int _batchByteSize;
|
||||
#pragma warning restore IDE0032
|
||||
|
||||
public void Add(T item)
|
||||
{
|
||||
_items.Add(item);
|
||||
_batchByteSize += item.ByteSize;
|
||||
}
|
||||
|
||||
public void TrimExcess()
|
||||
{
|
||||
_items.TrimExcess();
|
||||
_batchByteSize = _items.Sum(x => x.ByteSize);
|
||||
}
|
||||
|
||||
public int BatchByteSize => _batchByteSize;
|
||||
public List<T> Items => _items;
|
||||
|
||||
public void Dispose() => _pool.Return(_items);
|
||||
|
||||
public Memory<T> Memory => new(_items.ToArray());
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using System.Buffers;
|
||||
|
||||
namespace Speckle.Sdk.Serialisation.V2.Send;
|
||||
|
||||
public static class BatchExtensions
|
||||
{
|
||||
public static Batch<T> CreateBatch<T>()
|
||||
where T : IHasByteSize => new();
|
||||
|
||||
public static void TrimBatch<T>(ref IMemoryOwner<T> batch, bool isVerifiedFull)
|
||||
where T : IHasByteSize
|
||||
{
|
||||
if (!isVerifiedFull)
|
||||
{
|
||||
((Batch<T>)batch).TrimExcess();
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddBatchItem<T>(this IMemoryOwner<T> batch, T item)
|
||||
where T : IHasByteSize => ((Batch<T>)batch).Add(item);
|
||||
|
||||
public static int GetBatchSize<T>(this IMemoryOwner<T> batch, int maxBatchSize)
|
||||
where T : IHasByteSize
|
||||
{
|
||||
var currentSize = ((Batch<T>)batch).BatchByteSize;
|
||||
if (currentSize > maxBatchSize)
|
||||
{
|
||||
//doing this to say it's full since the channel reader only does full being equivalent
|
||||
return maxBatchSize;
|
||||
}
|
||||
|
||||
return currentSize;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Buffers;
|
||||
using System.Threading.Channels;
|
||||
using Open.ChannelExtensions;
|
||||
|
||||
namespace Speckle.Sdk.Serialisation.V2.Send;
|
||||
|
||||
public static class ChannelExtensions
|
||||
{
|
||||
public static BatchingChannelReader<T, IMemoryOwner<T>> BatchByByteSize<T>(
|
||||
this ChannelReader<T> source,
|
||||
int batchSize,
|
||||
bool singleReader = false,
|
||||
bool allowSynchronousContinuations = false
|
||||
)
|
||||
where T : IHasByteSize =>
|
||||
new SizeBatchingChannelReader<T>(
|
||||
source ?? throw new ArgumentNullException(nameof(source)),
|
||||
batchSize,
|
||||
singleReader,
|
||||
allowSynchronousContinuations
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
using System.Threading.Channels;
|
||||
using Open.ChannelExtensions;
|
||||
|
||||
namespace Speckle.Sdk.Dependencies.Serialization;
|
||||
|
||||
public abstract class ChannelLoader<T>(CancellationToken cancellationToken)
|
||||
{
|
||||
private const int RECEIVE_CAPACITY = 5000;
|
||||
|
||||
private const int HTTP_GET_CHUNK_SIZE = 500;
|
||||
private const int MAX_PARALLELISM_HTTP = 4;
|
||||
private static readonly TimeSpan HTTP_BATCH_TIMEOUT = TimeSpan.FromSeconds(2);
|
||||
private const int MAX_SAVE_CACHE_BATCH = 500;
|
||||
private const int MAX_SAVE_CACHE_PARALLELISM = 4;
|
||||
|
||||
private readonly CancellationTokenSource _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
|
||||
private readonly Channel<string> _channel = Channel.CreateBounded<string>(
|
||||
new BoundedChannelOptions(RECEIVE_CAPACITY)
|
||||
{
|
||||
AllowSynchronousContinuations = true,
|
||||
Capacity = RECEIVE_CAPACITY,
|
||||
SingleWriter = false,
|
||||
SingleReader = false,
|
||||
FullMode = BoundedChannelFullMode.Wait,
|
||||
},
|
||||
_ => throw new NotImplementedException("Dropping items not supported.")
|
||||
);
|
||||
|
||||
protected async Task GetAndCache(IEnumerable<string> allChildrenIds, int? maxParallelism = null) =>
|
||||
await _channel
|
||||
.Source(allChildrenIds, _cts.Token)
|
||||
.Pipe(maxParallelism ?? Environment.ProcessorCount, CheckCache, cancellationToken: _cts.Token)
|
||||
.Filter(x => x is not null)
|
||||
.Batch(HTTP_GET_CHUNK_SIZE)
|
||||
.WithTimeout(HTTP_BATCH_TIMEOUT)
|
||||
.PipeAsync(
|
||||
maxParallelism ?? MAX_PARALLELISM_HTTP,
|
||||
async x => await Download(x).ConfigureAwait(false),
|
||||
-1,
|
||||
false,
|
||||
_cts.Token
|
||||
)
|
||||
.Join()
|
||||
.Batch(MAX_SAVE_CACHE_BATCH)
|
||||
.WithTimeout(HTTP_BATCH_TIMEOUT)
|
||||
.ReadAllConcurrently(maxParallelism ?? MAX_SAVE_CACHE_PARALLELISM, SaveToCache, _cts.Token)
|
||||
.ContinueWith(
|
||||
t =>
|
||||
{
|
||||
Exception? ex = t.Exception;
|
||||
if (ex is null && t.Status is TaskStatus.Canceled && !_cts.Token.IsCancellationRequested)
|
||||
{
|
||||
ex = new OperationCanceledException();
|
||||
}
|
||||
|
||||
if (ex is not null)
|
||||
{
|
||||
RecordException(ex);
|
||||
}
|
||||
|
||||
_channel.Writer.TryComplete(ex);
|
||||
},
|
||||
_cts.Token,
|
||||
TaskContinuationOptions.ExecuteSynchronously,
|
||||
TaskScheduler.Current
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
public abstract string? CheckCache(string id);
|
||||
|
||||
public async Task<List<T>> Download(List<string?> ids)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await DownloadInternal(ids).ConfigureAwait(false);
|
||||
}
|
||||
#pragma warning disable CA1031
|
||||
catch (Exception ex)
|
||||
#pragma warning restore CA1031
|
||||
{
|
||||
RecordException(ex);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Task<List<T>> DownloadInternal(List<string?> batch);
|
||||
|
||||
public void SaveToCache(List<T> batch)
|
||||
{
|
||||
try
|
||||
{
|
||||
SaveToCacheInternal(batch);
|
||||
}
|
||||
#pragma warning disable CA1031
|
||||
catch (Exception ex)
|
||||
#pragma warning restore CA1031
|
||||
{
|
||||
RecordException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void SaveToCacheInternal(List<T> batch);
|
||||
|
||||
protected Exception? Exception { get; private set; }
|
||||
|
||||
private void RecordException(Exception ex)
|
||||
{
|
||||
Exception = ex;
|
||||
_channel.Writer.TryComplete(ex);
|
||||
//cancel everything!
|
||||
_cts.Cancel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
using System.Buffers;
|
||||
using System.Threading.Channels;
|
||||
using Open.ChannelExtensions;
|
||||
using Speckle.Sdk.Serialisation.V2.Send;
|
||||
|
||||
namespace Speckle.Sdk.Dependencies.Serialization;
|
||||
|
||||
public abstract class ChannelSaver<T>
|
||||
where T : IHasByteSize
|
||||
{
|
||||
private const int SEND_CAPACITY = 1000;
|
||||
private const int HTTP_SEND_CHUNK_SIZE = 25_000_000; //bytes
|
||||
private static readonly TimeSpan HTTP_BATCH_TIMEOUT = TimeSpan.FromSeconds(2);
|
||||
private const int MAX_PARALLELISM_HTTP = 4;
|
||||
private const int HTTP_CAPACITY = 500;
|
||||
private const int MAX_CACHE_WRITE_PARALLELISM = 4;
|
||||
private const int MAX_CACHE_BATCH = 500;
|
||||
|
||||
private readonly Channel<T> _checkCacheChannel = Channel.CreateBounded<T>(
|
||||
new BoundedChannelOptions(SEND_CAPACITY)
|
||||
{
|
||||
AllowSynchronousContinuations = true,
|
||||
Capacity = SEND_CAPACITY,
|
||||
SingleWriter = false,
|
||||
SingleReader = false,
|
||||
FullMode = BoundedChannelFullMode.Wait,
|
||||
},
|
||||
_ => throw new NotImplementedException("Dropping items not supported.")
|
||||
);
|
||||
|
||||
public Task Start(
|
||||
int? maxParallelism,
|
||||
int? httpBatchSize,
|
||||
int? cacheBatchSize,
|
||||
CancellationToken cancellationToken
|
||||
) =>
|
||||
_checkCacheChannel
|
||||
.Reader.BatchByByteSize(httpBatchSize ?? HTTP_SEND_CHUNK_SIZE)
|
||||
.WithTimeout(HTTP_BATCH_TIMEOUT)
|
||||
.PipeAsync(
|
||||
maxParallelism ?? MAX_PARALLELISM_HTTP,
|
||||
async x => await SendToServer(x).ConfigureAwait(false),
|
||||
HTTP_CAPACITY,
|
||||
false,
|
||||
cancellationToken
|
||||
)
|
||||
.Join()
|
||||
.Batch(cacheBatchSize ?? MAX_CACHE_BATCH)
|
||||
.WithTimeout(HTTP_BATCH_TIMEOUT)
|
||||
.ReadAllConcurrently(maxParallelism ?? MAX_CACHE_WRITE_PARALLELISM, SaveToCache, cancellationToken)
|
||||
.ContinueWith(
|
||||
t =>
|
||||
{
|
||||
Exception? ex = t.Exception;
|
||||
if (ex is null && t.Status is TaskStatus.Canceled && !cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
ex = new OperationCanceledException();
|
||||
}
|
||||
|
||||
if (ex is not null)
|
||||
{
|
||||
RecordException(ex);
|
||||
}
|
||||
_checkCacheChannel.Writer.TryComplete(ex);
|
||||
},
|
||||
cancellationToken,
|
||||
TaskContinuationOptions.ExecuteSynchronously,
|
||||
TaskScheduler.Current
|
||||
);
|
||||
|
||||
public async Task SaveAsync(T item, CancellationToken cancellationToken)
|
||||
{
|
||||
if (Exception is not null)
|
||||
{
|
||||
return; //don't save if we're already done through an error
|
||||
}
|
||||
//can switch to check then try pattern when back pressure is needed or exceptions are too much
|
||||
//the trees don't need to respond to back pressure
|
||||
await _checkCacheChannel.Writer.WriteAsync(item, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<IMemoryOwner<T>> SendToServer(IMemoryOwner<T> batch)
|
||||
{
|
||||
try
|
||||
{
|
||||
await SendToServerInternal((Batch<T>)batch).ConfigureAwait(false);
|
||||
return batch;
|
||||
}
|
||||
#pragma warning disable CA1031
|
||||
catch (Exception ex)
|
||||
#pragma warning restore CA1031
|
||||
{
|
||||
RecordException(ex);
|
||||
return batch;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Task SendToServerInternal(Batch<T> batch);
|
||||
|
||||
public abstract void SaveToCache(List<T> item);
|
||||
|
||||
public void DoneTraversing() => _checkCacheChannel.Writer.TryComplete();
|
||||
|
||||
public async Task DoneSaving()
|
||||
{
|
||||
if (!_checkCacheChannel.Reader.Completion.IsCompleted)
|
||||
{
|
||||
await _checkCacheChannel.Reader.Completion.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public Exception? Exception { get; set; }
|
||||
|
||||
private void RecordException(Exception ex)
|
||||
{
|
||||
Exception = ex;
|
||||
_checkCacheChannel.Writer.TryComplete(ex);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System.Buffers;
|
||||
using System.Threading.Channels;
|
||||
using Open.ChannelExtensions;
|
||||
|
||||
namespace Speckle.Sdk.Serialisation.V2.Send;
|
||||
|
||||
public interface IHasByteSize
|
||||
{
|
||||
int ByteSize { get; }
|
||||
}
|
||||
|
||||
public sealed class SizeBatchingChannelReader<T>(
|
||||
ChannelReader<T> source,
|
||||
int batchSize,
|
||||
bool singleReader,
|
||||
bool syncCont = false
|
||||
)
|
||||
: BatchingChannelReader<T, IMemoryOwner<T>>(
|
||||
_ => BatchExtensions.CreateBatch<T>(),
|
||||
source,
|
||||
batchSize,
|
||||
singleReader,
|
||||
syncCont
|
||||
)
|
||||
where T : IHasByteSize
|
||||
{
|
||||
private readonly int _batchSize = batchSize;
|
||||
|
||||
protected override IMemoryOwner<T> CreateBatch(int capacity) => BatchExtensions.CreateBatch<T>();
|
||||
|
||||
protected override void TrimBatch(ref IMemoryOwner<T> batch, bool isVerifiedFull) =>
|
||||
BatchExtensions.TrimBatch(ref batch, isVerifiedFull);
|
||||
|
||||
protected override void AddBatchItem(IMemoryOwner<T> batch, T item) => batch.AddBatchItem(item);
|
||||
|
||||
protected override int GetBatchSize(IMemoryOwner<T> batch) => batch.GetBatchSize(_batchSize);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup Label="Compiler Properties">
|
||||
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
|
||||
<Configurations>Debug;Release;Local</Configurations>
|
||||
<ILRepackTargetConfigurations>Debug;Release;Local</ILRepackTargetConfigurations>
|
||||
<ILRepackRenameInternalized>true</ILRepackRenameInternalized>
|
||||
<ILRepackMergeDebugSymbols>true</ILRepackMergeDebugSymbols>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Nugetspec Package Properties">
|
||||
<PackageId>Speckle.Sdk.Dependencies</PackageId>
|
||||
<Description>The .NET SDK for Speckle</Description>
|
||||
<PackageTags>$(PackageTags) core sdk</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Nuget Package Properties">
|
||||
<IsPackable>true</IsPackable>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="ILRepack.FullAuto">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.ObjectPool" PrivateAssets="all" />
|
||||
<PackageReference Include="Polly" PrivateAssets="all" />
|
||||
<PackageReference Include="Polly.Contrib.WaitAndRetry" PrivateAssets="all" />
|
||||
<PackageReference Include="Polly.Extensions.Http" PrivateAssets="all" />
|
||||
<PackageReference Include="Open.ChannelExtensions" PrivateAssets="all" />
|
||||
<PackageReference Include="System.Threading.Channels" PrivateAssets="all" />
|
||||
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,80 @@
|
||||
using Polly;
|
||||
using Speckle.Sdk.Logging;
|
||||
|
||||
namespace Speckle.Sdk.Helpers;
|
||||
|
||||
internal sealed class SpeckleHttpClientHandler : DelegatingHandler
|
||||
{
|
||||
private readonly IAsyncPolicy<HttpResponseMessage> _resiliencePolicy;
|
||||
private readonly ISdkActivityFactory _activityFactory;
|
||||
|
||||
internal SpeckleHttpClientHandler(
|
||||
HttpMessageHandler innerHandler,
|
||||
ISdkActivityFactory activityFactory,
|
||||
IAsyncPolicy<HttpResponseMessage> resiliencePolicy
|
||||
)
|
||||
: base(innerHandler)
|
||||
{
|
||||
_activityFactory = activityFactory;
|
||||
_resiliencePolicy = resiliencePolicy;
|
||||
}
|
||||
|
||||
/// <exception cref="OperationCanceledException"><paramref name="cancellationToken"/> requested cancel</exception>
|
||||
/// <exception cref="HttpRequestException">Send request failed</exception>
|
||||
protected override async Task<HttpResponseMessage> SendAsync(
|
||||
HttpRequestMessage request,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
// this is a preliminary client server correlation implementation
|
||||
// refactor this, when we have a better observability stack
|
||||
var context = new Context();
|
||||
using var activity = _activityFactory.Start("Http Request");
|
||||
{
|
||||
activity?.SetTag("http.method", request.Method);
|
||||
activity?.SetTag("http.url", request.RequestUri);
|
||||
activity?.SetTag("correlationId", context.CorrelationId);
|
||||
|
||||
context.Add("retryCount", 0);
|
||||
|
||||
request.Headers.Add("x-request-id", context.CorrelationId.ToString());
|
||||
activity?.InjectHeaders((k, v) => request.Headers.TryAddWithoutValidation(k, v));
|
||||
|
||||
var policyResult = await _resiliencePolicy
|
||||
.ExecuteAndCaptureAsync(
|
||||
ctx =>
|
||||
{
|
||||
return base.SendAsync(request, cancellationToken);
|
||||
},
|
||||
context
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
context.TryGetValue("retryCount", out var retryCount);
|
||||
activity?.SetTag("retryCount", retryCount);
|
||||
|
||||
if (policyResult.Outcome == OutcomeType.Successful)
|
||||
{
|
||||
activity?.SetStatus(SdkActivityStatusCode.Ok);
|
||||
return policyResult.Result;
|
||||
}
|
||||
|
||||
activity?.SetStatus(SdkActivityStatusCode.Error);
|
||||
|
||||
if (policyResult.FinalException is null)
|
||||
{
|
||||
// Outcome was not successful, but did not terminate with an exception (e.g. repeated 500 responses)
|
||||
return policyResult.FinalHandledResult;
|
||||
}
|
||||
|
||||
activity?.RecordException(policyResult.FinalException);
|
||||
|
||||
// if the policy failed due to a cancellation, AND it was our cancellation token, then don't wrap the exception, and rethrow an new cancellation
|
||||
if (policyResult.FinalException is OperationCanceledException)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
throw new HttpRequestException("Policy Failed", policyResult.FinalException);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using Polly;
|
||||
using Polly.Contrib.WaitAndRetry;
|
||||
using Polly.Extensions.Http;
|
||||
using Polly.Timeout;
|
||||
using Speckle.InterfaceGenerator;
|
||||
using Speckle.Sdk.Logging;
|
||||
|
||||
namespace Speckle.Sdk.Helpers;
|
||||
|
||||
[GenerateAutoInterface]
|
||||
public sealed class SpeckleHttpClientHandlerFactory(ISdkActivityFactory activityFactory)
|
||||
: ISpeckleHttpClientHandlerFactory
|
||||
{
|
||||
public const int DEFAULT_TIMEOUT_SECONDS = 60;
|
||||
|
||||
public IEnumerable<TimeSpan> DefaultDelay()
|
||||
{
|
||||
return Backoff.DecorrelatedJitterBackoffV2(TimeSpan.FromMilliseconds(200), 5);
|
||||
}
|
||||
|
||||
internal IAsyncPolicy<HttpResponseMessage> HttpAsyncPolicy(
|
||||
IEnumerable<TimeSpan>? delay = null,
|
||||
int timeoutSeconds = DEFAULT_TIMEOUT_SECONDS
|
||||
)
|
||||
{
|
||||
var retryPolicy = HttpPolicyExtensions
|
||||
.HandleTransientHttpError()
|
||||
.Or<TimeoutRejectedException>()
|
||||
.WaitAndRetryAsync(
|
||||
delay ?? DefaultDelay(),
|
||||
(ex, timeSpan, retryAttempt, context) =>
|
||||
{
|
||||
context.Remove("retryCount");
|
||||
context.Add("retryCount", retryAttempt);
|
||||
}
|
||||
);
|
||||
|
||||
var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(timeoutSeconds);
|
||||
|
||||
return Policy.WrapAsync(retryPolicy, timeoutPolicy);
|
||||
}
|
||||
|
||||
public DelegatingHandler Create(
|
||||
HttpMessageHandler? innerHandler = null,
|
||||
int timeoutSeconds = DEFAULT_TIMEOUT_SECONDS
|
||||
) =>
|
||||
new SpeckleHttpClientHandler(
|
||||
innerHandler ?? new HttpClientHandler(),
|
||||
activityFactory,
|
||||
HttpAsyncPolicy(timeoutSeconds: timeoutSeconds)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dependencies": {
|
||||
".NETStandard,Version=v2.0": {
|
||||
"ILRepack.FullAuto": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.6.0, )",
|
||||
"resolved": "1.6.0",
|
||||
"contentHash": "34qp/HQ0XRIWCjtNGUOslJ6p9eNWqHXZQ+xx1iBCvXy3mj8tEiqIwRG+LubFyKCJITqMh5cpFvFl20/6+Dmy+g==",
|
||||
"dependencies": {
|
||||
"ILRepack": "2.0.33"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.ObjectPool": {
|
||||
"type": "Direct",
|
||||
"requested": "[9.0.3, )",
|
||||
"resolved": "9.0.3",
|
||||
"contentHash": "4uPdnj9hLRrb3ZSeVEDtwIm9nNrrT9vAXYC9o1/yTW8lGOPwTyI2QlkcICwYEGM1LESGTFidcPMFACznUZKbIQ=="
|
||||
},
|
||||
"Microsoft.SourceLink.GitHub": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.0.0, )",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Build.Tasks.Git": "8.0.0",
|
||||
"Microsoft.SourceLink.Common": "8.0.0"
|
||||
}
|
||||
},
|
||||
"NETStandard.Library": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.0.3, )",
|
||||
"resolved": "2.0.3",
|
||||
"contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==",
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.Platforms": "1.1.0"
|
||||
}
|
||||
},
|
||||
"Open.ChannelExtensions": {
|
||||
"type": "Direct",
|
||||
"requested": "[9.0.0, )",
|
||||
"resolved": "9.0.0",
|
||||
"contentHash": "DP+l5S6G46wcuY4I4kNXE+RDOmJr0DKuMienOdt0mMBN9z7vmLSC8YQbqCyb9i9LNjXj1tgCx5LyitJiRr/v7g==",
|
||||
"dependencies": {
|
||||
"Microsoft.Bcl.AsyncInterfaces": "9.0.0",
|
||||
"System.Collections.Immutable": "9.0.0",
|
||||
"System.Threading.Channels": "9.0.0"
|
||||
}
|
||||
},
|
||||
"Polly": {
|
||||
"type": "Direct",
|
||||
"requested": "[7.2.3, )",
|
||||
"resolved": "7.2.3",
|
||||
"contentHash": "DeCY0OFbNdNxsjntr1gTXHJ5pKUwYzp04Er2LLeN3g6pWhffsGuKVfMBLe1lw7x76HrPkLxKEFxBlpRxS2nDEQ=="
|
||||
},
|
||||
"Polly.Contrib.WaitAndRetry": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.1.1, )",
|
||||
"resolved": "1.1.1",
|
||||
"contentHash": "1MUQLiSo4KDkQe6nzQRhIU05lm9jlexX5BVsbuw0SL82ynZ+GzAHQxJVDPVBboxV37Po3SG077aX8DuSy8TkaA=="
|
||||
},
|
||||
"Polly.Extensions.Http": {
|
||||
"type": "Direct",
|
||||
"requested": "[3.0.0, )",
|
||||
"resolved": "3.0.0",
|
||||
"contentHash": "drrG+hB3pYFY7w1c3BD+lSGYvH2oIclH8GRSehgfyP5kjnFnHKQuuBhuHLv+PWyFuaTDyk/vfRpnxOzd11+J8g==",
|
||||
"dependencies": {
|
||||
"Polly": "7.1.0"
|
||||
}
|
||||
},
|
||||
"PolySharp": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.15.0, )",
|
||||
"resolved": "1.15.0",
|
||||
"contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g=="
|
||||
},
|
||||
"Speckle.InterfaceGenerator": {
|
||||
"type": "Direct",
|
||||
"requested": "[0.9.6, )",
|
||||
"resolved": "0.9.6",
|
||||
"contentHash": "HKH7tYrYYlCK1ct483hgxERAdVdMtl7gUKW9ijWXxA1UsYR4Z+TrRHYmzZ9qmpu1NnTycSrp005NYM78GDKV1w=="
|
||||
},
|
||||
"System.Threading.Channels": {
|
||||
"type": "Direct",
|
||||
"requested": "[9.0.2, )",
|
||||
"resolved": "9.0.2",
|
||||
"contentHash": "pUmqkuBS9OxWHOlfNad09Oxc8gRbxgN9UQtsqHPst4jfcgZRxQetNcsT2oe+VnUpEFAtBy1FZcJZiOscrBmA7g==",
|
||||
"dependencies": {
|
||||
"Microsoft.Bcl.AsyncInterfaces": "9.0.2",
|
||||
"System.Threading.Tasks.Extensions": "4.5.4"
|
||||
}
|
||||
},
|
||||
"ILRepack": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.0.33",
|
||||
"contentHash": "xb2h1CsOepoYwdXEPui9VcQglwABQwNf9cccZbf+acarEzF5PUp8Xx71nFXIhOgEdm6wrxAoF6xAxK4m/XFRUQ=="
|
||||
},
|
||||
"Microsoft.Build.Tasks.Git": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ=="
|
||||
},
|
||||
"Microsoft.NETCore.Platforms": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.1.0",
|
||||
"contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A=="
|
||||
},
|
||||
"Microsoft.SourceLink.Common": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
|
||||
},
|
||||
"System.Buffers": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.1",
|
||||
"contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg=="
|
||||
},
|
||||
"System.Collections.Immutable": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.0",
|
||||
"contentHash": "QhkXUl2gNrQtvPmtBTQHb0YsUrDiDQ2QS09YbtTTiSjGcf7NBqtYbrG/BE06zcBPCKEwQGzIv13IVdXNOSub2w==",
|
||||
"dependencies": {
|
||||
"System.Memory": "4.5.5",
|
||||
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
|
||||
}
|
||||
},
|
||||
"System.Memory": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.5",
|
||||
"contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==",
|
||||
"dependencies": {
|
||||
"System.Buffers": "4.5.1",
|
||||
"System.Numerics.Vectors": "4.4.0",
|
||||
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
|
||||
}
|
||||
},
|
||||
"System.Numerics.Vectors": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.4.0",
|
||||
"contentHash": "UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ=="
|
||||
},
|
||||
"System.Runtime.CompilerServices.Unsafe": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
|
||||
},
|
||||
"System.Threading.Tasks.Extensions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.4",
|
||||
"contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==",
|
||||
"dependencies": {
|
||||
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
|
||||
}
|
||||
},
|
||||
"Microsoft.Bcl.AsyncInterfaces": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[5.0.0, )",
|
||||
"resolved": "9.0.2",
|
||||
"contentHash": "1CED0BGD7dCKsbe7tDhzpPB2Qdi9x35QChu6zkBEI4s0T5bDkkttGReqQnOeOfRNSxtP2WvpX6Ik/0O93XDuMw==",
|
||||
"dependencies": {
|
||||
"System.Threading.Tasks.Extensions": "4.5.4"
|
||||
}
|
||||
}
|
||||
},
|
||||
"net8.0": {
|
||||
"ILRepack.FullAuto": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.6.0, )",
|
||||
"resolved": "1.6.0",
|
||||
"contentHash": "34qp/HQ0XRIWCjtNGUOslJ6p9eNWqHXZQ+xx1iBCvXy3mj8tEiqIwRG+LubFyKCJITqMh5cpFvFl20/6+Dmy+g==",
|
||||
"dependencies": {
|
||||
"ILRepack": "2.0.33"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.ObjectPool": {
|
||||
"type": "Direct",
|
||||
"requested": "[9.0.3, )",
|
||||
"resolved": "9.0.3",
|
||||
"contentHash": "4uPdnj9hLRrb3ZSeVEDtwIm9nNrrT9vAXYC9o1/yTW8lGOPwTyI2QlkcICwYEGM1LESGTFidcPMFACznUZKbIQ=="
|
||||
},
|
||||
"Microsoft.SourceLink.GitHub": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.0.0, )",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Build.Tasks.Git": "8.0.0",
|
||||
"Microsoft.SourceLink.Common": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Open.ChannelExtensions": {
|
||||
"type": "Direct",
|
||||
"requested": "[9.0.0, )",
|
||||
"resolved": "9.0.0",
|
||||
"contentHash": "DP+l5S6G46wcuY4I4kNXE+RDOmJr0DKuMienOdt0mMBN9z7vmLSC8YQbqCyb9i9LNjXj1tgCx5LyitJiRr/v7g=="
|
||||
},
|
||||
"Polly": {
|
||||
"type": "Direct",
|
||||
"requested": "[7.2.3, )",
|
||||
"resolved": "7.2.3",
|
||||
"contentHash": "DeCY0OFbNdNxsjntr1gTXHJ5pKUwYzp04Er2LLeN3g6pWhffsGuKVfMBLe1lw7x76HrPkLxKEFxBlpRxS2nDEQ=="
|
||||
},
|
||||
"Polly.Contrib.WaitAndRetry": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.1.1, )",
|
||||
"resolved": "1.1.1",
|
||||
"contentHash": "1MUQLiSo4KDkQe6nzQRhIU05lm9jlexX5BVsbuw0SL82ynZ+GzAHQxJVDPVBboxV37Po3SG077aX8DuSy8TkaA=="
|
||||
},
|
||||
"Polly.Extensions.Http": {
|
||||
"type": "Direct",
|
||||
"requested": "[3.0.0, )",
|
||||
"resolved": "3.0.0",
|
||||
"contentHash": "drrG+hB3pYFY7w1c3BD+lSGYvH2oIclH8GRSehgfyP5kjnFnHKQuuBhuHLv+PWyFuaTDyk/vfRpnxOzd11+J8g==",
|
||||
"dependencies": {
|
||||
"Polly": "7.1.0"
|
||||
}
|
||||
},
|
||||
"PolySharp": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.15.0, )",
|
||||
"resolved": "1.15.0",
|
||||
"contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g=="
|
||||
},
|
||||
"Speckle.InterfaceGenerator": {
|
||||
"type": "Direct",
|
||||
"requested": "[0.9.6, )",
|
||||
"resolved": "0.9.6",
|
||||
"contentHash": "HKH7tYrYYlCK1ct483hgxERAdVdMtl7gUKW9ijWXxA1UsYR4Z+TrRHYmzZ9qmpu1NnTycSrp005NYM78GDKV1w=="
|
||||
},
|
||||
"System.Threading.Channels": {
|
||||
"type": "Direct",
|
||||
"requested": "[9.0.2, )",
|
||||
"resolved": "9.0.2",
|
||||
"contentHash": "pUmqkuBS9OxWHOlfNad09Oxc8gRbxgN9UQtsqHPst4jfcgZRxQetNcsT2oe+VnUpEFAtBy1FZcJZiOscrBmA7g=="
|
||||
},
|
||||
"ILRepack": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.0.33",
|
||||
"contentHash": "xb2h1CsOepoYwdXEPui9VcQglwABQwNf9cccZbf+acarEzF5PUp8Xx71nFXIhOgEdm6wrxAoF6xAxK4m/XFRUQ=="
|
||||
},
|
||||
"Microsoft.Build.Tasks.Git": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ=="
|
||||
},
|
||||
"Microsoft.SourceLink.Common": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
using Speckle.Sdk.Api.GraphQL;
|
||||
|
||||
namespace Speckle.Sdk.Api;
|
||||
|
||||
/// <summary>
|
||||
/// The base class for all GraphQL errors (these are errors in the graphql response)
|
||||
/// Some specific codes are maped to subtypes <see cref="GraphQLErrorHandler"/>
|
||||
/// <seealso cref="SpeckleGraphQLForbiddenException"/>
|
||||
/// <seealso cref="SpeckleGraphQLInternalErrorException"/>
|
||||
/// <seealso cref="SpeckleGraphQLBadInputException"/>
|
||||
/// <seealso cref="SpeckleGraphQLInvalidQueryException"/>
|
||||
/// </summary>
|
||||
public class SpeckleGraphQLException : SpeckleException
|
||||
{
|
||||
public SpeckleGraphQLException() { }
|
||||
|
||||
public SpeckleGraphQLException(string? message)
|
||||
: base(message) { }
|
||||
|
||||
public SpeckleGraphQLException(string? message, Exception? innerException)
|
||||
: base(message, innerException) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a "FORBIDDEN" or "UNAUTHORIZED" GraphQL error as an exception.
|
||||
/// https://www.apollographql.com/docs/apollo-server/v2/data/errors/#unauthenticated
|
||||
/// https://www.apollographql.com/docs/apollo-server/v2/data/errors/#forbidden
|
||||
/// </summary>
|
||||
public sealed class SpeckleGraphQLForbiddenException : SpeckleGraphQLException
|
||||
{
|
||||
public SpeckleGraphQLForbiddenException() { }
|
||||
|
||||
public SpeckleGraphQLForbiddenException(string? message)
|
||||
: base(message) { }
|
||||
|
||||
public SpeckleGraphQLForbiddenException(string? message, Exception? innerException)
|
||||
: base(message, innerException) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a "INTERNAL_SERVER_ERROR" GraphQL error as an exception.
|
||||
/// https://www.apollographql.com/docs/apollo-server/v2/data/errors#internal_server_error
|
||||
/// </summary>
|
||||
public sealed class SpeckleGraphQLInternalErrorException : SpeckleGraphQLException
|
||||
{
|
||||
public SpeckleGraphQLInternalErrorException() { }
|
||||
|
||||
public SpeckleGraphQLInternalErrorException(string? message)
|
||||
: base(message) { }
|
||||
|
||||
public SpeckleGraphQLInternalErrorException(string? message, Exception? innerException)
|
||||
: base(message, innerException) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the custom "STREAM_NOT_FOUND" GraphQL error as an exception.
|
||||
/// </summary>
|
||||
public sealed class SpeckleGraphQLStreamNotFoundException : SpeckleGraphQLException
|
||||
{
|
||||
public SpeckleGraphQLStreamNotFoundException() { }
|
||||
|
||||
public SpeckleGraphQLStreamNotFoundException(string? message)
|
||||
: base(message) { }
|
||||
|
||||
public SpeckleGraphQLStreamNotFoundException(string? message, Exception? innerException)
|
||||
: base(message, innerException) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a "BAD_USER_INPUT" GraphQL error as an exception.
|
||||
/// https://www.apollographql.com/docs/apollo-server/v2/data/errors#bad_user_input
|
||||
/// </summary>
|
||||
public sealed class SpeckleGraphQLBadInputException : SpeckleGraphQLException
|
||||
{
|
||||
public SpeckleGraphQLBadInputException() { }
|
||||
|
||||
public SpeckleGraphQLBadInputException(string? message)
|
||||
: base(message) { }
|
||||
|
||||
public SpeckleGraphQLBadInputException(string? message, Exception? innerException)
|
||||
: base(message, innerException) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a "GRAPHQL_PARSE_FAILED" or "GRAPHQL_VALIDATION_FAILED" GraphQL error as an exception.
|
||||
/// https://www.apollographql.com/docs/apollo-server/v2/data/errors#graphql_parse_failed
|
||||
/// https://www.apollographql.com/docs/apollo-server/v2/data/errors#graphql_validation_failed
|
||||
/// </summary>
|
||||
public sealed class SpeckleGraphQLInvalidQueryException : SpeckleGraphQLException
|
||||
{
|
||||
public SpeckleGraphQLInvalidQueryException() { }
|
||||
|
||||
public SpeckleGraphQLInvalidQueryException(string? message)
|
||||
: base(message) { }
|
||||
|
||||
public SpeckleGraphQLInvalidQueryException(string? message, Exception? innerException)
|
||||
: base(message, innerException) { }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
[*.{cs,vb}]
|
||||
|
||||
# Name properties with camelCase
|
||||
dotnet_naming_rule.properties_should_be_camel_case.severity = none
|
||||
dotnet_naming_rule.properties_should_be_camel_case.symbols = properties
|
||||
dotnet_naming_rule.properties_should_be_camel_case.style = property_style
|
||||
|
||||
dotnet_naming_symbols.properties.applicable_kinds = property
|
||||
dotnet_naming_style.property_style.capitalization = pascal_case
|
||||
@@ -0,0 +1,247 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Net.WebSockets;
|
||||
using System.Reflection;
|
||||
using GraphQL;
|
||||
using GraphQL.Client.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Speckle.InterfaceGenerator;
|
||||
using Speckle.Newtonsoft.Json;
|
||||
using Speckle.Newtonsoft.Json.Serialization;
|
||||
using Speckle.Sdk.Api.GraphQL;
|
||||
using Speckle.Sdk.Api.GraphQL.Resources;
|
||||
using Speckle.Sdk.Api.GraphQL.Serializer;
|
||||
using Speckle.Sdk.Credentials;
|
||||
using Speckle.Sdk.Dependencies;
|
||||
using Speckle.Sdk.Helpers;
|
||||
using Speckle.Sdk.Logging;
|
||||
|
||||
namespace Speckle.Sdk.Api;
|
||||
|
||||
public partial interface IClient : IDisposable
|
||||
{
|
||||
GraphQLHttpClient GQLClient { get; }
|
||||
}
|
||||
|
||||
[SuppressMessage("Maintainability", "CA1506:Avoid excessive class coupling", Justification = "Class needs refactor")]
|
||||
[GenerateAutoInterface]
|
||||
public sealed class Client : ISpeckleGraphQLClient, IClient
|
||||
{
|
||||
private readonly ILogger<Client> _logger;
|
||||
private readonly ISdkActivityFactory _activityFactory;
|
||||
public ProjectResource Project { get; }
|
||||
public ModelResource Model { get; }
|
||||
public VersionResource Version { get; }
|
||||
public ActiveUserResource ActiveUser { get; }
|
||||
public OtherUserResource OtherUser { get; }
|
||||
public ProjectInviteResource ProjectInvite { get; }
|
||||
public CommentResource Comment { get; }
|
||||
public SubscriptionResource Subscription { get; }
|
||||
|
||||
public Uri ServerUrl => new(Account.serverInfo.url);
|
||||
|
||||
[JsonIgnore]
|
||||
public Account Account { get; }
|
||||
|
||||
private HttpClient HttpClient { get; }
|
||||
|
||||
[AutoInterfaceIgnore]
|
||||
public GraphQLHttpClient GQLClient { get; }
|
||||
|
||||
/// <param name="account"></param>
|
||||
/// <exception cref="ArgumentException"><paramref name="account"/> was null</exception>
|
||||
public Client(
|
||||
ILogger<Client> logger,
|
||||
ISdkActivityFactory activityFactory,
|
||||
ISpeckleApplication application,
|
||||
ISpeckleHttp speckleHttp,
|
||||
Account account
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
_activityFactory = activityFactory;
|
||||
Account = account ?? throw new ArgumentException("Provided account is null.");
|
||||
|
||||
Project = new(this);
|
||||
Model = new(this);
|
||||
Version = new(this);
|
||||
ActiveUser = new(this);
|
||||
OtherUser = new(this);
|
||||
ProjectInvite = new(this);
|
||||
Comment = new(this);
|
||||
Subscription = new(this);
|
||||
|
||||
HttpClient = CreateHttpClient(application, speckleHttp, account);
|
||||
|
||||
GQLClient = CreateGraphQLClient(account, HttpClient);
|
||||
}
|
||||
|
||||
[AutoInterfaceIgnore]
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
Subscription.Dispose();
|
||||
GQLClient.Dispose();
|
||||
}
|
||||
catch (Exception ex) when (!ex.IsFatal()) { }
|
||||
}
|
||||
|
||||
internal async Task<T> ExecuteWithResiliencePolicies<T>(Func<Task<T>> func) =>
|
||||
await GraphQLRetry
|
||||
.ExecuteAsync<T, SpeckleGraphQLInternalErrorException>(
|
||||
func,
|
||||
(
|
||||
(ex, timeout) =>
|
||||
{
|
||||
_logger.LogDebug(
|
||||
ex,
|
||||
"The previous attempt at executing function to get {resultType} failed with {exceptionMessage}. Retrying after {timeout}",
|
||||
typeof(T).Name,
|
||||
ex.Message,
|
||||
timeout
|
||||
);
|
||||
}
|
||||
)
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<T> ExecuteGraphQLRequest<T>(GraphQLRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var activity = _activityFactory.Start();
|
||||
activity?.SetTag("responseType", typeof(T));
|
||||
activity?.SetTag("request.query", request.Query);
|
||||
activity?.SetTag("request.operationName", request.OperationName);
|
||||
activity?.SetTag("request.variables", request.Variables);
|
||||
activity?.SetTag("request.extensions", request.Extensions);
|
||||
activity?.SetTag("clientOptions.endPoint", GQLClient.Options.EndPoint);
|
||||
activity?.SetTag("clientOptions.medaType", GQLClient.Options.MediaType);
|
||||
activity?.SetTag("clientOptions.webSocketEndPoint", GQLClient.Options.WebSocketEndPoint);
|
||||
activity?.SetTag("clientOptions.webSocketProtocol", GQLClient.Options.WebSocketProtocol);
|
||||
|
||||
try
|
||||
{
|
||||
var ret = await ExecuteWithResiliencePolicies(async () =>
|
||||
{
|
||||
GraphQLResponse<T> result = await GQLClient
|
||||
.SendMutationAsync<T>(request, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
result.EnsureGraphQLSuccess();
|
||||
return result.Data;
|
||||
})
|
||||
.ConfigureAwait(false);
|
||||
activity?.SetStatus(SdkActivityStatusCode.Ok);
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
activity?.SetStatus(SdkActivityStatusCode.Error);
|
||||
activity?.RecordException(ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
[AutoInterfaceIgnore]
|
||||
IDisposable ISpeckleGraphQLClient.SubscribeTo<T>(GraphQLRequest request, Action<object, T> callback) =>
|
||||
SubscribeTo(request, callback);
|
||||
|
||||
/// <inheritdoc cref="ISpeckleGraphQLClient.SubscribeTo{T}"/>
|
||||
private IDisposable SubscribeTo<T>(GraphQLRequest request, Action<object, T> callback)
|
||||
{
|
||||
try
|
||||
{
|
||||
var res = GQLClient.CreateSubscriptionStream<T>(request);
|
||||
return res.Subscribe(
|
||||
response =>
|
||||
{
|
||||
try
|
||||
{
|
||||
response.EnsureGraphQLSuccess();
|
||||
|
||||
callback(this, response.Data);
|
||||
}
|
||||
catch (AggregateException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Subscription for {type} got a response with errors", typeof(T).Name);
|
||||
throw;
|
||||
}
|
||||
},
|
||||
ex =>
|
||||
{
|
||||
// we're logging this as an error for now, to keep track of failures
|
||||
// so far we've swallowed these errors
|
||||
_logger.LogError(
|
||||
ex,
|
||||
"Subscription for {resultType} terminated unexpectedly with {exceptionMessage}",
|
||||
typeof(T).Name,
|
||||
ex.Message
|
||||
);
|
||||
// we could be throwing like this:
|
||||
// throw ex;
|
||||
}
|
||||
);
|
||||
}
|
||||
catch (Exception ex) when (!ex.IsFatal() && ex is not ObjectDisposedException)
|
||||
{
|
||||
throw new SpeckleGraphQLException($"Subscription for {typeof(T)} failed to start", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static GraphQLHttpClient CreateGraphQLClient(Account account, HttpClient httpClient)
|
||||
{
|
||||
var gQLClient = new GraphQLHttpClient(
|
||||
new GraphQLHttpClientOptions
|
||||
{
|
||||
EndPoint = new Uri(new Uri(account.serverInfo.url), "/graphql"),
|
||||
UseWebSocketForQueriesAndMutations = false,
|
||||
WebSocketProtocol = "graphql-ws",
|
||||
ConfigureWebSocketConnectionInitPayload = _ =>
|
||||
{
|
||||
return SpeckleHttp.CanAddAuth(account.token, out string? authValue)
|
||||
? new { Authorization = authValue }
|
||||
: null;
|
||||
},
|
||||
},
|
||||
new NewtonsoftJsonSerializer(
|
||||
new JsonSerializerSettings()
|
||||
{
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver { IgnoreIsSpecifiedMembers = true }, //(Default)
|
||||
MissingMemberHandling = MissingMemberHandling.Error, //(not default) If you query for a member that doesn't exist, this will throw (except websocket responses see https://github.com/graphql-dotnet/graphql-client/issues/660)
|
||||
Converters =
|
||||
{
|
||||
new ConstantCaseEnumConverter(),
|
||||
} //(Default) enums will be serialized using the GraphQL const case standard
|
||||
,
|
||||
}
|
||||
),
|
||||
httpClient
|
||||
);
|
||||
|
||||
gQLClient.WebSocketReceiveErrors.Subscribe(e =>
|
||||
{
|
||||
if (e is WebSocketException we)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"WebSocketException: {we.Message} (WebSocketError {we.WebSocketErrorCode}, ErrorCode {we.ErrorCode}, NativeErrorCode {we.NativeErrorCode}"
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Exception in websocket receive stream: {e}");
|
||||
}
|
||||
});
|
||||
return gQLClient;
|
||||
}
|
||||
|
||||
private static HttpClient CreateHttpClient(ISpeckleApplication application, ISpeckleHttp speckleHttp, Account account)
|
||||
{
|
||||
var httpClient = speckleHttp.CreateHttpClient(timeoutSeconds: 30, authorizationToken: account.token);
|
||||
|
||||
httpClient.DefaultRequestHeaders.Add("apollographql-client-name", application.ApplicationAndVersion);
|
||||
httpClient.DefaultRequestHeaders.Add(
|
||||
"apollographql-client-version",
|
||||
Assembly.GetExecutingAssembly().GetName().Version?.ToString()
|
||||
);
|
||||
return httpClient;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Speckle.InterfaceGenerator;
|
||||
using Speckle.Sdk.Credentials;
|
||||
using Speckle.Sdk.Helpers;
|
||||
using Speckle.Sdk.Logging;
|
||||
|
||||
namespace Speckle.Sdk.Api;
|
||||
|
||||
[GenerateAutoInterface]
|
||||
public class ClientFactory(
|
||||
ILoggerFactory loggerFactory,
|
||||
ISdkActivityFactory activityFactory,
|
||||
ISpeckleApplication application,
|
||||
ISpeckleHttp speckleHttp
|
||||
) : IClientFactory
|
||||
{
|
||||
public IClient Create(Account account) =>
|
||||
new Client(loggerFactory.CreateLogger<Client>(), activityFactory, application, speckleHttp, account);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
||||
|
||||
//This enum isn't explicitly defined in the schema, instead its usages are int typed (But represent an enum)
|
||||
public enum FileUploadConversionStatus
|
||||
{
|
||||
Queued = 0,
|
||||
Processing = 1,
|
||||
Success = 2,
|
||||
Error = 3,
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
||||
|
||||
public enum ProjectCommentsUpdatedMessageType
|
||||
{
|
||||
ARCHIVED,
|
||||
CREATED,
|
||||
UPDATED,
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
||||
|
||||
public enum ProjectFileImportUpdatedMessageType
|
||||
{
|
||||
CREATED,
|
||||
UPDATED,
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
||||
|
||||
public enum ProjectModelsUpdatedMessageType
|
||||
{
|
||||
CREATED,
|
||||
DELETED,
|
||||
UPDATED,
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user