diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000000000000000000000000000000000..f76aad0b8e204ddad3a675375bd5be6cc73eed8b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,225 @@ +# 如果要从更高级别的目录继承 .editorconfig 设置,请删除以下行 +root = true + +# c# 文件 +[*.cs] + +#### Core EditorConfig 选项 #### + +# 缩进和间距 +indent_size = 4 +indent_style = space +tab_width = 4 + +# 新行首选项 +end_of_line = crlf +insert_final_newline = false + +#### .NET 编码约定 #### + +# 组织 Using +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false +file_header_template = unset + +# this. 和 Me. 首选项 +dotnet_style_qualification_for_event = false +dotnet_style_qualification_for_field = false +dotnet_style_qualification_for_method = false +dotnet_style_qualification_for_property = false + +# 语言关键字与 bcl 类型首选项 +dotnet_style_predefined_type_for_locals_parameters_members = true +dotnet_style_predefined_type_for_member_access = true + +# 括号首选项 +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_operators = never_if_unnecessary +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity + +# 修饰符首选项 +dotnet_style_require_accessibility_modifiers = for_non_interface_members + +# 表达式级首选项 +dotnet_style_coalesce_expression = true +dotnet_style_collection_initializer = true +dotnet_style_explicit_tuple_names = true +dotnet_style_namespace_match_folder = true +dotnet_style_null_propagation = true +dotnet_style_object_initializer = true +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true +dotnet_style_prefer_compound_assignment = true +dotnet_style_prefer_conditional_expression_over_assignment = true +dotnet_style_prefer_conditional_expression_over_return = true +dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed +dotnet_style_prefer_inferred_anonymous_type_member_names = true +dotnet_style_prefer_inferred_tuple_names = true +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:error +dotnet_style_prefer_simplified_boolean_expressions = true +dotnet_style_prefer_simplified_interpolation = true + +# 字段首选项 +dotnet_style_readonly_field = true + +# 参数首选项 +dotnet_code_quality_unused_parameters = all + +# 禁止显示首选项 +dotnet_remove_unnecessary_suppression_exclusions = 0 + +# 新行首选项 +dotnet_style_allow_multiple_blank_lines_experimental = true +dotnet_style_allow_statement_immediately_after_block_experimental = true + +#### c# 编码约定 #### + +# var 首选项 +csharp_style_var_elsewhere = false +csharp_style_var_for_built_in_types = false +csharp_style_var_when_type_is_apparent = false + +# Expression-bodied 成员 +csharp_style_expression_bodied_accessors = true +csharp_style_expression_bodied_constructors = false +csharp_style_expression_bodied_indexers = true +csharp_style_expression_bodied_lambdas = true +csharp_style_expression_bodied_local_functions = false +csharp_style_expression_bodied_methods = false +csharp_style_expression_bodied_operators = false +csharp_style_expression_bodied_properties = true + +# 模式匹配首选项 +csharp_style_pattern_matching_over_as_with_null_check = true +csharp_style_pattern_matching_over_is_with_cast_check = true +csharp_style_prefer_extended_property_pattern = true +csharp_style_prefer_not_pattern = true +csharp_style_prefer_pattern_matching = true +csharp_style_prefer_switch_expression = true + +# Null 检查首选项 +csharp_style_conditional_delegate_call = true + +# 修饰符首选项 +csharp_prefer_static_local_function = true +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async + +# 代码块首选项 +csharp_prefer_braces = true +csharp_prefer_simple_using_statement = true +csharp_style_namespace_declarations = block_scoped +csharp_style_prefer_method_group_conversion = true +csharp_style_prefer_top_level_statements = true + +# 表达式级首选项 +csharp_prefer_simple_default_expression = true +csharp_style_deconstructed_variable_declaration = true +csharp_style_implicit_object_creation_when_type_is_apparent = true +csharp_style_inlined_variable_declaration = true +csharp_style_prefer_index_operator = true +csharp_style_prefer_local_over_anonymous_function = true +csharp_style_prefer_null_check_over_type_check = true +csharp_style_prefer_range_operator = true +csharp_style_prefer_tuple_swap = true +csharp_style_prefer_utf8_string_literals = true +csharp_style_throw_expression = true +csharp_style_unused_value_assignment_preference = discard_variable +csharp_style_unused_value_expression_statement_preference = discard_variable + +# "using" 指令首选项 +csharp_using_directive_placement = outside_namespace + +# 新行首选项 +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true +csharp_style_allow_embedded_statements_on_same_line_experimental = true + +#### C# 格式规则 #### + +# 新行首选项 +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = accessors,anonymous_methods,anonymous_types,control_blocks,methods,object_collection_array_initializers,properties,types +csharp_new_line_between_query_expression_clauses = true + +# 缩进首选项 +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = false +csharp_indent_case_contents_when_block = false +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# 空格键首选项 +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# 包装首选项 +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#### 命名样式 #### + +# 命名规则 + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# 符号规范 + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# 命名样式 + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case diff --git a/.gitignore b/.gitignore index f87938bc17c2093cf788a972cac0d220f9964d8d..e910608fc1ecabe29cb4e88eeab0a35a2b752e7d 100644 --- a/.gitignore +++ b/.gitignore @@ -398,4 +398,13 @@ ASALocalRun/ # BeatPulse healthcheck temp database # BeatPulse healthcheck ʱݿ healthchecksdb + +#macos *.DS_Store + +#vscode +.ionide +.vscode + +#ifoxcad +#tests/TestConsole/ \ No newline at end of file diff --git a/IFoxCAD.sln b/IFoxCAD.sln index 309172c25fb1b97b8272542e4f372dffa18487a9..802ff1cc56ccadea875f242dea64a2a32a0c0d69 100644 --- a/IFoxCAD.sln +++ b/IFoxCAD.sln @@ -1,15 +1,65 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31025.194 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32113.165 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IFoxCAD.Cad", "src\IFoxCAD.Cad\IFoxCAD.Cad.csproj", "{D5FAED7C-A99B-4BEE-A745-45442DC44971}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Config", "Config", "{786E7347-B116-4F26-9AEF-33EB0AB88D58}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + .gitattributes = .gitattributes + .gitignore = .gitignore + EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DBTrans.test", "tests\DBTrans.test\DBTrans.test.csproj", "{B1602568-F023-46C7-B635-3CB281F1EC00}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestConsole", "tests\TestConsole\TestConsole.csproj", "{E2873F0B-CAD2-45E8-8FF0-C05C0C6AD955}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IFoxCAD.WPF", "src\IFoxCAD.WPF\IFoxCAD.WPF.csproj", "{D820D629-1AB3-4BE3-A772-CB753D98CCB8}" +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "TestShared", "tests\TestShared\TestShared.shproj", "{CED63D2D-0AF6-4831-806D-5E8E9B0D0A07}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IFoxCAD.Basal", "src\IFoxCAD.Basal\IFoxCAD.Basal.csproj", "{40BF07C4-100A-4810-A27B-4587CFB38E6E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestAcad09plus", "tests\TestAcad09plus\TestAcad09plus.csproj", "{5F478F68-19BC-4A3A-AF39-8728F8D396DD}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{C0BEFC15-6DF7-4636-BC76-4A0B88231470}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Basal", "Basal", "{1A5C27C1-FEF3-40A0-A1EE-3BC7BF2BD67A}" + ProjectSection(SolutionItems) = preProject + src\Basal\Directory.Build.props = src\Basal\Directory.Build.props + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IFox.Basal", "src\Basal\IFox.Basal\IFox.Basal.csproj", "{FF4E1CDE-EEB3-4BE8-8C1D-6B5B17A299C7}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "IFox.Basal.Shared", "src\Basal\IFox.Basal.Shared\IFox.Basal.Shared.shproj", "{C823514A-2BC2-45C2-ACED-18924A7B80BF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IFox.Basal.Source", "src\Basal\IFox.Basal.Source\IFox.Basal.Source.csproj", "{D0C6824C-53A3-4C16-AE7B-3227F18C5039}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "IFox.CAD.Shared", "src\CAD\IFox.CAD.Shared\IFox.CAD.Shared.shproj", "{20F254F7-AEE5-42AE-A9B3-149BBDC7397F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CAD", "CAD", "{B2AB7DC7-DBF4-4197-808B-0D2A6613AF5E}" + ProjectSection(SolutionItems) = preProject + src\CAD\Directory.Build.props = src\CAD\Directory.Build.props + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IFox.CAD.Source", "src\CAD\IFox.CAD.Source\IFox.CAD.Source.csproj", "{88F3CF78-23F5-494B-9D8F-2C6B4467E0B4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IFox.CAD.ACAD", "src\CAD\IFox.CAD.ACAD\IFox.CAD.ACAD.csproj", "{EF20E632-70A9-40EB-8C9A-539FD85FAFAD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IFox.CAD.GCAD", "src\CAD\IFox.CAD.GCAD\IFox.CAD.GCAD.csproj", "{F388F4B9-F636-414A-9BC9-2B008517B441}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IFox.CAD.ZCAD", "src\CAD\IFox.CAD.ZCAD\IFox.CAD.ZCAD.csproj", "{3D7D095D-EF0A-40B3-9776-5F08C61FD614}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WPF", "WPF", "{1EE1F817-39D3-43E5-B087-E7AD4D0BEC93}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IFox.WPF", "src\WPF\IFox.WPF.csproj", "{6ED0A677-6621-4493-AF58-71C2BF31DFCE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{DA8EA8E7-A98E-4565-A3BF-97855988DB8E}" + ProjectSection(SolutionItems) = preProject + docs\0x01代码规范.md = docs\0x01代码规范.md + docs\DBTrans.md = docs\DBTrans.md + LICENSE = LICENSE + README.md = README.md + docs\SelectionFilter.md = docs\SelectionFilter.md + docs\SymbolTable.md = docs\SymbolTable.md + docs\WPF.md = docs\WPF.md + docs\关于IFoxCAD的架构说明.md = docs\关于IFoxCAD的架构说明.md + docs\关于扩展函数的说明.md = docs\关于扩展函数的说明.md + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -17,27 +67,71 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {D5FAED7C-A99B-4BEE-A745-45442DC44971}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D5FAED7C-A99B-4BEE-A745-45442DC44971}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D5FAED7C-A99B-4BEE-A745-45442DC44971}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D5FAED7C-A99B-4BEE-A745-45442DC44971}.Release|Any CPU.Build.0 = Release|Any CPU - {B1602568-F023-46C7-B635-3CB281F1EC00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B1602568-F023-46C7-B635-3CB281F1EC00}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B1602568-F023-46C7-B635-3CB281F1EC00}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B1602568-F023-46C7-B635-3CB281F1EC00}.Release|Any CPU.Build.0 = Release|Any CPU - {D820D629-1AB3-4BE3-A772-CB753D98CCB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D820D629-1AB3-4BE3-A772-CB753D98CCB8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D820D629-1AB3-4BE3-A772-CB753D98CCB8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D820D629-1AB3-4BE3-A772-CB753D98CCB8}.Release|Any CPU.Build.0 = Release|Any CPU - {40BF07C4-100A-4810-A27B-4587CFB38E6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {40BF07C4-100A-4810-A27B-4587CFB38E6E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {40BF07C4-100A-4810-A27B-4587CFB38E6E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {40BF07C4-100A-4810-A27B-4587CFB38E6E}.Release|Any CPU.Build.0 = Release|Any CPU + {E2873F0B-CAD2-45E8-8FF0-C05C0C6AD955}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E2873F0B-CAD2-45E8-8FF0-C05C0C6AD955}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E2873F0B-CAD2-45E8-8FF0-C05C0C6AD955}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E2873F0B-CAD2-45E8-8FF0-C05C0C6AD955}.Release|Any CPU.Build.0 = Release|Any CPU + {5F478F68-19BC-4A3A-AF39-8728F8D396DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5F478F68-19BC-4A3A-AF39-8728F8D396DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5F478F68-19BC-4A3A-AF39-8728F8D396DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5F478F68-19BC-4A3A-AF39-8728F8D396DD}.Release|Any CPU.Build.0 = Release|Any CPU + {FF4E1CDE-EEB3-4BE8-8C1D-6B5B17A299C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FF4E1CDE-EEB3-4BE8-8C1D-6B5B17A299C7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FF4E1CDE-EEB3-4BE8-8C1D-6B5B17A299C7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FF4E1CDE-EEB3-4BE8-8C1D-6B5B17A299C7}.Release|Any CPU.Build.0 = Release|Any CPU + {D0C6824C-53A3-4C16-AE7B-3227F18C5039}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D0C6824C-53A3-4C16-AE7B-3227F18C5039}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D0C6824C-53A3-4C16-AE7B-3227F18C5039}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D0C6824C-53A3-4C16-AE7B-3227F18C5039}.Release|Any CPU.Build.0 = Release|Any CPU + {88F3CF78-23F5-494B-9D8F-2C6B4467E0B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {88F3CF78-23F5-494B-9D8F-2C6B4467E0B4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {88F3CF78-23F5-494B-9D8F-2C6B4467E0B4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {88F3CF78-23F5-494B-9D8F-2C6B4467E0B4}.Release|Any CPU.Build.0 = Release|Any CPU + {EF20E632-70A9-40EB-8C9A-539FD85FAFAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EF20E632-70A9-40EB-8C9A-539FD85FAFAD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EF20E632-70A9-40EB-8C9A-539FD85FAFAD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EF20E632-70A9-40EB-8C9A-539FD85FAFAD}.Release|Any CPU.Build.0 = Release|Any CPU + {F388F4B9-F636-414A-9BC9-2B008517B441}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F388F4B9-F636-414A-9BC9-2B008517B441}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F388F4B9-F636-414A-9BC9-2B008517B441}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F388F4B9-F636-414A-9BC9-2B008517B441}.Release|Any CPU.Build.0 = Release|Any CPU + {3D7D095D-EF0A-40B3-9776-5F08C61FD614}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3D7D095D-EF0A-40B3-9776-5F08C61FD614}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3D7D095D-EF0A-40B3-9776-5F08C61FD614}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3D7D095D-EF0A-40B3-9776-5F08C61FD614}.Release|Any CPU.Build.0 = Release|Any CPU + {6ED0A677-6621-4493-AF58-71C2BF31DFCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6ED0A677-6621-4493-AF58-71C2BF31DFCE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6ED0A677-6621-4493-AF58-71C2BF31DFCE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6ED0A677-6621-4493-AF58-71C2BF31DFCE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {E2873F0B-CAD2-45E8-8FF0-C05C0C6AD955} = {C0BEFC15-6DF7-4636-BC76-4A0B88231470} + {CED63D2D-0AF6-4831-806D-5E8E9B0D0A07} = {C0BEFC15-6DF7-4636-BC76-4A0B88231470} + {5F478F68-19BC-4A3A-AF39-8728F8D396DD} = {C0BEFC15-6DF7-4636-BC76-4A0B88231470} + {FF4E1CDE-EEB3-4BE8-8C1D-6B5B17A299C7} = {1A5C27C1-FEF3-40A0-A1EE-3BC7BF2BD67A} + {C823514A-2BC2-45C2-ACED-18924A7B80BF} = {1A5C27C1-FEF3-40A0-A1EE-3BC7BF2BD67A} + {D0C6824C-53A3-4C16-AE7B-3227F18C5039} = {1A5C27C1-FEF3-40A0-A1EE-3BC7BF2BD67A} + {20F254F7-AEE5-42AE-A9B3-149BBDC7397F} = {B2AB7DC7-DBF4-4197-808B-0D2A6613AF5E} + {88F3CF78-23F5-494B-9D8F-2C6B4467E0B4} = {B2AB7DC7-DBF4-4197-808B-0D2A6613AF5E} + {EF20E632-70A9-40EB-8C9A-539FD85FAFAD} = {B2AB7DC7-DBF4-4197-808B-0D2A6613AF5E} + {F388F4B9-F636-414A-9BC9-2B008517B441} = {B2AB7DC7-DBF4-4197-808B-0D2A6613AF5E} + {3D7D095D-EF0A-40B3-9776-5F08C61FD614} = {B2AB7DC7-DBF4-4197-808B-0D2A6613AF5E} + {6ED0A677-6621-4493-AF58-71C2BF31DFCE} = {1EE1F817-39D3-43E5-B087-E7AD4D0BEC93} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {31D6C754-CF6B-4AB0-9861-6923DD312350} EndGlobalSection + GlobalSection(SharedMSBuildProjectFiles) = preSolution + src\CAD\IFox.CAD.Shared\IFox.CAD.Shared.projitems*{20f254f7-aee5-42ae-a9b3-149bbdc7397f}*SharedItemsImports = 13 + src\CAD\IFox.CAD.Shared\IFox.CAD.Shared.projitems*{3d7d095d-ef0a-40b3-9776-5f08c61fd614}*SharedItemsImports = 5 + tests\TestShared\TestShared.projitems*{5f478f68-19bc-4a3a-af39-8728f8d396dd}*SharedItemsImports = 5 + src\Basal\IFox.Basal.Shared\IFox.Basal.Shared.projitems*{c823514a-2bc2-45c2-aced-18924a7b80bf}*SharedItemsImports = 13 + tests\TestShared\TestShared.projitems*{ced63d2d-0af6-4831-806d-5e8e9b0d0a07}*SharedItemsImports = 13 + src\CAD\IFox.CAD.Shared\IFox.CAD.Shared.projitems*{ef20e632-70a9-40eb-8c9a-539fd85fafad}*SharedItemsImports = 5 + src\CAD\IFox.CAD.Shared\IFox.CAD.Shared.projitems*{f388f4b9-f636-414a-9bc9-2b008517b441}*SharedItemsImports = 5 + src\Basal\IFox.Basal.Shared\IFox.Basal.Shared.projitems*{ff4e1cde-eeb3-4be8-8c1d-6b5b17a299c7}*SharedItemsImports = 5 + EndGlobalSection EndGlobal diff --git a/README.en.md b/README.en.md deleted file mode 100644 index 7c3d2c4742185f4b8d6f8b3319a78c74079f6822..0000000000000000000000000000000000000000 --- a/README.en.md +++ /dev/null @@ -1,36 +0,0 @@ -# IFoxCAD - -#### Description -基于.NET的Cad二次开发类库 - -#### Software Architecture -Software architecture description - -#### Installation - -1. xxxx -2. xxxx -3. xxxx - -#### Instructions - -1. xxxx -2. xxxx -3. xxxx - -#### Contribution - -1. Fork the repository -2. Create Feat_xxx branch -3. Commit your code -4. Create Pull Request - - -#### Gitee Feature - -1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md -2. Gitee blog [blog.gitee.com](https://blog.gitee.com) -3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) -4. The most valuable open source project [GVP](https://gitee.com/gvp) -5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) -6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/README.md b/README.md index f3128878b0a67fb90c186c1e8f9c8465f55e6a77..c29b07b76f66a8749a9effdd513d4cdc71b69bc3 100644 --- a/README.md +++ b/README.md @@ -1,91 +1,176 @@ -# IFoxCAD +# IFoxCAD 说明 -#### 介绍 +基于.NET的Cad二次开发类库。 -基于.NET的Cad二次开发类库 +#### 一、项目来源 -#### 软件架构及相关说明 +起初 **雪山飞狐(又狐哥)** 在明经论坛发布了[开源库](http://bbs.mjtd.com/thread-75701-1-1.html),后来狐哥自己的项目进行了极大的丰富后形成NFox类库。然后 **落魄山人** 在征得 雪山飞狐的同意后,对NFox类库进行了整理,增加了注释等,重新发布了NFox类库。 -- [软件架构说明](/docs/关于IFoxCAD的架构说明.md) +后来,经过一段时间的更新后,由于莫名其妙的原因NFox类库挂掉了。而这时山人同学已经基本吃透NFox类库,考虑到NFox的封装过于复杂,遂进行了重构。 -- [扩展函数说明](/docs/关于扩展函数的说明.md) +重构的类库命名为IFoxCAD, 寓意为:**I(爱)Fox(狐哥)**,本项目发布于**Inspire Function(中文名:跃动方程)** 组织下,感谢 **小轩轩** 给起的名字。 -#### 安装教程 +可以加群交流: -1. vs新建net standord 类库 -2. 修改项目TargetFramework为net45,保存重加载项目 -3. 右键项目,管理nuget程序包,搜索ifoxcad,安装最新版就可以了 +![ifoxcad用户交流群群二维码](./docs/png/ifoxcad用户交流群群二维码.png) -#### 使用说明 +#### 二、 快速入门 -1. 快速入门 +- 打开vs,新建一个standard类型的类库项目,**注意,需要选择类型的时候一定要选standard2.0** - - 打开vs,新建一个standard类型的类库项目,修改项目文件里的`netcore2.0`为`NET45`。其中的net45,可以改为NET35以上的标准TFM(如:net35、net40、net45、net46、net47等等)。同时可以指定多版本。具体的详细的教程见 [VS通过添加不同引用库,建立多条件编译]( https://www.yuque.com/vicwjb/zqpcd0/ufbwyl)。 - - 右键项目文件,选择管理nuget程序包。 - - 在nuget程序里搜索**ifoxcad**,直接选择最新的版本,然后点击安装**IFoxCAD.Cad**,nuget会自动安装ifoxcad依赖的库。 - - 添加引用 +- 双击项目,打开项目文件: + + - 修改项目文件里的`netcore2.0`为`NET45`。其中的net45,可以改为NET45以上的标准TFM(如:net45、net46、net47等等)。同时可以指定多版本。具体的详细的教程见 [VS通过添加不同引用库,建立多条件编译](https://www.yuque.com/vicwjb/zqpcd0/ufbwyl)。 + + - 在 ` xxx ` 中增加 `preview`,主要是为了支持最新的语法,本项目采用了最新的语法编写。项目文件现在的内容类似如下: - ```c# - using Autodesk.AutoCAD.ApplicationServices; - using Autodesk.AutoCAD.EditorInput; - using Autodesk.AutoCAD.Runtime; - using Autodesk.AutoCAD.Geometry; - using Autodesk.AutoCAD.DatabaseServices; - using IFoxCAD.Cad; - ``` +```xml + + + net45 + preview + + +``` - - 添加代码 +- 右键项目文件,选择管理nuget程序包。 - ```c# - [CommandMethod("hello")] - public void Hello() - { - using var tr = new DBTrans() - var line1 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - tr.CurrentSpace.AddEntity(line1); - } - ``` +- 在nuget程序里搜索**ifox**,记得将包括预发行版打钩。截止本文最后更新时,nuget上最新的版本为ifox.cad.source 0.5.2.1版本和ifox.Basal.source 0.5.2.3版本。点击安装就可以。 - 这段代码就是在cad的当前空间内添加了一条直线。 +- 添加引用,在新建的项目里的cs文件里添加相关的引用 - - F6生成,然后打开cad,netload命令将刚刚生成的dll加载。 - - 运行hello命令,然后缩放一下视图,现在一条直线和一个圆已经显示在屏幕上了。 +```csharp +using Autodesk.AutoCAD.ApplicationServices; +using Autodesk.AutoCAD.EditorInput; +using Autodesk.AutoCAD.Runtime; +using Autodesk.AutoCAD.Geometry; +using Autodesk.AutoCAD.DatabaseServices; +using IFoxCAD.Cad; +``` -2. [事务管理器用法](/docs/DBTrans.md) +- 添加代码 -3. [选择集过滤器用法](/docs/SelectionFilter.md) +```csharp +[CommandMethod(nameof(Hello))] +public void Hello() +{ + using DBTrans tr = new(); + var line1 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + tr.CurrentSpace.AddEntity(line1); + // 如果您没有添加preview到项目文件里的话:按如下旧语法: + // using(var tr = new DBTrans()) + // { + // var line1 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + // tr.CurrentSpace.AddEntity(line1); + // } +} +``` -4. [符号表用法](/docs/SymbolTable.md) +这段代码就是在cad的当前空间内添加了一条直线。 -5. [WPF支持](/docs/WPF.md) +- 生成,然后打开cad,netload命令将刚刚生成的dll加载。如果需要调试需要设置启动程序为cad。 -6. 天秀的自动加载与初始化 +- 运行hello命令,然后缩放一下视图,现在一条直线和一个圆已经显示在屏幕上了 - 为了将程序集的初始化和通过写注册表的方式实现自动加载统一设置,减少每次重复的工作量,内裤提供了`AutoRegAssem`抽象类来完成此功能,只要在需要初始化的类继承`AutoRegAssem`类,然后实现`Initialize()` 和`Terminate()`两个函数就可以了。特别强调的是,一个程序集里只能有一个类继承,不管是不是同一个命名空间。 +#### 三、屏蔽IFox的元组、索引、范围功能 - ```c# - public class Test : AutoRegAssem //继承 - { - public override void Initialize() //实现接口函数 - { - throw new NotImplementedException(); - } - public override void Terminate() //实现接口函数 - { - throw new NotImplementedException(); - } - } - ``` +特别提醒: 考虑到早期的框架没有提供System.Range类型(net core 开始提供)、System.Index类型(net core 开始提供)、System.ValueTuple类型(net 47开始提供),本项目IFox.Basal包里包含了他们。 如果引用了包含System.Range等类型的第三方包(如IndexRange等),请在项目文件中定义NOINDEX、NORANGE、NOVALUETUPLE常量,以避免重复定义。上述代码能起作用的前提是用源码包,普通包暂时无解。 -7. 天秀的打开模式提权 +```xml + + $(Configuration);NOINDEX;NORANGE;NOVALUETUPLE + +``` - 由于cad的对象是有打开模式,是否可写等等,为了安全起见,在处理对象时,一般是用读模式打开,然后需要写数据的时候在提权为写模式,然后在降级到读模式,但是这个过程中,很容易漏掉某些步骤,然后cad崩溃。为了处理这些情况,内裤提供了提权类来保证读写模式的有序转换。 +**NOINDEX、NORANGE、NOVALUETUPLE 分别针对三种类型,哪种类型冲突就定义哪种。** + +#### 四、编译 IFox 源码工程 + +由于vs2022抛弃了某几个net版本,所以我们同时安装vs2019和vs2022,然后使用vs2022; +其中的原因是vs2019拥有全部net版本,而vs2022拥有最新的分析器和语法。 + +编译本项目需要你准备好git,具体的安装教程可以去网上搜索一下。当然也可以利用vs的git来完成。 + +首先在gitee上fork本项目到你的账号,然后clone到本地。 + +原生git使用命令行,打开终端/powershell/cmd,cd到你要存放的目录,然后运行下面的命令,把里面的yourname替换为你的名字,这样就在本地创建了一个ifoxcad文件夹,里面就是本项目的所有源码。 + +``` +git clone https://gitee.com/yourname/ifoxcad.git +``` + +当然也可以采用vs的图形化操作,打开vs,选择 克隆存储库-->填入仓库地址和存放路径-->点击克隆。新手小白推荐用此办法。 + +打开ifoxcad文件夹,双击解决方案文件,打开vs,等待项目打开,加载nuget包,然后生成就可以了。 + +**切记,不要用低版本的vs打开本项目,因为本项目采用了某些新的语法,所以老版本的vs是不兼容的。** + +#### 五、IFoxCad 项目模版 + +可以在vs扩展菜单-管理扩展中搜索ifoxcad,即可安装项目模板。使用项目模版可以方便的创建支持多目标多版本的使用ifoxcad类库的项目和类。如果无法在vs的市场里下载,就去上面的QQ群里下载。 + +项目模版里的自动加载选择了简单api,ifox还提供了一套功能更强大的api,具体的可以参考[自动加载和初始化](/docs/autoreg.md)。 + +#### 六、使用IFoxCad的几种方式 - ```c# - using(line.ForWrite()) //开启对象写模式提权事务 +目前ifox提供了三种使用方式,**建议一般的用户使用第二种源码包的形式。有志于本项目发展并想提交点代码的可以选择第三种。** + +- 第一种是直接使用普通的nuget包。 + + 此种方式使用便捷,只要在项目中引用了IFox.CAD.ACAD的包,就可以直接使用了。缺点一是无法控制ifox提供的元组功能的屏蔽,导致和其他的三方包的冲突;二是生成目录里带有ifox的dll。 + +- 第二种是使用源码包。 + + 此种方式使用便捷,只要在项目中引用了IFox.Basal.Source和IFox.CAD.Source两个nuget包就可以直接使用了。优点就是使用简单,生成的目录里没有ifox的dll,同时还可以通过定义预处理常量的方式屏蔽ifox提供的元组等功能。缺点就是无法修改源码,即便解包修改了,也不会同步到nuget上。 + +- 第三种是使用git子模块。 + + 此种方法使用步骤复杂,需要熟悉git及其子模块的使用,需要引用ifox里的共享项目文件。优点就是可以使用最新的代码,可以修改代码。具体的可以参考如下说明进行: + + **让 IFox 作为您的子模块** + + IFox的develop分支是一个多cad版本分支,您可以利用此作为您的[git项目子模块](https://www.cnblogs.com/JJBox/p/13876501.html#_label13). + + 子模块是以`共享工程`的方式加入到您的工程的,其为`IFox.CAD.Shared`: +1. 千万不要用`IFox.CAD.ACAD`内的工程作为引用,否则您将遭遇cad加载失效. + +2. 一些全局命名空间的缺少,我们也建议您使用全局命名空间来补充, + 您只需要按照`IFox.CAD.ACAD`的`GlobalUsings.cs`文件一样添加就好了. + +3. 若您使用acad是09版本以下的,比如 07 08版本,建议你升级至09 版本以上. + +4. 上面的例子告诉了大家如何使用子模块。 + +#### 七、软件架构及相关说明 + +1. [软件架构说明](/docs/关于IFoxCAD的架构说明.md) + +2. [扩展函数说明](/docs/关于扩展函数的说明.md) + +3. [事务管理器用法](/docs/DBTrans.md) + +4. [选择集过滤器用法](/docs/SelectionFilter.md) + +5. [符号表用法](/docs/SymbolTable.md) + +6. [WPF支持](/docs/WPF.md) + +7. [自动加载与初始化](/docs/autoreg.md) + +8. 天秀的打开模式提权 + + 由于cad的对象是有打开模式,是否可写等等,为了安全起见,在处理对象时,一般是用读模式打开,然后需要写数据的时候在提权为写模式,然后在降级到读模式,但是这个过程中,很容易漏掉某些步骤,然后cad崩溃。为了处理这些情况,内裤提供了提权类来保证读写模式的有序转换。 + + ```csharp + // 第一种方式,采用的是事务管理的模式 + using(line.ForWrite()) // 开启对象写模式提权事务 { - //处理代码 - } //关闭事务自动处理读写模式 + // 处理代码 + } // 关闭事务自动处理读写模式 + // 第二种方式,采用的是委托的形式 + line.ForWrite(e => { + // 处理代码 + }); ``` -8. 未完待续。。。。 \ No newline at end of file +9. 未完待续。。。。 diff --git "a/docs/0x01\344\273\243\347\240\201\350\247\204\350\214\203.assets/2HJE@WH1`PPUBOH2ZFL$BT.png" "b/docs/0x01\344\273\243\347\240\201\350\247\204\350\214\203.assets/2HJE@WH1`PPUBOH2ZFL$BT.png" new file mode 100644 index 0000000000000000000000000000000000000000..459930b8d54d075d94b4d06e960fbbc7edbdd17b Binary files /dev/null and "b/docs/0x01\344\273\243\347\240\201\350\247\204\350\214\203.assets/2HJE@WH1`PPUBOH2ZFL$BT.png" differ diff --git "a/docs/0x01\344\273\243\347\240\201\350\247\204\350\214\203.md" "b/docs/0x01\344\273\243\347\240\201\350\247\204\350\214\203.md" new file mode 100644 index 0000000000000000000000000000000000000000..26f09091f8e0321b4418e199aaa47792282ff1b5 --- /dev/null +++ "b/docs/0x01\344\273\243\347\240\201\350\247\204\350\214\203.md" @@ -0,0 +1,132 @@ +# IFox工程规范 + +## 代码规范 + +### 0x01 分离逻辑代码和业务代码 + +Good: + +```c# +foreach (xx in yyy) + if (xx == "a") + { + 业务(); + break; + } +``` + +Bad: + +```c# +bool flag = false; +foreach (xx in yyy) + if (xx == "a") + { + 业务(); + flag = true; + break; + } +if(!flag) + 其他业务(); +``` + +Good: + +```c# +bool flag = false; +foreach (xx in yyy) + if (xx == "a") + { + flag = true; + break; + } +if(!flag) + 其他业务(); +else + 业务(); +``` + +主要原因是统一业务在判断分支上,能够更清晰分离逻辑代码和业务代码. + +### 0x02 分离逻辑代码和业务代码 + +![img](0x01%E4%BB%A3%E7%A0%81%E8%A7%84%E8%8C%83.assets/2HJE@WH1%60PPUBOH2ZFL$BT.png) + +上述代码中出现了这种情形: for {业务1,业务2,业务1,业务2....} + +如果有这样的逻辑,那么我们看代码的时候总是认为业务2某种条件必须要跟着业务1. + +优化代码的人一看:这代码就不能动了!! 相信我,若干年后的你就是这个优化代码的人. + +所以这样的情况下,我们采用的是用个`List`收集拆离(业务)的id,然后在最后进行循环拆离(业务). + +### 0x03 .editorconfig 配置要求 + +c#的代码风格是两个大括号隔行 + +```c# +if() +{ + ... +} +``` + +但是,由于vs没有制作好的原因,导致`委托箭头`代码格式化总是会出现格式化错误. +所以我们推荐用 .editorconfig 文件约束这个`委托箭头` + +在.edirorconfig文件上面增加此句: + +``` +csharp_style_var_elsewhere = false +``` + +没有这个文件的话,请使用如下步骤: + +```mermaid +graph LR +vs --> 选项 --> 文本编辑器 --> c# -->代码样式,展开它 --> 格式设置 --> 新行 --> 右页,大括号的新行选项 --> 将lambda表达式的左括号置于新行,取消掉勾勾 +``` + +保存为 .editorconfig 文件,并放在.sln旁边,加入git管理: + +```mermaid +graph LR +vs --> 选项 --> 文本编辑器 --> c# -->代码样式 --> 右页,基于设置生成.editorconfig文件 --> 保存到工程中 +``` + +以后每次打开工程vs会自动识别这个 .editorconfig 文件,而不会用你电脑默认设置的. + +### 0x04 所有的注释符号//后面加空格 + +利用此正则替换: + +``` +(? - { - ltt.AsciiDescription = "虚线"; - ltt.PatternLength = 0.95; //线型的总长度 - ltt.NumDashes = 4; //组成线型的笔画数目 - ltt.SetDashLengthAt(0, 0.5); //0.5个单位的划线 - ltt.SetDashLengthAt(1, -0.25); //0.25个单位的空格 - ltt.SetDashLengthAt(2, 0); // 一个点 - ltt.SetDashLengthAt(3, -0.25); //0.25个单位的空格 - }); + "hah", + ltt => + { + ltt.AsciiDescription = "虚线"; + ltt.PatternLength = 0.95; //线型的总长度 + ltt.NumDashes = 4; //组成线型的笔画数目 + ltt.SetDashLengthAt(0, 0.5); //0.5个单位的划线 + ltt.SetDashLengthAt(1, -0.25); //0.25个单位的空格 + ltt.SetDashLengthAt(2, 0); // 一个点 + ltt.SetDashLengthAt(3, -0.25); //0.25个单位的空格 + }); // 这段代码同时演示了 ifoxcad 类库关于符号表的public ObjectId Add(string name, Action action)这个函数的用法。 // 或者直接调用: tr.LinetypeTable.Add("hah", "虚线",0.95,new double[]{0.5,-0.25,0,-0.25}); // 获取线型表 tr.LinetypeTable["hah"]; ``` - - **其他符号表的操作类同。如果类库没有提供的Add函数的重载,那么Action委托可以完成你想完成的所有事情。** + + **其他符号表的操作类同。如果类库没有提供的Add函数的重载,那么Action委托可以完成你想完成的所有事情。** ## 基础属性操作 @@ -125,16 +125,15 @@ using (DBTrans tr = new DBTrans()) // 第一步,开启事务 ## 字典操作(未完待续) - 扩展字典 - + `SetXRecord` 保存扩展数据到字典 - + `GetXRecord ` 获取扩展数据 - 对象字典 - + `SetToDictionary` 保存数据到字典 - + `GetFromDictionary` 从字典获取数据 - + `GetSubDictionary` 获取子对象字典 - diff --git a/docs/SelectionFilter.md b/docs/SelectionFilter.md index 1e50098bf6fdba1cf6d0830d0ab68c73e6adf439..bc31b55c3c7d630bd238e99c76ba8e830b4210fe 100644 --- a/docs/SelectionFilter.md +++ b/docs/SelectionFilter.md @@ -9,8 +9,8 @@ 默认的使用桌子api来创建选择集(带过滤器)分三步: 1. 创建一个TypedValue数组来定义过滤器条件 - - ```c# + + ```csharp TypedValue[] acTypValAr = new TypedValue[1]; // 创建数组 acTypValAr.SetValue(new TypedValue((int)DxfCode.Start, "CIRCLE"), 0); // 添加一个过滤条件,例如选择圆 @@ -24,15 +24,15 @@ ``` 2. 创建SelectionFilter对象 - - ```c# + + ```csharp // 将过滤器条件赋值给 SelectionFilter 对象 SelectionFilter acSelFtr = new SelectionFilter(acTypValAr); ``` 3. 创建选择集 - - ```c# + + ```csharp // 请求用户在图形区域选择对象 PromptSelectionResult acSSPrompt; acSSPrompt = acDocEd.GetSelection(acSelFtr); @@ -42,147 +42,147 @@ 比如: - 1. 过滤半径大于等于5.0的圆 - - ```c# - TypedValue[] acTypValAr = { - new TypedValue((int)DxfCode.Start, "CIRCLE"), - new TypedValue((int)DxfCode.Operator, ">="), - new TypedValue(40, 5) - }; - ``` - - 2. 过滤单行文本或者多行文本 - - ```c# - TypedValue[] acTypValAr = { - new TypedValue((int)DxfCode.Operator, "") - }; - ``` - - 3. 更复杂的过滤条件呢?比如选择的对象为不是位于0图层的直线,或者位于2图层的组码10的x坐标>10,y坐标>10的非圆图元。 - - 对应的lisp代码如下: - - ```lisp - '((-4 . "") - (-4 . "not>") - (-4 . "") - (8 . "2") - (-4 . ">,>,*")(10 10 10 0) +1. 过滤半径大于等于5.0的圆 + + ```csharp + TypedValue[] acTypValAr = { + new TypedValue((int)DxfCode.Start, "CIRCLE"), + new TypedValue((int)DxfCode.Operator, ">="), + new TypedValue(40, 5) + }; + ``` + +2. 过滤单行文本或者多行文本 + + ```csharp + TypedValue[] acTypValAr = { + new TypedValue((int)DxfCode.Operator, "") + }; + ``` + +3. 更复杂的过滤条件呢?比如选择的对象为不是位于0图层的直线,或者位于2图层的组码10的x坐标>10,y坐标>10的非圆图元。 + + 对应的lisp代码如下: + + ```lisp + '((-4 . "") - (-4 . "or>")) - ``` - - 对应的c#代码: - - ```c# - TypedValue[] acTypValAr = { - new TypedValue((int)DxfCode.Operator, ""), - new TypedValue((int)DxfCode.Operator, "not>"), - new TypedValue((int)DxfCode.Operator, ""), - new TypedValue((int)DxfCode.LayerName, "2"), - new TypedValue((int)DxfCode.Operator, ">,>,*"), - new TypedValue(10, new Point3d(10,10,0)), - new TypedValue((int)DxfCode.Operator, "and>"), - new TypedValue((int)DxfCode.Operator, "or>") - }; - ``` - - 这个过滤器是不是看起来很乱,一眼看去根本不知道是要过滤什么,写起来也很麻烦。所以说,虽然桌子提供了api,但是简单的过滤条件很好用,但是复杂的过滤条件就很复杂了。 - - 因此NFox内裤提供了关于选择集过滤器的辅助类来帮助用户用更简单的方式来创建选择集的过滤器。 + (-4 . "not>") + (-4 . "") + (8 . "2") + (-4 . ">,>,*")(10 10 10 0) + (-4 . "and>") + (-4 . "or>")) + ``` + + 对应的c#代码: + + ```csharp + TypedValue[] acTypValAr = { + new TypedValue((int)DxfCode.Operator, ""), + new TypedValue((int)DxfCode.Operator, "not>"), + new TypedValue((int)DxfCode.Operator, ""), + new TypedValue((int)DxfCode.LayerName, "2"), + new TypedValue((int)DxfCode.Operator, ">,>,*"), + new TypedValue(10, new Point3d(10,10,0)), + new TypedValue((int)DxfCode.Operator, "and>"), + new TypedValue((int)DxfCode.Operator, "or>") + }; + ``` + + 这个过滤器是不是看起来很乱,一眼看去根本不知道是要过滤什么,写起来也很麻烦。所以说,虽然桌子提供了api,但是简单的过滤条件很好用,但是复杂的过滤条件就很复杂了。 + + 因此IFox内裤提供了关于选择集过滤器的辅助类来帮助用户用更简单的方式来创建选择集的过滤器。 ## 内裤过滤器对象与cad过滤器对应关系 IFoxCad内裤对于DxfCode.Operator枚举构建了一些辅助函数来表达关系运算和逻辑运算;提供了dxf函数来表达组码。其对应的关系如下表: -| 内裤过滤器对象、函数 | cad .net api 过滤器对象、函数、枚举 | 备注 | -| :------------------: | :---------------------------------: | :------------------------------: | -| OpFilter | SelectionFilter | 隐式转换 | -| OpOr | "" | | -| Op.Or | "" | | -| OpAnd | "" | | -| Op.And | "" | | -| OpNot | "" | | -| OpXor | "" | | -| OpEqual | 相等运算 | | -| OpComp | 比较运算符 | | -| Dxf() | 组码函数 | 仅用于过滤器中,不是组码操作函数 | -| ! | "" | | -| == | "=" | | -| != | "!=" | | -| > | ">" | | -| < | "<" | | -| >= | ">=" 或 ">,>,*" | ">,>,*"用于跟point3d比较 | -| <= | "<=" 或 "<,<,*" | "<,<,*"用于跟point3d比较 | -| & | "" | | -| ^ | "" | | -| \| | "" | | +| 内裤过滤器对象、函数 | cad .net api 过滤器对象、函数、枚举 | 备注 | +|:----------:|:------------------------:|:-------------------:| +| OpFilter | SelectionFilter | 隐式转换 | +| OpOr | "" | | +| Op.Or | "" | | +| OpAnd | "" | | +| Op.And | "" | | +| OpNot | "" | | +| OpXor | "" | | +| OpEqual | 相等运算 | | +| OpComp | 比较运算符 | | +| Dxf() | 组码函数 | 仅用于过滤器中,不是组码操作函数 | +| ! | "" | | +| == | "=" | | +| != | "!=" | | +| > | ">" | | +| < | "<" | | +| >= | ">=" 或 ">,>,*" | ">,>,*"用于跟point3d比较 | +| <= | "<=" 或 "<,<,*" | "<,<,*"用于跟point3d比较 | +| & | "" | | +| ^ | "" | | +| \| | "" | | ## 具体用法 IFoxCad内裤提供了三种方式来构建过滤器,其实大同小异,就是写法不一样,用户可以根据自己的喜好来选择。 - 第一种 - - ```c# + + ```csharp var fd = - new OpOr //定义一个 (-4 . "") - { - !new OpAnd //定义(-4 . "")(-4 . "not>") - { - { 0, "line" }, //{组码,组码值} - { 8, "0" }, //{组码,组码值} - }, - new OpAnd //定义(-4 . "") - { - !new OpEqual(0, "circle"), //定义(-4 . "") - { 8, "2" }, //{组码,组码值} - { 10, new Point3d(10,10,0), ">,>,*" } //(-4 . ">,>,*")(10 10 10 0) - }, - }; + new OpOr //定义一个 (-4 . "") + { + !new OpAnd //定义(-4 . "")(-4 . "not>") + { + { 0, "line" }, //{组码,组码值} + { 8, "0" }, //{组码,组码值} + }, + new OpAnd //定义(-4 . "") + { + !new OpEqual(0, "circle"), //定义(-4 . "") + { 8, "2" }, //{组码,组码值} + { 10, new Point3d(10,10,0), ">,>,*" } //(-4 . ">,>,*")(10 10 10 0) + }, + }; editor.SelectAll(fd); //这里直接传入fd就可以了 ``` - + 以上代码的含义为:选择的对象为不是位于0图层的直线,或者位于2图层的组码10的x坐标>10,y坐标>10的非圆图元。其同含义的lisp代码如下: - + ```lisp '((-4 . "") - (-4 . "not>") - (-4 . "") + (-4 . "not>") + (-4 . "") (8 . "2") - (-4 . ">,>,*")(10 10 10 0) + (-4 . ">,>,*")(10 10 10 0) (-4 . "and>") (-4 . "or>")) ``` - 第二种 - - ```c# + + ```csharp var p = new Point3d(10, 10, 0); var f = OpFilter.Bulid(e => !(e.Dxf(0) == "line" & e.Dxf(8) == "0") @@ -191,12 +191,12 @@ IFoxCad内裤提供了三种方式来构建过滤器,其实大同小异,就 & e.Dxf(10) >= p); editor.SelectAll(f); //这里直接传入f就可以了 ``` - + 代码含义如第一种。 - 第三种 - - ```c# + + ```csharp var f2 = OpFilter.Bulid( e =>e.Or( !e.And(e.Dxf(0) == "line", e.Dxf(8) == "0"), @@ -204,5 +204,5 @@ IFoxCad内裤提供了三种方式来构建过滤器,其实大同小异,就 ); editor.SelectAll(f2); //这里直接传入f2就可以了 ``` - + 代码含义如第一种,第三种和第二种的写法非常像,区别就是关于 and 、or 、not 等运算符,一个是采用c#的语法,一个是采用定义的函数。and 与&等价,or与|等价,not 与!等价。 \ No newline at end of file diff --git a/docs/SymbolTable.md b/docs/SymbolTable.md index 0d450439eb0c17e953ee73163b268a5194f1a05e..a5144baea56a722688da09f929cfd04e9d243ed7 100644 --- a/docs/SymbolTable.md +++ b/docs/SymbolTable.md @@ -2,21 +2,21 @@ 每个图形文件都包含有9个固定的符号表。不能往数据库里添加新的符号表。如图层表(LayerTable),其中包含图层表记录,还有块表(BlockTable),其中包含块表记录等。所有的图形实体(线、圆、弧等等)都属于一个块表记录。缺省情况下,任何图形文件都包含为模型空间和图纸空间预定义的块表记录。每个符号表都有对应的符号表记录,可以理解为符号表是一个集合,而符号表记录是这个集合的元素。CAD的符号表和符号表记录的对应关系如下: -| 名称 | 符号表 | 符号表记录 | -| :------------: | :------------: | :------------------: | -| 块表 | BlockTable | BlockTableRecord | -| 标注样式表 | DimStyleTable | DimStyleTableRecord | -| 图层表 | LayerTable | LayerTableRecord | -| 线型表 | LinetypeTable | LinetypeTableRecord | -| 注册应用程序表 | RegAppTable | RegAppTableRecord | -| 字体样式表 | TextStyleTable | TextStyleTableRecord | -| 坐标系表 | UcsTable | UcsTableRecord | -| 视口表 | ViewportTable | ViewportTableRecord | -| 视图表 | ViewTable | ViewTableRecord | +| 名称 | 符号表 | 符号表记录 | +|:-------:|:--------------:|:--------------------:| +| 块表 | BlockTable | BlockTableRecord | +| 标注样式表 | DimStyleTable | DimStyleTableRecord | +| 图层表 | LayerTable | LayerTableRecord | +| 线型表 | LinetypeTable | LinetypeTableRecord | +| 注册应用程序表 | RegAppTable | RegAppTableRecord | +| 字体样式表 | TextStyleTable | TextStyleTableRecord | +| 坐标系表 | UcsTable | UcsTableRecord | +| 视口表 | ViewportTable | ViewportTableRecord | +| 视图表 | ViewTable | ViewTableRecord | 那么如何来操作这些符号表呢?下面是一个新建图层的例子: -```c# +```csharp Document acDoc = Application.DocumentManager.MdiActiveDocument; Database acCurDb = acDoc.Database; using (Transaction acTrans = acCurDb.TransactionManager.StartTransaction()) @@ -43,7 +43,7 @@ using (Transaction acTrans = acCurDb.TransactionManager.StartTransaction()) 上面的例子用了20多行的代码来完成一个很简单的功能,这就是AutoCAD提供的api太过于基础,没有进行进一步的封装造成。那么如果我们单独为图层表封装一个函数来处理图层表,其他的8个符号表也要同样的各自封装函数,这样看起来没什么问题,但是对于代码的复用却没有很好的考虑进去。仔细思考一下,其实对于符号来说无非就是增删改三个主要的操作等,对于符号表记录来说无非就是一些属性的操作,增加实体的操作等。那么有没有一种办法可以统一管理9个符号表呢?其实AutoCAD提供了9个符号表和符号表记录的抽象基类,SymbolTable和SymbolTableRecord,但是这两个类提供的功能又很简单,只有寥寥几个函数和属性,完全不能满足我们的需求。因此ifoxcad内裤提供了符号表类来封装9个符号表的大部分功能。那么用内裤来完成上述的操作是什么样子的呢?见下面的例子: -```c# +```csharp // 以下代码采用最新的c#版本语法 using var tr = new DBTrans(); // 打开事务 var layertable = tr.LayerTable.Add("MyLayer"); //添加图层 @@ -67,8 +67,6 @@ var layertable = tr.LayerTable.Add("MyLayer"); //添加图层 看,我教会了你操作图层表,那么其他的8个表你都会了,都是一样的操作。 - - ## 块表添加图元 一般的情况下,添加图元的操作都是要在事务里完成。目前大部分的添加图元的自定义函数都是DataBase或Editor对象的扩展函数。但是实际上添加图元的本质是读写图形数据库,具体的手段是对块表里的块表记录的读写。而实际的操作其实都是在事务里完成,所以符合cad操作规则的写法其实应该是事务作为一系列操作的主体来进行。因此ifoxcad内裤的封装思路为:扩展块表记录的函数,在事务管理器类里通过属性调用AddEntity函数来添加图元。 @@ -82,8 +80,8 @@ var layertable = tr.LayerTable.Add("MyLayer"); //添加图层 下面看示例: - 添加图元到当前空间 - - ```c# + + ```csharp // 以下代码采用最新的c#版本语法 using var tr = new DBTrans(); //开启事务管理器 var line1 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); //定义一个直线 @@ -91,8 +89,8 @@ var layertable = tr.LayerTable.Add("MyLayer"); //添加图层 ``` - 添加图元到模型/图纸空间 - - ```c# + + ```csharp // 以下代码采用最新的c#版本语法 using var tr = new DBTrans(); //开启事务管理器 var line1 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); //定义一个直线 @@ -102,48 +100,47 @@ var layertable = tr.LayerTable.Add("MyLayer"); //添加图层 ``` - 添加图元到块表 - - ```c# + + ```csharp // 以下代码采用最新的c#版本语法 using var tr = new DBTrans(); //开启事务管理器 var line1 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); //定义一个直线 var btr = tr.BlockTable.Add("test"); //定义一个块表记录 btr.AddEntity(line1); // 将直线添加到当前控件的块表记录 ``` - + 那么大家猜一猜,这个添加到块表是实现了一种什么样的功能。 - 块表 - + 块表这里需要特殊的说明一下: - + 比如说添加一个块,用如下代码: - + `tr.BlockTable.Add(blockName, btr => btr.AddEntity(ents));` - + 这里的blockName就是块名,ents就是图元列表。这种方式虽然可以更细粒度的控制定义的块。 - + 插入块参照,比如: - + `tr.InsertBlock(point,objectid); // 用于插入块参照,提供了重载函数来满足不同的需求` - 其他函数的介绍 - + `tr.BlockTable.GetRecord()` 函数,可以获取到块表的块表记录,同理层表等符号表也有同样的函数。 - + `tr.BlockTable.GetRecordFrom()` 函数,可以从文件拷贝块表记录,同理层表等符号表也有同样的函数。 - + `tr.BlockTable.GetBlockFrom()` 函数,从文件拷贝块定义,同理层表等符号表也有同样用途的函数。 - 添加图元函数 - + 内裤提供了一些便利的添加图元函数,可以不用先定义一个entity对象,然后添加到块表记录。 - - ```c# + + ```csharp using var tr = new DBTrans(); tr.CurrentSpace.AddLine(new Point3d(0,0,0),new Point3d(1,1,0)); tr.CurrentSpace.AddCircle(new Point3d(0,0,0),10); ``` - diff --git a/docs/WPF.md b/docs/WPF.md index 08d7c28c998bb6de4a1ef40db96a0110928ae9a0..73f4f1116be4ef9cff68117b0f2fd86acf32d382 100644 --- a/docs/WPF.md +++ b/docs/WPF.md @@ -225,7 +225,7 @@ public class TestViewModel : ViewModelBase 首先是在xaml里引入命名空间。 -`xmlns:eb="clr-namespace:IFoxCad.WPF;assembly=IFoxCad"` +`xmlns:eb="clr-namespace:IFoxCAD.WPF;assembly=IFoxCAD.WPF"` 然后 diff --git a/docs/autoreg.md b/docs/autoreg.md new file mode 100644 index 0000000000000000000000000000000000000000..72f377c24b7129336ebfa5a8e22f55fbc57697c5 --- /dev/null +++ b/docs/autoreg.md @@ -0,0 +1,62 @@ +## 自动加载与初始化 + +### 1、简单版 + +为了将程序集的初始化和通过写注册表的方式实现自动加载统一设置,减少每次重复的工作量,类裤提供了`AutoLoad`抽象类来完成此功能,只要在需要初始化的类继承`AutoLoad`类,然后实现`Initialize()` 和 `Terminate()` 两个函数就可以了。 +特别强调的是,一个程序集里只能有一个类继承,不管是不是同一个命名空间。 + +如果要将dll的目录加入支持文件目录,请在 `Initialize` 函数中调用`AppendSupportPath(CurrentDirectory.FullName);` + +其他需要初始化执行的函数及设置都需要在 `Initialize` 函数中执行。 + +### 2、功能版 + +使用特性进行分段初始化是目前最佳选择,下面的说明已和最新版本不符,等待修正吧。 + +```csharp + using Autodesk.AutoCAD.Runtime; + using IFoxCAD.Cad; + using System; + using System.Reflection; + +/* + * 自动执行接口 + * 这里必须要实现一次这个接口,才能使用 IFoxInitialize 特性进行自动执行 + */ +public class CmdINI : AutoRegAssem +{ + // 这里可以写任何普通的函数,也可以写下面 AutoTest 类里的实现了 IFoxInitialize 特性的初始化函数 + // 继承AutoRegAssem的主要作用是写注册表用来自动加载dll,同时执行实现了 IFoxInitialize 特性的函数 + // 注意这里的自动执行是在cad启动后,加载了dll之后执行,而不是运行命令后执行。 + + [IFoxInitialize] + public void InitOne() + { + // TODO 您想在加载dll之后自动执行的函数 + // 可以随便在哪里类里 可以多次实现 IFoxInitialize 特性 + } + +} + +// 其他的类中的函数: +// 实现自动接口之后,在任意一个函数上面使用此特性,减少每次改动 CmdINI 类 +public class AutoTest +{ + [IFoxInitialize] + public void Initialize() + { + // TODO 您想在加载dll之后自动执行的函数 + } + [IFoxInitialize] + public void InitTwo() + { + // TODO 您想在加载dll之后自动执行的函数 + // 可以随便在哪里类里 可以多次实现 IFoxInitialize 特性 + } + [IFoxInitialize(isInitialize: false)] // 特性的参数为false的时候就表示卸载时执行的函数 + public void Terminate() + { + // TODO 您想在关闭cad时自动执行的函数 + } +} +``` diff --git "a/docs/png/ifoxcad\347\224\250\346\210\267\344\272\244\346\265\201\347\276\244\347\276\244\344\272\214\347\273\264\347\240\201.png" "b/docs/png/ifoxcad\347\224\250\346\210\267\344\272\244\346\265\201\347\276\244\347\276\244\344\272\214\347\273\264\347\240\201.png" new file mode 100644 index 0000000000000000000000000000000000000000..a9d08c483b80f21d4b0ac6a0afe80f9b6b4233e6 Binary files /dev/null and "b/docs/png/ifoxcad\347\224\250\346\210\267\344\272\244\346\265\201\347\276\244\347\276\244\344\272\214\347\273\264\347\240\201.png" differ diff --git a/docs/png/nuget.png b/docs/png/nuget.png new file mode 100644 index 0000000000000000000000000000000000000000..d0af980fedd9eca39d0e0bf787111685242994ac Binary files /dev/null and b/docs/png/nuget.png differ diff --git a/docs/png/nuget1.png b/docs/png/nuget1.png new file mode 100644 index 0000000000000000000000000000000000000000..91983ffef3b246ea796296b14fe09963663de184 Binary files /dev/null and b/docs/png/nuget1.png differ diff --git a/docs/png/standard.png b/docs/png/standard.png new file mode 100644 index 0000000000000000000000000000000000000000..dfc34fecb32120f54436115208a4abaea2dac516 Binary files /dev/null and b/docs/png/standard.png differ diff --git "a/docs/\345\205\263\344\272\216IFoxCAD\347\232\204\346\236\266\346\236\204\350\257\264\346\230\216.md" "b/docs/\345\205\263\344\272\216IFoxCAD\347\232\204\346\236\266\346\236\204\350\257\264\346\230\216.md" index d8282200a6734c08bfbb1b74d08845f5806f51e9..f96d39d487c1f6c2af8a2f3257fa3cc3bbfd619d 100644 --- "a/docs/\345\205\263\344\272\216IFoxCAD\347\232\204\346\236\266\346\236\204\350\257\264\346\230\216.md" +++ "b/docs/\345\205\263\344\272\216IFoxCAD\347\232\204\346\236\266\346\236\204\350\257\264\346\230\216.md" @@ -1,4 +1,68 @@ -# 关于IFoxCAD的架构说明 +# IFoxCAD的架构说明 + +AutoCAD 的 .net api 的架构是如下这样的: + +1. Application 对象 + +```mermaid +graph LR; +a(Application)-->DocumentManager +a-->DocumentWindowCollection +a-->InfoCenter +a-->MainWindow +a-->MenuBar +a-->MenuGroups +a-->Preferences +a-->Publisher +a-->StatusBar +a-->UserConfigurationManager +``` + +2. Document 对象 + +```mermaid +graph LR; +Application-->DocumentManager-->b[Document] +b-->Database +b-->Editor +b-->GraphicsManager +b-->StatusBar +b-->TransactionManager +b-->UserData +b-->Window +``` + +3. Database 对象 + +```mermaid +flowchart TB; +subgraph NamedDictionaris + direction TB + Layout-Dictionary-->Object + Others-->OtherObject +end +subgraph Tables + direction TB + BlockTable-->BlockTableRecord-->Entity + OthersTable-->OthersTableRecord +end +Application-->DocumentManager-->Document-->d[Database]-->Tables +d-->NamedDictionaris +``` + +4. Transation 对象 + +```mermaid +flowchart LR; +subgraph Transation + direction LR + f(StartTransation)--modify objects-->e{isOK} + e--Yes-->h(commit) + e--No-->abort +end +h--write-->d[Database] +g[Document or Database]--start-->f +``` IFoxCAD是基于NFOX类库的重制版,主要是提供一个最小化的内核,即DBTrans、SymbolTable、ResultData、SelectFilter等基础类,其他的功能都通过扩展方法的方式来实现。 @@ -7,33 +71,42 @@ IFoxCAD是基于NFOX类库的重制版,主要是提供一个最小化的内核 ## 一、组织结构图 - IFoxCAD - - IFoxCAD.Cad - cad相关的类库 - - LinqEx - linq扩展类 - - LoopList - 环链表 - - IFoxCAD.Basal - cad以外常用的类库 - - Runtime - 包含系统级别的功能 - - AcadVersion - cad版本号类 - - AssemInfo - 程序集信息 - - AutoRegAssem - 程序集加载类型 - - DBTrans - 事务处理类 - - Env - 系统管理类 - - SymbolTable - 符号表类 - - ExtensionMethod - 扩展函数,以Ex结尾 - - SymbolTableEx - 符号表扩展类 - - SymbolTableRecordEx - 符号表记录扩展类 - - EntityEx - 实体扩展类 - - 。。。。。。 - - ResultData - - 待补充。。。 - - SelectionFilter - - 待补充。。。 - - IFoxCAD.WPF - wpf的mvvm模式相关的类库 - - 待补充。。。 - - ![输入图片说明](https://images.gitee.com/uploads/images/2021/0701/225449_2b18eb89_9063830.png "屏幕截图.png") - ![输入图片说明](https://images.gitee.com/uploads/images/2021/0701/225550_840a862a_9063830.png "屏幕截图.png") - ![输入图片说明](https://images.gitee.com/uploads/images/2021/0701/225525_b246bbd2_9063830.png "屏幕截图.png") -## 二、关于DBTrans类的说明 + + - IFoxCAD.Basal - cad以外常用的类库 + + - LinqEx - linq扩展类 + + - LoopList - 环链表 + + - IFoxCAD.Cad - cad相关的类库 + + - Runtime - 包含系统级别的功能 + + - AcadVersion - cad版本号类 + - AssemInfo - 程序集信息 + - AutoRegAssem - 程序集加载类型 + - DBTrans - 事务处理类 + - Env - 系统管理类 + - SymbolTable - 符号表类 + + - ExtensionMethod - 扩展函数,以Ex结尾 + + - SymbolTableEx - 符号表扩展类 + - SymbolTableRecordEx - 符号表记录扩展类 + - EntityEx - 实体扩展类 + - 。。。。。。 + + - ResultData + + - 待补充。。。 + + - SelectionFilter + + - 待补充。。。 + + - IFoxCAD.WPF - wpf的mvvm模式相关的类库 + + ## 二、关于DBTrans类的说明 ### 2.1 为什么要构建DBTrans类? @@ -45,14 +118,16 @@ IFoxCAD是基于NFOX类库的重制版,主要是提供一个最小化的内核 ### 2.2 关于DBTrans类的具体构成元素的意义 -DBTrans类里基本的封装就是Transaction,然后是Document、Database、Editor、符号表、命名字典等,而抓这些其实都是cad二次开发关于图元操作经常打交道的概念。 +DBTrans类里基本的封装就是Transaction,然后是Document、Database、Editor、符号表、命名字典等,而这些其实都是cad二次开发关于图元操作经常打交道的概念。 DBTrans的每个实例都具有这些属性,而这些属性就对应于cad的相关类库,通过这些属性就可以对数据进行相应的操作。特别是符号表中最常用的就是块表,通过对块表的操作来实现添加图元等。 ### 2.3 DBTrans类应该具有的成员 + 为了尽量少的封装方法,减少类的复杂度,目前计划的方法主要为: 属性: + - Top ---返回当前事务 - Database ---数据库 - Document ---文档 @@ -60,11 +135,13 @@ DBTrans的每个实例都具有这些属性,而这些属性就对应于cad的 - Trans ---事务管理器 构造函数: + - DBTrans(Document doc = null, bool commit = true) - DBTrans(Database database, bool commit = true) - DBTrans(string fileName, bool commit = true) 符号表: + - BlockTable 块表 - LayerTable 层表 - TextStyleTable 文字样式表 @@ -76,10 +153,12 @@ DBTrans的每个实例都具有这些属性,而这些属性就对应于cad的 - ViewportTable 视口表 方法: + - GetObject ---根据对象id获取图元对象 - 。。。 接口: + - Abort ---放弃事务 - Commit ---提交事务 - Dispose --- 执行与释放非托管资源 @@ -96,10 +175,13 @@ DBTrans的每个实例都具有这些属性,而这些属性就对应于cad的 - 有了这个类,DBTrans类就可以直接通过属性获取符号表的关联关系,然后进行符号表的处理。 ### 3.2 SymbolTable类应该具有的成员 + 属性: + - CurrentSymbolTable ---当前的符号表对象 方法: + - this ---索引器符号表记录函数 - Add ---添加符号表记录函数 - Remove --- 删除符号表记录函数(层表请使用扩展方法Delete) @@ -109,15 +191,6 @@ DBTrans的每个实例都具有这些属性,而这些属性就对应于cad的 - Has --- 判断符号表是否有符号表记录的函数 - 。。。 -特殊说明:当符号表为块表时,上述函数实际操作的是块定义、属性定义等。所以为了添加图元,需要特殊写法。 +特殊说明:当符号表为块表时,上述函数实际操作的是块定义、属性定义等。所以为了添加图元,需要特殊写法,原因在于cad的实体都是存在符号表记录里的,通常为模型这个块表记录。 # 慢慢完善,想到哪写到哪。。。 - - - - - - - - - diff --git a/src/Basal/Directory.Build.props b/src/Basal/Directory.Build.props new file mode 100644 index 0000000000000000000000000000000000000000..77a2d3711db05b158825e750d59ed1f4f864e9a9 --- /dev/null +++ b/src/Basal/Directory.Build.props @@ -0,0 +1,37 @@ + + + + 0.5.2.4 + 完善源码包,源码包映射原始目录 + + + + preview + enable + true + ..\..\..\bin\$(Configuration)\ + true + true + True + True + + + + + + InspireFunction + xsfhlzh;vicwjb;liuqihong + InspireFunction + 基于.NET的二次开发基本类库. + MIT + true + https://gitee.com/inspirefunction/ifoxcad + https://gitee.com/inspirefunction/ifoxcad.git + git + IFox;C#;NET;Common;Basal + + + + + + \ No newline at end of file diff --git a/src/Basal/IFox.Basal.Shared/CLS/Index.cs b/src/Basal/IFox.Basal.Shared/CLS/Index.cs new file mode 100644 index 0000000000000000000000000000000000000000..b84f4dffd68334952188669e6fa487d76dcb89d0 --- /dev/null +++ b/src/Basal/IFox.Basal.Shared/CLS/Index.cs @@ -0,0 +1,151 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +#if !NOINDEX +namespace System; + + +#if NET45 +using System.Runtime.CompilerServices; +#endif +/// Represent a type can be used to index a collection either from the start or the end. +/// +/// Index is used by the C# compiler to support the new index syntax +/// +/// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ; +/// int lastElement = someArray[^1]; // lastElement = 5 +/// +/// +public readonly struct Index : IEquatable +{ + private readonly int _value; + + /// Construct an Index using a value and indicating if the index is from the start or from the end. + /// The index value. it has to be zero or positive number. + /// Indicating if the index is from the start or from the end. + /// + /// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element. + /// +#if NET45 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public Index(int value, bool fromEnd = false) + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); + } + + if (fromEnd) + _value = ~value; + else + _value = value; + } + + // The following private constructors mainly created for perf reason to avoid the checks + private Index(int value) + { + _value = value; + } + + /// Create an Index pointing at first element. + public static Index Start => new(0); + + /// Create an Index pointing at beyond last element. + public static Index End => new(~0); + + /// Create an Index from the start at the position indicated by the value. + /// The index value from the start. +#if NET45 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static Index FromStart(int value) + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); + } + + return new Index(value); + } + + /// Create an Index from the end at the position indicated by the value. + /// The index value from the end. +#if NET45 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static Index FromEnd(int value) + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); + } + + return new Index(~value); + } + + /// Returns the index value. + public int Value + { + get + { + if (_value < 0) + return ~_value; + else + return _value; + } + } + + /// Indicates whether the index is from the start or the end. + public bool IsFromEnd => _value < 0; + + /// Calculate the offset from the start using the giving collection length. + /// The length of the collection that the Index will be used with. length has to be a positive value + /// + /// For performance reason, we don't validate the input length parameter and the returned offset value against negative values. + /// we don't validate either the returned offset is greater than the input length. + /// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and + /// then used to index a collection will get out of range exception which will be same affect as the validation. + /// +#if NET45 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public int GetOffset(int length) + { + int offset = _value; + if (IsFromEnd) + { + // offset = length - (~value) + // offset = length + (~(~value) + 1) + // offset = length + value + 1 + + offset += length + 1; + } + return offset; + } + + /// Indicates whether the current Index object is equal to another object of the same type. + /// An object to compare with this object + public override bool Equals(object? value) => value is Index index && _value == index._value; + + /// Indicates whether the current Index object is equal to another Index object. + /// An object to compare with this object + public bool Equals(Index other) => _value == other._value; + + /// Returns the hash code for this instance. + public override int GetHashCode() => _value; + + /// Converts integer number to an Index. + public static implicit operator Index(int value) => FromStart(value); + + /// Converts the value of the current Index object to its equivalent string representation. + public override string ToString() + { + if (IsFromEnd) + return "^" + ((uint)Value).ToString(); + + return ((uint)Value).ToString(); + } +} + +#endif \ No newline at end of file diff --git a/src/Basal/IFox.Basal.Shared/CLS/Range.cs b/src/Basal/IFox.Basal.Shared/CLS/Range.cs new file mode 100644 index 0000000000000000000000000000000000000000..0318216b8a9dc01a2712633037053f66bdf83c07 --- /dev/null +++ b/src/Basal/IFox.Basal.Shared/CLS/Range.cs @@ -0,0 +1,107 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +#if !NORANGE +namespace System; + + + +#if NET45 +using System.Runtime.CompilerServices; +#endif + +/// Represent a range has start and end indexes. +/// +/// Range is used by the C# compiler to support the range syntax. +/// +/// int[] someArray = new int[5] { 1, 2, 3, 4, 5 }; +/// int[] subArray1 = someArray[0..2]; // { 1, 2 } +/// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 } +/// +/// +public readonly struct Range : IEquatable +{ + /// Represent the inclusive start index of the Range. + public Index Start { get; } + + /// Represent the exclusive end index of the Range. + public Index End { get; } + + /// Construct a Range object using the start and end indexes. + /// Represent the inclusive start index of the range. + /// Represent the exclusive end index of the range. + public Range(Index start, Index end) + { + Start = start; + End = end; + } + + /// Indicates whether the current Range object is equal to another object of the same type. + /// An object to compare with this object + public override bool Equals(object? value) => + value is Range r && + r.Start.Equals(Start) && + r.End.Equals(End); + + /// Indicates whether the current Range object is equal to another Range object. + /// An object to compare with this object + public bool Equals(Range other) => other.Start.Equals(Start) && other.End.Equals(End); + + /// Returns the hash code for this instance. + public override int GetHashCode() + { + return Start.GetHashCode() * 31 + End.GetHashCode(); + } + + /// Converts the value of the current Range object to its equivalent string representation. + public override string ToString() + { + return Start + ".." + End; + } + + /// Create a Range object starting from start index to the end of the collection. + public static Range StartAt(Index start) => new(start, Index.End); + + /// Create a Range object starting from first element in the collection to the end Index. + public static Range EndAt(Index end) => new(Index.Start, end); + + /// Create a Range object starting from first element to the end. + public static Range All => new(Index.Start, Index.End); + + /// Calculate the start offset and length of range object using a collection length. + /// The length of the collection that the range will be used with. length has to be a positive value. + /// + /// For performance reason, we don't validate the input length parameter against negative values. + /// It is expected Range will be used with collections which always have non negative length/count. + /// We validate the range is inside the length scope though. + /// +#if NET45 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + // [CLSCompliant(false)] + public (int Offset, int Length) GetOffsetAndLength(int length) + { + int start; + Index startIndex = Start; + if (startIndex.IsFromEnd) + start = length - startIndex.Value; + else + start = startIndex.Value; + + int end; + Index endIndex = End; + if (endIndex.IsFromEnd) + end = length - endIndex.Value; + else + end = endIndex.Value; + + if ((uint)end > (uint)length || (uint)start > (uint)end) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + return (start, end - start); + } +} + +#endif \ No newline at end of file diff --git a/src/Basal/IFox.Basal.Shared/CLS/RuntimeHelpers.cs b/src/Basal/IFox.Basal.Shared/CLS/RuntimeHelpers.cs new file mode 100644 index 0000000000000000000000000000000000000000..841c0d59100a271c323809fdee0fedd34f618f1e --- /dev/null +++ b/src/Basal/IFox.Basal.Shared/CLS/RuntimeHelpers.cs @@ -0,0 +1,57 @@ + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using IFoxCAD.Basal; + +namespace System.Runtime.CompilerServices; + +/* + * 1.编译提示多个程序集中定义,屏蔽不了,但是不影响编译 + * 2.发现可以通过定义一个条件编译常量来进行屏蔽。 + * 3.普通包会提示编译器缺少GetSubArray函数,但是源码包不会。所以解决方案就是使用普通包的时候安装TA.System.Runtime.CompilerServices.RuntimeHelpers.GetSubArray nuget包来解决,此包为一个符号包,不会生成多余的dll + */ + + +#if !NORuntimeHelpers +/// +/// +/// +internal static class RuntimeHelpers + +{ + /// + /// Slices the specified array using the specified range. + /// + public static T[] GetSubArray(T[] array, Range range) + { + //if (array == null) + // throw new ArgumentNullException(nameof(array)); + array.NotNull(nameof(array)); + + (int offset, int length) = range.GetOffsetAndLength(array.Length); + + if (default(T)! != null || typeof(T[]) == array.GetType()) // NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757) + { + // We know the type of the array to be exactly T[]. + if (length == 0) + { + // return Array.Empty(); + return new T[0]; + } + + var dest = new T[length]; + Array.Copy(array, offset, dest, 0, length); + return dest; + } + else + { + // The array is actually a U[] where U:T. + T[] dest = (T[])Array.CreateInstance(array.GetType().GetElementType()!, length); + Array.Copy(array, offset, dest, 0, length); + return dest; + } + } +} +#endif diff --git a/src/Basal/IFox.Basal.Shared/CLS/TupleElementNamesAttribute.cs b/src/Basal/IFox.Basal.Shared/CLS/TupleElementNamesAttribute.cs new file mode 100644 index 0000000000000000000000000000000000000000..93e61930c31b8277948fa060629cb1d813a04ba1 --- /dev/null +++ b/src/Basal/IFox.Basal.Shared/CLS/TupleElementNamesAttribute.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.CompilerServices; + +/// +/// Indicates that the use of on a member is meant to be treated as a tuple with element names. +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Event)] +public sealed class TupleElementNamesAttribute : Attribute +{ + private readonly string[] _transformNames; + + /// + /// Initializes a new instance of the class. + /// + /// + /// Specifies, in a pre-order depth-first traversal of a type's + /// construction, which occurrences are + /// meant to carry element names. + /// + /// + /// This constructor is meant to be used on types that contain an + /// instantiation of that contains + /// element names. For instance, if C is a generic type with + /// two type parameters, then a use of the constructed type C{, might be intended to + /// treat the first type argument as a tuple with element names and the + /// second as a tuple without element names. In which case, the + /// appropriate attribute specification should use a + /// transformNames value of { "name1", "name2", null, null, + /// null }. + /// + public TupleElementNamesAttribute(string[] transformNames) + { + _transformNames = transformNames ?? throw new ArgumentNullException(nameof(transformNames)); + } + + /// + /// Specifies, in a pre-order depth-first traversal of a type's + /// construction, which elements are + /// meant to carry element names. + /// + public IList TransformNames => _transformNames; +} \ No newline at end of file diff --git a/src/Basal/IFox.Basal.Shared/CLS/ValueTuple.cs b/src/Basal/IFox.Basal.Shared/CLS/ValueTuple.cs new file mode 100644 index 0000000000000000000000000000000000000000..c20902433c6b2e27faccc0f8cf436837c42e460b --- /dev/null +++ b/src/Basal/IFox.Basal.Shared/CLS/ValueTuple.cs @@ -0,0 +1,2152 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// #pragma warning disable SA1141 // explicitly not using tuple syntax in tuple implementation +#if !NOVALUETUPLE + +using System.Diagnostics; +using System.Numerics.Hashing; +/* + * 惊惊: + * 首先是因为有人想要编译的时候只形成一个dll,然后把元组塞入IFox,同时也补充了NET35没有元组的遗憾. + * + * 而利用nuget元组包必然会形成依赖地狱. + * + * 如果你的工程使用了nuget元组包,就造成了必须要剔除IFox. + * + * 改IFox的元组命名空间倒是可以分离两者,但是 vs编译器 无法识别带其他命名空间的元组. + * 所以元组本身就是冲突的,需要把其他元组卸载掉,由IFox提供. + */ + +/* + * 1. 元组是net47之后提供的特性,所以net47之前的版本是都没有的 + * 2. 通过定义常量的办法将元组屏蔽,很奇怪的是源码包可以,但是普通包不行,所以推荐使用源码包 index和range的情况类似 + * 这种问题就没有很好的解决方式,因为用了ifox就不能用其他的index,range,valuetuple类库,但是其他的三方库却依赖 + * 这些类库,所以还是使用源码包吧。 +*/ + +#if NET35 +namespace System.Collections +{ + public interface IStructuralComparable + { + int CompareTo(object? other, IComparer comparer); + } + public interface IStructuralEquatable + { + bool Equals(object? other, IEqualityComparer comparer); + int GetHashCode(IEqualityComparer comparer); + } +} +#endif + + + +namespace System.Numerics.Hashing +{ + internal static class HashHelpers + { + public static readonly int RandomSeed = Guid.NewGuid().GetHashCode(); + + public static int Combine(int h1, int h2) + { + unchecked + { + // RyuJIT optimizes this to use the ROL instruction + // Related GitHub pull request: dotnet/coreclr#1830 + + // RyuJIT 对此进行了优化以使用 ROL 指令 + // 相关 GitHub 拉取请求:dotnet/coreclr#1830 + uint rol5 = ((uint)h1 << 5) | ((uint)h1 >> 27); + return ((int)rol5 + h1) ^ h2; + } + } + } +} + + + + +namespace System +{ + + // internal static class SR + internal sealed partial class SR + { + // public const string ArgumentException_ValueTupleIncorrectType = "The parameter should be a ValueTuple type of appropriate arity."; + // public const string ArgumentException_ValueTupleLastArgumentNotAValueTuple = "The TRest type argument of ValueTuple`8 must be a ValueTuple."; + public const string ArgumentException_ValueTupleIncorrectType = "该参数应该是适当数量的 ValueTuple 类型."; + public const string ArgumentException_ValueTupleLastArgumentNotAValueTuple = "ValueTuple`8 的 TREST 类型参数必须是 ValueTuple."; + } + + // Helper so we can call some tuple methods recursively without knowing the underlying types. + /// + /// 帮助器,因此我们可以在不知道底层类型的情况下递归调用一些元组方法. + /// + internal interface ITupleInternal + { + int GetHashCode(IEqualityComparer comparer); + int Size { get; } + string ToStringEnd(); + } + + + // The ValueTuple types (from arity 0 to 8) comprise the runtime implementation that underlies tuples in C# and struct tuples in F#. + // Aside from created via language syntax, they are most easily created via the ValueTuple.Create factory methods. + // The System.ValueTuple types differ from the System.Tuple types in that: + // - they are structs rather than classes, + // - they are mutable rather than readonly, and + // - their members (such as Item1, Item2, etc) are fields rather than properties. + /// + /// ValueTuple 类型(从 arity 0 到 8)包含运行时实现,它是 C# 中的元组和 F# 中的结构元组的基础. + /// 除了通过语言语法创建之外,它们最容易通过 ValueTuple.Create 工厂方法创建. + /// System.ValueTuple 类型与 System.Tuple 类型的不同之处在于: + /// - 它们是结构而不是类, + /// - 它们是可变的而不是只读的,并且 + /// - 它们的成员(例如 Item1、Item2 等)是字段而不是属性. + /// + public struct ValueTuple + : IEquatable, IStructuralEquatable, IStructuralComparable, IComparable, IComparable, ITupleInternal + { + /// + /// Returns a value that indicates whether the current instance is equal to a specified object. + /// + /// The object to compare with this instance. + /// if is a . + public override bool Equals(object obj) + { + return obj is ValueTuple; + } + + /// Returns a value indicating whether this instance is equal to a specified value. + /// An instance to compare to this instance. + /// true if has the same value as this instance; otherwise, false. + public bool Equals(ValueTuple other) + { + return true; + } + + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) + { + return other is ValueTuple; + } + + int IComparable.CompareTo(object other) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + return 0; + } + + /// Compares this instance to a specified instance and returns an indication of their relative values. + /// An instance to compare. + /// + /// A signed number indicating the relative values of this instance and . + /// Returns less than zero if this instance is less than , zero if this + /// instance is equal to , and greater than zero if this instance is greater + /// than . + /// + public int CompareTo(ValueTuple other) + { + return 0; + } + + int IStructuralComparable.CompareTo(object? other, IComparer comparer) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + return 0; + } + + /// Returns the hash code for this instance. + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return 0; + } + + int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) + { + return 0; + } + + int ITupleInternal.GetHashCode(IEqualityComparer comparer) + { + return 0; + } + + /// + /// Returns a string that represents the value of this instance. + /// + /// The string representation of this instance. + /// + /// The string returned by this method takes the form (). + /// + public override string ToString() + { + return "()"; + } + + string ITupleInternal.ToStringEnd() + { + return ")"; + } + + int ITupleInternal.Size => 0; + + /// Creates a new struct 0-tuple. + /// A 0-tuple. + public static ValueTuple Create() => new(); + + /// Creates a new struct 1-tuple, or singleton. + /// The type of the first component of the tuple. + /// The value of the first component of the tuple. + /// A 1-tuple (singleton) whose value is (item1). + public static ValueTuple Create(T1 item1) => new(item1); + + /// Creates a new struct 2-tuple, or pair. + /// The type of the first component of the tuple. + /// The type of the second component of the tuple. + /// The value of the first component of the tuple. + /// The value of the second component of the tuple. + /// A 2-tuple (pair) whose value is (item1, item2). + public static ValueTuple Create(T1 item1, T2 item2) => new(item1, item2); + + /// Creates a new struct 3-tuple, or triple. + /// The type of the first component of the tuple. + /// The type of the second component of the tuple. + /// The type of the third component of the tuple. + /// The value of the first component of the tuple. + /// The value of the second component of the tuple. + /// The value of the third component of the tuple. + /// A 3-tuple (triple) whose value is (item1, item2, item3). + public static ValueTuple Create(T1 item1, T2 item2, T3 item3) => + new(item1, item2, item3); + + /// Creates a new struct 4-tuple, or quadruple. + /// The type of the first component of the tuple. + /// The type of the second component of the tuple. + /// The type of the third component of the tuple. + /// The type of the fourth component of the tuple. + /// The value of the first component of the tuple. + /// The value of the second component of the tuple. + /// The value of the third component of the tuple. + /// The value of the fourth component of the tuple. + /// A 4-tuple (quadruple) whose value is (item1, item2, item3, item4). + public static ValueTuple Create(T1 item1, T2 item2, T3 item3, T4 item4) => + new(item1, item2, item3, item4); + + /// Creates a new struct 5-tuple, or quintuple. + /// The type of the first component of the tuple. + /// The type of the second component of the tuple. + /// The type of the third component of the tuple. + /// The type of the fourth component of the tuple. + /// The type of the fifth component of the tuple. + /// The value of the first component of the tuple. + /// The value of the second component of the tuple. + /// The value of the third component of the tuple. + /// The value of the fourth component of the tuple. + /// The value of the fifth component of the tuple. + /// A 5-tuple (quintuple) whose value is (item1, item2, item3, item4, item5). + public static ValueTuple Create(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5) => + new(item1, item2, item3, item4, item5); + + /// Creates a new struct 6-tuple, or sextuple. + /// The type of the first component of the tuple. + /// The type of the second component of the tuple. + /// The type of the third component of the tuple. + /// The type of the fourth component of the tuple. + /// The type of the fifth component of the tuple. + /// The type of the sixth component of the tuple. + /// The value of the first component of the tuple. + /// The value of the second component of the tuple. + /// The value of the third component of the tuple. + /// The value of the fourth component of the tuple. + /// The value of the fifth component of the tuple. + /// The value of the sixth component of the tuple. + /// A 6-tuple (sextuple) whose value is (item1, item2, item3, item4, item5, item6). + public static ValueTuple Create(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6) => + new(item1, item2, item3, item4, item5, item6); + + /// Creates a new struct 7-tuple, or septuple. + /// The type of the first component of the tuple. + /// The type of the second component of the tuple. + /// The type of the third component of the tuple. + /// The type of the fourth component of the tuple. + /// The type of the fifth component of the tuple. + /// The type of the sixth component of the tuple. + /// The type of the seventh component of the tuple. + /// The value of the first component of the tuple. + /// The value of the second component of the tuple. + /// The value of the third component of the tuple. + /// The value of the fourth component of the tuple. + /// The value of the fifth component of the tuple. + /// The value of the sixth component of the tuple. + /// The value of the seventh component of the tuple. + /// A 7-tuple (septuple) whose value is (item1, item2, item3, item4, item5, item6, item7). + public static ValueTuple Create(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7) => + new(item1, item2, item3, item4, item5, item6, item7); + + /// Creates a new struct 8-tuple, or octuple. + /// The type of the first component of the tuple. + /// The type of the second component of the tuple. + /// The type of the third component of the tuple. + /// The type of the fourth component of the tuple. + /// The type of the fifth component of the tuple. + /// The type of the sixth component of the tuple. + /// The type of the seventh component of the tuple. + /// The type of the eighth component of the tuple. + /// The value of the first component of the tuple. + /// The value of the second component of the tuple. + /// The value of the third component of the tuple. + /// The value of the fourth component of the tuple. + /// The value of the fifth component of the tuple. + /// The value of the sixth component of the tuple. + /// The value of the seventh component of the tuple. + /// The value of the eighth component of the tuple. + /// An 8-tuple (octuple) whose value is (item1, item2, item3, item4, item5, item6, item7, item8). + public static ValueTuple> Create(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8) => + new(item1, item2, item3, item4, item5, item6, item7, ValueTuple.Create(item8)); + + internal static int CombineHashCodes(int h1, int h2) + { + return HashHelpers.Combine(HashHelpers.Combine(HashHelpers.RandomSeed, h1), h2); + } + + internal static int CombineHashCodes(int h1, int h2, int h3) + { + return HashHelpers.Combine(CombineHashCodes(h1, h2), h3); + } + + internal static int CombineHashCodes(int h1, int h2, int h3, int h4) + { + return HashHelpers.Combine(CombineHashCodes(h1, h2, h3), h4); + } + + internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5) + { + return HashHelpers.Combine(CombineHashCodes(h1, h2, h3, h4), h5); + } + + internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6) + { + return HashHelpers.Combine(CombineHashCodes(h1, h2, h3, h4, h5), h6); + } + + internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6, int h7) + { + return HashHelpers.Combine(CombineHashCodes(h1, h2, h3, h4, h5, h6), h7); + } + + internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6, int h7, int h8) + { + return HashHelpers.Combine(CombineHashCodes(h1, h2, h3, h4, h5, h6, h7), h8); + } + } + + /// Represents a 1-tuple, or singleton, as a value type. + /// The type of the tuple's only component. + public struct ValueTuple + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal + { + /// + /// The current instance's first component. + /// + public T1 Item1; + + /// + /// Initializes a new instance of the value type. + /// + /// The value of the tuple's first component. + public ValueTuple(T1 item1) + { + Item1 = item1; + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified object. + /// + /// The object to compare with this instance. + /// if the current instance is equal to the specified object; otherwise, . + /// + /// The parameter is considered to be equal to the current instance under the following conditions: + /// + /// It is a value type. + /// Its components are of the same types as those of the current instance. + /// Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + /// + /// + public override bool Equals(object obj) + { + return obj is ValueTuple tuple && Equals(tuple); + } + + /// + /// Returns a value that indicates whether the current + /// instance is equal to a specified . + /// + /// The tuple to compare with this instance. + /// if the current instance is equal to the specified tuple; otherwise, . + /// + /// The parameter is considered to be equal to the current instance if each of its field + /// is equal to that of the current instance, using the default comparer for that field's type. + /// + public bool Equals(ValueTuple other) + { + return EqualityComparer.Default.Equals(Item1, other.Item1); + } + + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) + { + if (other == null || other is not ValueTuple) return false; + + var objTuple = (ValueTuple)other; + + return comparer.Equals(Item1, objTuple.Item1); + } + + int IComparable.CompareTo(object other) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + var objTuple = (ValueTuple)other; + + return Comparer.Default.Compare(Item1, objTuple.Item1); + } + + /// Compares this instance to a specified instance and returns an indication of their relative values. + /// An instance to compare. + /// + /// A signed number indicating the relative values of this instance and . + /// Returns less than zero if this instance is less than , zero if this + /// instance is equal to , and greater than zero if this instance is greater + /// than . + /// + public int CompareTo(ValueTuple other) + { + return Comparer.Default.Compare(Item1, other.Item1); + } + + int IStructuralComparable.CompareTo(object? other, IComparer comparer) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + var objTuple = (ValueTuple)other; + + return comparer.Compare(Item1, objTuple.Item1); + } + + /// + /// Returns the hash code for the current instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return EqualityComparer.Default.GetHashCode(Item1); + } + + int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) + { + return comparer.GetHashCode(Item1); + } + + int ITupleInternal.GetHashCode(IEqualityComparer comparer) + { + return comparer.GetHashCode(Item1); + } + + /// + /// Returns a string that represents the value of this instance. + /// + /// The string representation of this instance. + /// + /// The string returned by this method takes the form (Item1), + /// where Item1 represents the value of . If the field is , + /// it is represented as . + /// + public override string ToString() + { + return "(" + Item1?.ToString() + ")"; + } + + string ITupleInternal.ToStringEnd() + { + return Item1?.ToString() + ")"; + } + + int ITupleInternal.Size => 1; + } + + /// + /// Represents a 2-tuple, or pair, as a value type. + /// + /// The type of the tuple's first component. + /// The type of the tuple's second component. + public struct ValueTuple + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal + { + /// + /// The current instance's first component. + /// + public T1 Item1; + + /// + /// The current instance's first component. + /// + public T2 Item2; + + /// + /// Initializes a new instance of the value type. + /// + /// The value of the tuple's first component. + /// The value of the tuple's second component. + public ValueTuple(T1 item1, T2 item2) + { + Item1 = item1; + Item2 = item2; + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified object. + /// + /// The object to compare with this instance. + /// if the current instance is equal to the specified object; otherwise, . + /// + /// + /// The parameter is considered to be equal to the current instance under the following conditions: + /// + /// It is a value type. + /// Its components are of the same types as those of the current instance. + /// Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + /// + /// + public override bool Equals(object obj) + { + return obj is ValueTuple tuple && Equals(tuple); + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified . + /// + /// The tuple to compare with this instance. + /// if the current instance is equal to the specified tuple; otherwise, . + /// + /// The parameter is considered to be equal to the current instance if each of its fields + /// are equal to that of the current instance, using the default comparer for that field's type. + /// + public bool Equals(ValueTuple other) + { + return EqualityComparer.Default.Equals(Item1, other.Item1) + && EqualityComparer.Default.Equals(Item2, other.Item2); + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified object based on a specified comparison method. + /// + /// The object to compare with this instance. + /// An object that defines the method to use to evaluate whether the two objects are equal. + /// if the current instance is equal to the specified object; otherwise, . + /// + /// + /// This member is an explicit interface member implementation. It can be used only when the + /// instance is cast to an interface. + /// + /// The implementation is called only if other is not , + /// and if it can be successfully cast (in C#) or converted (in Visual Basic) to a + /// whose components are of the same types as those of the current instance. The IStructuralEquatable.Equals(Object, IEqualityComparer) method + /// first passes the values of the objects to be compared to the + /// implementation. If this method call returns , the method is + /// called again and passed the values of the two instances. + /// + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) + { + if (other is null or not ValueTuple) return false; + + var objTuple = (ValueTuple)other; + + return comparer.Equals(Item1, objTuple.Item1) + && comparer.Equals(Item2, objTuple.Item2); + } + + int IComparable.CompareTo(object other) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + return CompareTo((ValueTuple)other); + } + + /// Compares this instance to a specified instance and returns an indication of their relative values. + /// An instance to compare. + /// + /// A signed number indicating the relative values of this instance and . + /// Returns less than zero if this instance is less than , zero if this + /// instance is equal to , and greater than zero if this instance is greater + /// than . + /// + public int CompareTo(ValueTuple other) + { + int c = Comparer.Default.Compare(Item1, other.Item1); + if (c != 0) return c; + + return Comparer.Default.Compare(Item2, other.Item2); + } + + int IStructuralComparable.CompareTo(object? other, IComparer comparer) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + var objTuple = (ValueTuple)other; + + int c = comparer.Compare(Item1, objTuple.Item1); + if (c != 0) return c; + + return comparer.Compare(Item2, objTuple.Item2); + } + + /// + /// Returns the hash code for the current instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item1), + EqualityComparer.Default.GetHashCode(Item2)); + } + + int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + private int GetHashCodeCore(IEqualityComparer comparer) + { + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item1), + comparer.GetHashCode(Item2)); + } + + int ITupleInternal.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + /// + /// Returns a string that represents the value of this instance. + /// + /// The string representation of this instance. + /// + /// The string returned by this method takes the form (Item1, Item2), + /// where Item1 and Item2 represent the values of the + /// and fields. If either field value is , + /// it is represented as . + /// + public override string ToString() + { + return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ")"; + } + + string ITupleInternal.ToStringEnd() + { + return Item1?.ToString() + ", " + Item2?.ToString() + ")"; + } + + int ITupleInternal.Size => 2; + } + + /// + /// Represents a 3-tuple, or triple, as a value type. + /// + /// The type of the tuple's first component. + /// The type of the tuple's second component. + /// The type of the tuple's third component. + public struct ValueTuple + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal + { + /// + /// The current instance's first component. + /// + public T1 Item1; + /// + /// The current instance's second component. + /// + public T2 Item2; + /// + /// The current instance's third component. + /// + public T3 Item3; + + /// + /// Initializes a new instance of the value type. + /// + /// The value of the tuple's first component. + /// The value of the tuple's second component. + /// The value of the tuple's third component. + public ValueTuple(T1 item1, T2 item2, T3 item3) + { + Item1 = item1; + Item2 = item2; + Item3 = item3; + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified object. + /// + /// The object to compare with this instance. + /// if the current instance is equal to the specified object; otherwise, . + /// + /// The parameter is considered to be equal to the current instance under the following conditions: + /// + /// It is a value type. + /// Its components are of the same types as those of the current instance. + /// Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + /// + /// + public override bool Equals(object obj) + { + return obj is ValueTuple tuple && Equals(tuple); + } + + /// + /// Returns a value that indicates whether the current + /// instance is equal to a specified . + /// + /// The tuple to compare with this instance. + /// if the current instance is equal to the specified tuple; otherwise, . + /// + /// The parameter is considered to be equal to the current instance if each of its fields + /// are equal to that of the current instance, using the default comparer for that field's type. + /// + public bool Equals(ValueTuple other) + { + return EqualityComparer.Default.Equals(Item1, other.Item1) + && EqualityComparer.Default.Equals(Item2, other.Item2) + && EqualityComparer.Default.Equals(Item3, other.Item3); + } + + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) + { + if (other == null || other is not ValueTuple) return false; + + var objTuple = (ValueTuple)other; + + return comparer.Equals(Item1, objTuple.Item1) + && comparer.Equals(Item2, objTuple.Item2) + && comparer.Equals(Item3, objTuple.Item3); + } + + int IComparable.CompareTo(object other) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + return CompareTo((ValueTuple)other); + } + + /// Compares this instance to a specified instance and returns an indication of their relative values. + /// An instance to compare. + /// + /// A signed number indicating the relative values of this instance and . + /// Returns less than zero if this instance is less than , zero if this + /// instance is equal to , and greater than zero if this instance is greater + /// than . + /// + public int CompareTo(ValueTuple other) + { + int c = Comparer.Default.Compare(Item1, other.Item1); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item2, other.Item2); + if (c != 0) return c; + + return Comparer.Default.Compare(Item3, other.Item3); + } + + int IStructuralComparable.CompareTo(object? other, IComparer comparer) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + var objTuple = (ValueTuple)other; + + int c = comparer.Compare(Item1, objTuple.Item1); + if (c != 0) return c; + + c = comparer.Compare(Item2, objTuple.Item2); + if (c != 0) return c; + + return comparer.Compare(Item3, objTuple.Item3); + } + + /// + /// Returns the hash code for the current instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item1), + EqualityComparer.Default.GetHashCode(Item2), + EqualityComparer.Default.GetHashCode(Item3)); + } + + int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + private int GetHashCodeCore(IEqualityComparer comparer) + { + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item1), + comparer.GetHashCode(Item2), + comparer.GetHashCode(Item3)); + } + + int ITupleInternal.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + /// + /// Returns a string that represents the value of this instance. + /// + /// The string representation of this instance. + /// + /// The string returned by this method takes the form (Item1, Item2, Item3). + /// If any field value is , it is represented as . + /// + public override string ToString() + { + return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ")"; + } + + string ITupleInternal.ToStringEnd() + { + return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ")"; + } + + int ITupleInternal.Size => 3; + } + + /// + /// Represents a 4-tuple, or quadruple, as a value type. + /// + /// The type of the tuple's first component. + /// The type of the tuple's second component. + /// The type of the tuple's third component. + /// The type of the tuple's fourth component. + public struct ValueTuple + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal + { + /// + /// The current instance's first component. + /// + public T1 Item1; + /// + /// The current instance's second component. + /// + public T2 Item2; + /// + /// The current instance's third component. + /// + public T3 Item3; + /// + /// The current instance's fourth component. + /// + public T4 Item4; + + /// + /// Initializes a new instance of the value type. + /// + /// The value of the tuple's first component. + /// The value of the tuple's second component. + /// The value of the tuple's third component. + /// The value of the tuple's fourth component. + public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4) + { + Item1 = item1; + Item2 = item2; + Item3 = item3; + Item4 = item4; + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified object. + /// + /// The object to compare with this instance. + /// if the current instance is equal to the specified object; otherwise, . + /// + /// The parameter is considered to be equal to the current instance under the following conditions: + /// + /// It is a value type. + /// Its components are of the same types as those of the current instance. + /// Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + /// + /// + public override bool Equals(object obj) + { + return obj is ValueTuple tuple && Equals(tuple); + } + + /// + /// Returns a value that indicates whether the current + /// instance is equal to a specified . + /// + /// The tuple to compare with this instance. + /// if the current instance is equal to the specified tuple; otherwise, . + /// + /// The parameter is considered to be equal to the current instance if each of its fields + /// are equal to that of the current instance, using the default comparer for that field's type. + /// + public bool Equals(ValueTuple other) + { + return EqualityComparer.Default.Equals(Item1, other.Item1) + && EqualityComparer.Default.Equals(Item2, other.Item2) + && EqualityComparer.Default.Equals(Item3, other.Item3) + && EqualityComparer.Default.Equals(Item4, other.Item4); + } + + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) + { + if (other == null || other is not ValueTuple) return false; + + var objTuple = (ValueTuple)other; + + return comparer.Equals(Item1, objTuple.Item1) + && comparer.Equals(Item2, objTuple.Item2) + && comparer.Equals(Item3, objTuple.Item3) + && comparer.Equals(Item4, objTuple.Item4); + } + + int IComparable.CompareTo(object other) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + return CompareTo((ValueTuple)other); + } + + /// Compares this instance to a specified instance and returns an indication of their relative values. + /// An instance to compare. + /// + /// A signed number indicating the relative values of this instance and . + /// Returns less than zero if this instance is less than , zero if this + /// instance is equal to , and greater than zero if this instance is greater + /// than . + /// + public int CompareTo(ValueTuple other) + { + int c = Comparer.Default.Compare(Item1, other.Item1); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item2, other.Item2); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item3, other.Item3); + if (c != 0) return c; + + return Comparer.Default.Compare(Item4, other.Item4); + } + + int IStructuralComparable.CompareTo(object? other, IComparer comparer) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + var objTuple = (ValueTuple)other; + + int c = comparer.Compare(Item1, objTuple.Item1); + if (c != 0) return c; + + c = comparer.Compare(Item2, objTuple.Item2); + if (c != 0) return c; + + c = comparer.Compare(Item3, objTuple.Item3); + if (c != 0) return c; + + return comparer.Compare(Item4, objTuple.Item4); + } + + /// + /// Returns the hash code for the current instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item1), + EqualityComparer.Default.GetHashCode(Item2), + EqualityComparer.Default.GetHashCode(Item3), + EqualityComparer.Default.GetHashCode(Item4)); + } + + int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + private int GetHashCodeCore(IEqualityComparer comparer) + { + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item1), + comparer.GetHashCode(Item2), + comparer.GetHashCode(Item3), + comparer.GetHashCode(Item4)); + } + + int ITupleInternal.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + /// + /// Returns a string that represents the value of this instance. + /// + /// The string representation of this instance. + /// + /// The string returned by this method takes the form (Item1, Item2, Item3, Item4). + /// If any field value is , it is represented as . + /// + public override string ToString() + { + return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ")"; + } + + string ITupleInternal.ToStringEnd() + { + return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ")"; + } + + int ITupleInternal.Size => 4; + } + + /// + /// Represents a 5-tuple, or quintuple, as a value type. + /// + /// The type of the tuple's first component. + /// The type of the tuple's second component. + /// The type of the tuple's third component. + /// The type of the tuple's fourth component. + /// The type of the tuple's fifth component. + public struct ValueTuple + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal + { + /// + /// The current instance's first component. + /// + public T1 Item1; + /// + /// The current instance's second component. + /// + public T2 Item2; + /// + /// The current instance's third component. + /// + public T3 Item3; + /// + /// The current instance's fourth component. + /// + public T4 Item4; + /// + /// The current instance's fifth component. + /// + public T5 Item5; + + /// + /// Initializes a new instance of the value type. + /// + /// The value of the tuple's first component. + /// The value of the tuple's second component. + /// The value of the tuple's third component. + /// The value of the tuple's fourth component. + /// The value of the tuple's fifth component. + public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5) + { + Item1 = item1; + Item2 = item2; + Item3 = item3; + Item4 = item4; + Item5 = item5; + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified object. + /// + /// The object to compare with this instance. + /// if the current instance is equal to the specified object; otherwise, . + /// + /// The parameter is considered to be equal to the current instance under the following conditions: + /// + /// It is a value type. + /// Its components are of the same types as those of the current instance. + /// Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + /// + /// + public override bool Equals(object obj) + { + return obj is ValueTuple tuple && Equals(tuple); + } + + /// + /// Returns a value that indicates whether the current + /// instance is equal to a specified . + /// + /// The tuple to compare with this instance. + /// if the current instance is equal to the specified tuple; otherwise, . + /// + /// The parameter is considered to be equal to the current instance if each of its fields + /// are equal to that of the current instance, using the default comparer for that field's type. + /// + public bool Equals(ValueTuple other) + { + return EqualityComparer.Default.Equals(Item1, other.Item1) + && EqualityComparer.Default.Equals(Item2, other.Item2) + && EqualityComparer.Default.Equals(Item3, other.Item3) + && EqualityComparer.Default.Equals(Item4, other.Item4) + && EqualityComparer.Default.Equals(Item5, other.Item5); + } + + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) + { + if (other == null || other is not ValueTuple) return false; + + var objTuple = (ValueTuple)other; + + return comparer.Equals(Item1, objTuple.Item1) + && comparer.Equals(Item2, objTuple.Item2) + && comparer.Equals(Item3, objTuple.Item3) + && comparer.Equals(Item4, objTuple.Item4) + && comparer.Equals(Item5, objTuple.Item5); + } + + int IComparable.CompareTo(object other) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + return CompareTo((ValueTuple)other); + } + + /// Compares this instance to a specified instance and returns an indication of their relative values. + /// An instance to compare. + /// + /// A signed number indicating the relative values of this instance and . + /// Returns less than zero if this instance is less than , zero if this + /// instance is equal to , and greater than zero if this instance is greater + /// than . + /// + public int CompareTo(ValueTuple other) + { + int c = Comparer.Default.Compare(Item1, other.Item1); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item2, other.Item2); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item3, other.Item3); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item4, other.Item4); + if (c != 0) return c; + + return Comparer.Default.Compare(Item5, other.Item5); + } + + int IStructuralComparable.CompareTo(object? other, IComparer comparer) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + var objTuple = (ValueTuple)other; + + int c = comparer.Compare(Item1, objTuple.Item1); + if (c != 0) return c; + + c = comparer.Compare(Item2, objTuple.Item2); + if (c != 0) return c; + + c = comparer.Compare(Item3, objTuple.Item3); + if (c != 0) return c; + + c = comparer.Compare(Item4, objTuple.Item4); + if (c != 0) return c; + + return comparer.Compare(Item5, objTuple.Item5); + } + + /// + /// Returns the hash code for the current instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item1), + EqualityComparer.Default.GetHashCode(Item2), + EqualityComparer.Default.GetHashCode(Item3), + EqualityComparer.Default.GetHashCode(Item4), + EqualityComparer.Default.GetHashCode(Item5)); + } + + int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + private int GetHashCodeCore(IEqualityComparer comparer) + { + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item1), + comparer.GetHashCode(Item2), + comparer.GetHashCode(Item3), + comparer.GetHashCode(Item4), + comparer.GetHashCode(Item5)); + } + + int ITupleInternal.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + /// + /// Returns a string that represents the value of this instance. + /// + /// The string representation of this instance. + /// + /// The string returned by this method takes the form (Item1, Item2, Item3, Item4, Item5). + /// If any field value is , it is represented as . + /// + public override string ToString() + { + return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ")"; + } + + string ITupleInternal.ToStringEnd() + { + return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ")"; + } + + int ITupleInternal.Size => 5; + } + + /// + /// Represents a 6-tuple, or sixtuple, as a value type. + /// + /// The type of the tuple's first component. + /// The type of the tuple's second component. + /// The type of the tuple's third component. + /// The type of the tuple's fourth component. + /// The type of the tuple's fifth component. + /// The type of the tuple's sixth component. + public struct ValueTuple + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal + { + /// + /// The current instance's first component. + /// + public T1 Item1; + /// + /// The current instance's second component. + /// + public T2 Item2; + /// + /// The current instance's third component. + /// + public T3 Item3; + /// + /// The current instance's fourth component. + /// + public T4 Item4; + /// + /// The current instance's fifth component. + /// + public T5 Item5; + /// + /// The current instance's sixth component. + /// + public T6 Item6; + + /// + /// Initializes a new instance of the value type. + /// + /// The value of the tuple's first component. + /// The value of the tuple's second component. + /// The value of the tuple's third component. + /// The value of the tuple's fourth component. + /// The value of the tuple's fifth component. + /// The value of the tuple's sixth component. + public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6) + { + Item1 = item1; + Item2 = item2; + Item3 = item3; + Item4 = item4; + Item5 = item5; + Item6 = item6; + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified object. + /// + /// The object to compare with this instance. + /// if the current instance is equal to the specified object; otherwise, . + /// + /// The parameter is considered to be equal to the current instance under the following conditions: + /// + /// It is a value type. + /// Its components are of the same types as those of the current instance. + /// Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + /// + /// + public override bool Equals(object obj) + { + return obj is ValueTuple tuple && Equals(tuple); + } + + /// + /// Returns a value that indicates whether the current + /// instance is equal to a specified . + /// + /// The tuple to compare with this instance. + /// if the current instance is equal to the specified tuple; otherwise, . + /// + /// The parameter is considered to be equal to the current instance if each of its fields + /// are equal to that of the current instance, using the default comparer for that field's type. + /// + public bool Equals(ValueTuple other) + { + return EqualityComparer.Default.Equals(Item1, other.Item1) + && EqualityComparer.Default.Equals(Item2, other.Item2) + && EqualityComparer.Default.Equals(Item3, other.Item3) + && EqualityComparer.Default.Equals(Item4, other.Item4) + && EqualityComparer.Default.Equals(Item5, other.Item5) + && EqualityComparer.Default.Equals(Item6, other.Item6); + } + + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) + { + if (other == null || other is not ValueTuple) return false; + + var objTuple = (ValueTuple)other; + + return comparer.Equals(Item1, objTuple.Item1) + && comparer.Equals(Item2, objTuple.Item2) + && comparer.Equals(Item3, objTuple.Item3) + && comparer.Equals(Item4, objTuple.Item4) + && comparer.Equals(Item5, objTuple.Item5) + && comparer.Equals(Item6, objTuple.Item6); + } + + int IComparable.CompareTo(object other) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + return CompareTo((ValueTuple)other); + } + + /// Compares this instance to a specified instance and returns an indication of their relative values. + /// An instance to compare. + /// + /// A signed number indicating the relative values of this instance and . + /// Returns less than zero if this instance is less than , zero if this + /// instance is equal to , and greater than zero if this instance is greater + /// than . + /// + public int CompareTo(ValueTuple other) + { + int c = Comparer.Default.Compare(Item1, other.Item1); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item2, other.Item2); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item3, other.Item3); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item4, other.Item4); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item5, other.Item5); + if (c != 0) return c; + + return Comparer.Default.Compare(Item6, other.Item6); + } + + int IStructuralComparable.CompareTo(object? other, IComparer comparer) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + var objTuple = (ValueTuple)other; + + int c = comparer.Compare(Item1, objTuple.Item1); + if (c != 0) return c; + + c = comparer.Compare(Item2, objTuple.Item2); + if (c != 0) return c; + + c = comparer.Compare(Item3, objTuple.Item3); + if (c != 0) return c; + + c = comparer.Compare(Item4, objTuple.Item4); + if (c != 0) return c; + + c = comparer.Compare(Item5, objTuple.Item5); + if (c != 0) return c; + + return comparer.Compare(Item6, objTuple.Item6); + } + + /// + /// Returns the hash code for the current instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item1), + EqualityComparer.Default.GetHashCode(Item2), + EqualityComparer.Default.GetHashCode(Item3), + EqualityComparer.Default.GetHashCode(Item4), + EqualityComparer.Default.GetHashCode(Item5), + EqualityComparer.Default.GetHashCode(Item6)); + } + + int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + private int GetHashCodeCore(IEqualityComparer comparer) + { + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item1), + comparer.GetHashCode(Item2), + comparer.GetHashCode(Item3), + comparer.GetHashCode(Item4), + comparer.GetHashCode(Item5), + comparer.GetHashCode(Item6)); + } + + int ITupleInternal.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + /// + /// Returns a string that represents the value of this instance. + /// + /// The string representation of this instance. + /// + /// The string returned by this method takes the form (Item1, Item2, Item3, Item4, Item5, Item6). + /// If any field value is , it is represented as . + /// + public override string ToString() + { + return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ")"; + } + + string ITupleInternal.ToStringEnd() + { + return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ")"; + } + + int ITupleInternal.Size => 6; + } + + /// + /// Represents a 7-tuple, or sentuple, as a value type. + /// + /// The type of the tuple's first component. + /// The type of the tuple's second component. + /// The type of the tuple's third component. + /// The type of the tuple's fourth component. + /// The type of the tuple's fifth component. + /// The type of the tuple's sixth component. + /// The type of the tuple's seventh component. + public struct ValueTuple + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal + { + /// + /// The current instance's first component. + /// + public T1 Item1; + /// + /// The current instance's second component. + /// + public T2 Item2; + /// + /// The current instance's third component. + /// + public T3 Item3; + /// + /// The current instance's fourth component. + /// + public T4 Item4; + /// + /// The current instance's fifth component. + /// + public T5 Item5; + /// + /// The current instance's sixth component. + /// + public T6 Item6; + /// + /// The current instance's seventh component. + /// + public T7 Item7; + + /// + /// Initializes a new instance of the value type. + /// + /// The value of the tuple's first component. + /// The value of the tuple's second component. + /// The value of the tuple's third component. + /// The value of the tuple's fourth component. + /// The value of the tuple's fifth component. + /// The value of the tuple's sixth component. + /// The value of the tuple's seventh component. + public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7) + { + Item1 = item1; + Item2 = item2; + Item3 = item3; + Item4 = item4; + Item5 = item5; + Item6 = item6; + Item7 = item7; + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified object. + /// + /// The object to compare with this instance. + /// if the current instance is equal to the specified object; otherwise, . + /// + /// The parameter is considered to be equal to the current instance under the following conditions: + /// + /// It is a value type. + /// Its components are of the same types as those of the current instance. + /// Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + /// + /// + public override bool Equals(object obj) + { + return obj is ValueTuple tuple && Equals(tuple); + } + + /// + /// Returns a value that indicates whether the current + /// instance is equal to a specified . + /// + /// The tuple to compare with this instance. + /// if the current instance is equal to the specified tuple; otherwise, . + /// + /// The parameter is considered to be equal to the current instance if each of its fields + /// are equal to that of the current instance, using the default comparer for that field's type. + /// + public bool Equals(ValueTuple other) + { + return EqualityComparer.Default.Equals(Item1, other.Item1) + && EqualityComparer.Default.Equals(Item2, other.Item2) + && EqualityComparer.Default.Equals(Item3, other.Item3) + && EqualityComparer.Default.Equals(Item4, other.Item4) + && EqualityComparer.Default.Equals(Item5, other.Item5) + && EqualityComparer.Default.Equals(Item6, other.Item6) + && EqualityComparer.Default.Equals(Item7, other.Item7); + } + + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) + { + if (other == null || other is not ValueTuple) return false; + + var objTuple = (ValueTuple)other; + + return comparer.Equals(Item1, objTuple.Item1) + && comparer.Equals(Item2, objTuple.Item2) + && comparer.Equals(Item3, objTuple.Item3) + && comparer.Equals(Item4, objTuple.Item4) + && comparer.Equals(Item5, objTuple.Item5) + && comparer.Equals(Item6, objTuple.Item6) + && comparer.Equals(Item7, objTuple.Item7); + } + + int IComparable.CompareTo(object other) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + return CompareTo((ValueTuple)other); + } + + /// Compares this instance to a specified instance and returns an indication of their relative values. + /// An instance to compare. + /// + /// A signed number indicating the relative values of this instance and . + /// Returns less than zero if this instance is less than , zero if this + /// instance is equal to , and greater than zero if this instance is greater + /// than . + /// + public int CompareTo(ValueTuple other) + { + int c = Comparer.Default.Compare(Item1, other.Item1); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item2, other.Item2); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item3, other.Item3); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item4, other.Item4); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item5, other.Item5); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item6, other.Item6); + if (c != 0) return c; + + return Comparer.Default.Compare(Item7, other.Item7); + } + + int IStructuralComparable.CompareTo(object? other, IComparer comparer) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + var objTuple = (ValueTuple)other; + + int c = comparer.Compare(Item1, objTuple.Item1); + if (c != 0) return c; + + c = comparer.Compare(Item2, objTuple.Item2); + if (c != 0) return c; + + c = comparer.Compare(Item3, objTuple.Item3); + if (c != 0) return c; + + c = comparer.Compare(Item4, objTuple.Item4); + if (c != 0) return c; + + c = comparer.Compare(Item5, objTuple.Item5); + if (c != 0) return c; + + c = comparer.Compare(Item6, objTuple.Item6); + if (c != 0) return c; + + return comparer.Compare(Item7, objTuple.Item7); + } + + /// + /// Returns the hash code for the current instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item1), + EqualityComparer.Default.GetHashCode(Item2), + EqualityComparer.Default.GetHashCode(Item3), + EqualityComparer.Default.GetHashCode(Item4), + EqualityComparer.Default.GetHashCode(Item5), + EqualityComparer.Default.GetHashCode(Item6), + EqualityComparer.Default.GetHashCode(Item7)); + } + + int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + private int GetHashCodeCore(IEqualityComparer comparer) + { + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item1), + comparer.GetHashCode(Item2), + comparer.GetHashCode(Item3), + comparer.GetHashCode(Item4), + comparer.GetHashCode(Item5), + comparer.GetHashCode(Item6), + comparer.GetHashCode(Item7)); + } + + int ITupleInternal.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + /// + /// Returns a string that represents the value of this instance. + /// + /// The string representation of this instance. + /// + /// The string returned by this method takes the form (Item1, Item2, Item3, Item4, Item5, Item6, Item7). + /// If any field value is , it is represented as . + /// + public override string ToString() + { + return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ", " + Item7?.ToString() + ")"; + } + + string ITupleInternal.ToStringEnd() + { + return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ", " + Item7?.ToString() + ")"; + } + + int ITupleInternal.Size => 7; + } + + /// + /// Represents an 8-tuple, or octuple, as a value type. + /// + /// The type of the tuple's first component. + /// The type of the tuple's second component. + /// The type of the tuple's third component. + /// The type of the tuple's fourth component. + /// The type of the tuple's fifth component. + /// The type of the tuple's sixth component. + /// The type of the tuple's seventh component. + /// The type of the tuple's eighth component. + public struct ValueTuple + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal + where TRest : struct + { + /// + /// The current instance's first component. + /// + public T1 Item1; + /// + /// The current instance's second component. + /// + public T2 Item2; + /// + /// The current instance's third component. + /// + public T3 Item3; + /// + /// The current instance's fourth component. + /// + public T4 Item4; + /// + /// The current instance's fifth component. + /// + public T5 Item5; + /// + /// The current instance's sixth component. + /// + public T6 Item6; + /// + /// The current instance's seventh component. + /// + public T7 Item7; + /// + /// The current instance's eighth component. + /// + public TRest Rest; + + /// + /// Initializes a new instance of the value type. + /// + /// The value of the tuple's first component. + /// The value of the tuple's second component. + /// The value of the tuple's third component. + /// The value of the tuple's fourth component. + /// The value of the tuple's fifth component. + /// The value of the tuple's sixth component. + /// The value of the tuple's seventh component. + /// The value of the tuple's eight component. + public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, TRest rest) + { + if (rest is not ITupleInternal) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleLastArgumentNotAValueTuple); + } + + Item1 = item1; + Item2 = item2; + Item3 = item3; + Item4 = item4; + Item5 = item5; + Item6 = item6; + Item7 = item7; + Rest = rest; + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified object. + /// + /// The object to compare with this instance. + /// if the current instance is equal to the specified object; otherwise, . + /// + /// The parameter is considered to be equal to the current instance under the following conditions: + /// + /// It is a value type. + /// Its components are of the same types as those of the current instance. + /// Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + /// + /// + public override bool Equals(object obj) + { + return obj is ValueTuple tuple && Equals(tuple); + } + + /// + /// Returns a value that indicates whether the current + /// instance is equal to a specified . + /// + /// The tuple to compare with this instance. + /// if the current instance is equal to the specified tuple; otherwise, . + /// + /// The parameter is considered to be equal to the current instance if each of its fields + /// are equal to that of the current instance, using the default comparer for that field's type. + /// + public bool Equals(ValueTuple other) + { + return EqualityComparer.Default.Equals(Item1, other.Item1) + && EqualityComparer.Default.Equals(Item2, other.Item2) + && EqualityComparer.Default.Equals(Item3, other.Item3) + && EqualityComparer.Default.Equals(Item4, other.Item4) + && EqualityComparer.Default.Equals(Item5, other.Item5) + && EqualityComparer.Default.Equals(Item6, other.Item6) + && EqualityComparer.Default.Equals(Item7, other.Item7) + && EqualityComparer.Default.Equals(Rest, other.Rest); + } + + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) + { + if (other == null || other is not ValueTuple) return false; + + var objTuple = (ValueTuple)other; + + return comparer.Equals(Item1, objTuple.Item1) + && comparer.Equals(Item2, objTuple.Item2) + && comparer.Equals(Item3, objTuple.Item3) + && comparer.Equals(Item4, objTuple.Item4) + && comparer.Equals(Item5, objTuple.Item5) + && comparer.Equals(Item6, objTuple.Item6) + && comparer.Equals(Item7, objTuple.Item7) + && comparer.Equals(Rest, objTuple.Rest); + } + + int IComparable.CompareTo(object other) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + return CompareTo((ValueTuple)other); + } + + /// Compares this instance to a specified instance and returns an indication of their relative values. + /// An instance to compare. + /// + /// A signed number indicating the relative values of this instance and . + /// Returns less than zero if this instance is less than , zero if this + /// instance is equal to , and greater than zero if this instance is greater + /// than . + /// + public int CompareTo(ValueTuple other) + { + int c = Comparer.Default.Compare(Item1, other.Item1); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item2, other.Item2); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item3, other.Item3); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item4, other.Item4); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item5, other.Item5); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item6, other.Item6); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item7, other.Item7); + if (c != 0) return c; + + return Comparer.Default.Compare(Rest, other.Rest); + } + + int IStructuralComparable.CompareTo(object? other, IComparer comparer) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + var objTuple = (ValueTuple)other; + + int c = comparer.Compare(Item1, objTuple.Item1); + if (c != 0) return c; + + c = comparer.Compare(Item2, objTuple.Item2); + if (c != 0) return c; + + c = comparer.Compare(Item3, objTuple.Item3); + if (c != 0) return c; + + c = comparer.Compare(Item4, objTuple.Item4); + if (c != 0) return c; + + c = comparer.Compare(Item5, objTuple.Item5); + if (c != 0) return c; + + c = comparer.Compare(Item6, objTuple.Item6); + if (c != 0) return c; + + c = comparer.Compare(Item7, objTuple.Item7); + if (c != 0) return c; + + return comparer.Compare(Rest, objTuple.Rest); + } + + /// + /// Returns the hash code for the current instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + // We want to have a limited hash in this case. We'll use the last 8 elements of the tuple + if (Rest is not ITupleInternal rest) + { + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item1), + EqualityComparer.Default.GetHashCode(Item2), + EqualityComparer.Default.GetHashCode(Item3), + EqualityComparer.Default.GetHashCode(Item4), + EqualityComparer.Default.GetHashCode(Item5), + EqualityComparer.Default.GetHashCode(Item6), + EqualityComparer.Default.GetHashCode(Item7)); + } + + int size = rest.Size; + if (size >= 8) { return rest.GetHashCode(); } + + // In this case, the rest member has less than 8 elements so we need to combine some our elements with the elements in rest + int k = 8 - size; + switch (k) + { + case 1: + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item7), + rest.GetHashCode()); + case 2: + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item6), + EqualityComparer.Default.GetHashCode(Item7), + rest.GetHashCode()); + case 3: + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item5), + EqualityComparer.Default.GetHashCode(Item6), + EqualityComparer.Default.GetHashCode(Item7), + rest.GetHashCode()); + case 4: + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item4), + EqualityComparer.Default.GetHashCode(Item5), + EqualityComparer.Default.GetHashCode(Item6), + EqualityComparer.Default.GetHashCode(Item7), + rest.GetHashCode()); + case 5: + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item3), + EqualityComparer.Default.GetHashCode(Item4), + EqualityComparer.Default.GetHashCode(Item5), + EqualityComparer.Default.GetHashCode(Item6), + EqualityComparer.Default.GetHashCode(Item7), + rest.GetHashCode()); + case 6: + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item2), + EqualityComparer.Default.GetHashCode(Item3), + EqualityComparer.Default.GetHashCode(Item4), + EqualityComparer.Default.GetHashCode(Item5), + EqualityComparer.Default.GetHashCode(Item6), + EqualityComparer.Default.GetHashCode(Item7), + rest.GetHashCode()); + case 7: + case 8: + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item1), + EqualityComparer.Default.GetHashCode(Item2), + EqualityComparer.Default.GetHashCode(Item3), + EqualityComparer.Default.GetHashCode(Item4), + EqualityComparer.Default.GetHashCode(Item5), + EqualityComparer.Default.GetHashCode(Item6), + EqualityComparer.Default.GetHashCode(Item7), + rest.GetHashCode()); + } + + Debug.Assert(false, "Missed all cases for computing ValueTuple hash code"); + return -1; + } + + int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + private int GetHashCodeCore(IEqualityComparer comparer) + { + // We want to have a limited hash in this case. We'll use the last 8 elements of the tuple + if (Rest is not ITupleInternal rest) + { + return ValueTuple.CombineHashCodes( + comparer.GetHashCode(Item1), + comparer.GetHashCode(Item2), + comparer.GetHashCode(Item3), + comparer.GetHashCode(Item4), + comparer.GetHashCode(Item5), + comparer.GetHashCode(Item6), + comparer.GetHashCode(Item7)); + } + + int size = rest.Size; + if (size >= 8) { return rest.GetHashCode(comparer); } + + // In this case, the rest member has less than 8 elements so we need to combine some our elements with the elements in rest + int k = 8 - size; + switch (k) + { + case 1: + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item7), rest.GetHashCode(comparer)); + case 2: + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item6), comparer.GetHashCode(Item7), rest.GetHashCode(comparer)); + case 3: + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item5), comparer.GetHashCode(Item6), comparer.GetHashCode(Item7), + rest.GetHashCode(comparer)); + case 4: + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item4), comparer.GetHashCode(Item5), comparer.GetHashCode(Item6), + comparer.GetHashCode(Item7), rest.GetHashCode(comparer)); + case 5: + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item3), comparer.GetHashCode(Item4), comparer.GetHashCode(Item5), + comparer.GetHashCode(Item6), comparer.GetHashCode(Item7), rest.GetHashCode(comparer)); + case 6: + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item2), comparer.GetHashCode(Item3), comparer.GetHashCode(Item4), + comparer.GetHashCode(Item5), comparer.GetHashCode(Item6), comparer.GetHashCode(Item7), + rest.GetHashCode(comparer)); + case 7: + case 8: + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item1), comparer.GetHashCode(Item2), comparer.GetHashCode(Item3), + comparer.GetHashCode(Item4), comparer.GetHashCode(Item5), comparer.GetHashCode(Item6), + comparer.GetHashCode(Item7), rest.GetHashCode(comparer)); + } + + Debug.Assert(false, "Missed all cases for computing ValueTuple hash code"); + return -1; + } + + int ITupleInternal.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + /// + /// Returns a string that represents the value of this instance. + /// + /// The string representation of this instance. + /// + /// The string returned by this method takes the form (Item1, Item2, Item3, Item4, Item5, Item6, Item7, Rest). + /// If any field value is , it is represented as . + /// + public override string ToString() + { + if (Rest is not ITupleInternal rest) + { + return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ", " + Item7?.ToString() + ", " + Rest.ToString() + ")"; + } + else + { + return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ", " + Item7?.ToString() + ", " + rest.ToStringEnd(); + } + } + + string ITupleInternal.ToStringEnd() + { + if (Rest is not ITupleInternal rest) + { + return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ", " + Item7?.ToString() + ", " + Rest.ToString() + ")"; + } + else + { + return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ", " + Item7?.ToString() + ", " + rest.ToStringEnd(); + } + } + + int ITupleInternal.Size + { + get + { + // ITupleInternal? rest = Rest as ITupleInternal; + // return rest == null ? 8 : 7 + rest.Size; + return Rest is not ITupleInternal rest ? 8 : 7 + rest.Size; + } + } + } + +} + +#endif \ No newline at end of file diff --git a/src/Basal/IFox.Basal.Shared/ExpressionTrees/ParameterRebinder.cs b/src/Basal/IFox.Basal.Shared/ExpressionTrees/ParameterRebinder.cs new file mode 100644 index 0000000000000000000000000000000000000000..e4dfcd4b761f4392fa297105cf7a855d1ac8af69 --- /dev/null +++ b/src/Basal/IFox.Basal.Shared/ExpressionTrees/ParameterRebinder.cs @@ -0,0 +1,41 @@ +#if NET45 +namespace IFoxCAD.Basal; + +/// +/// ذ +/// +public class ParameterRebinder : SqlExpressionVisitor +{ + private readonly Dictionary map; + /// + /// ذ + /// + /// ֵ + public ParameterRebinder(Dictionary map) + { + this.map = map ?? new Dictionary(); + } + /// + /// 滻 + /// + /// ֵ + /// ʽ + /// ʽ + public static Expression? ReplaceParameters(Dictionary map, Expression expression) + { + return new ParameterRebinder(map).Visit(expression); + } + /// + /// ʲ + /// + /// ʽ + /// ʽ + protected override Expression VisitParameter(ParameterExpression expression) + { + if (map.TryGetValue(expression, out var parameterExpression)) + expression = parameterExpression; + + return base.VisitParameter(expression); + } +} +#endif \ No newline at end of file diff --git a/src/Basal/IFox.Basal.Shared/ExpressionTrees/PredicateBuilder.cs b/src/Basal/IFox.Basal.Shared/ExpressionTrees/PredicateBuilder.cs new file mode 100644 index 0000000000000000000000000000000000000000..7153e7901983cbca5dff2f4e0ad03b7b02358951 --- /dev/null +++ b/src/Basal/IFox.Basal.Shared/ExpressionTrees/PredicateBuilder.cs @@ -0,0 +1,83 @@ + +#if NET45 +namespace IFoxCAD.Basal; +/// +/// Predicateίй +/// +public static class PredicateBuilder +{ + /// + /// ίбʽ + /// + /// ķ + /// + public static Expression> True() + { + return param => true; + } + /// + /// ؼٵίбʽ + /// + /// ķ + /// + public static Expression> False() + { + return param => false; + } + /// + /// predicateί + /// + /// + /// ίбʽ + /// ίбʽ + public static Expression> Create(Expression> predicate) + { + return predicate; + } + /// + /// ʾıʽ + /// + /// + /// һ + /// ڶ + /// ʽ + public static Expression> And(this Expression> first, Expression> second) + { + return first.Compose(second, Expression.AndAlso); + } + /// + /// ʾıʽ + /// + /// + /// һ + /// ڶ + /// ʽ + public static Expression> Or(this Expression> first, Expression> second) + { + return first.Compose(second, Expression.OrElse); + } + /// + /// Ƿıʽ + /// + /// + /// ʽ + /// ʽ + public static Expression> Not(this Expression> expression) + { + return Expression.Lambda>(Expression.Not(expression.Body), expression.Parameters); + } + + private static Expression Compose(this Expression first, Expression second, Func merge) + { + var map = first.Parameters.Select((f, i) => new{f,s = second.Parameters[i]}).ToDictionary(p => p.s, p => p.f); + var expression = ParameterRebinder.ReplaceParameters(map, second.Body); + if (expression != null) + { + return Expression.Lambda(merge(first.Body, expression), first.Parameters); + } + return first; + + } +} + +#endif \ No newline at end of file diff --git a/src/Basal/IFox.Basal.Shared/ExpressionTrees/SqlExpressionVisitor.cs b/src/Basal/IFox.Basal.Shared/ExpressionTrees/SqlExpressionVisitor.cs new file mode 100644 index 0000000000000000000000000000000000000000..bfd62836c6627f7f1cf251d8d8dee76cdb6cca35 --- /dev/null +++ b/src/Basal/IFox.Basal.Shared/ExpressionTrees/SqlExpressionVisitor.cs @@ -0,0 +1,450 @@ +#if NET45 +namespace IFoxCAD.Basal; + +/// +/// sqlʽ +/// +public abstract class SqlExpressionVisitor +{ + /// + /// + /// + /// ʽ + /// ʽ + /// + protected virtual Expression? Visit(Expression expression) + { + if (expression is null) + return null; + + return expression.NodeType switch + { + ExpressionType.Add => VisitBinary((BinaryExpression)expression), + ExpressionType.AddChecked => VisitBinary((BinaryExpression)expression), + ExpressionType.And => VisitBinary((BinaryExpression)expression), + ExpressionType.AndAlso => VisitBinary((BinaryExpression)expression), + ExpressionType.ArrayIndex => VisitBinary((BinaryExpression)expression), + ExpressionType.Coalesce => VisitBinary((BinaryExpression)expression), + ExpressionType.Divide => VisitBinary((BinaryExpression)expression), + ExpressionType.Equal => VisitBinary((BinaryExpression)expression), + ExpressionType.ExclusiveOr => VisitBinary((BinaryExpression)expression), + ExpressionType.GreaterThan => VisitBinary((BinaryExpression)expression), + ExpressionType.GreaterThanOrEqual => VisitBinary((BinaryExpression)expression), + ExpressionType.LeftShift => VisitBinary((BinaryExpression)expression), + ExpressionType.LessThan => VisitBinary((BinaryExpression)expression), + ExpressionType.LessThanOrEqual => VisitBinary((BinaryExpression)expression), + ExpressionType.Modulo => VisitBinary((BinaryExpression)expression), + ExpressionType.Multiply => VisitBinary((BinaryExpression)expression), + ExpressionType.MultiplyChecked => VisitBinary((BinaryExpression)expression), + ExpressionType.NotEqual => VisitBinary((BinaryExpression)expression), + ExpressionType.Or => VisitBinary((BinaryExpression)expression), + ExpressionType.OrElse => VisitBinary((BinaryExpression)expression), + ExpressionType.RightShift => VisitBinary((BinaryExpression)expression), + ExpressionType.Subtract => VisitBinary((BinaryExpression)expression), + ExpressionType.SubtractChecked => VisitBinary((BinaryExpression)expression), + ExpressionType.ArrayLength => VisitUnary((UnaryExpression)expression), + ExpressionType.Convert => VisitUnary((UnaryExpression)expression), + ExpressionType.ConvertChecked => VisitUnary((UnaryExpression)expression), + ExpressionType.Negate => VisitUnary((UnaryExpression)expression), + ExpressionType.NegateChecked => VisitUnary((UnaryExpression)expression), + ExpressionType.Not => VisitUnary((UnaryExpression)expression), + ExpressionType.Quote => VisitUnary((UnaryExpression)expression), + ExpressionType.TypeAs => VisitUnary((UnaryExpression)expression), + ExpressionType.Call => VisitMethodCall((MethodCallExpression)expression), + ExpressionType.Conditional => VisitConditional((ConditionalExpression)expression), + ExpressionType.Constant => VisitConstant((ConstantExpression)expression), + ExpressionType.Invoke => VisitInvocation((InvocationExpression)expression), + ExpressionType.Lambda => VisitLambda((LambdaExpression)expression), + ExpressionType.ListInit => VisitListInit((ListInitExpression)expression), + ExpressionType.MemberAccess => VisitMemberAccess((MemberExpression)expression), + ExpressionType.MemberInit => VisitMemberInit((MemberInitExpression)expression), + ExpressionType.New => VisitNew((NewExpression)expression), + ExpressionType.NewArrayInit => VisitNewArray((NewArrayExpression)expression), + ExpressionType.NewArrayBounds => VisitNewArray((NewArrayExpression)expression), + ExpressionType.Parameter => VisitParameter((ParameterExpression)expression), + ExpressionType.TypeIs => VisitTypeIs((TypeBinaryExpression)expression), + _ => throw new RuntimeBinderException(nameof(expression.NodeType)) + }; + } + /// + /// ߰ + /// + /// 󶨵 + /// 󶨵 + /// + protected virtual MemberBinding VisitBinding(MemberBinding binding) + { + return binding.BindingType switch + { + MemberBindingType.Assignment => VisitMemberAssignment((MemberAssignment)binding), + MemberBindingType.MemberBinding => VisitMemberMemberBinding((MemberMemberBinding)binding), + MemberBindingType.ListBinding => VisitMemberListBinding((MemberListBinding)binding), + _ => throw new RuntimeBinderException(nameof(binding.BindingType)) + }; + } + /// + /// ʼϳʼ趨 + /// + /// ϳʼ趨 + /// ϳʼ趨 + protected virtual ElementInit VisitElementInitializer(ElementInit initializer) + { + var arguments = VisitExpressionList(initializer.Arguments); + + if (arguments != initializer.Arguments) + return Expression.ElementInit(initializer.AddMethod, arguments); + + return initializer; + } + /// + /// һԪ + /// + /// һԪ + /// ʽ + protected virtual Expression VisitUnary(UnaryExpression unary) + { + var operand = Visit(unary.Operand); + + if (operand != unary.Operand) + return Expression.MakeUnary(unary.NodeType, operand, unary.Type, unary.Method); + + return unary; + } + /// + /// ʶ + /// + /// + /// ʽ + protected virtual Expression VisitBinary(BinaryExpression binary) + { + var left = Visit(binary.Left); + var right = Visit(binary.Right); + var conversion = Visit(binary.Conversion); + + if (left == binary.Left && right == binary.Right && conversion == binary.Conversion) + return binary; + + if (binary.NodeType == ExpressionType.Coalesce && binary.Conversion != null) + return Expression.Coalesce(left, right, conversion as LambdaExpression); + + return Expression.MakeBinary(binary.NodeType, left, right, binary.IsLiftedToNull, binary.Method); + } + /// + /// + /// + /// + /// ʽ + protected virtual Expression VisitTypeIs(TypeBinaryExpression typeBinary) + { + var expression = Visit(typeBinary.Expression); + + if (expression != typeBinary.Expression) + return Expression.TypeIs(expression, typeBinary.TypeOperand); + + return typeBinary; + } + /// + /// ʳֵ + /// + /// ֵ + /// ʽ + protected virtual Expression VisitConstant(ConstantExpression constant) + { + return constant; + } + /// + /// + /// + /// + /// ʽ + protected virtual Expression VisitConditional(ConditionalExpression conditional) + { + var test = Visit(conditional.Test); + var ifTrue = Visit(conditional.IfTrue); + var ifFalse = Visit(conditional.IfFalse); + + if (test != conditional.Test) + return Expression.Condition(test, ifTrue, ifFalse); + + if (ifTrue != conditional.IfTrue) + return Expression.Condition(test, ifTrue, ifFalse); + + if (ifFalse != conditional.IfFalse) + return Expression.Condition(test, ifTrue, ifFalse); + + return conditional; + } + /// + /// ʲ + /// + /// + /// ʽ + protected virtual Expression VisitParameter(ParameterExpression parameter) + { + return parameter; + } + /// + /// ʳԱ + /// + /// Ա + /// ʽ + protected virtual Expression VisitMemberAccess(MemberExpression member) + { + var expression = Visit(member.Expression); + + if (expression != member.Expression) + return Expression.MakeMemberAccess(expression, member.Member); + + return member; + } + /// + /// ʷ + /// + /// + /// ʽ + protected virtual Expression VisitMethodCall(MethodCallExpression methodCall) + { + var instance = Visit(methodCall.Object); + var arguments = (IEnumerable)VisitExpressionList(methodCall.Arguments); + + if (instance != methodCall.Object || !Equals(arguments, methodCall.Arguments)) + return Expression.Call(instance, methodCall.Method, arguments); + + return methodCall; + } + /// + /// ʱʽ + /// + /// ʽ + /// ʽֻ + protected virtual ReadOnlyCollection VisitExpressionList(ReadOnlyCollection original) + { + var index1 = 0; + var expressions = default(List); + + for (var count = original.Count; index1 < count; ++index1) + { + var expression = Visit(original[index1]); + if (expression != null) + { + if (expressions != null) + { + expressions.Add(expression); + } + + else if (expression != original[index1]) + { + expressions = new List(count); + + for (var index2 = 0; index2 < index1; ++index2) + expressions.Add(original[index2]); + + expressions.Add(expression); + } + } + + } + + return expressions != null ? expressions.AsReadOnly() : original; + } + /// + /// ʳԱֵ + /// + /// Աֵ + /// + protected virtual MemberAssignment VisitMemberAssignment(MemberAssignment assignment) + { + var expression = Visit(assignment.Expression); + + if (expression != assignment.Expression) + return Expression.Bind(assignment.Member, expression); + + return assignment; + } + /// + /// ¶ԱijԱ + /// + /// ¶ԱijԱ + /// ¶ԱijԱ + protected virtual MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding binding) + { + var bindings = VisitBindingList(binding.Bindings); + + if (!Equals(bindings, binding.Bindings)) + return Expression.MemberBind(binding.Member, bindings); + + return binding; + } + /// + /// ʳԱʼ + /// + /// Աʼ + /// Աʼ + protected virtual MemberListBinding VisitMemberListBinding(MemberListBinding binding) + { + var initializers = VisitElementInitializerList(binding.Initializers); + + if (!Equals(initializers, binding.Initializers)) + return Expression.ListBind(binding.Member, initializers); + + return binding; + } + /// + /// ʳԱʼб + /// + /// Աʼб + /// Աʼб + protected virtual IEnumerable VisitBindingList(ReadOnlyCollection original) + { + var index1 = 0; + var bindings = default(List); + + for (var count = original.Count; index1 < count; ++index1) + { + var memberBinding = VisitBinding(original[index1]); + + if (bindings != null) + { + bindings.Add(memberBinding); + } + + else if (memberBinding != original[index1]) + { + bindings = new List(count); + + for (var index2 = 0; index2 < index1; ++index2) + bindings.Add(original[index2]); + + bindings.Add(memberBinding); + } + } + + return (IEnumerable)bindings! ?? original; + } + /// + /// ʼ趨 + /// + /// 趨 + /// 趨 + protected virtual IEnumerable VisitElementInitializerList(ReadOnlyCollection original) + { + var index1 = 0; + var initializers = default(List); + + for (var count = original.Count; index1 < count; ++index1) + { + var initializer = VisitElementInitializer(original[index1]); + + if (initializers != null) + { + initializers.Add(initializer); + } + + else if (initializer != original[index1]) + { + initializers = new List(count); + + for (var index2 = 0; index2 < index1; ++index2) + initializers.Add(original[index2]); + + initializers.Add(initializer); + } + } + + return (IEnumerable)initializers! ?? original; + } + /// + /// lambdaʽ + /// + /// lambdaʽ + /// ʽ + protected virtual Expression VisitLambda(LambdaExpression lambda) + { + var body = Visit(lambda.Body); + + if (body != lambda.Body) + return Expression.Lambda(lambda.Type, body, lambda.Parameters); + + return lambda; + } + /// + /// ʹ캯 + /// + /// 캯 + /// + protected virtual NewExpression VisitNew(NewExpression expression) + { + var arguments = VisitExpressionList(expression.Arguments); + + if (Equals(arguments, expression.Arguments)) + return expression; + + if (expression.Members != null) + return Expression.New(expression.Constructor, arguments, expression.Members); + + return Expression.New(expression.Constructor, arguments); + } + /// + /// ʳԱʼ + /// + /// Աʼ + /// ʽ + protected virtual Expression VisitMemberInit(MemberInitExpression memberInit) + { + var expression = VisitNew(memberInit.NewExpression); + var bindings = VisitBindingList(memberInit.Bindings); + + if (expression != memberInit.NewExpression || !Equals(bindings, memberInit.Bindings)) + return Expression.MemberInit(expression, bindings); + + return memberInit; + } + /// + /// ʼϳʼ + /// + /// ϳʼ + /// ʽ + protected virtual Expression VisitListInit(ListInitExpression listInit) + { + var expression = VisitNew(listInit.NewExpression); + var initializers = VisitElementInitializerList(listInit.Initializers); + + if (expression != listInit.NewExpression || !Equals(initializers, listInit.Initializers)) + return Expression.ListInit(expression, initializers); + + return listInit; + } + /// + /// + /// + /// + /// ʽ + protected virtual Expression VisitNewArray(NewArrayExpression newArray) + { + var expressions = VisitExpressionList(newArray.Expressions); + + if (Equals(expressions, newArray.Expressions)) + return newArray; + + if (newArray.NodeType == ExpressionType.NewArrayInit) + return Expression.NewArrayInit(newArray.Type.GetElementType(), expressions); + + return Expression.NewArrayBounds(newArray.Type.GetElementType(), expressions); + } + /// + /// ίеñʽ + /// + /// ίеñʽ + /// ʽ + protected virtual Expression VisitInvocation(InvocationExpression invocation) + { + var arguments = VisitExpressionList(invocation.Arguments); + var expression = Visit(invocation.Expression); + + if (arguments != invocation.Arguments || expression != invocation.Expression) + return Expression.Invoke(expression, arguments); + + return invocation; + } +} +#endif \ No newline at end of file diff --git a/src/Basal/IFox.Basal.Shared/General/ArgumentNullEx.cs b/src/Basal/IFox.Basal.Shared/General/ArgumentNullEx.cs new file mode 100644 index 0000000000000000000000000000000000000000..b9691436a9c76b7fb28b2c8998b0fa97bec54d31 --- /dev/null +++ b/src/Basal/IFox.Basal.Shared/General/ArgumentNullEx.cs @@ -0,0 +1,23 @@ + + +namespace IFoxCAD.Basal +{ + /// + /// 参数null检查类 + /// + public static class ArgumentNullEx + { + /// + /// 检查参数是否为 null + /// + /// 参数类型 + /// 参数 + /// 参数为null时的提示信息 + /// + public static void NotNull(this T? argument, string? argumentExpression = null) + { + if (argument == null) throw new ArgumentNullException(paramName: argumentExpression); + } + + } +} diff --git a/src/Basal/IFox.Basal.Shared/General/ArrayEx.cs b/src/Basal/IFox.Basal.Shared/General/ArrayEx.cs new file mode 100644 index 0000000000000000000000000000000000000000..5b0e2cdddec5fec8cab1cc773b75ee0104ba460d --- /dev/null +++ b/src/Basal/IFox.Basal.Shared/General/ArrayEx.cs @@ -0,0 +1,58 @@ +namespace IFoxCAD.Basal; + +/* + * 由于linq的函数大部分带有状态机,而cad是一个单机程序, + * 使用状态机会变得缓慢,因此我们设计的时候着重于时间优化, + * 本工具类在着重于数组遍历时候替代linq + */ +/// +/// 数组扩展类 +/// +public static class ArrayEx +{ + /// + /// 合并数组 + /// + /// + /// + public static T[] Combine2(this T[] a, T[] b) + { + var c = new T[a.Length + b.Length]; + Array.Copy(a, 0, c, 0, a.Length); + Array.Copy(b, 0, c, a.Length, b.Length); + return c; + } + + /// + /// 一维数组按规则消除
+ /// 本例适用于数值类型比较,特定规则比较
+ /// 如果是哈希比较,建议更改为: + /// set = new(); + /// foreach (var item in listInOut) + /// set.Add(item); + /// ]]> + ///
+ /// + /// 传入有重复成员的数组,原数组修改 + /// + /// 传出参数1:数组开头
+ /// 传出参数2:数组结尾
+ /// 返回值比较结尾为就移除
+ /// + [System.Diagnostics.DebuggerStepThrough] + public static void Deduplication(List lst, Func func) + { + // 头和尾比较,满足条件移除尾巴 + for (int i = 0; i < lst.Count; i++) + { + var first = lst[i]; + for (int j = lst.Count - 1; j > i/*符号是 >= 而且是i*/; j--) + { + var last = lst[j]; + if (func(first, last)) + lst.RemoveAt(j); + } + } + } +} \ No newline at end of file diff --git a/src/Basal/IFox.Basal.Shared/General/DebugHelper.cs b/src/Basal/IFox.Basal.Shared/General/DebugHelper.cs new file mode 100644 index 0000000000000000000000000000000000000000..58ae2fc30a545fb22c9107bc66e166687d7f0d1e --- /dev/null +++ b/src/Basal/IFox.Basal.Shared/General/DebugHelper.cs @@ -0,0 +1,33 @@ +using System.Diagnostics; +using System.Threading; + +namespace IFoxCAD.Basal; + +public static class Debugx +{ + /// + /// cad命令切换: Debugx + /// + /// 打印信息 + /// 打印时间 + [DebuggerHidden] + public static void Printl(object message, bool time = true) + { + var flag = Environment.GetEnvironmentVariable("debugx", EnvironmentVariableTarget.User); + if (flag == null || flag == "0") + return; + + if (time) + //message = $"{DateTime.Now.ToLongDateString() + DateTime.Now.TimeOfDay}\n" + + message = $"{DateTime.Now.TimeOfDay} ThreadId:{Thread.CurrentThread.ManagedThreadId}\n" + + $"\t\t{message}"; + + //System.Diagnostics.Debug.Indent(); +#if DEBUG + System.Diagnostics.Debug.WriteLine(message); +#else + System.Diagnostics.Trace.WriteLine(message); +#endif + //System.Diagnostics.Debug.Unindent(); + } +} \ No newline at end of file diff --git a/src/Basal/IFox.Basal.Shared/General/DictEx.cs b/src/Basal/IFox.Basal.Shared/General/DictEx.cs new file mode 100644 index 0000000000000000000000000000000000000000..296fb163a24bdbc93f562e4d79b79f3fa1f88e39 --- /dev/null +++ b/src/Basal/IFox.Basal.Shared/General/DictEx.cs @@ -0,0 +1,15 @@ +namespace IFoxCAD.Basal; + +public static class DictEx +{ + // public static TKey? GetKey(this IDictionary dict!!, TKey key!!) + // { + // if (dict.ContainsKey(key)) + // { + // foreach (var item in dict.Keys) + // if (key.Equals(item)) + // return item; + // } + // return default; + // } +} diff --git a/src/Basal/IFox.Basal.Shared/General/EnumEx.cs b/src/Basal/IFox.Basal.Shared/General/EnumEx.cs new file mode 100644 index 0000000000000000000000000000000000000000..50ab654a6d915fe91971ff55d5f95a7a5978044d --- /dev/null +++ b/src/Basal/IFox.Basal.Shared/General/EnumEx.cs @@ -0,0 +1,113 @@ +namespace IFoxCAD.Basal; + +using System.ComponentModel; +using System.Linq; + +public static class EnumEx +{ + /// + /// 清理缓存 + /// + public static void CleanCache() + { + _cache.Clear(); + } + + // (类型完整名,描述组合) + static readonly Dictionary> _cache = new(); + + /// + /// 打印枚举的特性注释内容 + /// + /// 枚举 + /// 注释内容 + public static HashSet? GetAttribute(this Enum e, bool noDescrToString = true) + where T : DescriptionAttribute + { + var eType = e.GetType(); + string eFullName = eType.FullName + "." + e.ToString(); + + if (_cache.ContainsKey(eFullName)) + return _cache[eFullName]; + + var fieldInfo = eType.GetField(Enum.GetName(eType, e)); + if (fieldInfo == null) + return null!; + + // 注释存放的容器 + HashSet nodes = new(); + if (Attribute.GetCustomAttribute(fieldInfo, typeof(T)) is T attribute) + { + nodes.Add(attribute.Description); + _cache.Add(eFullName, nodes); + return nodes; + } + + // 通常到这里的就是 ALL = A | B | C + // 遍历所有的枚举,组合每个注释 + List enumHas = new(); + + // 遍历这个枚举类型,获取枚举按位包含的成员 + foreach (Enum em in Enum.GetValues(eType)) + if ((e.GetHashCode() & em.GetHashCode()) == em.GetHashCode() && + e.GetHashCode() != em.GetHashCode()) + enumHas.Add(em); + + + // 采取的行为是:注释的行为是特殊的,就按照注释的,否则,遍历子元素提取注释 + // 大的在前面才能判断是否按位包含后面的,后面的就是要移除的 + enumHas = enumHas.OrderByDescending(a => a.GetHashCode()).ToList(); + ArrayEx.Deduplication(enumHas, (a, b) => { + return (a.GetHashCode() & b.GetHashCode()) == b.GetHashCode(); + }); + + // 逆序仅仅为排序后处理,不一定和书写顺序一样,尤其是递归可能存在重复的元素 + for (int i = enumHas.Count - 1; i >= 0; i--) + { + var atts = GetAttribute(enumHas[i], noDescrToString);// 递归 + if (atts == null) + continue; + foreach (var item in atts) + nodes.Add(item); + } + + if (nodes.Count == 0 && noDescrToString) + nodes.Add(e.ToString()); + + _cache.Add(eFullName, nodes); + return nodes; + } + + /// + /// 打印枚举的特性注释内容 + /// + public static string? PrintNote(this Enum e, bool noDescToString = true) + { + var hash = GetAttribute(e, noDescToString); + if (hash != null) + return string.Join("|", hash.ToArray()); + return null; + } + + // 不按位运算的情况下,直接获取比较快捷 + public static string GetDesc(this Enum e) + { + return GetDesc(e.GetType(), e.ToString()); + } + + /// + /// 获取字段的描述内容 + /// + /// + /// + /// + public static string GetDesc(this Type type, string field) + { + var memberInfo = type.GetMember(field); + var attributes = memberInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false); + // 如果没有定义描述,就把当前枚举值的对应名称返回 + if (attributes is null || attributes.Length != 1) + return field; + return ((DescriptionAttribute)attributes.Single()).Description; + } +} \ No newline at end of file diff --git a/src/Basal/IFox.Basal.Shared/General/LinkedHashMap.cs b/src/Basal/IFox.Basal.Shared/General/LinkedHashMap.cs new file mode 100644 index 0000000000000000000000000000000000000000..0e6232c6c37ca087577550cd01dcb2714c1e30a5 --- /dev/null +++ b/src/Basal/IFox.Basal.Shared/General/LinkedHashMap.cs @@ -0,0 +1,171 @@ +namespace IFoxCAD.Basal; + + +/// +/// A least-recently-used cache stored like a dictionary. +/// +/// +/// The type of the key to the cached item +/// +/// +/// The type of the cached item. +/// +/// +/// Derived from https://stackoverflow.com/a/3719378/240845 +/// https://stackoverflow.com/users/240845/mheyman +/// +public class LinkedHashMap +{ + private readonly Dictionary> cacheMap = new(); + + private readonly LinkedList lruList = new(); + + private readonly Action? dispose; + + /// + /// Initializes a new instance of the + /// class. + /// + /// + /// Maximum number of elements to cache. + /// + /// + /// When elements cycle out of the cache, disposes them. May be null. + /// + public LinkedHashMap(int capacity, Action? dispose = null) + { + this.Capacity = capacity; + this.dispose = dispose; + } + + /// + /// Gets the capacity of the cache. + /// + public int Capacity { get; } + + /// Gets the value associated with the specified key. + /// + /// The key of the value to get. + /// + /// + /// When this method returns, contains the value associated with the specified + /// key, if the key is found; otherwise, the default value for the type of the + /// parameter. This parameter is passed + /// uninitialized. + /// + /// + /// true if the + /// contains an element with the specified key; otherwise, false. + /// + public bool TryGetValue(TKey key, out TValue? value) + { + lock (this.cacheMap) + { + if (this.cacheMap.TryGetValue(key, out LinkedListNode.MapItem> node)) + { + value = node.Value.Value; + this.lruList.Remove(node); + this.lruList.AddLast(node); + return true; + } + + value = default; + return false; + } + } + + /// + /// Looks for a value for the matching . If not found, + /// calls to retrieve the value and add it to + /// the cache. + /// + /// + /// The key of the value to look up. + /// + /// + /// Generates a value if one isn't found. + /// + /// + /// The requested value. + /// + public TValue Get(TKey key, Func valueGenerator) + { + lock (this.cacheMap) + { + TValue value; + if (this.cacheMap.TryGetValue(key, out LinkedListNode.MapItem> node)) + { + value = node.Value.Value; + this.lruList.Remove(node); + this.lruList.AddLast(node); + } + else + { + value = valueGenerator(); + if (this.cacheMap.Count >= this.Capacity) + { + this.RemoveFirst(); + } + + MapItem cacheItem = new(key, value); + node = new LinkedListNode(cacheItem); + this.lruList.AddLast(node); + this.cacheMap.Add(key, node); + } + + return value; + } + } + + /// + /// Adds the specified key and value to the dictionary. + /// + /// + /// The key of the element to add. + /// + /// + /// The value of the element to add. The value can be null for reference types. + /// + public void Add(TKey key, TValue value) + { + lock (this.cacheMap) + { + if (this.cacheMap.Count >= this.Capacity) + { + this.RemoveFirst(); + } + + MapItem cacheItem = new(key, value); + LinkedListNode node = new(cacheItem); + this.lruList.AddLast(node); + this.cacheMap.Add(key, node); + } + } + + private void RemoveFirst() + { + // Remove from LRUPriority + LinkedListNode node = this.lruList.First; + this.lruList.RemoveFirst(); + + // Remove from cache + this.cacheMap.Remove(node.Value.Key); + + // dispose + this.dispose?.Invoke(node.Value.Value); + } + + private class MapItem + { + public MapItem(TKey k, TValue v) + { + this.Key = k; + this.Value = v; + } + + public TKey Key { get; } + + public TValue Value { get; } + } +} + diff --git a/src/Basal/IFox.Basal.Shared/General/LinkedHashSet.cs b/src/Basal/IFox.Basal.Shared/General/LinkedHashSet.cs new file mode 100644 index 0000000000000000000000000000000000000000..6efaa04d11abd67c4904dbcd5750863b5f7da9b3 --- /dev/null +++ b/src/Basal/IFox.Basal.Shared/General/LinkedHashSet.cs @@ -0,0 +1,222 @@ +namespace IFoxCAD.Basal; + +public class LinkedHashSet : ICollection where T : IComparable +{ + private readonly IDictionary> m_Dictionary; + private readonly LoopList m_LinkedList; + + public LinkedHashSet() + { + m_Dictionary = new Dictionary>(); + m_LinkedList = new LoopList(); + } + + public LoopListNode? First => m_LinkedList.First; + + public LoopListNode? Last => m_LinkedList.Last; + + public LoopListNode? MinNode { get; set; } + + public bool Add(T item) + { + if (m_Dictionary.ContainsKey(item)) + return false; + var node = m_LinkedList.AddLast(item); + m_Dictionary.Add(item, node); + + if (MinNode is null) + { + MinNode = node; + } + else + { + if (item.CompareTo(MinNode.Value) < 0) + { + MinNode = node; + } + } + + + + return true; + } + + void ICollection.Add(T item) + { + Add(item); + } + + public LoopListNode AddFirst(T value) + { + if (m_Dictionary.ContainsKey(value)) + { + return m_Dictionary[value]; + } + var node = m_LinkedList.AddFirst(value); + m_Dictionary.Add(value, node); + if (MinNode is null) + { + MinNode = node; + } + else + { + if (value.CompareTo(MinNode.Value) < 0) + { + MinNode = node; + } + } + return node; + } + + public void AddRange(IEnumerable collection) + { + foreach (var item in collection) + { + Add(item); + } + } + + + public void Clear() + { + m_LinkedList.Clear(); + m_Dictionary.Clear(); + } + + public bool Remove(T item) + { + bool found = m_Dictionary.TryGetValue(item, out LoopListNode node); + if (!found) return false; + m_Dictionary.Remove(item); + m_LinkedList.Remove(node); + return true; + } + + public int Count + { + get { return m_Dictionary.Count; } + } + + public void For(LoopListNode from, Action action) + { + var first = from; + var last = from; + if (first is null) return; + + for (int i = 0; i < Count; i++) + { + action.Invoke(i, first!.Value, last!.Value); + first = first.Next; + last = last.Previous; + } + } + + public List ToList() + { + return m_LinkedList.ToList(); + } + + [System.Diagnostics.DebuggerStepThrough] + public IEnumerator GetEnumerator() + { + return m_LinkedList.GetEnumerator(); + } + + [System.Diagnostics.DebuggerStepThrough] + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + + public bool Contains(T item) + { + return m_Dictionary.ContainsKey(item); + } + + public void CopyTo(T[] array, int arrayIndex) + { + // m_LinkedList.CopyTo(array, arrayIndex); + return; + } + + public bool SetFirst(LoopListNode node) + { + return m_LinkedList.SetFirst(node); + } + + public LinkedHashSet Clone() + { + var newset = new LinkedHashSet(); + foreach (var item in this) + { + newset.Add(item); + } + return newset; + } + + public virtual bool IsReadOnly + { + get { return m_Dictionary.IsReadOnly; } + } + + public override string ToString() + { + return m_LinkedList.ToString(); + } + + public void UnionWith(IEnumerable other) + { + throw GetNotSupportedDueToSimplification(); + } + + public void IntersectWith(IEnumerable other) + { + throw GetNotSupportedDueToSimplification(); + } + + public void ExceptWith(IEnumerable other) + { + throw GetNotSupportedDueToSimplification(); + } + + public bool IsSubsetOf(IEnumerable other) + { + throw GetNotSupportedDueToSimplification(); + } + + public void SymmetricExceptWith(IEnumerable other) + { + throw GetNotSupportedDueToSimplification(); + } + + public bool IsSupersetOf(IEnumerable other) + { + throw GetNotSupportedDueToSimplification(); + } + + public bool IsProperSupersetOf(IEnumerable other) + { + throw GetNotSupportedDueToSimplification(); + } + + public bool IsProperSubsetOf(IEnumerable other) + { + throw GetNotSupportedDueToSimplification(); + } + + public bool Overlaps(IEnumerable other) + { + throw GetNotSupportedDueToSimplification(); + } + + public bool SetEquals(IEnumerable other) + { + throw GetNotSupportedDueToSimplification(); + } + + private static Exception GetNotSupportedDueToSimplification() + { + return new NotSupportedException("This method is not supported due to simplification of example code."); + } +} \ No newline at end of file diff --git a/src/IFoxCAD.Basal/LinqEx.cs b/src/Basal/IFox.Basal.Shared/General/LinqEx.cs similarity index 93% rename from src/IFoxCAD.Basal/LinqEx.cs rename to src/Basal/IFox.Basal.Shared/General/LinqEx.cs index 836063f9d87d532d4ca62e4dfe4d4106ad7dbe6e..8112e63d87a23a94379b1591ecfbe87a636845cf 100644 --- a/src/IFoxCAD.Basal/LinqEx.cs +++ b/src/Basal/IFox.Basal.Shared/General/LinqEx.cs @@ -20,7 +20,7 @@ public static TValue FindByMax(this IEnumerable source, Fu { var itor = source.GetEnumerator(); if (!itor.MoveNext()) - throw new ArgumentNullException(); + throw new ArgumentNullException(nameof(source), "对象为 null"); TValue value = itor.Current; TKey key = func(value); @@ -51,7 +51,7 @@ public static TValue FindByMax(this IEnumerable source, ou { var itor = source.GetEnumerator(); if (!itor.MoveNext()) - throw new ArgumentNullException(); + throw new ArgumentNullException(nameof(source), "对象为 null"); TValue value = itor.Current; TKey key = func(value); @@ -80,7 +80,7 @@ public static TValue FindByMax(this IEnumerable source, Comparis { var itor = source.GetEnumerator(); if (!itor.MoveNext()) - throw new ArgumentNullException(); + throw new ArgumentNullException(nameof(source), "对象为 null"); TValue value = itor.Current; @@ -110,7 +110,7 @@ public static TValue FindByMin(this IEnumerable source, ou { var itor = source.GetEnumerator(); if (!itor.MoveNext()) - throw new ArgumentNullException(); + throw new ArgumentNullException(nameof(source), "对象为 null"); TValue value = itor.Current; TKey key = func(value); @@ -141,7 +141,7 @@ public static TValue FindByMin(this IEnumerable source, Fu { var itor = source.GetEnumerator(); if (!itor.MoveNext()) - throw new ArgumentNullException(); + throw new ArgumentNullException(nameof(source), "对象为 null"); TValue value = itor.Current; TKey key = func(value); @@ -169,7 +169,7 @@ public static TValue FindByMin(this IEnumerable source, Comparis { var itor = source.GetEnumerator(); if (!itor.MoveNext()) - throw new ArgumentNullException(); + throw new ArgumentNullException(nameof(source), "对象为 null"); TValue value = itor.Current; @@ -198,7 +198,7 @@ public static TValue[] FindByExt(this IEnumerable source, { var itor = source.GetEnumerator(); if (!itor.MoveNext()) - throw new ArgumentNullException(); + throw new ArgumentNullException(nameof(source), "对象为 null"); TValue[] values = new TValue[2]; values[0] = values[1] = itor.Current; @@ -234,7 +234,7 @@ public static TValue[] FindByExt(this IEnumerable source, Compar { var itor = source.GetEnumerator(); if (!itor.MoveNext()) - throw new ArgumentNullException(); + throw new ArgumentNullException(nameof(source), "对象为 null"); TValue[] values = new TValue[2]; values[0] = values[1] = itor.Current; @@ -262,7 +262,7 @@ public static TKey[] FindExt(this IEnumerable source, Func { var itor = source.GetEnumerator(); if (!itor.MoveNext()) - throw new ArgumentNullException(); + throw new ArgumentNullException(nameof(source), "对象为 null"); TKey[] keys = new TKey[2]; keys[0] = keys[1] = func(itor.Current); @@ -288,7 +288,7 @@ public static TKey[] FindExt(this IEnumerable source, Func /// 泛型 private class SpecComparer : IComparer { - private Comparison _comp; + private readonly Comparison _comp; internal SpecComparer(Comparison comp) { @@ -296,12 +296,10 @@ internal SpecComparer(Comparison comp) } #region IComparer 成员 - public int Compare(T x, T y) { return _comp(x, y); } - #endregion IComparer 成员 } diff --git a/src/Basal/IFox.Basal.Shared/General/ListEx.cs b/src/Basal/IFox.Basal.Shared/General/ListEx.cs new file mode 100644 index 0000000000000000000000000000000000000000..528b6ad614eaf66f74ee4f27a7bd0f417a649241 --- /dev/null +++ b/src/Basal/IFox.Basal.Shared/General/ListEx.cs @@ -0,0 +1,30 @@ +namespace IFoxCAD.Basal; + + +public static class ListEx +{ + public static bool EqualsAll(this IList a, IList b) + { + return EqualsAll(a, b, null); + // there is a slight performance gain in passing null here. + // It is how it is done in other parts of the framework. + } + + public static bool EqualsAll(this IList a, IList b, IEqualityComparer? comparer) + { + if (a is null) + return b is null; + else if (b is null) + return false; + + if (a.Count != b.Count) + return false; + + comparer ??= EqualityComparer.Default; + + for (int i = 0; i < a.Count; i++) + if (!comparer.Equals(a[i], b[i])) + return false; + return true; + } +} diff --git a/src/Basal/IFox.Basal.Shared/General/LoopList.cs b/src/Basal/IFox.Basal.Shared/General/LoopList.cs new file mode 100644 index 0000000000000000000000000000000000000000..243ced664120ae5260e54db6e3080aa113e3cc60 --- /dev/null +++ b/src/Basal/IFox.Basal.Shared/General/LoopList.cs @@ -0,0 +1,748 @@ +namespace IFoxCAD.Basal; + +#line hidden // 调试的时候跳过它 + +/// +/// 环链表节点 +/// +/// +public class LoopListNode +{ + #region 成员 + /// + /// 取值 + /// + public T Value; + + /// + /// 上一个节点 + /// + public LoopListNode? Previous { internal set; get; } + + /// + /// 下一个节点 + /// + public LoopListNode? Next { internal set; get; } + + /// + /// 环链表序列 + /// + public LoopList? List { internal set; get; } + #endregion + + #region 构造 + /// + /// 环链表节点构造函数 + /// + /// 节点值 + public LoopListNode(T value, LoopList ts) + { + Value = value; + List = ts; + } + + /// + /// 获取当前节点的临近节点 + /// + /// 搜索方向标志,为向前搜索,为向后搜索 + /// + public LoopListNode? GetNext(bool forward) + { + return forward ? Next : Previous; + } + #endregion + + #region 方法 + /// + /// 无效化成员 + /// + internal void Invalidate() + { + List = null; + Next = null; + Previous = null; + } + #endregion +} + +/// +/// 环链表 +/// +/// +public class LoopList : IEnumerable, IFormattable +{ + #region 成员 + + /// + /// 节点数 + /// + public int Count { get; private set; } + + /// + /// 首节点 + /// + public LoopListNode? First { get; private set; } + + /// + /// 尾节点 + /// + public LoopListNode? Last => First?.Previous; + + + + + #endregion + + #region 构造 + + /// + /// 默认构造函数 + /// + public LoopList() { } + + /// + /// 环链表构造函数 + /// + /// 节点迭代器 + [System.Diagnostics.DebuggerStepThrough] + public LoopList(IEnumerable values) + { + var ge = values.GetEnumerator(); + while (ge.MoveNext()) + Add(ge.Current); + } + + #endregion + + #region 方法 + + /// + /// 设置首节点 + /// + /// 节点 + /// + public bool SetFirst(LoopListNode node) + { + if (!Contains(node)) + return false; + + First = node; + return true; + } + + /// + /// 交换两个节点的值 + /// + /// 第一个节点 + /// 第二个节点 + public void Swap(LoopListNode node1, LoopListNode node2) + { + (node2.Value, node1.Value) = (node1.Value, node2.Value); + } + + /// + /// 链内翻转 + /// + public void Reverse() + { + var first = First; + if (first is null) + return; + var last = Last; + for (int i = 0; i < Count / 2; i++) + { + Swap(first!, last!); + first = first!.Next; + last = last!.Previous; + } + } + + /// + /// 清理 + /// + public void Clear() + { + // 移除头部,表示链表再也无法遍历得到 + First = null; + Count = 0; + } + + /// + /// 从头遍历_非迭代器(此处和通用ForEach冲突,所以内部用) + /// + /// + void ForEach(Func, bool> action) + { + var node = First; + if (node is null) + return; + for (int i = 0; i < Count; i++) + { + if (action(node!)) + break; + node = node!.Next; + } + } + + /// + /// 从头遍历_非迭代器(扔出计数) + /// + /// + public void For(Func, bool> action) + { + var node = First; + if (node is null) + return; + for (int i = 0; i < Count; i++) + { + if (action(i, node!)) + break; + node = node!.Next; + } + } + + #region Contains + + /// + /// 是否包含节点 + /// + /// + /// + public bool Contains(LoopListNode node) + { + return node is not null && node.List == this; + } + + /// + /// 是否包含值 + /// + /// + /// + public bool Contains(T value) + { + bool result = false; + ForEach(node => { + if (node.Value!.Equals(value)) + { + result = true; + return true; + } + return false; + }); + return result; + } + + /// + /// 查找第一个出现的节点 + /// + /// + /// + public LoopListNode? Find(T value) + { + // LoopListNode result = null; + // ForEach(node => + // { + // if (node.Value.Equals(t2)) + // { + // result = node; + // return true; + // } + // return false; + // }); + // return result; + + LoopListNode? node = First; + var c = EqualityComparer.Default; + if (node is not null) + { + if (value is not null) + { + do + { + if (c.Equals(node!.Value, value)) + return node; + node = node.Next; + } while (node != First); + } + else + { + do + { + if (node!.Value is null) + return node; + node = node.Next; + } while (node != First); + } + } + return null; + } + + /// + /// 查找所有出现的节点 + /// + /// + /// + public IEnumerable>? Finds(T value) + { + LoopListNode? node = First; + if (node is null) + return null; + + List> result = new(); + var c = EqualityComparer.Default; + if (value is not null) + { + do + { + if (c.Equals(node!.Value, value)) + result.Add(node); + node = node.Next; + } while (node != First); + } + else + { + do + { + if (node!.Value is null) + result.Add(node); + node = node.Next; + } while (node != First); + } + return result; + } + + /// + /// 获取节点 + /// + /// + /// + public LoopListNode? GetNode(Func func) + { + LoopListNode? result = null; + ForEach(node => { + if (func(node.Value)) + { + result = node; + return true; + } + return false; + }); + return result; + } + + #endregion + + #region Add + + /// + /// 在首节点之前插入节点,并设置新节点为首节点 + /// + /// + /// + public LoopListNode AddFirst(T value) + { + var node = new LoopListNode(value, this); + + if (Count == 0) + { + First = node; + First.Previous = First.Next = node; + } + else + { + LoopListNode last = Last!; + First!.Previous = last.Next = node; + node.Next = First; + node.Previous = last; + First = node; + } + Count++; + return First; + } + + /// + /// 在尾节点之后插入节点,并设置新节点为尾节点 + /// + /// + /// + public LoopListNode Add(T value) + { + var node = new LoopListNode(value, this); + + if (Count == 0) + { + First = node; + First.Previous = First.Next = node; + } + else + { + var last = First!.Previous!; + First.Previous = last.Next = node; + node.Next = First; + node.Previous = last; + } + Count++; + return Last!; + } + + /// + /// 在尾节点之后插入节点,并设置新节点为尾节点_此函数仅为与LinkedList同名方法 + /// + /// + /// + [System.Diagnostics.DebuggerStepThrough] + public LoopListNode AddLast(T value) + { + return Add(value); + } + + /// + /// 容器内容全部加入到末尾 + /// + /// + [System.Diagnostics.DebuggerStepThrough] + public void AddRange(IEnumerable list) + { + var ge = list.GetEnumerator(); + while (ge.MoveNext()) + Add(ge.Current); + } + + /// + /// 前面增加节点 + /// + /// + /// + /// + public LoopListNode AddBefore(LoopListNode node, T value) + { + if (node == First) + return AddFirst(value); + + var tnode = new LoopListNode(value, this); + node.Previous!.Next = tnode; + tnode.Previous = node.Previous; + node.Previous = tnode; + tnode.Next = node; + Count++; + return tnode; + } + + /// + /// 后面增加节点 + /// + /// + /// + /// + public LoopListNode AddAfter(LoopListNode node, T value) + { + var tnode = new LoopListNode(value, this); + node.Next!.Previous = tnode; + tnode.Next = node.Next; + node.Next = tnode; + tnode.Previous = node; + Count++; + return tnode; + } + + #endregion + + #region Remove + + /// + /// 删除首节点 + /// + /// + public bool RemoveFirst() + { + switch (Count) + { + case 0: + return false; + + case 1: + First = null; + break; + + default: + LoopListNode last = Last!; + First = First!.Next; + First!.Previous = last; + last.Next = First; + break; + } + Count--; + return true; + } + + /// + /// 删除尾节点 + /// + /// + public bool RemoveLast() + { + switch (Count) + { + case 0: + return false; + + case 1: + First = null; + break; + + default: + LoopListNode last = Last!.Previous!; + last.Next = First; + First!.Previous = last; + break; + } + Count--; + return true; + } + + /// + /// 删除此参数节点(唯一) + /// + /// 指定节点 + /// + public bool Remove(LoopListNode node) + { + if (!Contains(node)) + return false; + InternalRemove(node); + return true; + } + + /// + /// 删除含有此参数节点(所有) + /// + /// 将移除所有含有此值 + /// + public bool Remove(T value) + { + var lst = Finds(value); + if (lst is null) + return false; + + var ge = lst!.GetEnumerator(); + while (ge.MoveNext()) + InternalRemove(ge.Current); + return true; + } + + /// + /// 删除节点_内部调用 + /// + /// 此值肯定存在当前链表 + /// + void InternalRemove(LoopListNode node) + { + if (Count == 1 || node == First) + { + RemoveFirst();// 此处会减数字 + } + else + { + node.Next!.Previous = node.Previous; + node.Previous!.Next = node.Next; + Count--; + } + node.Invalidate(); + } + + #endregion + + #region LinkTo + + /// + /// 链接两节点,并去除这两个节点间的所有节点 + /// + /// + /// + public void LinkTo(LoopListNode from, LoopListNode to) + { + if (from != to && Contains(from) && Contains(to)) + { + LoopListNode node = from.Next!; + bool isFirstChanged = false; + int number = 0; + + while (node != to) + { + if (node == First) + isFirstChanged = true; + + node = node.Next!; + number++; + } + + from.Next = to; + to.Previous = from; + + if (number > 0 && isFirstChanged) + First = to; + + Count -= number; + } + } + + /// + /// 链接两节点,并去除这两个节点间的所有节点 + /// + /// + /// + /// + public void LinkTo(LoopListNode from, LoopListNode to, int number) + { + if (from != to && Contains(from) && Contains(to)) + { + from.Next = to; + to.Previous = from; + First = to; + Count -= number; + } + } + + /// + /// 链接两节点,并去除这两个节点间的所有节点 + /// + /// + /// + /// + /// + public void LinkTo(LoopListNode from, LoopListNode to, int number, bool isFirstChanged) + { + if (from != to && Contains(from) && Contains(to)) + { + from.Next = to; + to.Previous = from; + if (isFirstChanged) + First = to; + Count -= number; + } + } + + #endregion + + #endregion + + #region IEnumerable + + /// + /// 获取节点的查询器 + /// + /// + /// + [System.Diagnostics.DebuggerStepThrough] + public IEnumerable> GetNodes(LoopListNode from) + { + var node = from; + for (int i = 0; i < Count; i++) + { + yield return node!; + node = node!.Next; + } + } + + /// + /// 获取节点的查询器 + /// + /// + [System.Diagnostics.DebuggerStepThrough] + public IEnumerable> GetNodes() + { + LoopListNode node = First!; + for (int i = 0; i < Count; i++) + { + yield return node!; + node = node.Next!; + } + } + + /// + /// 获取节点值的查询器 + /// + /// + [System.Diagnostics.DebuggerStepThrough] + public IEnumerator GetEnumerator() + { + LoopListNode node = First!; + for (int i = 0; i < Count; i++) + { + yield return node!.Value; + node = node.Next!; + } + } + + [System.Diagnostics.DebuggerStepThrough] + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #region IEnumerable 成员 + + [System.Diagnostics.DebuggerStepThrough] + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #endregion IEnumerable 成员 + + #endregion + + #region IFormattable + /// + /// 转换为字符串_格式化实现 + /// + /// + /// + /// + string IFormattable.ToString(string? format, IFormatProvider? formatProvider) + { + return ToString(format, formatProvider); + } + + /// + /// 转换为字符串_无参调用 + /// + /// + public override string ToString() + { + return ToString(null, null); + } + + /// + /// 转换为字符串_有参调用 + /// + /// + public string ToString(string? format, IFormatProvider? formatProvider = null) + { + var s = new StringBuilder(); + s.Append($"Count = {Count};"); + if (format is null) + { + s.Append("{ "); + foreach (T value in this) + s.Append($"{value} "); + s.Append(" }"); + } + return s.ToString(); + } + #endregion + + #region ICloneable + /* 山人说无法分辨ICloneable接口是深浅克隆, + * 因此不要在泛型模板实现克隆函数, + * 让用户自己来 new(xx)实现浅克隆,所以也不提供Clone()了 + * + * 因此约定了:CopyTo(T,index)是深克隆;MemberwiseClone()是浅克隆; + * public object Clone() + * { + * var lst = new LoopList>(); + * ForEach(node => { + * lst.Add(node); + * return false; + * }); + * return lst; + * } + */ + #endregion +} + +#line default \ No newline at end of file diff --git a/src/Basal/IFox.Basal.Shared/General/LoopState.cs b/src/Basal/IFox.Basal.Shared/General/LoopState.cs new file mode 100644 index 0000000000000000000000000000000000000000..76ff63fae9fce2ba6fabd8e8635bb84b67ae637e --- /dev/null +++ b/src/Basal/IFox.Basal.Shared/General/LoopState.cs @@ -0,0 +1,67 @@ +namespace IFoxCAD.Basal; + +#line hidden // 调试的时候跳过它 +/// +/// 控制循环结束 +/// +public class LoopState +{ + const int PLS_NONE = 0; + const int PLS_EXCEPTIONAL = 1; + const int PLS_BROKEN = 2; + const int PLS_STOPPED = 4; + const int PLS_CANCELED = 8; + + private volatile int _flag = PLS_NONE; + + public bool IsRun => _flag == PLS_NONE; + public bool IsExceptional => (_flag & PLS_EXCEPTIONAL) == PLS_EXCEPTIONAL; + public bool IsBreak => (_flag & PLS_BROKEN) == PLS_BROKEN; + public bool IsStop => (_flag & PLS_STOPPED) == PLS_STOPPED; + public bool IsCancel => (_flag & PLS_CANCELED) == PLS_CANCELED; + + public void Exceptional() + { + if ((_flag & PLS_EXCEPTIONAL) != PLS_EXCEPTIONAL) + _flag |= PLS_EXCEPTIONAL; + } + public void Break() => _flag = PLS_BROKEN; + public void Stop() => _flag = PLS_STOPPED; + public void Cancel() => _flag = PLS_CANCELED; + public void Reset() => _flag = PLS_NONE; +} +#line default + +/// +/// 控制程序流程 +/// +public class ProState +{ + const int PLS_NONE = 0; // 初始化(构造就立马运行,将导致构造函数中也被检测,这是浪费性能及挖坑给自己的) + const int PLS_RUN = 1; // 运行 + const int PLS_BROKEN = 2; + const int PLS_STOPPED = 4; + const int PLS_CANCELED = 8; + const int PLS_EXCEPTIONAL = 16; // 异常 用于附加状态 + + private volatile int _flag = PLS_NONE; + + public bool IsNone => _flag == PLS_NONE; + public bool IsRun => (_flag & PLS_RUN) == PLS_RUN; + public bool IsBreak => (_flag & PLS_BROKEN) == PLS_BROKEN; + public bool IsStop => (_flag & PLS_STOPPED) == PLS_STOPPED; + public bool IsCancel => (_flag & PLS_CANCELED) == PLS_CANCELED; + public bool IsExceptional => (_flag & PLS_EXCEPTIONAL) == PLS_EXCEPTIONAL; + + public void Exceptional() + { + if ((_flag & PLS_EXCEPTIONAL) != PLS_EXCEPTIONAL) + _flag |= PLS_EXCEPTIONAL; + } + public void Break() => _flag = PLS_BROKEN; + public void Stop() => _flag = PLS_STOPPED; + public void Cancel() => _flag = PLS_CANCELED; + public void Start() => _flag = PLS_RUN; + public void None() => _flag = PLS_NONE; +} +#line default \ No newline at end of file diff --git a/src/Basal/IFox.Basal.Shared/General/RandomEx.cs b/src/Basal/IFox.Basal.Shared/General/RandomEx.cs new file mode 100644 index 0000000000000000000000000000000000000000..4d743244994bb16ba958cd52fd0f80bfb81514a6 --- /dev/null +++ b/src/Basal/IFox.Basal.Shared/General/RandomEx.cs @@ -0,0 +1,201 @@ +/* +*┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━模块信息━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +*┃ 作 者:YxrWendao +*┃ 创建时间:2022/8/30 22:49:30 +*┃ 模块描述:随机数生成器 +*┃ 使用范围:通用 +*┃ 说 明:本模块中除GetRandom与NextColor方法是IFoxCAD原有的以外,其他方法均通过网络收集整理而来。 +*┃ 代码版本:1.0 +*┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +*/ + +namespace IFoxCAD.Basal; + +/// +/// 随机值扩展类 +/// +public static class RandomEx +{ + /// + /// 生成一个指定范围的浮点数值
+ /// 相关链接 + ///
+ /// 一个随机值产生器 + /// 范围最小浮点数值 + /// 范围最大浮点数值 + /// + public static double NextDouble(Random ran, double minValue, double maxValue) + { + return ran.NextDouble() * (maxValue - minValue) + minValue; + } + /// + /// 生成一个指定范围的浮点数值 + /// + /// 范围最小浮点数值 + /// 范围最大浮点数值 + /// + public static double NextDouble(double minValue, double maxValue) + { + return NextDouble(GetRandom(), minValue, maxValue); + } + /// + /// 生成一个布尔随机数 + /// + /// + public static bool NextBool() + { + return NextBool(GetRandom()); + } + /// + /// 生成一个布尔随机数
+ ///
+ /// + public static bool NextBool(Random ran) + { + bool[] arr = { true, false }; + return arr[ran.Next(2)]; + } + /// + /// 生成一个不连续或指定值的随机值 + /// + /// 一个字符串数组 + /// + public static string NextString(string[] arr) + { + return NextString(GetRandom(), arr); + } + /// + /// 生成一个不连续或指定值的随机值 + /// + /// 一个随机值产生器 + /// 一个字符串数组 + /// + public static string NextString(Random ran, string[] arr) + { + ran ??= GetRandom(); + int n = ran.Next(arr.Length - 1); + return arr[n]; + } + /// + /// 生成一个不连续或指定值的随机值 + /// + /// 一个双精度值数组 + /// + public static double NextDouble(double[] arr) + { + return NextDouble(GetRandom(), arr); + } + /// + /// 生成不连续或指定值的随机值 + /// + /// 一个随机值产生器 + /// 一个双精度值数组 + /// + public static double NextDouble(Random ran, double[] arr) + { + ran ??= GetRandom(); + int n = ran.Next(arr.Length - 1); + return arr[n]; + } + /// + /// 生成指定范围内的整数 + /// + /// 范围最大整数值 + /// + public static int NextInt(int max) + { + return NextInt(GetRandom(), max); + } + /// + /// 生成指定范围内的整数 + /// + /// 一个随机值产生器 + /// 范围最大整数值 + /// + public static int NextInt(Random ran, int max) + { + ran ??= GetRandom(); + return ran.Next(max); + } + /// + /// 生成指定范围内的整数 + /// + /// 范围的最小整数 + /// 范围的最大整数 + /// 返回一个介于之间的整数 + public static int NextInt(int min, int max) + { + return NextInt(GetRandom(), min, max); + } + /// + /// 生成指定范围内的整数 + /// + /// 一个随机值产生器 + /// 范围的最小整数 + /// 范围的最大整数 + /// 返回一个介于之间的整数 + public static int NextInt(Random ran, int min, int max) + { + ran ??= GetRandom(); + return ran.Next(min, max); + } + + /// + /// 生成一个随机颜色 + /// + /// 返回 + public static System.Drawing.Color NextColor() + { + return NextColor(GetRandom()); + } + /// + /// 生成一个随机颜色 + /// + /// + public static System.Drawing.Color NextColor(Random ran) + { + ran ??= GetRandom(); + int R = ran.Next(255); + int G = ran.Next(255); + int B = ran.Next(255); + B = (R + G > 400) ? R + G - 400 : B;// 0 : 380 - R - G; + B = (B > 255) ? 255 : B; + return System.Drawing.Color.FromArgb(R, G, B); + } + + + /* + * 知识准备: + * | 高位64位 | 低位32位 | + * Convert.ToString(int.MaxValue, 2)输出二进制 "1111111111111111111111111111111" 31个;最高位是符号位,所以少1位 + * Convert.ToString(long.MaxValue,2)输出二进制,刚好长一倍 "11111111111111111111111111111111 1111111111111111111111111111111" 63个;最高位是符号位,所以少1位 + * Convert.ToString(0xffffffffL, 2)int.MaxValue再按位多1 "1 1111111111111111111111111111111" 32个;前面的0不会打印出来 + * + * Convert.ToString(long.MaxValue>>32, 2)相当于平移高位的到低位范围,也就是上面少打印的二进制 + * 验证右移是不是高位保留,答案是 + * var a = Convert.ToInt64("101111111111111111111111111111111111111111111111111111111111111", 2); + * Convert.ToString(a >> 32,2); + * + * 解释代码: + * 0x01: + * (int)(long.MaxValue & 0xffffffffL) | (int)(long.MaxValue >> 32); + * Convert.ToString(long.MaxValue & 0xffffffffL, 2)// 去掉高位:"11111111111111111111111111111111" 32个,再强转int + * 按位与&是保证符号位肯定是1,其他尽可能为0,高位被去掉只是MaxValue&0的原因,强转才是去掉高位..."尽可能"一词带来第一次随机性 + * 0x02: + * Convert.ToString((long.MaxValue >> 32), 2) // 去掉低位: "1111111111111111111111111111111" 31个,再强转int + * 按位或|是尽可能为1..."尽可能"一词带来第二次随机性 + * + */ + + /// + /// 带有随机种子的随机数
+ /// 为什么这样写随机种子呢 + ///
+ /// + public static Random GetRandom() + { + var tick = DateTime.Now.Ticks; + var tickSeeds = (int)(tick & 0xffffffffL) | (int)(tick >> 32); + return new Random(tickSeeds); + } +} \ No newline at end of file diff --git a/src/Basal/IFox.Basal.Shared/General/Timer.cs b/src/Basal/IFox.Basal.Shared/General/Timer.cs new file mode 100644 index 0000000000000000000000000000000000000000..d489cb547aadf5323bdd7e29ddbeb07ee0042088 --- /dev/null +++ b/src/Basal/IFox.Basal.Shared/General/Timer.cs @@ -0,0 +1,157 @@ +namespace IFoxCAD.Basal; + +/* +// 测试例子,同时验证两个计时器 +var stopwatch = new Stopwatch(); +Timer.RunTime(() => { + stopwatch.Start(); + for (int i = 0; i < 10000000; i++) + i++; + stopwatch.Stop(); +}, Timer.TimeEnum.Millisecond, "运行:"); +Console.WriteLine("运行毫秒:" + stopwatch.ElapsedMilliseconds); + */ + +/// +/// 时间定时类 +/// +public class Timer +{ + /// + /// 时间单位枚举 + /// + [Flags] + public enum TimeEnum + { + /// + /// 秒 + /// + Second, + /// + /// 毫秒 + /// + Millisecond, + /// + /// 微秒 + /// + Microsecond, + /// + /// 纳秒 + /// + Nanosecond, + } + + [DllImport("Kernel32.dll")] + static extern bool QueryPerformanceCounter(out long lpPerformanceCount); + + /// + /// 这个函数会检索性能计数器的频率. + /// 性能计数器的频率在系统启动时是固定的,并且在所有处理器上都是一致的 + /// 因此,只需在应用初始化时查询频率,即可缓存结果 + /// 在运行 Windows XP 或更高版本的系统上,该函数将始终成功,因此永远不会返回零 + /// + /// + /// + [DllImport("Kernel32.dll")] + static extern bool QueryPerformanceFrequency(out long lpFrequency); + + long _startTime, _stopTime; + readonly long _freq; + /// + /// 构造函数 + /// + /// + public Timer() + { + _startTime = 0; + _stopTime = 0; + + if (!QueryPerformanceFrequency(out _freq)) + throw new Win32Exception("不支持高性能计数器"); + } + + /// + /// 开始计时器 + /// + public void Start() + { + System.Threading.Thread.Sleep(0); + QueryPerformanceCounter(out _startTime); + } + + /// + /// 停止计时器 + /// + public void Stop() + { + QueryPerformanceCounter(out _stopTime); + _Second = (double)(_stopTime - _startTime) / _freq; + } + double _Second = 0; + + // 返回计时器经过时间 + /// + /// 秒 + /// + public double Second => _Second; + /// + /// 毫秒 + /// + public double Millisecond => _Second * 1000.0; + /// + /// 微秒 + /// + public double Microsecond => _Second * 1000000.0; + /// + /// 纳秒 + /// + public double Nanosecond => _Second * 1000000000.0; + /// + /// 计算执行委托的时间 + /// + /// 要执行的委托 + /// 时间单位 + /// 执行委托的时间 + public static double RunTime(Action action, + TimeEnum timeEnum = TimeEnum.Millisecond) + { + var nanoSecond = new Timer(); + nanoSecond.Start(); + action(); + nanoSecond.Stop(); + + var time = 0.0; + switch (timeEnum) + { + case TimeEnum.Second: + time = nanoSecond.Second; + break; + case TimeEnum.Millisecond: + time = nanoSecond.Millisecond; + break; + case TimeEnum.Microsecond: + time = nanoSecond.Microsecond; + break; + case TimeEnum.Nanosecond: + time = nanoSecond.Nanosecond; + break; + } + //string timeNameZn = ""; + //switch (timeEnum) + //{ + // case TimeEnum.Second: + // timeNameZn = " 秒"; + // break; + // case TimeEnum.Millisecond: + // timeNameZn = " 毫秒"; + // break; + // case TimeEnum.Microsecond: + // timeNameZn = " 微秒"; + // break; + // case TimeEnum.Nanosecond: + // timeNameZn = " 纳秒"; + // break; + //} + return time; + } +} \ No newline at end of file diff --git a/src/Basal/IFox.Basal.Shared/IFox.Basal.Shared.projitems b/src/Basal/IFox.Basal.Shared/IFox.Basal.Shared.projitems new file mode 100644 index 0000000000000000000000000000000000000000..814c3818c166aeba98c640233005b2acd6387cab --- /dev/null +++ b/src/Basal/IFox.Basal.Shared/IFox.Basal.Shared.projitems @@ -0,0 +1,42 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + c823514a-2bc2-45c2-aced-18924a7b80bf + + + IFox.Basal.Shared + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Basal/IFox.Basal.Shared/IFox.Basal.Shared.shproj b/src/Basal/IFox.Basal.Shared/IFox.Basal.Shared.shproj new file mode 100644 index 0000000000000000000000000000000000000000..e04b5b68e529f1f58915661291d96b71846eb154 --- /dev/null +++ b/src/Basal/IFox.Basal.Shared/IFox.Basal.Shared.shproj @@ -0,0 +1,13 @@ + + + + c823514a-2bc2-45c2-aced-18924a7b80bf + 14.0 + + + + + + + + diff --git a/src/Basal/IFox.Basal.Shared/Sortedset/ISet.cs b/src/Basal/IFox.Basal.Shared/Sortedset/ISet.cs new file mode 100644 index 0000000000000000000000000000000000000000..22b95092cf3957a7fee1c032bbc7eaa033db5286 --- /dev/null +++ b/src/Basal/IFox.Basal.Shared/Sortedset/ISet.cs @@ -0,0 +1,65 @@ +#if NET35 +// ==++== +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// ==--== +/*============================================================ +** +** Interface: ISet +** +** kimhamil +** +** +** Purpose: Base interface for all generic sets. +** +** +===========================================================*/ +namespace System.Collections.Generic +{ + using System; + using System.Runtime.CompilerServices; + + + /// + /// Generic collection that guarantees the uniqueness of its elements, as defined + /// by some comparer. It also supports basic set operations such as Union, Intersection, + /// Complement and Exclusive Complement. + /// + public interface ISet : ICollection + { + // Add ITEM to the set, return true if added, false if duplicate + new bool Add(T item); + + // Transform this set into its union with the IEnumerable other + void UnionWith(IEnumerable other); + + // Transform this set into its intersection with the IEnumberable other + void IntersectWith(IEnumerable other); + + // Transform this set so it contains no elements that are also in other + void ExceptWith(IEnumerable other); + + // Transform this set so it contains elements initially in this or in other, but not both + void SymmetricExceptWith(IEnumerable other); + + // Check if this set is a subset of other + bool IsSubsetOf(IEnumerable other); + + // Check if this set is a superset of other + bool IsSupersetOf(IEnumerable other); + + // Check if this set is a subset of other, but not the same as it + bool IsProperSupersetOf(IEnumerable other); + + // Check if this set is a superset of other, but not the same as it + bool IsProperSubsetOf(IEnumerable other); + + // Check if this set has any elements in common with other + bool Overlaps(IEnumerable other); + + // Check if this set contains the same and only the same elements as other + bool SetEquals(IEnumerable other); + } +} +#endif \ No newline at end of file diff --git a/src/Basal/IFox.Basal.Shared/Sortedset/SR.cs b/src/Basal/IFox.Basal.Shared/Sortedset/SR.cs new file mode 100644 index 0000000000000000000000000000000000000000..88b2bcf35a702a7d6f7ce1c8b5076234ec99799d --- /dev/null +++ b/src/Basal/IFox.Basal.Shared/Sortedset/SR.cs @@ -0,0 +1,902 @@ +#if NET35 +#pragma warning disable CS8603 // 可能返回 null 引用。 +namespace System; + + +using System; +using System.Reflection; +using System.Globalization; +using System.Resources; +using System.Text; +using System.ComponentModel; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +[AttributeUsage(AttributeTargets.All)] +internal sealed class SRDescriptionAttribute : DescriptionAttribute +{ + public SRDescriptionAttribute(string description) + { + DescriptionValue = SR.GetString(description); + } + + public SRDescriptionAttribute(string description, string resourceSet) + { + ResourceManager rm = new ResourceManager(resourceSet, Assembly.GetExecutingAssembly()); + DescriptionValue = rm.GetString(description); + System.Diagnostics.Debug.Assert(DescriptionValue != null, string.Format(CultureInfo.CurrentCulture, "String resource {0} not found.", new object[] { description })); + } +} + +[AttributeUsage(AttributeTargets.All)] +internal sealed class SRCategoryAttribute : CategoryAttribute +{ + private string resourceSet = String.Empty; + + public SRCategoryAttribute(string category) + : base(category) + { + } + + public SRCategoryAttribute(string category, string resourceSet) + : base(category) + { + this.resourceSet = resourceSet; + } + + protected override string GetLocalizedString(string value) + { + if (this.resourceSet.Length > 0) + { + ResourceManager rm = new ResourceManager(resourceSet, Assembly.GetExecutingAssembly()); + String localizedString = rm.GetString(value); + System.Diagnostics.Debug.Assert(localizedString != null, string.Format(CultureInfo.CurrentCulture, "String resource {0} not found.", new object[] { value })); + return localizedString; + } + else + { + return SR.GetString(value); + } + } +} + +[AttributeUsage(AttributeTargets.All)] +internal sealed class SRDisplayNameAttribute : DisplayNameAttribute +{ + public SRDisplayNameAttribute(string name) + { + DisplayNameValue = SR.GetString(name); + } + + public SRDisplayNameAttribute(string name, string resourceSet) + { + ResourceManager rm = new ResourceManager(resourceSet, Assembly.GetExecutingAssembly()); + DisplayNameValue = rm.GetString(name); + System.Diagnostics.Debug.Assert(DisplayNameValue != null, string.Format(CultureInfo.CurrentCulture, "String resource {0} not found.", new object[] { name })); + } +} + +/// +/// AutoGenerated resource class. Usage: +/// +/// string s = SR.GetString(SR.MyIdenfitier); +/// +internal sealed partial class SR +{ +#pragma warning disable CS8625 // 无法将 null 字面量转换为非 null 的引用类型。 + static SR loader = null; +#pragma warning restore CS8625 // 无法将 null 字面量转换为非 null 的引用类型。 + ResourceManager resources; + + internal SR() + { + resources = new System.Resources.ResourceManager("System.Workflow.ComponentModel.StringResources", Assembly.GetExecutingAssembly()); + } + + private static SR GetLoader() + { + if (loader == null) + loader = new SR(); + return loader; + } + + private static CultureInfo Culture + { + get { return null/*use ResourceManager default, CultureInfo.CurrentUICulture*/; } + } + + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal static string GetString(string name, params object[] args) + { + return GetString(SR.Culture, name, args); + } + + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal static string GetString(CultureInfo culture, string name, params object[] args) + { + SR sys = GetLoader(); + if (sys == null) + return null; + string res = sys.resources.GetString(name, culture); + System.Diagnostics.Debug.Assert(res != null, string.Format(CultureInfo.CurrentCulture, "String resource {0} not found.", new object[] { name })); + if (args != null && args.Length > 0) + return string.Format(CultureInfo.CurrentCulture, res, args); + return res; + } + + internal static string GetString(string name) + { + return GetString(SR.Culture, name); + } + + internal static string GetString(CultureInfo culture, string name) + { + SR sys = GetLoader(); + if (sys == null) + return null; + string res = sys.resources.GetString(name, culture); + System.Diagnostics.Debug.Assert(res != null, string.Format(CultureInfo.CurrentCulture, "String resource {0} not found.", new object[] { name })); + return res; + } + + // All these strings should be present in StringResources.resx + internal const string Activity = "Activity"; + internal const string Handlers = "Handlers"; + internal const string Conditions = "Conditions"; + internal const string ConditionedActivityConditions = "ConditionedActivityConditions"; + internal const string Correlations = "Correlations"; + internal const string CorrelationSet = "CorrelationSet"; + internal const string NameDescr = "NameDescr"; + internal const string EnabledDescr = "EnabledDescr"; + internal const string DescriptionDescr = "DescriptionDescr"; + internal const string UnlessConditionDescr = "UnlessConditionDescr"; + internal const string InitializeDescr = "InitializeDescr"; + internal const string CatchTypeDescr = "CatchTypeDescr"; + internal const string ExceptionTypeDescr = "ExceptionTypeDescr"; + internal const string FaultDescription = "FaultDescription"; + internal const string FaultTypeDescription = "FaultTypeDescription"; + internal const string ContainingAssemblyDescr = "ContainingAssemblyDescr"; + internal const string ExecutionModeDescr = "ExecutionModeDescr"; + internal const string Error_ReadOnlyTemplateActivity = "Error_ReadOnlyTemplateActivity"; + internal const string Error_TypeNotString = "Error_TypeNotString"; + internal const string Error_InvalidErrorType = "Error_InvalidErrorType"; + internal const string Error_LiteralConversionFailed = "Error_LiteralConversionFailed"; + internal const string Error_TypeNotPrimitive = "Error_TypeNotPrimitive"; + internal const string CompletedCaleeDescr = "CompletedCaleeDescr"; + internal const string ProxyClassDescr = "ProxyClassDescr"; + internal const string ActivitySetDescr = "ActivitySetDescr"; + internal const string VersionDescr = "VersionDescr"; + internal const string ActivationDescr = "ActivationDescr"; + internal const string CorrelationSetsDescr = "CorrelationSetsDescr"; + internal const string CompanionClassDescr = "CompanionClassDescr"; + internal const string TransactionTypeDescr = "TransactionTypeDescr"; + internal const string SynchronizedDescr = "SynchronizedDescr"; + internal const string IsolationLevelDescr = "IsolationLevelDescr"; + internal const string TimeoutDescr = "TimeoutDescr"; + internal const string BatchableDescr = "BatchableDescr"; + internal const string LRTTimeoutDescr = "LRTTimeoutDescr"; + internal const string OnGetCalleeCountDescr = "OnGetCalleeCountDescr"; + internal const string CompensatableActivityDescr = "CompensatableActivityDescr"; + internal const string OnAfterEventDescr = "OnAfterEventDescr"; + internal const string OnBeforeMethodInvokeDescr = "OnBeforeMethodInvokeDescr"; + internal const string AssignedToDescr = "AssignedToDescr"; + internal const string TypeDescr = "TypeDescr"; + internal const string TemplateActivityDescr = "TemplateActivityDescr"; + internal const string ErrorMessageDescr = "ErrorMessageDescr"; + internal const string WebServiceSynchronizedDescr = "WebServiceSynchronizedDescr"; + internal const string CorrelationSetDescr = "CorrelationSetDescr"; + internal const string ExecutionTypeDescr = "ExecutionTypeDescr"; + internal const string RoleDescr = "RoleDescr"; + internal const string OnInitializeClonesDescr = "OnInitializeClonesDescr"; + internal const string CorrelationSetDisplayName = "CorrelationSetDisplayName"; + internal const string PastingActivities = "PastingActivities"; + internal const string DeletingActivities = "DeletingActivities"; + internal const string DragDropActivities = "DragDropActivities"; + internal const string ChangingEnabled = "ChangingEnabled"; + internal const string ChangingHandler = "ChangingHandler"; + internal const string ChangingParameter = "ChangingParameter"; + internal const string CollectionItem = "CollectionItem"; + internal const string AddingConditionalBranch = "AddingConditionalBranch"; + internal const string AddingEventActivity = "AddingEventActivity"; + internal const string AddingListenBranch = "AddingListenBranch"; + internal const string AddingParallelBranch = "AddingParallelBranch"; + internal const string CurrentProject = "CurrentProject"; + internal const string ReferencedAssemblies = "ReferencedAssemblies"; + internal const string CollectionText = "CollectionText"; + internal const string ParameterDescription = "ParameterDescription"; + internal const string InvokeParameterDescription = "InvokeParameterDescription"; + internal const string ParametersDescription = "ParametersDescription"; + internal const string ChangingParameters = "ChangingParameters"; + internal const string Condition = "ConditionRule"; + internal const string MovingActivities = "MovingActivities"; + internal const string MemberNameDescr = "MemberNameDescr"; + internal const string OnScopeInitializedDescr = "OnScopeInitializedDescr"; + internal const string OnGeneratorInitializedDescr = "OnGeneratorInitializedDescr"; + internal const string OnScopeCompletedDescr = "OnScopeCompletedDescr"; + internal const string OnGeneratorCompletedDescr = "OnGeneratorCompletedDescr"; + internal const string DataElementRuntimeTypeDescr = "DataElementRuntimeTypeDescr"; + internal const string RuleConditionReferencesDescr = "RuleConditionReferencesDescr"; + internal const string CreateActivityFromToolbox = "CreateActivityFromToolbox"; + internal const string MoveMultipleActivities = "MoveMultipleActivities"; + internal const string MoveSingleActivity = "MoveSingleActivity"; + internal const string CutMultipleActivities = "CutMultipleActivities"; + internal const string CutSingleActivity = "CutSingleActivity"; + internal const string CutActivity = "CutActivity"; + internal const string FaultActivityDescription = "FaultActivityDescription"; + internal const string NullConditionExpression = "NullConditionExpression"; + internal const string ParameterTypeDescription = "ParameterTypeDescription"; + internal const string ParameterCategory = "ParameterCategory"; + internal const string ParameterDirectionDescription = "ParameterDirectionDescription"; + internal const string ParameterElementDescription = "ParameterElementDescription"; + internal const string ParameterDlgDescription = "ParameterDlgDescription"; + internal const string ParameterDlgHeader = "ParameterDlgHeader"; + internal const string SuspendActivityDescription = "SuspendActivityDescription"; + internal const string SuspendErrorMessageDescr = "SuspendErrorMessageDescr"; + internal const string TerminateActivityDescription = "TerminateActivityDescription"; + internal const string TerminateErrorMessageDescr = "TerminateErrorMessageDescr"; + internal const string DeclarationCategory = "DeclarationCategory"; + internal const string NoValidActivityPropertiesAvailable = "NoValidActivityPropertiesAvailable"; + internal const string ChooseActivityDatasource = "ChooseActivityDatasource"; + internal const string Promote = "Promote"; + internal const string Type = "Type"; + internal const string NoMatchingActivityProperties = "NoMatchingActivityProperties"; + internal const string ActivityBindIDDescription = "ActivityBindIDDescription"; + internal const string ActivityBindPathDescription = "ActivityBindPathDescription"; + internal const string XPathDescription = "XPathDescription"; + internal const string TransformerDescription = "TransformerDescription"; + internal const string CustomPropertiesCollectionFormHeader = "CustomPropertiesCollectionFormHeader"; + internal const string CustomPropertiesCollectionFormDescription = "CustomPropertiesCollectionFormDescription"; + internal const string BaseTypePropertyName = "BaseTypePropertyName"; + internal const string CustomActivityBaseClassTypeFilterProviderDesc = "CustomActivityBaseClassTypeFilterProviderDesc"; + internal const string CustomActivityDesignerTypeFilterProviderDesc = "CustomActivityDesignerTypeFilterProviderDesc"; + internal const string CustomActivityValidatorTypeFilterProviderDesc = "CustomActivityValidatorTypeFilterProviderDesc"; + internal const string CustomActivityExecutorTypeFilterProviderDesc = "CustomActivityExecutorTypeFilterProviderDesc"; + internal const string GenericParameters = "GenericParameters"; + internal const string ToolboxItem = "ToolboxItem"; + internal const string ToolboxItemCompanionClassDesc = "ToolboxItemCompanionClassDesc"; + internal const string Error_SerializationInsufficientState = "Error_SerializationInsufficientState"; + internal const string Error_ActivityHasParent = "Error_ActivityHasParent"; + internal const string Error_CompensantionParentNotScope = "Error_CompensantionParentNotScope"; + internal const string Error_ConditionedActivityParentNotCAG = "Error_ConditionedActivityParentNotCAG"; + internal const string Error_CorrelationTypeNotComparable = "Error_CorrelationTypeNotComparable"; + internal const string Error_ArgumentTypeNotMatchParameter = "Error_ArgumentTypeNotMatchParameter"; + internal const string Error_TypeTypeMismatch = "Error_TypeTypeMismatch"; + internal const string Error_ParameterTypeMismatch = "Error_ParameterTypeMismatch"; + internal const string Error_InvokeParameterTypeMismatch = "Error_InvokeParameterTypeMismatch"; + internal const string Error_ParameterPropertyNotSet = "Error_ParameterPropertyNotSet"; + internal const string Error_DataSourceNameNotSet = "Error_DataSourceNameNotSet"; + internal const string Error_DataSourceInvalidIdentifier = "Error_DataSourceInvalidIdentifier"; + internal const string Error_ParameterTypeNotExist = "Error_ParameterTypeNotExist"; + internal const string Error_InvalidParameterName = "Error_InvalidParameterName"; + internal const string Error_InvalidParameterType = "Error_InvalidParameterType"; + internal const string Error_InvalidParameterElement = "Error_InvalidParameterElement"; + internal const string Error_InvalidPropertyType = "Error_InvalidPropertyType"; + internal const string Error_TypeNotResolvedInMethodName = "Error_TypeNotResolvedInMethodName"; + internal const string Error_DelegateNoInvoke = "Error_DelegateNoInvoke"; + internal const string Error_TypeNotDelegate = "Error_TypeNotDelegate"; + internal const string Error_MethodSignatureMismatch = "Error_MethodSignatureMismatch"; + internal const string Error_MethodReturnTypeMismatch = "Error_MethodReturnTypeMismatch"; + internal const string Error_PropertyNotSet = "Error_PropertyNotSet"; + internal const string Error_ScopeCouldNotBeResolved = "Error_ScopeCouldNotBeResolved"; + internal const string Error_IfElseNotAllIfElseBranchDecl = "Error_ConditionalNotAllConditionalBranchDecl"; + internal const string Error_TypeTypeMismatchAmbiguity = "Error_TypeTypeMismatchAmbiguity"; + internal const string Error_InvalidCorrelationSetDatasource = "Error_InvalidCorrelationSetDatasource"; + internal const string Error_InvalidCorrelationSetType = "Error_InvalidCorrelationSetType"; + internal const string Error_MissingCorrelationParameterAttribute = "Error_MissingCorrelationParameterAttribute"; + internal const string Error_CorrelationTypeNotConsistent = "Error_CorrelationTypeNotConsistent"; + internal const string Error_CorrelationInvalid = "Error_CorrelationInvalid"; + internal const string Error_MissingDelegateMethod = "Error_MissingDelegateMethod"; + internal const string Error_MissingHostInterface = "Error_MissingHostInterface"; + internal const string Error_MissingMethodName = "Error_MissingMethodName"; + internal const string Error_NoBoundType = "Error_NoBoundType"; + internal const string Error_PortTypeNotAnInterface = "Error_PortTypeNotAnInterface"; + internal const string Error_MethodNotExists = "Error_MethodNotExists"; + internal const string Error_InvalidRequestResponseMethod = "Error_InvalidRequestResponseMethod"; + internal const string General_MissingService = "General_MissingService"; + internal const string Error_ScopeDuplicatedNameActivity = "Error_ScopeDuplicatedNameActivity"; + internal const string Error_DuplicatedActivityID = "Error_DuplicatedActivityID"; + internal const string Error_DuplicatedParameterName = "Error_DuplicatedParameterName"; + internal const string Error_ScopeMissingSerializableAttribute = "Error_ScopeMissingSerializableAttribute"; + internal const string Error_FieldNotExists = "Error_FieldNotExists"; + internal const string Error_PropertyNotExists = "Error_PropertyNotExists"; + internal const string Error_FieldTypeMismatch = "Error_FieldTypeMismatch"; + internal const string Error_PropertyTypeMismatch = "Error_PropertyTypeMismatch"; + internal const string Error_TypeNotResolvedInFieldName = "Error_TypeNotResolvedInFieldName"; + internal const string Error_TypeNotResolvedInPropertyName = "Error_TypeNotResolvedInPropertyName"; + internal const string Error_FieldGenericParamTypeMismatch = "Error_FieldGenericParamTypeMismatch"; + internal const string Error_TypeNotResolved = "Error_TypeNotResolved"; + internal const string Error_TypeIsUnboundedGeneric = "Error_TypeIsUnboundedGeneric"; + internal const string Error_MissingRootActivity = "Error_MissingRootActivity"; + internal const string Error_PropertyNotReadable = "Error_PropertyNotReadable"; + internal const string Error_PropertyNotWritable = "Error_PropertyNotWritable"; + internal const string Error_NotCompositeActivity = "Error_NotCompositeActivity"; + internal const string Error_TypeNotExist = "Error_TypeNotExist"; + internal const string Error_ActivityRefNotResolved = "Error_ActivityRefNotResolved"; + internal const string Error_ActivityRefNotMatchType = "Error_ActivityRefNotMatchType"; + internal const string Error_ActivityValidation = "Error_ActivityValidation"; + internal const string Error_ActiveChildExist = "Error_ActiveChildExist"; + internal const string Error_ActiveChildContextExist = "Error_ActiveChildContextExist"; + internal const string Error_CannotCompleteContext = "Error_CannotCompleteContext"; + internal const string Error_NoPasteSupport = "Error_NoPasteSupport"; + internal const string Error_UnknownSerializationStore = "Error_UnknownSerializationStore"; + internal const string Error_MissingCorrelationSet = "Error_MissingCorrelationSet"; + internal const string Error_CreateVariable = "Error_CreateVariable"; + internal const string Error_DuplicateCorrelationSetName = "Error_DuplicateCorrelationSetName"; + internal const string Error_DragDropInvalid = "Error_DragDropInvalid"; + internal const string AddingImplicitActivity = "AddingImplicitActivity"; + internal const string Failure_DoDefaultAction = "Failure_DoDefaultAction"; + internal const string Failure_DoDefaultActionCaption = "Failure_DoDefaultActionCaption"; + internal const string Error_FaultInsideAtomicScope = "Error_FaultInsideAtomicScope"; + internal const string Error_ListenNotMoreThanOneDelay = "Error_ListenNotMoreThanOneDelay"; + internal const string Error_AtomicScopeWithFaultHandlersActivityDecl = "Error_AtomicScopeWithFaultHandlersActivityDecl"; + internal const string Error_AtomicScopeWithCancellationHandlerActivity = "Error_AtomicScopeWithCancellationHandlerActivity"; + internal const string Error_ScopeDuplicateFaultHandlerActivityForAll = "Error_ScopeDuplicateFaultHandlerActivityForAll"; + internal const string Error_ScopeDuplicateFaultHandlerActivityFor = "Error_ScopeDuplicateFaultHandlerActivityFor"; + internal const string Error_AtomicScopeNestedInNonLRT = "Error_AtomicScopeNestedInNonLRT"; + internal const string Error_LRTScopeNestedInNonLRT = "Error_LRTScopeNestedInNonLRT"; + internal const string Error_CAGNotAllChildrenConditioned = "Error_CAGNotAllChildrenConditioned"; + internal const string Error_ConditionedActivityChildCount = "Error_ConditionedActivityChildCount"; + internal const string Error_NegativeValue = "Error_NegativeValue"; + internal const string Error_MethodWithReturnType = "Error_MethodWithReturnType"; + internal const string Error_SendReceiveOrderIncorrect = "Error_SendReceiveOrderIncorrect"; + internal const string Error_ReceiveSendOrderIncorrect = "Error_ReceiveSendOrderIncorrect"; + internal const string Error_CompensateBadNesting = "Error_CompensateBadNesting"; + internal const string Error_ReferencedAssemblyIsInvalid = "Error_ReferencedAssemblyIsInvalid"; + internal const string Error_TypeToXsdConversion = "Error_TypeToXsdConversion"; + internal const string Error_FieldTypeNotResolved = "Error_FieldTypeNotResolved"; + internal const string Error_PropertyTypeNotResolved = "Error_PropertyTypeNotResolved"; + internal const string Error_CouldNotDeserializeXomlFile = "Error_CouldNotDeserializeXomlFile"; + internal const string Error_InternalCompilerError = "Error_InternalCompilerError"; + internal const string Error_TypeNotAsseblyQualified = "Error_TypeNotAsseblyQualified"; + internal const string CompilerWarning_StandardAssemlbyInReferences = "CompilerWarning_StandardAssemlbyInReferences"; + internal const string Error_SuspendInAtomicScope = "Error_SuspendInAtomicScope"; + internal const string Error_InvalidActivityExecutionContext = "Error_InvalidActivityExecutionContext"; + internal const string Error_NoRuntimeAvailable = "Error_NoRuntimeAvailable"; + internal const string Error_CanNotChangeAtRuntime = "Error_CanNotChangeAtRuntime"; + internal const string Error_DataContextNotInitialized = "Error_DataContextNotInitialized"; + internal const string Error_DataContextAlreadyInitialized = "Error_DataContextAlreadyInitialized"; + internal const string Error_ParseActivityNameDoesNotExist = "Error_ParseActivityNameDoesNotExist"; + internal const string Error_NoParameterPropertyDeclared = "Error_NoParameterPropertyDeclared"; + internal const string Error_PropertyInvalidIdentifier = "Error_PropertyInvalidIdentifier"; + internal const string Error_WorkflowDefinitionModified = "Error_WorkflowDefinitionModified"; + internal const string Error_FieldAlreadyExist = "Error_FieldAlreadyExist"; + internal const string Failure_FieldAlreadyExist = "Failure_FieldAlreadyExist"; + internal const string Error_DifferentTypeFieldExists = "Error_DifferentTypeFieldExists"; + internal const string Error_RootActivityTypeInvalid = "Error_RootActivityTypeInvalid"; + internal const string Error_RootActivityTypeInvalid2 = "Error_RootActivityTypeInvalid2"; + internal const string Error_CannotCompile_No_XClass = "Error_CannotCompile_No_XClass"; + internal const string Error_TemplateActivityIsNotActivity = "Error_TemplateActivityIsNotActivity"; + internal const string Error_TypeIsNotRootActivity = "Error_TypeIsNotRootActivity"; + internal const string Error_NoTypeProvider = "Error_NoTypeProvider"; + internal const string Error_NotCodeGeneratorType = "Error_NotCodeGeneratorType"; + internal const string Error_NotDataContext = "Error_NotDataContext"; + internal const string Error_MissingDefaultConstructor = "Error_MissingDefaultConstructor"; + internal const string Error_ContextStackItemMissing = "Error_ContextStackItemMissing"; + internal const string Error_UnexpectedArgumentType = "Error_UnexpectedArgumentType"; + internal const string Error_EmptyArgument = "Error_EmptyArgument"; + internal const string Error_DPAlreadyExist = "Error_DPAlreadyExist"; + internal const string Error_DuplicateDynamicProperty = "Error_DuplicateDynamicProperty"; + internal const string Error_DynamicPropertyTypeValueMismatch = "Error_DynamicPropertyTypeValueMismatch"; + internal const string Error_DynamicPropertyNoSupport = "Error_DynamicPropertyNoSupport"; + internal const string Error_NoContextForDatasource = "Error_NoContextForDatasource"; + internal const string Error_NoContextForDatasourceCaption = "Error_NoContextForDatasourceCaption"; + internal const string Error_DataSourceHasParent = "Error_DataSourceHasParent"; + internal const string OnTaskCompletedDescr = "OnTaskCompletedDescr"; + internal const string OnTaskInitializedDescr = "OnTaskInitializedDescr"; + internal const string Error_InvalidXmlData = "Error_InvalidXmlData"; + internal const string Error_HandlerNotOnRoot = "Error_HandlerNotOnRoot"; + internal const string Error_InvalidArgumentIndex = "Error_InvalidArgumentIndex"; + internal const string Error_UITypeEditorTypeNotUITypeEditor = "Error_UITypeEditorTypeNotUITypeEditor"; + internal const string FilterDescription_UITypeEditor = "FilterDescription_UITypeEditor"; + internal const string Error_UserCodeFilesNotAllowed = "Error_UserCodeFilesNotAllowed"; + internal const string Error_CodeWithinNotAllowed = "Error_CodeWithinNotAllowed"; + internal const string Error_TypeNotAuthorized = "Error_TypeNotAuthorized"; + internal const string Error_CantDetermineBaseType = "Error_CantDetermineBaseType"; + internal const string Error_MultipleSelectNotSupportedForBindAndPromote = "Error_MultipleSelectNotSupportedForBindAndPromote"; + internal const string Error_CantDetermineBaseTypeCaption = "Error_CantDetermineBaseTypeCaption"; + internal const string Error_CantDeterminePropertyBaseType = "Error_CantDeterminePropertyBaseType"; + internal const string Error_NullCustomActivityTypeName = "Error_NullCustomActivityTypeName"; + internal const string Error_InvalidAttribute = "Error_InvalidAttribute"; + internal const string Error_InvalidAttributes = "Error_InvalidAttributes"; + internal const string Error_ConfigFileMissingOrInvalid = "Error_ConfigFileMissingOrInvalid"; + internal const string Error_CantHaveContextActivity = "Error_CantHaveContextActivity"; + internal const string Error_SynchronizedNeedsDataContext = "Error_SynchronizedNeedsDataContext"; + internal const string Error_MoreThanOneFaultHandlersActivityDecl = "Error_MoreThanOneFaultHandlersActivityDecl"; + internal const string Error_MoreThanOneEventHandlersDecl = "Error_MoreThanOneEventHandlersDecl"; + internal const string Error_MoreThanOneCancelHandler = "Error_MoreThanOneCancelHandler"; + internal const string Error_MetaDataInterfaceMissing = "Error_MetaDataInterfaceMissing"; + internal const string Error_NonActivityExecutor = "Error_NonActivityExecutor"; + internal const string Error_DynamicUpdateEvaluation = "Error_DynamicUpdateEvaluation"; + internal const string Error_CollectionHasNullEntry = "Error_CollectionHasNullEntry"; + internal const string Error_MissingContextProperty = "Error_MissingContextProperty"; + internal const string Error_AssociatedDesignerMissing = "Error_AssociatedDesignerMissing"; + internal const string Error_MissingContextActivityProperty = "Error_MissingContextActivityProperty"; + internal const string Error_MissingActivityProperty = "Error_MissingActivityProperty"; + internal const string Error_MissingOwnerTypeProperty = "Error_MissingOwnerTypeProperty"; + internal const string Error_DOIsNotAnActivity = "Error_DOIsNotAnActivity"; + internal const string Error_PropertyCanBeOnlyCleared = "Error_PropertyCanBeOnlyCleared"; + internal const string Error_PropertyDefaultTypeMismatch = "Error_PropertyDefaultTypeMismatch"; + internal const string Error_PropertyDefaultIsReference = "Error_PropertyDefaultIsReference"; + // workflow load errors + internal const string Error_WorkflowLoadFailed = "Error_WorkflowLoadFailed"; + internal const string Error_WorkflowLoadValidationFailed = "Error_WorkflowLoadValidationFailed"; + internal const string Error_WorkflowLoadDeserializationFailed = "Error_WorkflowLoadDeserializationFailed"; + internal const string Error_WorkflowLoadTypeMismatch = "Error_WorkflowLoadTypeMismatch"; + internal const string Error_WorkflowLoadInvalidXoml = "Error_WorkflowLoadInvalidXoml"; + internal const string Error_WorkflowLoadNotValidRootType = "Error_WorkflowLoadNotValidRootType"; + internal const string Error_CantCreateInstanceOfComponent = "Error_CantCreateInstanceOfComponent"; + internal const string Error_NotComponentFactoryType = "Error_NotComponentFactoryType"; + internal const string Error_WorkflowTerminated = "Error_WorkflowTerminated"; + + // serializer errrors + internal const string Error_SerializerAttributesFoundInComplexProperty = "Error_SerializerAttributesFoundInComplexProperty"; + internal const string Error_InvalidDataFound = "Error_InvalidDataFound"; + internal const string Error_InvalidDataFoundForType = "Error_InvalidDataFoundForType"; + internal const string Error_InvalidDataFoundForType1 = "Error_InvalidDataFoundForType1"; + internal const string Error_SerializerTypeNotResolved = "Error_SerializerTypeNotResolved"; + internal const string Error_MarkupSerializerTypeNotResolved = "Error_MarkupSerializerTypeNotResolved"; + internal const string Error_SerializerTypeNotResolvedWithInnerError = "Error_SerializerTypeNotResolvedWithInnerError"; + internal const string Error_SerializerNotAvailable = "Error_SerializerNotAvailable"; + internal const string Error_SerializerNotAvailableForSerialize = "Error_SerializerNotAvailableForSerialize"; + internal const string Error_SerializerCreateInstanceFailed = "Error_SerializerCreateInstanceFailed"; + internal const string Error_SerializerAddChildFailed = "Error_SerializerAddChildFailed"; + internal const string Error_SerializerNoPropertyAvailable = "Error_SerializerNoPropertyAvailable"; + internal const string Error_SerializerPrimitivePropertyReadOnly = "Error_SerializerPrimitivePropertyReadOnly"; + internal const string Error_SerializerCantChangeIsLocked = "Error_SerializerCantChangeIsLocked"; + internal const string Error_SerializerPrimitivePropertySetFailed = "Error_SerializerPrimitivePropertySetFailed"; + internal const string Error_SerializerPropertyGetFailed = "Error_SerializerPropertyGetFailed"; + internal const string Error_SerializerPrimitivePropertyNoLogic = "Error_SerializerPrimitivePropertyNoLogic"; + internal const string Error_SerializerPrimitivePropertyParentIsNull = "Error_SerializerPrimitivePropertyParentIsNull"; + internal const string Error_SerializerComplexPropertySetFailed = "Error_SerializerComplexPropertySetFailed"; + internal const string Error_SerializerNoChildNotion = "Error_SerializerNoChildNotion"; + internal const string Error_SerializerNoDynamicPropertySupport = "Error_SerializerNoDynamicPropertySupport"; + internal const string Error_SerializerNoSerializeLogic = "Error_SerializerNoSerializeLogic"; + internal const string Error_SerializerReadOnlyPropertyAndValueIsNull = "Error_SerializerReadOnlyPropertyAndValueIsNull"; + internal const string Error_SerializerReadOnlyParametersNoChild = "Error_SerializerReadOnlyParametersNoChild"; + internal const string Error_SerializerNotParameterBindingObject = "Error_SerializerNotParameterBindingObject"; + internal const string Error_SerializerThrewException = "Error_SerializerThrewException"; + internal const string Error_ActivityCollectionSerializer = "Error_ActivityCollectionSerializer"; + internal const string Error_MissingClassAttribute = "Error_MissingClassAttribute"; + internal const string Error_MissingClassAttributeValue = "Error_MissingClassAttributeValue"; + internal const string ExecutorCreationFailedErrorMessage = "ExecutorCreationFailedErrorMessage"; + internal const string VariableGetterCode_VB = "VariableGetterCode_VB"; + internal const string VariableGetterCode_CS = "VariableGetterCode_CS"; + internal const string VariableSetterCode_VB = "VariableSetterCode_VB"; + internal const string VariableSetterCode_CS = "VariableSetterCode_CS"; + internal const string StaticVariableGetterCode_VB = "StaticVariableGetterCode_VB"; + internal const string StaticVariableGetterCode_CS = "StaticVariableGetterCode_CS"; + internal const string StaticVariableSetterCode_VB = "StaticVariableSetterCode_VB"; + internal const string StaticVariableSetterCode_CS = "StaticVariableSetterCode_CS"; + internal const string EnterCodeBesidesCode_VB = "EnterCodeBesidesCode_VB"; + internal const string EnterCodeBesidesCode_CS = "EnterCodeBesidesCode_CS"; + internal const string LeaveCodeBesides1Code_VB = "LeaveCodeBesides1Code_VB"; + internal const string LeaveCodeBesides2Code_VB = "LeaveCodeBesides2Code_VB"; + internal const string LeaveCodeBesides1Code_CS = "LeaveCodeBesides1Code_CS"; + internal const string LeaveCodeBesides2Code_CS = "LeaveCodeBesides2Code_CS"; + internal const string VariableSetterName = "VariableSetterName"; + internal const string VariableGetterName = "VariableGetterName"; + internal const string HandlerGetterName = "HandlerGetterName"; + internal const string WorkflowCreatorName = "WorkflowCreatorName"; + internal const string ActivityMethod = "ActivityMethod"; + internal const string CustomActivityPrivateField = "CustomActivityPrivateField"; + internal const string InitializedVariableDeclaration_VB = "InitializedVariableDeclaration_VB"; + internal const string InitializedVariableDeclaration_CS = "InitializedVariableDeclaration_CS"; + internal const string In = "In"; + internal const string Out = "Out"; + internal const string Ref = "Ref"; + internal const string Required = "Required"; + internal const string Optional = "Optional"; + internal const string Parameters = "Parameters"; + internal const string Properties = "Properties"; + internal const string Error_RecursionDetected = "Error_RecursionDetected"; + internal const string Warning_UnverifiedRecursion = "Warning_UnverifiedRecursion"; + internal const string AddConstructorCode = "AddConstructorCode"; + internal const string Error_UninitializedCorrelation = "Error_UninitializedCorrelation"; + internal const string Error_CorrelationAlreadyInitialized = "Error_CorrelationAlreadyInitialized"; + internal const string Error_CorrelatedSendReceiveAtomicScope = "Error_CorrelatedSendReceiveAtomicScope"; + internal const string Warning_ActivityValidation = "Warning_ActivityValidation"; + internal const string Warning_EmptyBehaviourActivity = "Warning_EmptyBehaviourActivity"; + internal const string Error_ParallelActivationNoCorrelation = "Error_ParallelActivationNoCorrelation"; + internal const string Error_MethodNotAccessible = "Error_MethodNotAccessible"; + internal const string Error_FieldNotAccessible = "Error_FieldNotAccessible"; + internal const string Error_PropertyNotAccessible = "Error_PropertyNotAccessible"; + internal const string Error_GenericArgumentsNotAllowed = "Error_GenericArgumentsNotAllowed"; + internal const string Error_InvalidIdentifier = "Error_InvalidIdentifier"; + internal const string Error_InvalidLanguageIdentifier = "Error_InvalidLanguageIdentifier"; + internal const string DuplicateActivityIdentifier = "DuplicateActivityIdentifier"; + internal const string Error_MissingAttribute = "Error_MissingAttribute"; + internal const string Error_LoadUIPropertiesFile = "Error_LoadUIPropertiesFile"; + internal const string Error_SerializerEventGetFailed = "Error_SerializerEventGetFailed"; + internal const string Error_SerializerEventFailed = "Error_SerializerEventFailed"; + internal const string Error_SerializerNoMemberFound = "Error_SerializerNoMemberFound"; + internal const string Error_DynamicEventConflict = "Error_DynamicEventConflict"; + internal const string Error_SerializerMemberSetFailed = "Error_SerializerMemberSetFailed"; + internal const string Error_ContentPropertyCouldNotBeFound = "Error_ContentPropertyCouldNotBeFound"; + internal const string Error_ContentPropertyValueInvalid = "Error_ContentPropertyValueInvalid"; + internal const string Error_ContentPropertyNoSetter = "Error_ContentPropertyNoSetter"; + internal const string Error_ContentCanNotBeConverted = "Error_ContentCanNotBeConverted"; + internal const string Error_ContentPropertyCanNotBeNull = "Error_ContentPropertyCanNotBeNull"; + internal const string Error_SerializerTypeMismatch = "Error_SerializerTypeMismatch"; + internal const string Error_CouldNotAddValueInContentProperty = "Error_CouldNotAddValueInContentProperty"; + internal const string Error_SerializerTypeRequirement = "Error_SerializerTypeRequirement"; + internal const string Error_CanNotAddActivityInBlackBoxActivity = "Error_CanNotAddActivityInBlackBoxActivity"; + internal const string Error_ContentPropertyCanNotSupportCompactFormat = "Error_ContentPropertyCanNotSupportCompactFormat"; + internal const string Error_ContentPropertyNoMultipleContents = "Error_ContentPropertyNoMultipleContents"; + internal const string Error_InternalSerializerError = "Error_InternalSerializerError"; + internal const string Error_DictionarySerializerNonDictionaryObject = "Error_DictionarySerializerNonDictionaryObject"; + internal const string Error_DictionarySerializerKeyNotFound = "Error_DictionarySerializerKeyNotFound"; + internal const string Error_InvalidCancelActivityState = "Error_InvalidCancelActivityState"; + internal const string Error_InvalidCompensateActivityState = "Error_InvalidCompensateActivityState"; + internal const string Error_InvalidCloseActivityState = "Error_InvalidCloseActivityState"; + internal const string Error_SealedPropertyMetadata = "Error_SealedPropertyMetadata"; + internal const string Error_MemberNotFound = "Error_MemberNotFound"; + internal const string Error_EmptyPathValue = "Error_EmptyPathValue"; + internal const string Error_InvalidCompensatingState = "Error_InvalidCompensatingState"; + internal const string Error_InvalidCancelingState = "Error_InvalidCancelingState"; + internal const string Error_InvalidClosingState = "Error_InvalidClosingState"; + internal const string Error_InvalidStateToExecuteChild = "Error_InvalidStateToExecuteChild"; + internal const string Error_InvalidExecutionState = "Error_InvalidExecutionState"; + internal const string Error_InvalidInitializingState = "Error_InvalidInitializingState"; + internal const string Error_InvalidInvokingState = "Error_InvalidInvokingState"; + internal const string Error_NotRegisteredAs = "Error_NotRegisteredAs"; + internal const string Error_AlreadyRegisteredAs = "Error_AlreadyRegisteredAs"; + internal const string Error_InsertingChildControls = "Error_InsertingChildControls"; + internal const string Error_EmptyToolTipRectangle = "Error_EmptyToolTipRectangle"; + internal const string Error_EmptyRectangleValue = "Error_EmptyRectangleValue"; + internal const string Error_InvalidShadowRectangle = "Error_InvalidShadowRectangle"; + internal const string Error_InvalidShadowDepth = "Error_InvalidShadowDepth"; + internal const string Error_InvalidLightSource = "Error_InvalidLightSource"; + internal const string Error_ChangingDock = "Error_ChangingDock"; + internal const string Error_NullOrEmptyValue = "Error_NullOrEmptyValue"; + internal const string Error_InvalidStateImages = "Error_InvalidStateImages"; + internal const string Error_InvalidConnectorSegment = "Error_InvalidConnectorSegment"; + internal const string Error_InvalidConnectorSource = "Error_InvalidConnectorSource"; + internal const string Error_CreatingToolTip = "Error_CreatingToolTip"; + internal const string Error_InvalidDockStyle = "Error_InvalidDockStyle"; + internal const string Error_InvalidConnectorValue = "Error_InvalidConnectorValue"; + internal const string Error_InvalidDesignerVerbValue = "Error_InvalidDesignerVerbValue"; + internal const string Error_InvalidRuntimeType = "Error_InvalidRuntimeType"; + internal const string Error_InvalidArgumentValue = "Error_InvalidArgumentValue"; + internal const string Error_InvalidRadiusValue = "Error_InvalidRadiusValue"; + internal const string ToolTipString = "ToolTipString"; + + // Collection Editor Resources + internal const string CollectionEditorCaption = "CollectionEditorCaption"; + internal const string CollectionEditorProperties = "CollectionEditorProperties"; + internal const string CollectionEditorPropertiesMultiSelect = "CollectionEditorPropertiesMultiSelect"; + internal const string CollectionEditorPropertiesNone = "CollectionEditorPropertiesNone"; + internal const string CollectionEditorCantRemoveItem = "CollectionEditorCantRemoveItem"; + internal const string CollectionEditorUndoBatchDesc = "CollectionEditorUndoBatchDesc"; + internal const string CollectionEditorInheritedReadOnlySelection = "CollectionEditorInheritedReadOnlySelection"; + internal const string Error_ParameterAlreadyExists = "Error_ParameterAlreadyExists"; + internal const string Error_PropertyAlreadyExists = "Error_PropertyAlreadyExists"; + internal const string Error_HiddenPropertyAlreadyExists = "Error_HiddenPropertyAlreadyExists"; + internal const string Error_CorrelationInUse = "Error_CorrelationInUse"; + internal const string Error_ItemNotExists = "Error_ItemNotExists"; + internal const string Error_NoHelpAvailable = "Error_NoHelpAvailable"; + internal const string Error_DuplicateWorkflow = "Error_DuplicateWorkflow"; + internal const string Error_Recursion = "Error_Recursion"; + internal const string Error_RootActivity = "Error_RootActivity"; + internal const string Error_ConditionDefinitionDeserializationFailed = "Error_ConditionDefinitionDeserializationFailed"; + internal const string Error_InvalidConditionDefinition = "Error_InvalidConditionDefinition"; + internal const string SR_InvokeTransactionalFromAtomic = "SR_InvokeTransactionalFromAtomic"; + internal const string Error_SuspendInAtomicCallChain = "Error_SuspendInAtomicCallChain"; + internal const string Error_LiteralPassedToOutRef = "Error_LiteralPassedToOutRef"; + internal const string Error_GeneratorShouldContainSingleActivity = "Error_GeneratorShouldContainSingleActivity"; + internal const string Error_DeclaringPropertyNotSupported = "Error_DeclaringPropertyNotSupported"; + internal const string Error_DeclaringEventNotSupported = "Error_DeclaringEventNotSupported"; + internal const string Error_DynamicEventNotSupported = "Error_DynamicEventNotSupported"; + internal const string Error_DynamicPropertyNotSupported = "Error_DynamicPropertyNotSupported"; + internal const string Error_ParameterTypeResolution = "Error_ParameterTypeResolution"; + + // Dynamic Validations + internal const string Error_DynamicActivity = "Error_DynamicActivity"; + internal const string Error_DynamicActivity2 = "Error_DynamicActivity2"; + internal const string Error_CompilerValidationFailed = "Error_CompilerValidationFailed"; + internal const string Error_RuntimeValidationFailed = "Error_RuntimeValidationFailed"; + internal const string Error_TransactionAlreadyCanceled = "Error_TransactionAlreadyCanceled"; + internal const string Error_RemoveExecutingActivity = "Error_RemoveExecutingActivity"; + internal const string Error_InsideAtomicScope = "Error_InsideAtomicScope"; + internal const string SuspendReason_WorkflowChange = "SuspendReason_WorkflowChange"; + + // type filtering + internal const string FilterDescription_ParameterDeclaration = "FilterDescription_ParameterDeclaration"; + internal const string FilterDescription_GenericArgument = "FilterDescription_GenericArgument"; + + + internal const string LibraryPathIsInvalid = "LibraryPathIsInvalid"; + + // Activity Set + internal const string Error_CreateValidator = "Error_CreateValidator"; + internal const string Error_InvalidPackageFile = "Error_InvalidPackageFile"; + internal const string Error_AddAssemblyRef = "Error_AddAssemblyRef"; + internal const string Error_AssemblyBadImage = "Error_AssemblyBadImage"; + internal const string BindPropertySetterName = "BindPropertySetterName"; + + // Bind validations + internal const string Error_CannotResolveActivity = "Error_CannotResolveActivity"; + internal const string Error_CannotResolveRelativeActivity = "Error_CannotResolveRelativeActivity"; + internal const string Error_PathNotSetForActivitySource = "Error_PathNotSetForActivitySource"; + internal const string Error_InvalidMemberPath = "Error_InvalidMemberPath"; + internal const string Error_TargetTypeMismatch = "Error_TargetTypeMismatch"; + internal const string Warning_ParameterBinding = "Warning_ParameterBinding"; + internal const string Error_ReferencedActivityPropertyNotBind = "Error_ReferencedActivityPropertyNotBind"; + internal const string Error_TargetTypeDataSourcePathMismatch = "Error_TargetTypeDataSourcePathMismatch"; + internal const string Bind_ActivityDataSourceRecursionDetected = "Bind_ActivityDataSourceRecursionDetected"; + internal const string Bind_DuplicateDataSourceNames = "Bind_DuplicateDataSourceNames"; + internal const string Error_PathNotSetForXmlDataSource = "Error_PathNotSetForXmlDataSource"; + internal const string Error_XmlDocumentLoadFailed = "Error_XmlDocumentLoadFailed"; + internal const string Error_XmlDataSourceInvalidPath = "Error_XmlDataSourceInvalidPath"; + internal const string Error_XmlDataSourceMultipleNodes = "Error_XmlDataSourceMultipleNodes"; + internal const string Error_XmlDataSourceInvalidXPath = "Error_XmlDataSourceInvalidXPath"; + internal const string Error_InvalidObjectRefFormat = "Error_InvalidObjectRefFormat"; + internal const string Error_ReadOnlyDataSource = "Error_ReadOnlyDataSource"; + internal const string Error_HandlerReadOnly = "Error_HandlerReadOnly"; + internal const string Error_XmlDataSourceReadOnly = "Error_XmlDataSourceReadOnly"; + internal const string Error_DataSourceNotExist = "Error_DataSourceNotExist"; + internal const string Error_PropertyNoGetter = "Error_PropertyNoGetter"; + internal const string Error_PropertyNoSetter = "Error_PropertyNoSetter"; + internal const string Error_PropertyHasNoGetterDefined = "Error_PropertyHasNoGetterDefined"; + internal const string Error_PropertyHasNoSetterDefined = "Error_PropertyHasNoSetterDefined"; + internal const string Error_PropertyReferenceNoGetter = "Error_PropertyReferenceNoGetter"; + internal const string Error_PropertyReferenceGetterNoAccess = "Error_PropertyReferenceGetterNoAccess"; + internal const string Error_PropertyHasIndexParameters = "Error_PropertyHasIndexParameters"; + internal const string Error_ReadOnlyField = "Error_ReadOnlyField"; + internal const string Error_NoEnclosingContext = "Error_NoEnclosingContext"; + internal const string Error_NestedPersistOnClose = "Error_NestedPersistOnClose"; + internal const string Error_NestedCompensatableActivity = "Error_NestedCompensatableActivity"; + internal const string Error_InvalidActivityForObjectDatasource = "Error_InvalidActivityForObjectDatasource"; + internal const string Error_DataSourceTypeConversionFailed = "Error_DataSourceTypeConversionFailed"; + internal const string Error_BindDialogWrongPropertyType = "Error_BindDialogWrongPropertyType"; + internal const string Error_BindDialogNoValidPropertySelected = "Error_BindDialogNoValidPropertySelected"; + internal const string Error_BindDialogBindNotValid = "Error_BindDialogBindNotValid"; + internal const string Error_BindDialogCanNotBindToItself = "Error_BindDialogCanNotBindToItself"; + internal const string Error_BindActivityReference = "Error_BindActivityReference"; + internal const string Error_NoTargetTypeForMethod = "Error_NoTargetTypeForMethod"; + internal const string Error_MethodDataSourceIsReadOnly = "Error_MethodDataSourceIsReadOnly"; + internal const string Error_NotMethodDataSource = "Error_NotMethodDataSource"; + internal const string Error_MethodDataSourceWithPath = "Error_MethodDataSourceWithPath"; + internal const string Error_PathSyntax = "Error_PathSyntax"; + internal const string Error_UnmatchedParen = "Error_UnmatchedParen"; + internal const string Error_UnmatchedBracket = "Error_UnmatchedBracket"; + internal const string Error_MemberWithSameNameExists = "Error_MemberWithSameNameExists"; + internal const string Error_ActivityIdentifierCanNotBeEmpty = "Error_ActivityIdentifierCanNotBeEmpty"; + internal const string Error_InvalidActivityIdentifier = "Error_InvalidActivityIdentifier"; + internal const string Error_ActivityBindTypeConversionError = "Error_ActivityBindTypeConversionError"; + internal const string EmptyValue = "EmptyValue"; + internal const string Error_PropertyTypeNotDefined = "Error_PropertyTypeNotDefined"; + + internal const string Error_CompilationFailed = "Error_CompilationFailed"; + internal const string Error_MissingCompilationContext = "Error_MissingCompilationContext"; + + internal const string InvokeWorkflowReference_VB = "InvokeWorkflowReference_VB"; + internal const string InvokeWorkflowReference_CS = "InvokeWorkflowReference_CS"; + internal const string Error_InvalidListItem = "Error_InvalidListItem"; + + internal const string ParserMapPINoWhitespace = "ParserMapPINoWhitespace"; + internal const string ParserMapPIBadCharEqual = "ParserMapPIBadCharEqual"; + internal const string ParserMapPIBadCharQuote = "ParserMapPIBadCharQuote"; + internal const string ParserMapPIBadKey = "ParserMapPIBadKey"; + internal const string ParserMapPIMissingKey = "ParserMapPIMissingKey"; + internal const string ParserMapPIKeyNotSet = "ParserMapPIKeyNotSet"; + internal const string ParserMismatchDelimiter = "ParserMismatchDelimiter"; + internal const string ParserDanglingClause = "ParserDanglingClause"; + internal const string UnknownDefinitionTag = "UnknownDefinitionTag"; + internal const string CDATASection = "CDATASection"; + internal const string TextSection = "TextSection"; + internal const string IncorrectSyntax = "IncorrectSyntax"; + internal const string IncorrectTypeSyntax = "IncorrectTypeSyntax"; + internal const string Error_MultipleRootActivityCreator = "Error_MultipleRootActivityCreator"; + internal const string Error_MustHaveParent = "Error_MustHaveParent"; + + // Workflow References + internal const string Error_ReferenceObjNotInitialized = "Error_ReferenceObjNotInitialized"; + internal const string Error_ReferenceInitResourceManager = "Error_ReferenceInitResourceManager"; + internal const string Error_ResourceReferenceGetObject = "Error_ResourceReferenceGetObject"; + internal const string Error_RefBindCantFindRef = "Error_RefBindCantFindRef"; + internal const string Error_RefBindMissingReferenceName = "Error_RefBindMissingReferenceName"; + internal const string Error_RefBindMissingAttribute = "Error_RefBindMissingAttribute"; + internal const string Error_ReferenceLoad = "Error_ReferenceLoad"; + internal const string Error_ReferenceMissingAttribute = "Error_ReferenceMissingAttribute"; + internal const string Error_ReferenceInvalidResourceFile = "Error_ReferenceInvalidResourceFile"; + internal const string Error_ReferenceEmptyName = "Error_ReferenceEmptyName"; + + internal const string HandlerInvokerName = "HandlerInvokerName"; + internal const string HandlerInvokerSwitchPrefix_CS = "HandlerInvokerSwitchPrefix_CS"; + internal const string HandlerInvokerSwitchPrefix_VB = "HandlerInvokerSwitchPrefix_VB"; + internal const string HandlerInvokerSwitchSuffix_CS = "HandlerInvokerSwitchSuffix_CS"; + internal const string HandlerInvokerSwitchSuffix_VB = "HandlerInvokerSwitchSuffix_VB"; + internal const string HandlerInvokerCaseBegin_CS = "HandlerInvokerCaseBegin_CS"; + internal const string HandlerInvokerCaseBegin_VB = "HandlerInvokerCaseBegin_VB"; + + // Activity Category + internal const string Standard = "Standard"; + internal const string Base = "Base"; + + // CustomActivityDesigner + internal const string ValidatorCompanionClassDesc = "ValidatorCompanionClassDesc"; + internal const string ExecutorCompanionClassDesc = "ExecutorCompanionClassDesc"; + internal const string DesignerCompanionClassDesc = "DesignerCompanionClassDesc"; + internal const string CustomActivityBaseTypeDesc = "CustomActivityBaseTypeDesc"; + internal const string ActivityProperties = "ActivityProperties"; + internal const string ActivityPropertiesDesc = "ActivityPropertiesDesc"; + internal const string CompanionClasses = "CompanionClasses"; + internal const string ActivityDesc = "Activity"; + internal const string Error_TypeConversionFailed = "Error_TypeConversionFailed"; + internal const string SupportDataContext = "SupportDataContext"; + internal const string AdvancedCategory = "AdvancedCategory"; + internal const string SupportDataContextDesc = "SupportDataContextDesc"; + internal const string BaseCompanionClassName = "BaseCompanionClassName"; + internal const string BaseCompanionClassDesc = "BaseCompanionClassDesc"; + internal const string Designer = "Designer"; + internal const string Validator = "Validator"; + internal const string Executor = "Executor"; + internal const string BaseActivityType = "BaseActivityType"; + internal const string Error_NotBuiltInActivity = "Error_NotBuiltInActivity"; + internal const string NoChildActivities_Message = "NoChildActivities_Message"; + internal const string NoChildActivities_Caption = "NoChildActivities_Caption"; + internal const string Error_CustomActivityCantCreate = "Error_CustomActivityCantCreate"; + internal const string Error_CantChangeBuiltInActivity = "Error_CantChangeBuiltInActivity"; + internal const string Error_CantAddBeforeBuiltInActivity = "Error_CantAddBeforeBuiltInActivity"; + internal const string Error_CantAddAfterNonBuiltInActivity = "Error_CantAddAfterNonBuiltInActivity"; + internal const string Error_CannotAddRemoveChildActivities = "Error_CannotAddRemoveChildActivities"; + internal const string Error_CantFindBuiltInActivity = "Error_CantFindBuiltInActivity"; + internal const string Error_MissingBaseCompanionClassAttribute = "Error_MissingBaseCompanionClassAttribute"; + internal const string Error_CantFindBuiltInParent = "Error_CantFindBuiltInParent"; + internal const string Error_CantCreateInstanceOfBaseType = "Error_CantCreateInstanceOfBaseType"; + internal const string Error_CustomActivityTypeCouldNotBeFound = "Error_CustomActivityTypeCouldNotBeFound"; + internal const string None = "None"; + internal const string AtomicTransaction = "AtomicTransaction"; + internal const string LocalDataContext = "LocalDataContext"; + internal const string LocalDataContextDesc = "LocalDataContextDesc"; + internal const string CompanionClass = "CompanionClass"; + internal const string Error_AlreadyRootActivity = "Error_AlreadyRootActivity"; + internal const string RootActivityName = "RootActivityName"; + internal const string RootActivityNameDesc = "RootActivityNameDesc"; + internal const string CustomProperties = "CustomProperties"; + internal const string VisibleDescr = "VisibleDescr"; + internal const string EditableDescr = "EditableDescr"; + internal const string Error_CantCreateMethod = "Error_CantCreateMethod"; + internal const string Error_CantEditNullValue = "Error_CantEditNullValue"; + internal const string Error_CompanionTypeNotSet = "Error_CompanionTypeNotSet"; + internal const string Error_CompanionClassNameCanNotBeEmpty = "Error_CompanionClassNameCanNotBeEmpty"; + internal const string Error_CouldNotEmitFieldInLocalDataContext = "Error_CouldNotEmitFieldInLocalDataContext"; + internal const string Error_CouldNotEmitMethodInLocalDataContext = "Error_CouldNotEmitMethodInLocalDataContext"; + internal const string Error_DerivationFromTypeWithLocalDataContext = "Error_DerivationFromTypeWithLocalDataContext"; + internal const string Error_CompanionTypeDerivationError = "Error_CompanionTypeDerivationError"; + internal const string Error_CantCreateDataContextClass = "Error_CantCreateDataContextClass"; + internal const string ArrayExistingBind = "ArrayExistingBind"; + internal const string Error_NoMatchingFieldsOrProperties = "Error_NoMatchingFieldsOrProperties"; + internal const string ChooseFieldPropertyDatasource = "ChooseFieldPropertyDatasource"; + + internal const string SupportsTransaction = "SupportsTransaction"; + internal const string SupportsExceptions = "SupportsExceptions"; + internal const string SupportsCancellationHandlerActivity = "SupportsCancellationHandlerActivity"; + internal const string SupportsEvents = "SupportsEvents"; + internal const string SupportsDataSources = "SupportsDataSources"; + internal const string SupportsCompensationHandler = "SupportsCompensationHandler"; + internal const string SupportsCompensationHandlerDesc = "SupportsCompensationHandlerDesc"; + internal const string SupportsTransactionDesc = "SupportsTransactionDesc"; + internal const string SupportsExceptionsDesc = "SupportsExceptionsDesc"; + internal const string SupportsCancelHandlerDesc = "SupportsCancelHandlerDesc"; + internal const string SupportsEventsDesc = "SupportsEventsDesc"; + internal const string TransactionDesc = "TransactionDesc"; + + internal const string Error_BaseTypeMustBeActivity = "Error_BaseTypeMustBeActivity"; + internal const string ExistingActivityBindTitle = "ExistingActivityBindTitle"; + internal const string ExistingActivityBindLabel = "ExistingActivityBindLabel"; + internal const string ExistingFieldPropBindTitle = "ExistingFieldPropBindTitle"; + internal const string ExistingFieldPropBindLabel = "ExistingFieldPropBindLabel"; + internal const string ProvidesSynchronization = "ProvidesSynchronization"; + internal const string ProvidesSynchronizationDesc = "ProvidesSynchronizationDesc"; + internal const string SynchronizationHandles = "SynchronizationHandles"; + internal const string SynchronizationHandlesDesc = "SynchronizationHandlesDesc"; + + internal const string Error_TransactionAlreadyApplied = "Error_TransactionAlreadyApplied"; + internal const string Error_BindBaseTypeNotSpecified = "Error_BindBaseTypeNotSpecified"; + internal const string NonDelegateTargetType = "NonDelegateTargetType"; + internal const string Error_ClassnameNotInRootNamespace = "Error_ClassnameNotInRootNamespace"; + internal const string Error_CantUseCurrentProjectTypeAsBase = "Error_CantUseCurrentProjectTypeAsBase"; + internal const string Error_UnboundGenericType = "Error_UnboundGenericType"; + internal const string Error_UnboundGenericTypeDataSource = "Error_UnboundGenericTypeDataSource"; + internal const string Error_BaseTypeUnknown = "Error_BaseTypeUnknown"; + internal const string Error_UnconfiguredBind = "Error_UnconfiguredBind"; + internal const string Error_CanNotEmitMemberInLocalDataContext = "Error_CanNotEmitMemberInLocalDataContext"; + internal const string Error_DesignedTypeNotFound = "Error_DesignedTypeNotFound"; + internal const string Error_PathCouldNotBeResolvedToMember = "Error_PathCouldNotBeResolvedToMember"; + internal const string Error_EdittingNullCollection = "Error_EdittingNullCollection"; + internal const string Error_MoreThanOneCompensationDecl = "Error_MoreThanOneCompensationDecl"; + internal const string Error_ParentDoesNotSupportCompensation = "Error_ParentDoesNotSupportCompensation"; + internal const string Error_CantResolveEventHandler = "Error_CantResolveEventHandler"; + internal const string Error_XSDObjectTypeNotSerializable = "Error_XSDObjectTypeNotSerializable"; + internal const string AEC_InvalidActivity = "AEC_InvalidActivity"; + internal const string GetDynamicActivities_InvalidActivity = "GetDynamicActivities_InvalidActivity"; + internal const string AEC_InvalidNestedActivity = "AEC_InvalidNestedActivity"; + internal const string Error_IDNotSetForActivitySource = "Error_IDNotSetForActivitySource"; + internal const string Error_InvalidCustomPropertyName = "Error_InvalidCustomPropertyName"; + internal const string Error_InvalidCustomPropertyType = "Error_InvalidCustomPropertyType"; + + internal const string Error_DPReadOnly = "Error_DPReadOnly"; + internal const string Error_DPMetaPropertyBinding = "Error_DPMetaPropertyBinding"; + internal const string Error_DPSetValueBind = "Error_DPSetValueBind"; + internal const string Error_DPSetValueHandler = "Error_DPSetValueHandler"; + internal const string Error_DPGetValueHandler = "Error_DPGetValueHandler"; + internal const string Error_DPAddHandlerNonEvent = "Error_DPAddHandlerNonEvent"; + internal const string Error_DPAddHandlerMetaProperty = "Error_DPAddHandlerMetaProperty"; + internal const string Error_DPRemoveHandlerBind = "Error_DPRemoveHandlerBind"; + internal const string Error_LanguageNeedsToBeVBCSharp = "Error_LanguageNeedsToBeVBCSharp"; + internal const string Error_TargetFxNotSupported = "Error_TargetFxNotSupported"; + internal const string Error_CantConvertValueValue = "Error_CantConvertValueValue"; + internal const string Error_TypeIsNotValid = "Error_TypeIsNotValid"; + internal const string Error_TypePropertyInvalid = "Error_TypePropertyInvalid"; + internal const string Error_EventCantBeMetaProperty = "Error_EventCantBeMetaProperty"; + internal const string Error_EventMustBeDelegate = "Error_EventMustBeDelegate"; + internal const string Error_DPPropertyTypeMissing = "Error_DPPropertyTypeMissing"; + + internal const string TransactionalContextActivityDescription = "TransactionalContextActivityDescription"; + internal const string CompensatableTransactionalContextActivityDescription = "CompensatableTransactionalContextActivityDescription"; + internal const string SynchronizationScopeActivityDescription = "SynchronizationScopeActivityDescription"; + internal const string SequenceActivityDescription = "SequenceActivityDescription"; + internal const string CompensateActivityDescription = "CompensateActivityDescription"; + internal const string Error_CompensateBadTargetTX = "Error_CompensateBadTargetTX"; + internal const string Error_CancelHandlerParentNotScope = "Error_CancelHandlerParentNotScope"; + internal const string FaultHandlerActivityDescription = "FaultHandlerActivityDescription"; + internal const string Error_ExceptionTypeNotException = "Error_ExceptionTypeNotException"; + internal const string Error_FaultIsNotOfFaultType = "Error_FaultIsNotOfFaultType"; + internal const string Error_FaultTypeNoDefaultConstructor = "Error_FaultTypeNoDefaultConstructor"; + internal const string FilterDescription_FaultHandlerActivity = "FilterDescription_FaultHandlerActivity"; + internal const string Error_FaultHandlerActivityParentNotFaultHandlersActivity = "Error_FaultHandlerActivityParentNotFaultHandlersActivity"; + internal const string Error_FaultHandlerActivityAllMustBeLast = "Error_FaultHandlerActivityAllMustBeLast"; + internal const string Error_FaultHandlersActivityDeclNotAllFaultHandlerActivityDecl = "Error_FaultHandlersActivityDeclNotAllFaultHandlerActivityDecl"; + internal const string Error_FaultHandlerActivityWrongOrder = "Error_FaultHandlerActivityWrongOrder"; + internal const string Error_SenderMustBeActivityExecutionContext = "Error_SenderMustBeActivityExecutionContext"; + internal const string Error_XomlWorkflowHasCode = "Error_XomlWorkflowHasCode"; + internal const string Error_WrongParamForActivityResolveEventArgs = "Error_WrongParamForActivityResolveEventArgs"; + internal const string Error_ValidatorThrewException = "Error_ValidatorThrewException"; + internal const string Error_Missing_CanModifyProperties_True = "Error_Missing_CanModifyProperties_True"; + internal const string Error_Missing_CanModifyProperties_False = "Error_Missing_CanModifyProperties_False"; + internal const string Error_ModelingConstructsCanNotContainModelingConstructs = "Error_ModelingConstructsCanNotContainModelingConstructs"; + internal const string Error_RootIsNotEnabled = "Error_RootIsNotEnabled"; + internal const string Error_MissingSetAccessor = "Error_MissingSetAccessor"; + internal const string Error_MissingAddHandler = "Error_MissingAddHandler"; + internal const string Error_MissingCLRProperty = "Error_MissingCLRProperty"; + + internal const string Error_NotReadOnlyProperty = "Error_NotReadOnlyProperty"; + internal const string Error_InvalidDependencyProperty = "Error_InvalidDependencyProperty"; + internal const string Error_ActivityNameExist = "Error_ActivityNameExist"; + internal const string CannotCreateAttribute = "CannotCreateAttribute"; + internal const string NamespaceAndDeclaringTypeCannotBeNull = "NamespaceAndDeclaringTypeCannotBeNull"; + internal const string NotElementType = "NotElementType"; + + // Layout persistence errors + internal const string Error_LayoutSerializationActivityNotFound = "Error_LayoutSerializationActivityNotFound"; + internal const string Error_LayoutSerializationAssociatedActivityNotFound = "Error_LayoutSerializationAssociatedActivityNotFound"; + internal const string Error_LayoutSerializationPersistenceSupport = "Error_LayoutSerializationPersistenceSupport"; + internal const string Error_LayoutSerializationRootDesignerNotFound = "Error_LayoutSerializationRootDesignerNotFound"; + internal const string Error_ParameterCannotBeEmpty = "Error_ParameterCannotBeEmpty"; + internal const string InvalidExecutionStatus = "InvalidExecutionStatus"; + internal const string Error_LayoutDeserialization = "Error_LayoutDeserialization"; + internal const string Error_LayoutSerialization = "Error_LayoutSerialization"; + + internal const string Error_SerializerStackOverflow = "Error_SerializerStackOverflow"; + internal const string Error_InvalidActivityForWorkflowChanges = "Error_InvalidActivityForWorkflowChanges"; + internal const string Error_InvalidMemberType = "Error_InvalidMemberType"; + internal const string Error_BindPathNullValue = "Error_BindPathNullValue"; + internal const string Error_MarkupExtensionMissingTerminatingCharacter = "Error_MarkupExtensionMissingTerminatingCharacter"; + internal const string Error_MarkupExtensionDeserializeFailed = "Error_MarkupExtensionDeserializeFailed"; + internal const string Error_ApplyDynamicChangeFailed = "Error_ApplyDynamicChangeFailed"; + internal const string Error_ActivityCircularReference = "Error_ActivityCircularReference"; + internal const string Error_ValidatorTypeIsInvalid = "Error_ValidatorTypeIsInvalid"; + internal const string Error_InvalidServiceProvider = "Error_InvalidServiceProvider"; + internal const string Error_InvalidRootForWorkflowChanges = "Error_InvalidRootForWorkflowChanges"; + internal const string Error_ExtraCharacterFoundAtEnd = "Error_ExtraCharacterFoundAtEnd"; + internal const string Error_WorkflowChangesNotSupported = "Error_WorkflowChangesNotSupported"; + internal const string Error_TypeSystemAttributeArgument = "Error_TypeSystemAttributeArgument"; + + internal const string Error_InvalidElementFoundForType = "Error_InvalidElementFoundForType"; + internal const string Error_ActivitySaveLoadNotCalled = "Error_ActivitySaveLoadNotCalled"; + internal const string Error_CanNotBindProperty = "Error_CanNotBindProperty"; +} +#pragma warning restore CS8603 // 可能返回 null 引用。 + +#endif \ No newline at end of file diff --git a/src/Basal/IFox.Basal.Shared/Sortedset/Sortedset.cs b/src/Basal/IFox.Basal.Shared/Sortedset/Sortedset.cs new file mode 100644 index 0000000000000000000000000000000000000000..66a6a7f50279475f9524d491193fc933709b7b46 --- /dev/null +++ b/src/Basal/IFox.Basal.Shared/Sortedset/Sortedset.cs @@ -0,0 +1,2901 @@ +#if NET35 +#pragma warning disable CS8603 // 可能返回 null 引用。 +#pragma warning disable CS8601 // 引用类型赋值可能为 null。 +#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。 +#pragma warning disable CS8625 // 无法将 null 字面量转换为非 null 的引用类型。 +#pragma warning disable IDE0059 // 不需要赋值 +#pragma warning disable CS8600 // 将 null 字面量或可能为 null 的值转换为非 null 类型。 +#pragma warning disable CS8602 // 解引用可能出现空引用。 +#pragma warning disable CS8604 // 引用类型参数可能为 null。 +// #define USING_HASH_SET +// ==++== +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// ==--== +/*============================================================ +** +** Class: SortedSet +** +** Purpose: A generic sorted set. +** +** Date: August 15, 2008 +** +===========================================================*/ + + +namespace System.Collections.Generic +{ + using IFoxCAD.Basal; + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Runtime.Serialization; + + + + // + // A binary search tree is a red-black tree if it satisfies the following red-black properties: + // 1. Every node is either red or black + // 2. Every leaf (nil node) is black + // 3. If a node is red, then both its children are black + // 4. Every simple path from a node to a descendant leaf contains the same number of black nodes + // + // The basic idea of red-black tree is to represent 2-3-4 trees as standard BSTs but to add one extra bit of information + // per node to encode 3-nodes and 4-nodes. + // 4-nodes will be represented as: B + // R R + // 3 -node will be represented as: B or B + // R B B R + // + // For a detailed description of the algorithm, take a look at "Algorithms" by Robert Sedgewick. + // + + internal delegate bool TreeWalkPredicate(SortedSet.Node node); + + internal enum TreeRotation + { + LeftRotation = 1, + RightRotation = 2, + RightLeftRotation = 3, + LeftRightRotation = 4, + } + + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "by design name choice")] + [DebuggerTypeProxy(nameof(SortedSet))]/*这句改了*/ + [DebuggerDisplay("Count = {Count}")] +#if !FEATURE_NETCORE + [Serializable] + public class SortedSet : ISet, ICollection, ICollection, ISerializable, IDeserializationCallback// , IReadOnlyCollection + { +#else + public class SortedSet : ISet, ICollection, ICollection, IReadOnlyCollection { +#endif // !FEATURE_NETCORE + #region local variables/constants + Node root; + IComparer comparer; + int count; + int version; + private Object _syncRoot; + + private const String ComparerName = "Comparer"; + private const String CountName = "Count"; + private const String ItemsName = "Items"; + private const String VersionName = "Version"; + // needed for enumerator + private const String TreeName = "Tree"; + private const String NodeValueName = "Item"; + private const String EnumStartName = "EnumStarted"; + private const String ReverseName = "Reverse"; + private const String EnumVersionName = "EnumVersion"; + +#if !FEATURE_NETCORE + // needed for TreeSubset + private const String minName = "Min"; + private const String maxName = "Max"; + private const String lBoundActiveName = "lBoundActive"; + private const String uBoundActiveName = "uBoundActive"; + + private SerializationInfo siInfo; // A temporary variable which we need during deserialization. +#endif + internal const int StackAllocThreshold = 100; + + #endregion + + #region Constructors + public SortedSet() + { + this.comparer = Comparer.Default; + } + + public SortedSet(IComparer comparer) + { + if (comparer == null) + { + this.comparer = Comparer.Default; + } + else + { + this.comparer = comparer; + } + } + + + public SortedSet(IEnumerable collection) : this(collection, Comparer.Default) + { + } + + public SortedSet(IEnumerable collection, IComparer comparer) + : this(comparer) + { + //if (collection == null) + //{ + // throw new ArgumentNullException("collection"); + //} + collection.NotNull(nameof(collection)); + // these are explicit type checks in the mould of HashSet. It would have worked better + // with something like an ISorted (we could make this work for SortedList.Keys etc) + SortedSet baseTreeSubSet = collection as TreeSubSet; + if (collection is SortedSet baseSortedSet && baseTreeSubSet == null && AreComparersEqual(this, baseSortedSet)) + { + // breadth first traversal to recreate nodes + if (baseSortedSet.Count == 0) + { + count = 0; + version = 0; + root = null; + return; + } + + + // pre order way to replicate nodes + Stack theirStack = new(2 * Log2(baseSortedSet.Count) + 2); + Stack myStack = new(2 * Log2(baseSortedSet.Count) + 2); + Node theirCurrent = baseSortedSet.root; + Node myCurrent = (theirCurrent != null ? new SortedSet.Node(theirCurrent.Item, theirCurrent.IsRed) : null); + root = myCurrent; + while (theirCurrent != null) + { + theirStack.Push(theirCurrent); + myStack.Push(myCurrent); + myCurrent.Left = (theirCurrent.Left != null ? new SortedSet.Node(theirCurrent.Left.Item, theirCurrent.Left.IsRed) : null); + theirCurrent = theirCurrent.Left; + myCurrent = myCurrent.Left; + } + while (theirStack.Count != 0) + { + theirCurrent = theirStack.Pop(); + myCurrent = myStack.Pop(); + Node theirRight = theirCurrent.Right; + Node myRight = null; + if (theirRight != null) + { + myRight = new SortedSet.Node(theirRight.Item, theirRight.IsRed); + } + myCurrent.Right = myRight; + + while (theirRight != null) + { + theirStack.Push(theirRight); + myStack.Push(myRight); + myRight.Left = (theirRight.Left != null ? new SortedSet.Node(theirRight.Left.Item, theirRight.Left.IsRed) : null); + theirRight = theirRight.Left; + myRight = myRight.Left; + } + } + count = baseSortedSet.count; + version = 0; + } + else + { // As it stands, you're doing an NlogN sort of the collection + List els = new(collection); + els.Sort(this.comparer); + for (int i = 1; i < els.Count; i++) + { + if (comparer.Compare(els[i], els[i - 1]) == 0) + { + els.RemoveAt(i); + i--; + } + } + root = ConstructRootFromSortedArray(els.ToArray(), 0, els.Count - 1, null); + count = els.Count; + version = 0; + } + } + + +#if !FEATURE_NETCORE + + protected SortedSet(SerializationInfo info, StreamingContext context) + { + siInfo = info; + } +#endif + #endregion + + #region Bulk Operation Helpers + private void AddAllElements(IEnumerable collection) + { + foreach (T item in collection) + { + if (!this.Contains(item)) + Add(item); + } + } + + private void RemoveAllElements(IEnumerable collection) + { + T min = this.Min; + T max = this.Max; + foreach (T item in collection) + { + if (!(comparer.Compare(item, min) < 0 || comparer.Compare(item, max) > 0) && this.Contains(item)) + this.Remove(item); + } + } + + private bool ContainsAllElements(IEnumerable collection) + { + foreach (T item in collection) + { + if (!this.Contains(item)) + { + return false; + } + } + return true; + } + + // + // Do a in order walk on tree and calls the delegate for each node. + // If the action delegate returns false, stop the walk. + // + // Return true if the entire tree has been walked. + // Otherwise returns false. + // + internal bool InOrderTreeWalk(TreeWalkPredicate action) + { + return InOrderTreeWalk(action, false); + } + + // Allows for the change in traversal direction. Reverse visits nodes in descending order + internal virtual bool InOrderTreeWalk(TreeWalkPredicate action, bool reverse) + { + if (root == null) + { + return true; + } + + // The maximum height of a red-black tree is 2*lg(n+1). + // See page 264 of "Introduction to algorithms" by Thomas H. Cormen + // note: this should be logbase2, but since the stack grows itself, we + // don't want the extra cost + Stack stack = new(2 * (int)(SortedSet.Log2(Count + 1))); + Node current = root; + while (current != null) + { + stack.Push(current); + current = (reverse ? current.Right : current.Left); + } + while (stack.Count != 0) + { + current = stack.Pop(); + if (!action(current)) + { + return false; + } + + Node node = (reverse ? current.Left : current.Right); + while (node != null) + { + stack.Push(node); + node = (reverse ? node.Right : node.Left); + } + } + return true; + } + + // + // Do a left to right breadth first walk on tree and + // calls the delegate for each node. + // If the action delegate returns false, stop the walk. + // + // Return true if the entire tree has been walked. + // Otherwise returns false. + // + internal virtual bool BreadthFirstTreeWalk(TreeWalkPredicate action) + { + if (root == null) + { + return true; + } + + List processQueue = new() + { + root + }; + Node current; + + while (processQueue.Count != 0) + { + current = processQueue[0]; + processQueue.RemoveAt(0); + if (!action(current)) + { + return false; + } + if (current.Left != null) + { + processQueue.Add(current.Left); + } + if (current.Right != null) + { + processQueue.Add(current.Right); + } + } + return true; + } + #endregion + + #region Properties + public int Count + { + get + { + VersionCheck(); + return count; + } + } + + public IComparer Comparer + { + get + { + return comparer; + } + } + + bool ICollection.IsReadOnly + { + get + { + return false; + } + } + + bool ICollection.IsSynchronized + { + get + { + return false; + } + } + + object ICollection.SyncRoot + { + get + { + if (_syncRoot == null) + { + System.Threading.Interlocked.CompareExchange(ref _syncRoot, new Object(), null); + } + return _syncRoot; + } + } + #endregion + + #region Subclass helpers + + // virtual function for subclass that needs to update count + internal virtual void VersionCheck() { } + + + // virtual function for subclass that needs to do range checks + internal virtual bool IsWithinRange(T item) + { + return true; + } + #endregion + + #region ICollection Members + /// + /// Add the value ITEM to the tree, returns true if added, false if duplicate + /// + /// item to be added + public bool Add(T item) + { + return AddIfNotPresent(item); + } + + void ICollection.Add(T item) + { + AddIfNotPresent(item); + } + + + /// + /// Adds ITEM to the tree if not already present. Returns TRUE if value was successfully added + /// or FALSE if it is a duplicate + /// + internal virtual bool AddIfNotPresent(T item) + { + if (root == null) + { // empty tree + root = new Node(item, false); + count = 1; + version++; + return true; + } + + // + // Search for a node at bottom to insert the new node. + // If we can guanratee the node we found is not a 4-node, it would be easy to do insertion. + // We split 4-nodes along the search path. + // + Node current = root; + Node parent = null; + Node grandParent = null; + Node greatGrandParent = null; + + // even if we don't actually add to the set, we may be altering its structure (by doing rotations + // and such). so update version to disable any enumerators/subsets working on it + version++; + + + int order = 0; + while (current != null) + { + order = comparer.Compare(item, current.Item); + if (order == 0) + { + // We could have changed root node to red during the search process. + // We need to set it to black before we return. + root.IsRed = false; + return false; + } + + // split a 4-node into two 2-nodes + if (Is4Node(current)) + { + Split4Node(current); + // We could have introduced two consecutive red nodes after split. Fix that by rotation. + if (IsRed(parent)) + { + InsertionBalance(current, ref parent, grandParent, greatGrandParent); + } + } + greatGrandParent = grandParent; + grandParent = parent; + parent = current; + current = (order < 0) ? current.Left : current.Right; + } + + Debug.Assert(parent != null, "Parent node cannot be null here!"); + // ready to insert the new node + Node node = new(item); + if (order > 0) + { + parent.Right = node; + } + else + { + parent.Left = node; + } + + // the new node will be red, so we will need to adjust the colors if parent node is also red + if (parent.IsRed) + { + InsertionBalance(node, ref parent, grandParent, greatGrandParent); + } + + // Root node is always black + root.IsRed = false; + ++count; + return true; + } + + /// + /// Remove the T ITEM from this SortedSet. Returns true if successfully removed. + /// + /// + /// + public bool Remove(T item) + { + return this.DoRemove(item); // so it can be made non-virtual + } + + internal virtual bool DoRemove(T item) + { + if (root == null) + { + return false; + } + + + // Search for a node and then find its succesor. + // Then copy the item from the succesor to the matching node and delete the successor. + // If a node doesn't have a successor, we can replace it with its left child (if not empty.) + // or delete the matching node. + // + // In top-down implementation, it is important to make sure the node to be deleted is not a 2-node. + // Following code will make sure the node on the path is not a 2 Node. + + // even if we don't actually remove from the set, we may be altering its structure (by doing rotations + // and such). so update version to disable any enumerators/subsets working on it + version++; + + Node current = root; + Node parent = null; + Node grandParent = null; + Node match = null; + Node parentOfMatch = null; + bool foundMatch = false; + while (current != null) + { + if (Is2Node(current)) + { // fix up 2-Node + if (parent == null) + { // current is root. Mark it as red + current.IsRed = true; + } + else + { + Node sibling = GetSibling(current, parent); + if (sibling.IsRed) + { + // If parent is a 3-node, flip the orientation of the red link. + // We can acheive this by a single rotation + // This case is converted to one of other cased below. + Debug.Assert(!parent.IsRed, "parent must be a black node!"); + if (parent.Right == sibling) + { + RotateLeft(parent); + } + else + { + RotateRight(parent); + } + + parent.IsRed = true; + sibling.IsRed = false; // parent's color + // sibling becomes child of grandParent or root after rotation. Update link from grandParent or root + ReplaceChildOfNodeOrRoot(grandParent, parent, sibling); + // sibling will become grandParent of current node + grandParent = sibling; + if (parent == match) + { + parentOfMatch = sibling; + } + + // update sibling, this is necessary for following processing + sibling = (parent.Left == current) ? parent.Right : parent.Left; + } + Debug.Assert(sibling != null || sibling.IsRed == false, "sibling must not be null and it must be black!"); + + if (Is2Node(sibling)) + { + Merge2Nodes(parent, current, sibling); + } + else + { + // current is a 2-node and sibling is either a 3-node or a 4-node. + // We can change the color of current to red by some rotation. + TreeRotation rotation = RotationNeeded(parent, current, sibling); + Node newGrandParent = null; + switch (rotation) + { + case TreeRotation.RightRotation: + Debug.Assert(parent.Left == sibling, "sibling must be left child of parent!"); + Debug.Assert(sibling.Left.IsRed, "Left child of sibling must be red!"); + sibling.Left.IsRed = false; + newGrandParent = RotateRight(parent); + break; + case TreeRotation.LeftRotation: + Debug.Assert(parent.Right == sibling, "sibling must be left child of parent!"); + Debug.Assert(sibling.Right.IsRed, "Right child of sibling must be red!"); + sibling.Right.IsRed = false; + newGrandParent = RotateLeft(parent); + break; + + case TreeRotation.RightLeftRotation: + Debug.Assert(parent.Right == sibling, "sibling must be left child of parent!"); + Debug.Assert(sibling.Left.IsRed, "Left child of sibling must be red!"); + newGrandParent = RotateRightLeft(parent); + break; + + case TreeRotation.LeftRightRotation: + Debug.Assert(parent.Left == sibling, "sibling must be left child of parent!"); + Debug.Assert(sibling.Right.IsRed, "Right child of sibling must be red!"); + newGrandParent = RotateLeftRight(parent); + break; + } + + newGrandParent.IsRed = parent.IsRed; + parent.IsRed = false; + current.IsRed = true; + ReplaceChildOfNodeOrRoot(grandParent, parent, newGrandParent); + if (parent == match) + { + parentOfMatch = newGrandParent; + } + grandParent = newGrandParent; + } + } + } + + // we don't need to compare any more once we found the match + int order = foundMatch ? -1 : comparer.Compare(item, current.Item); + if (order == 0) + { + // save the matching node + foundMatch = true; + match = current; + parentOfMatch = parent; + } + + grandParent = parent; + parent = current; + + if (order < 0) + { + current = current.Left; + } + else + { + current = current.Right; // continue the search in right sub tree after we find a match + } + } + + // move successor to the matching node position and replace links + if (match != null) + { + ReplaceNode(match, parentOfMatch, parent, grandParent); + --count; + } + + if (root != null) + { + root.IsRed = false; + } + return foundMatch; + } + + public virtual void Clear() + { + root = null; + count = 0; + ++version; + } + + + public virtual bool Contains(T item) + { + return FindNode(item) != null; + } + + + + + public void CopyTo(T[] array) + { + CopyTo(array, 0, Count); + } + + public void CopyTo(T[] array, int index) + { + CopyTo(array, index, Count); + } + + public void CopyTo(T[] array, int index, int count) + { + if (array == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + } + + if (index < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException("count", SR.GetString("SR.ArgumentOutOfRange_NeedNonNegNum")); + } + + // will array, starting at arrayIndex, be able to hold elements? Note: not + // checking arrayIndex >= array.Length (consistency with list of allowing + // count of 0; subsequent check takes care of the rest) + if (index > array.Length || count > array.Length - index) + { + throw new ArgumentException(SR.GetString("SR.Arg_ArrayPlusOffTooSmall")); + } + // upper bound + count += index; + + InOrderTreeWalk(delegate (Node node) + { + if (index >= count) + { + return false; + } + else + { + array[index++] = node.Item; + return true; + } + }); + } + + void ICollection.CopyTo(Array array, int index) + { + if (array == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + } + + if (array.Rank != 1) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported); + } + + if (array.GetLowerBound(0) != 0) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound); + } + + if (index < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.arrayIndex, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } + + if (array.Length - index < Count) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall); + } + + if (array is T[] tarray) + { + CopyTo(tarray, index); + } + else + { + object[] objects = array as object[]; + if (objects == null) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType); + } + + try + { + InOrderTreeWalk(delegate (Node node) { objects[index++] = node.Item; return true; }); + } + catch (ArrayTypeMismatchException) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType); + } + } + } + + #endregion + + #region IEnumerable members + [System.Diagnostics.DebuggerStepThrough] + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } + + [System.Diagnostics.DebuggerStepThrough] + IEnumerator IEnumerable.GetEnumerator() + { + return new Enumerator(this); + } + + [System.Diagnostics.DebuggerStepThrough] + IEnumerator IEnumerable.GetEnumerator() + { + return new Enumerator(this); + } + #endregion + + #region Tree Specific Operations + + private static Node GetSibling(Node node, Node parent) + { + if (parent.Left == node) + { + return parent.Right; + } + return parent.Left; + } + + // After calling InsertionBalance, we need to make sure current and parent up-to-date. + // It doesn't matter if we keep grandParent and greatGrantParent up-to-date + // because we won't need to split again in the next node. + // By the time we need to split again, everything will be correctly set. + // + private void InsertionBalance(Node current, ref Node parent, Node grandParent, Node greatGrandParent) + { + Debug.Assert(grandParent != null, "Grand parent cannot be null here!"); + bool parentIsOnRight = (grandParent.Right == parent); + bool currentIsOnRight = (parent.Right == current); + + Node newChildOfGreatGrandParent; + if (parentIsOnRight == currentIsOnRight) + { // same orientation, single rotation + newChildOfGreatGrandParent = currentIsOnRight ? RotateLeft(grandParent) : RotateRight(grandParent); + } + else + { // different orientaton, double rotation + newChildOfGreatGrandParent = currentIsOnRight ? RotateLeftRight(grandParent) : RotateRightLeft(grandParent); + // current node now becomes the child of greatgrandparent + parent = greatGrandParent; + } + // grand parent will become a child of either parent of current. + grandParent.IsRed = true; + newChildOfGreatGrandParent.IsRed = false; + + ReplaceChildOfNodeOrRoot(greatGrandParent, grandParent, newChildOfGreatGrandParent); + } + + private static bool Is2Node(Node node) + { + Debug.Assert(node != null, "node cannot be null!"); + return IsBlack(node) && IsNullOrBlack(node.Left) && IsNullOrBlack(node.Right); + } + + private static bool Is4Node(Node node) + { + return IsRed(node.Left) && IsRed(node.Right); + } + + private static bool IsBlack(Node node) + { + return (node != null && !node.IsRed); + } + + private static bool IsNullOrBlack(Node node) + { + return (node == null || !node.IsRed); + } + + private static bool IsRed(Node node) + { + return (node != null && node.IsRed); + } + + private static void Merge2Nodes(Node parent, Node child1, Node child2) + { + Debug.Assert(IsRed(parent), "parent must be be red"); + // combing two 2-nodes into a 4-node + parent.IsRed = false; + child1.IsRed = true; + child2.IsRed = true; + } + + // Replace the child of a parent node. + // If the parent node is null, replace the root. + private void ReplaceChildOfNodeOrRoot(Node parent, Node child, Node newChild) + { + if (parent != null) + { + if (parent.Left == child) + { + parent.Left = newChild; + } + else + { + parent.Right = newChild; + } + } + else + { + root = newChild; + } + } + + // Replace the matching node with its succesor. + private void ReplaceNode(Node match, Node parentOfMatch, Node succesor, Node parentOfSuccesor) + { + if (succesor == match) + { // this node has no successor, should only happen if right child of matching node is null. + Debug.Assert(match.Right == null, "Right child must be null!"); + succesor = match.Left; + } + else + { + Debug.Assert(parentOfSuccesor != null, "parent of successor cannot be null!"); + Debug.Assert(succesor.Left == null, "Left child of succesor must be null!"); + Debug.Assert((succesor.Right == null && succesor.IsRed) || (succesor.Right.IsRed && !succesor.IsRed), "Succesor must be in valid state"); + if (succesor.Right != null) + { + succesor.Right.IsRed = false; + } + + if (parentOfSuccesor != match) + { // detach succesor from its parent and set its right child + parentOfSuccesor.Left = succesor.Right; + succesor.Right = match.Right; + } + + succesor.Left = match.Left; + } + + if (succesor != null) + { + succesor.IsRed = match.IsRed; + } + + ReplaceChildOfNodeOrRoot(parentOfMatch, match, succesor); + } + + internal virtual Node FindNode(T item) + { + Node current = root; + while (current != null) + { + int order = comparer.Compare(item, current.Item); + if (order == 0) + { + return current; + } + else + { + current = (order < 0) ? current.Left : current.Right; + } + } + + return null; + } + + // used for bithelpers. Note that this implementation is completely different + // from the Subset's. The two should not be mixed. This indexes as if the tree were an array. + // http://en.wikipedia.org/wiki/Binary_Tree#Methods_for_storing_binary_trees + internal virtual int InternalIndexOf(T item) + { + Node current = root; + int count = 0; + while (current != null) + { + int order = comparer.Compare(item, current.Item); + if (order == 0) + { + return count; + } + else + { + current = (order < 0) ? current.Left : current.Right; + count = (order < 0) ? (2 * count + 1) : (2 * count + 2); + } + } + return -1; + } + + + + internal Node FindRange(T from, T to) + { + return FindRange(from, to, true, true); + } + internal Node FindRange(T from, T to, bool lowerBoundActive, bool upperBoundActive) + { + Node current = root; + while (current != null) + { + if (lowerBoundActive && comparer.Compare(from, current.Item) > 0) + { + current = current.Right; + } + else + { + if (upperBoundActive && comparer.Compare(to, current.Item) < 0) + { + current = current.Left; + } + else + { + return current; + } + } + } + + return null; + } + + internal void UpdateVersion() + { + ++version; + } + + + private static Node RotateLeft(Node node) + { + Node x = node.Right; + node.Right = x.Left; + x.Left = node; + return x; + } + + private static Node RotateLeftRight(Node node) + { + Node child = node.Left; + Node grandChild = child.Right; + + node.Left = grandChild.Right; + grandChild.Right = node; + child.Right = grandChild.Left; + grandChild.Left = child; + return grandChild; + } + + private static Node RotateRight(Node node) + { + Node x = node.Left; + node.Left = x.Right; + x.Right = node; + return x; + } + + private static Node RotateRightLeft(Node node) + { + Node child = node.Right; + Node grandChild = child.Left; + + node.Right = grandChild.Left; + grandChild.Left = node; + child.Left = grandChild.Right; + grandChild.Right = child; + return grandChild; + } + /// + /// Testing counter that can track rotations + /// + + + private static TreeRotation RotationNeeded(Node parent, Node current, Node sibling) + { + Debug.Assert(IsRed(sibling.Left) || IsRed(sibling.Right), "sibling must have at least one red child"); + if (IsRed(sibling.Left)) + { + if (parent.Left == current) + { + return TreeRotation.RightLeftRotation; + } + return TreeRotation.RightRotation; + } + else + { + if (parent.Left == current) + { + return TreeRotation.LeftRotation; + } + return TreeRotation.LeftRightRotation; + } + } + + /// + /// Used for deep equality of SortedSet testing + /// + /// + public static IEqualityComparer> CreateSetComparer() + { + return new SortedSetEqualityComparer(); + } + + /// + /// Create a new set comparer for this set, where this set's members' equality is defined by the + /// memberEqualityComparer. Note that this equality comparer's definition of equality must be the + /// same as this set's Comparer's definition of equality + /// + public static IEqualityComparer> CreateSetComparer(IEqualityComparer memberEqualityComparer) + { + return new SortedSetEqualityComparer(memberEqualityComparer); + } + + + /// + /// Decides whether these sets are the same, given the comparer. If the EC's are the same, we can + /// just use SetEquals, but if they aren't then we have to manually check with the given comparer + /// + internal static bool SortedSetEquals(SortedSet set1, SortedSet set2, IComparer comparer) + { + // handle null cases first + if (set1 == null) + { + return (set2 == null); + } + else if (set2 == null) + { + // set1 != null + return false; + } + + if (AreComparersEqual(set1, set2)) + { + if (set1.Count != set2.Count) + return false; + + return set1.SetEquals(set2); + } + else + { + bool found = false; + foreach (T item1 in set1) + { + found = false; + foreach (T item2 in set2) + { + if (comparer.Compare(item1, item2) == 0) + { + found = true; + break; + } + } + if (!found) + return false; + } + return true; + } + } + + + // This is a little frustrating because we can't support more sorted structures + private static bool AreComparersEqual(SortedSet set1, SortedSet set2) + { + return set1.Comparer.Equals(set2.Comparer); + } + + + private static void Split4Node(Node node) + { + node.IsRed = true; + node.Left.IsRed = false; + node.Right.IsRed = false; + } + + /// + /// Copies this to an array. Used for DebugView + /// + /// + internal T[] ToArray() + { + T[] newArray = new T[Count]; + CopyTo(newArray); + return newArray; + } + + + #endregion + + #region ISet Members + + /// + /// Transform this set into its union with the IEnumerable OTHER + /// Attempts to insert each element and rejects it if it exists. + /// NOTE: The caller object is important as UnionWith uses the Comparator + /// associated with THIS to check equality + /// Throws ArgumentNullException if OTHER is null + /// + /// + public void UnionWith(IEnumerable other) + { + //if (other == null) + //{ + // throw new ArgumentNullException("other"); + //} + other.NotNull(nameof(other)); + + SortedSet s = other as SortedSet; + TreeSubSet t = this as TreeSubSet; + + if (t != null) + VersionCheck(); + + if (s != null && t == null && this.count == 0) + { + SortedSet dummy = new(s, this.comparer); + this.root = dummy.root; + this.count = dummy.count; + this.version++; + return; + } + + + if (s != null && t == null && AreComparersEqual(this, s) && (s.Count > this.Count / 2)) + { // this actually hurts if N is much greater than M the /2 is arbitrary + // first do a merge sort to an array. + T[] merged = new T[s.Count + this.Count]; + int c = 0; + Enumerator mine = this.GetEnumerator(); + Enumerator theirs = s.GetEnumerator(); + bool mineEnded = !mine.MoveNext(), theirsEnded = !theirs.MoveNext(); + while (!mineEnded && !theirsEnded) + { + int comp = Comparer.Compare(mine.Current, theirs.Current); + if (comp < 0) + { + merged[c++] = mine.Current; + mineEnded = !mine.MoveNext(); + } + else if (comp == 0) + { + merged[c++] = theirs.Current; + mineEnded = !mine.MoveNext(); + theirsEnded = !theirs.MoveNext(); + } + else + { + merged[c++] = theirs.Current; + theirsEnded = !theirs.MoveNext(); + } + } + + if (!mineEnded || !theirsEnded) + { + Enumerator remaining = (mineEnded ? theirs : mine); + do + { + merged[c++] = remaining.Current; + } while (remaining.MoveNext()); + } + + // now merged has all c elements + + // safe to gc the root, we have all the elements + root = null; + + + root = SortedSet.ConstructRootFromSortedArray(merged, 0, c - 1, null); + count = c; + version++; + } + else + { + AddAllElements(other); + } + } + + + private static Node ConstructRootFromSortedArray(T[] arr, int startIndex, int endIndex, Node redNode) + { + // what does this do? + // you're given a sorted array... say 1 2 3 4 5 6 + // 2 cases: + // If there are odd # of elements, pick the middle element (in this case 4), and compute + // its left and right branches + // If there are even # of elements, pick the left middle element, save the right middle element + // and call the function on the rest + // 1 2 3 4 5 6 -> pick 3, save 4 and call the fn on 1,2 and 5,6 + // now add 4 as a red node to the lowest element on the right branch + // 3 3 + // 1 5 -> 1 5 + // 2 6 2 4 6 + // As we're adding to the leftmost of the right branch, nesting will not hurt the red-black properties + // Leaf nodes are red if they have no sibling (if there are 2 nodes or if a node trickles + // down to the bottom + + + // the iterative way to do this ends up wasting more space than it saves in stack frames (at + // least in what i tried) + // so we're doing this recursively + // base cases are described below + int size = endIndex - startIndex + 1; + if (size == 0) + { + return null; + } + Node root = null; + if (size == 1) + { + root = new Node(arr[startIndex], false); + if (redNode != null) + { + root.Left = redNode; + } + } + else if (size == 2) + { + root = new Node(arr[startIndex], false) + { + Right = new Node(arr[endIndex], false) + }; + root.Right.IsRed = true; + if (redNode != null) + { + root.Left = redNode; + } + } + else if (size == 3) + { + root = new Node(arr[startIndex + 1], false) + { + Left = new Node(arr[startIndex], false), + Right = new Node(arr[endIndex], false) + }; + if (redNode != null) + { + root.Left.Left = redNode; + } + } + else + { + int midpt = ((startIndex + endIndex) / 2); + root = new Node(arr[midpt], false) + { + Left = ConstructRootFromSortedArray(arr, startIndex, midpt - 1, redNode) + }; + if (size % 2 == 0) + { + root.Right = ConstructRootFromSortedArray(arr, midpt + 2, endIndex, new Node(arr[midpt + 1], true)); + } + else + { + root.Right = ConstructRootFromSortedArray(arr, midpt + 1, endIndex, null); + } + } + return root; + } + + + /// + /// Transform this set into its intersection with the IEnumerable OTHER + /// NOTE: The caller object is important as IntersectionWith uses the + /// comparator associated with THIS to check equality + /// Throws ArgumentNullException if OTHER is null + /// + /// + public virtual void IntersectWith(IEnumerable other) + { + //if (other == null) + //{ + // throw new ArgumentNullException("other"); + //} + other.NotNull(nameof(other)); + + if (Count == 0) + return; + + // HashSet optimizations can't be done until equality comparers and comparers are related + + // Technically, this would work as well with an ISorted + TreeSubSet t = this as TreeSubSet; + if (t != null) + VersionCheck(); + // only let this happen if i am also a SortedSet, not a SubSet + if (other is SortedSet s && t == null && AreComparersEqual(this, s)) + { + // first do a merge sort to an array. + T[] merged = new T[this.Count]; + int c = 0; + Enumerator mine = this.GetEnumerator(); + Enumerator theirs = s.GetEnumerator(); + bool mineEnded = !mine.MoveNext(), theirsEnded = !theirs.MoveNext(); + T max = Max; + T min = Min; + + while (!mineEnded && !theirsEnded && Comparer.Compare(theirs.Current, max) <= 0) + { + int comp = Comparer.Compare(mine.Current, theirs.Current); + if (comp < 0) + { + mineEnded = !mine.MoveNext(); + } + else if (comp == 0) + { + merged[c++] = theirs.Current; + mineEnded = !mine.MoveNext(); + theirsEnded = !theirs.MoveNext(); + } + else + { + theirsEnded = !theirs.MoveNext(); + } + } + + // now merged has all c elements + + // safe to gc the root, we have all the elements + root = null; + + root = SortedSet.ConstructRootFromSortedArray(merged, 0, c - 1, null); + count = c; + version++; + } + else + { + IntersectWithEnumerable(other); + } + } + + internal virtual void IntersectWithEnumerable(IEnumerable other) + { + // + List toSave = new(this.Count); + foreach (T item in other) + { + if (this.Contains(item)) + { + toSave.Add(item); + this.Remove(item); + } + } + this.Clear(); + AddAllElements(toSave); + } + + + + /// + /// Transform this set into its complement with the IEnumerable OTHER + /// NOTE: The caller object is important as ExceptWith uses the + /// comparator associated with THIS to check equality + /// Throws ArgumentNullException if OTHER is null + /// + /// + public void ExceptWith(IEnumerable other) + { + //if (other == null) + //{ + // throw new ArgumentNullException("other"); + //} + other.NotNull(nameof(other)); + + if (count == 0) + return; + + if (other == this) + { + this.Clear(); + return; + } + + + if (other is SortedSet asSorted && AreComparersEqual(this, asSorted)) + { + // outside range, no point doing anything + if (!(comparer.Compare(asSorted.Max, this.Min) < 0 || comparer.Compare(asSorted.Min, this.Max) > 0)) + { + T min = this.Min; + T max = this.Max; + foreach (T item in other) + { + if (comparer.Compare(item, min) < 0) + continue; + if (comparer.Compare(item, max) > 0) + break; + Remove(item); + } + } + } + else + { + RemoveAllElements(other); + } + } + + /// + /// Transform this set so it contains elements in THIS or OTHER but not both + /// NOTE: The caller object is important as SymmetricExceptWith uses the + /// comparator associated with THIS to check equality + /// Throws ArgumentNullException if OTHER is null + /// + /// + public void SymmetricExceptWith(IEnumerable other) + { + //if (other == null) + //{ + // throw new ArgumentNullException("other"); + //} + other.NotNull(nameof(other)); + if (this.Count == 0) + { + this.UnionWith(other); + return; + } + + if (other == this) + { + this.Clear(); + return; + } + + + +#if USING_HASH_SET + HashSet asHash = other as HashSet; +#endif + if (other is SortedSet asSorted && AreComparersEqual(this, asSorted)) + { + SymmetricExceptWithSameEC(asSorted); + } +#if USING_HASH_SET + else if (asHash != null && this.comparer.Equals(Comparer.Default) && asHash.Comparer.Equals(EqualityComparer.Default)) { + SymmetricExceptWithSameEC(asHash); + } +#endif + else + { + // need perf improvement on this + T[] elements = (new List(other)).ToArray(); + Array.Sort(elements, this.Comparer); + SymmetricExceptWithSameEC(elements); + } + } + + // OTHER must be a set + internal void SymmetricExceptWithSameEC(ISet other) + { + foreach (T item in other) + { + // yes, it is classier to say + // if (!this.Remove(item))this.Add(item); + // but this ends up saving on rotations + if (this.Contains(item)) + { + this.Remove(item); + } + else + { + this.Add(item); + } + } + } + + // OTHER must be a sorted array + internal void SymmetricExceptWithSameEC(T[] other) + { + if (other.Length == 0) + { + return; + } + T last = other[0]; + for (int i = 0; i < other.Length; i++) + { + while (i < other.Length && i != 0 && comparer.Compare(other[i], last) == 0) + i++; + if (i >= other.Length) + break; + if (this.Contains(other[i])) + { + this.Remove(other[i]); + } + else + { + this.Add(other[i]); + } + last = other[i]; + } + } + + + /// + /// Checks whether this Tree is a subset of the IEnumerable other + /// + /// + /// + [System.Security.SecuritySafeCritical] + public bool IsSubsetOf(IEnumerable other) + { + //if (other == null) + //{ + // throw new ArgumentNullException("other"); + //} + other.NotNull(nameof(other)); + if (Count == 0) + return true; + + + if (other is SortedSet asSorted && AreComparersEqual(this, asSorted)) + { + if (this.Count > asSorted.Count) + return false; + return IsSubsetOfSortedSetWithSameEC(asSorted); + } + else + { + // worst case: mark every element in my set and see if i've counted all + // O(MlogN) + + ElementCount result = CheckUniqueAndUnfoundElements(other, false); + return (result.uniqueCount == Count && result.unfoundCount >= 0); + } + } + + private bool IsSubsetOfSortedSetWithSameEC(SortedSet asSorted) + { + SortedSet prunedOther = asSorted.GetViewBetween(this.Min, this.Max); + foreach (T item in this) + { + if (!prunedOther.Contains(item)) + return false; + } + return true; + } + + + /// + /// Checks whether this Tree is a proper subset of the IEnumerable other + /// + /// + /// + [System.Security.SecuritySafeCritical] + public bool IsProperSubsetOf(IEnumerable other) + { + //if (other == null) + //{ + // throw new ArgumentNullException("other"); + //} + other.NotNull(nameof(other)); + if ((other as ICollection) != null) + { + if (Count == 0) + return (other as ICollection).Count > 0; + } + + +#if USING_HASH_SET + // do it one way for HashSets + HashSet asHash = other as HashSet; + if (asHash != null && comparer.Equals(Comparer.Default) && asHash.Comparer.Equals(EqualityComparer.Default)) { + return asHash.IsProperSupersetOf(this); + } +#endif + // another for sorted sets with the same comparer + if (other is SortedSet asSorted && AreComparersEqual(this, asSorted)) + { + if (this.Count >= asSorted.Count) + return false; + return IsSubsetOfSortedSetWithSameEC(asSorted); + } + + + // worst case: mark every element in my set and see if i've counted all + // O(MlogN). + ElementCount result = CheckUniqueAndUnfoundElements(other, false); + return (result.uniqueCount == Count && result.unfoundCount > 0); + } + + + /// + /// Checks whether this Tree is a super set of the IEnumerable other + /// + /// + /// + public bool IsSupersetOf(IEnumerable other) + { + //if (other == null) + //{ + // throw new ArgumentNullException("other"); + //} + other.NotNull(nameof(other)); + if ((other as ICollection) != null && (other as ICollection).Count == 0) + return true; + + // do it one way for HashSets +#if USING_HASH_SET + HashSet asHash = other as HashSet; + if (asHash != null && comparer.Equals(Comparer.Default) && asHash.Comparer.Equals(EqualityComparer.Default)) { + return asHash.IsSubsetOf(this); + } +#endif + // another for sorted sets with the same comparer + if (other is SortedSet asSorted && AreComparersEqual(this, asSorted)) + { + if (this.Count < asSorted.Count) + return false; + SortedSet pruned = GetViewBetween(asSorted.Min, asSorted.Max); + foreach (T item in asSorted) + { + if (!pruned.Contains(item)) + return false; + } + return true; + } + // and a third for everything else + return ContainsAllElements(other); + } + + /// + /// Checks whether this Tree is a proper super set of the IEnumerable other + /// + /// + /// + [System.Security.SecuritySafeCritical] + public bool IsProperSupersetOf(IEnumerable other) + { + //if (other == null) + //{ + // throw new ArgumentNullException("other"); + //} + other.NotNull(nameof(other)); + if (Count == 0) + return false; + + if ((other as ICollection) != null && (other as ICollection).Count == 0) + return true; + +#if USING_HASH_SET + // do it one way for HashSets + + HashSet asHash = other as HashSet; + if (asHash != null && comparer.Equals(Comparer.Default) && asHash.Comparer.Equals(EqualityComparer.Default)) { + return asHash.IsProperSubsetOf(this); + } +#endif + // another way for sorted sets + if (other is SortedSet asSorted && AreComparersEqual(asSorted, this)) + { + if (asSorted.Count >= this.Count) + return false; + SortedSet pruned = GetViewBetween(asSorted.Min, asSorted.Max); + foreach (T item in asSorted) + { + if (!pruned.Contains(item)) + return false; + } + return true; + } + + + // worst case: mark every element in my set and see if i've counted all + // O(MlogN) + // slight optimization, put it into a HashSet and then check can do it in O(N+M) + // but slower in better cases + wastes space + ElementCount result = CheckUniqueAndUnfoundElements(other, true); + return (result.uniqueCount < Count && result.unfoundCount == 0); + } + + + + /// + /// Checks whether this Tree has all elements in common with IEnumerable other + /// + /// + /// + [System.Security.SecuritySafeCritical] + public bool SetEquals(IEnumerable other) + { + //if (other == null) + //{ + // throw new ArgumentNullException("other"); + //} + other.NotNull(nameof(other)); +#if USING_HASH_SET + HashSet asHash = other as HashSet; + if (asHash != null && comparer.Equals(Comparer.Default) && asHash.Comparer.Equals(EqualityComparer.Default)) { + return asHash.SetEquals(this); + } +#endif + if (other is SortedSet asSorted && AreComparersEqual(this, asSorted)) + { + IEnumerator mine = this.GetEnumerator(); + IEnumerator theirs = asSorted.GetEnumerator(); + bool mineEnded = !mine.MoveNext(); + bool theirsEnded = !theirs.MoveNext(); + while (!mineEnded && !theirsEnded) + { + if (Comparer.Compare(mine.Current, theirs.Current) != 0) + { + return false; + } + mineEnded = !mine.MoveNext(); + theirsEnded = !theirs.MoveNext(); + } + return mineEnded && theirsEnded; + } + + // worst case: mark every element in my set and see if i've counted all + // O(N) by size of other + ElementCount result = CheckUniqueAndUnfoundElements(other, true); + return (result.uniqueCount == Count && result.unfoundCount == 0); + } + + + + /// + /// Checks whether this Tree has any elements in common with IEnumerable other + /// + /// + /// + public bool Overlaps(IEnumerable other) + { + //if (other == null) + //{ + // throw new ArgumentNullException("other"); + //} + other.NotNull(nameof(other)); + if (this.Count == 0) + return false; + + if ((other as ICollection != null) && (other as ICollection).Count == 0) + return false; + + if (other is SortedSet asSorted && AreComparersEqual(this, asSorted) && (comparer.Compare(Min, asSorted.Max) > 0 || comparer.Compare(Max, asSorted.Min) < 0)) + { + return false; + } +#if USING_HASH_SET + HashSet asHash = other as HashSet; + if (asHash != null && comparer.Equals(Comparer.Default) && asHash.Comparer.Equals(EqualityComparer.Default)) { + return asHash.Overlaps(this); + } +#endif + foreach (T item in other) + { + if (this.Contains(item)) + { + return true; + } + } + return false; + } + + /// + /// This works similar to HashSet's CheckUniqueAndUnfound (description below), except that the bit + /// array maps differently than in the HashSet. We can only use this for the bulk boolean checks. + /// + /// Determines counts that can be used to determine equality, subset, and superset. This + /// is only used when other is an IEnumerable and not a HashSet. If other is a HashSet + /// these properties can be checked faster without use of marking because we can assume + /// other has no duplicates. + /// + /// The following count checks are performed by callers: + /// 1. Equals: checks if unfoundCount = 0 and uniqueFoundCount = Count; i.e. everything + /// in other is in this and everything in this is in other + /// 2. Subset: checks if unfoundCount >= 0 and uniqueFoundCount = Count; i.e. other may + /// have elements not in this and everything in this is in other + /// 3. Proper subset: checks if unfoundCount > 0 and uniqueFoundCount = Count; i.e + /// other must have at least one element not in this and everything in this is in other + /// 4. Proper superset: checks if unfound count = 0 and uniqueFoundCount strictly less + /// than Count; i.e. everything in other was in this and this had at least one element + /// not contained in other. + /// + /// An earlier implementation used delegates to perform these checks rather than returning + /// an ElementCount struct; however this was changed due to the perf overhead of delegates. + /// + /// + /// Allows us to finish faster for equals and proper superset + /// because unfoundCount must be 0. + /// + // + // + // + // + // + // + [System.Security.SecurityCritical] + private unsafe ElementCount CheckUniqueAndUnfoundElements(IEnumerable other, bool returnIfUnfound) + { + ElementCount result; + + // need special case in case this has no elements. + if (Count == 0) + { + int numElementsInOther = 0; + foreach (T item in other) + { + numElementsInOther++; + // break right away, all we want to know is whether other has 0 or 1 elements + break; + } + result.uniqueCount = 0; + result.unfoundCount = numElementsInOther; + return result; + } + + + int originalLastIndex = Count; + int intArrayLength = BitHelper.ToIntArrayLength(originalLastIndex); + + BitHelper bitHelper; + if (intArrayLength <= StackAllocThreshold) + { + int* bitArrayPtr = stackalloc int[intArrayLength]; + bitHelper = new BitHelper(bitArrayPtr, intArrayLength); + } + else + { + int[] bitArray = new int[intArrayLength]; + bitHelper = new BitHelper(bitArray, intArrayLength); + } + + // count of items in other not found in this + int unfoundCount = 0; + // count of unique items in other found in this + int uniqueFoundCount = 0; + + foreach (T item in other) + { + int index = InternalIndexOf(item); + if (index >= 0) + { + if (!bitHelper.IsMarked(index)) + { + // item hasn't been seen yet + bitHelper.MarkBit(index); + uniqueFoundCount++; + } + } + else + { + unfoundCount++; + if (returnIfUnfound) + { + break; + } + } + } + + result.uniqueCount = uniqueFoundCount; + result.unfoundCount = unfoundCount; + return result; + } + public int RemoveWhere(Predicate match) + { + //if (match == null) + //{ + // throw new ArgumentNullException("match"); + //} + match.NotNull(nameof(match)); + List matches = new(this.Count); + + BreadthFirstTreeWalk(delegate (Node n) + { + if (match(n.Item)) + { + matches.Add(n.Item); + } + return true; + }); + // reverse breadth first to (try to) incur low cost + int actuallyRemoved = 0; + for (int i = matches.Count - 1; i >= 0; i--) + { + if (this.Remove(matches[i])) + { + actuallyRemoved++; + } + } + + return actuallyRemoved; + } + + + #endregion + + #region ISorted Members + + + public T Min + { + get + { + T ret = default; + InOrderTreeWalk(delegate (SortedSet.Node n) { ret = n.Item; return false; }); + return ret; + } + } + + public T Max + { + get + { + T ret = default; + InOrderTreeWalk(delegate (SortedSet.Node n) { ret = n.Item; return false; }, true); + return ret; + } + } + + public IEnumerable Reverse() + { + Enumerator e = new(this, true); + while (e.MoveNext()) + { + yield return e.Current; + } + } + + + /// + /// Returns a subset of this tree ranging from values lBound to uBound + /// Any changes made to the subset reflect in the actual tree + /// + /// Lowest Value allowed in the subset + /// Highest Value allowed in the subset + public virtual SortedSet GetViewBetween(T lowerValue, T upperValue) + { + if (Comparer.Compare(lowerValue, upperValue) > 0) + { + throw new ArgumentException("lowerBound is greater than upperBound"); + } + return new TreeSubSet(this, lowerValue, upperValue, true, true); + } + +#if DEBUG + + /// + /// debug status to be checked whenever any operation is called + /// + /// + internal virtual bool VersionUpToDate() + { + return true; + } +#endif + + + /// + /// This class represents a subset view into the tree. Any changes to this view + /// are reflected in the actual tree. Uses the Comparator of the underlying tree. + /// + /// +#if !FEATURE_NETCORE + [Serializable] + internal sealed class TreeSubSet : SortedSet, ISerializable, IDeserializationCallback + { +#else + internal sealed class TreeSubSet : SortedSet { +#endif + SortedSet underlying; + T min, max; + // these exist for unbounded collections + // for instance, you could allow this subset to be defined for i>10. The set will throw if + // anything <=10 is added, but there is no upperbound. These features Head(), Tail(), were punted + // in the spec, and are not available, but the framework is there to make them available at some point. + bool lBoundActive, uBoundActive; + // used to see if the count is out of date + + +#if DEBUG + internal override bool VersionUpToDate() + { + return (this.version == underlying.version); + } +#endif + + public TreeSubSet(SortedSet Underlying, T Min, T Max, bool lowerBoundActive, bool upperBoundActive) + : base(Underlying.Comparer) + { + underlying = Underlying; + min = Min; + max = Max; + lBoundActive = lowerBoundActive; + uBoundActive = upperBoundActive; + root = underlying.FindRange(min, max, lBoundActive, uBoundActive); // root is first element within range + count = 0; + version = -1; + VersionCheckImpl(); + } + +#if !FEATURE_NETCORE + /// + /// For serialization and deserialization + /// + private TreeSubSet() + { + comparer = null; + } + + + [SuppressMessage("Microsoft.Usage", "CA2236:CallBaseClassMethodsOnISerializableTypes", Justification = "special case TreeSubSet serialization")] + private TreeSubSet(SerializationInfo info, StreamingContext context) + { + siInfo = info; + OnDeserializationImpl(info); + } +#endif // !FEATURE_NETCORE + + /// + /// Additions to this tree need to be added to the underlying tree as well + /// + + internal override bool AddIfNotPresent(T item) + { + if (!IsWithinRange(item)) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.collection); + } + + bool ret = underlying.AddIfNotPresent(item); + VersionCheck(); +#if DEBUG + Debug.Assert(this.VersionUpToDate() && this.root == this.underlying.FindRange(min, max)); +#endif + + return ret; + } + + + public override bool Contains(T item) + { + VersionCheck(); +#if DEBUG + Debug.Assert(this.VersionUpToDate() && this.root == this.underlying.FindRange(min, max)); +#endif + return base.Contains(item); + } + + internal override bool DoRemove(T item) + { + if (!IsWithinRange(item)) + { + return false; + } + + bool ret = underlying.Remove(item); + VersionCheck(); +#if DEBUG + Debug.Assert(this.VersionUpToDate() && this.root == this.underlying.FindRange(min, max)); +#endif + return ret; + } + + public override void Clear() + { + if (count == 0) + { + return; + } + + List toRemove = new(); + BreadthFirstTreeWalk(delegate (Node n) { toRemove.Add(n.Item); return true; }); + while (toRemove.Count != 0) + { + underlying.Remove(toRemove[^1]); + toRemove.RemoveAt(toRemove.Count - 1); + } + root = null; + count = 0; + version = underlying.version; + } + + + internal override bool IsWithinRange(T item) + { + int comp = (lBoundActive ? Comparer.Compare(min, item) : -1); + if (comp > 0) + { + return false; + } + comp = (uBoundActive ? Comparer.Compare(max, item) : 1); + if (comp < 0) + { + return false; + } + return true; + } + + internal override bool InOrderTreeWalk(TreeWalkPredicate action, Boolean reverse) + { + VersionCheck(); + + if (root == null) + { + return true; + } + + // The maximum height of a red-black tree is 2*lg(n+1). + // See page 264 of "Introduction to algorithms" by Thomas H. Cormen + Stack stack = new(2 * (int)SortedSet.Log2(count + 1)); // this is not exactly right if count is out of date, but the stack can grow + Node current = root; + while (current != null) + { + if (IsWithinRange(current.Item)) + { + stack.Push(current); + current = (reverse ? current.Right : current.Left); + } + else if (lBoundActive && Comparer.Compare(min, current.Item) > 0) + { + current = current.Right; + } + else + { + current = current.Left; + } + } + + while (stack.Count != 0) + { + current = stack.Pop(); + if (!action(current)) + { + return false; + } + + Node node = (reverse ? current.Left : current.Right); + while (node != null) + { + if (IsWithinRange(node.Item)) + { + stack.Push(node); + node = (reverse ? node.Right : node.Left); + } + else if (lBoundActive && Comparer.Compare(min, node.Item) > 0) + { + node = node.Right; + } + else + { + node = node.Left; + } + } + } + return true; + } + + internal override bool BreadthFirstTreeWalk(TreeWalkPredicate action) + { + VersionCheck(); + + if (root == null) + { + return true; + } + + List processQueue = new() + { + root + }; + Node current; + + while (processQueue.Count != 0) + { + current = processQueue[0]; + processQueue.RemoveAt(0); + if (IsWithinRange(current.Item) && !action(current)) + { + return false; + } + if (current.Left != null && (!lBoundActive || Comparer.Compare(min, current.Item) < 0)) + { + processQueue.Add(current.Left); + } + if (current.Right != null && (!uBoundActive || Comparer.Compare(max, current.Item) > 0)) + { + processQueue.Add(current.Right); + } + } + return true; + } + + internal override SortedSet.Node FindNode(T item) + { + if (!IsWithinRange(item)) + { + return null; + } + VersionCheck(); +#if DEBUG + Debug.Assert(this.VersionUpToDate() && this.root == this.underlying.FindRange(min, max)); +#endif + return base.FindNode(item); + } + + // this does indexing in an inefficient way compared to the actual sortedset, but it saves a + // lot of space + internal override int InternalIndexOf(T item) + { + int count = -1; + foreach (T i in this) + { + count++; + if (Comparer.Compare(item, i) == 0) + return count; + } +#if DEBUG + Debug.Assert(this.VersionUpToDate() && this.root == this.underlying.FindRange(min, max)); +#endif + return -1; + } + /// + /// checks whether this subset is out of date. updates if necessary. + /// + internal override void VersionCheck() + { + VersionCheckImpl(); + } + + private void VersionCheckImpl() + { + Debug.Assert(underlying != null, "Underlying set no longer exists"); + if (this.version != underlying.version) + { + this.root = underlying.FindRange(min, max, lBoundActive, uBoundActive); + this.version = underlying.version; + count = 0; + InOrderTreeWalk(delegate (Node n) { count++; return true; }); + } + } + + + + // This passes functionality down to the underlying tree, clipping edges if necessary + // There's nothing gained by having a nested subset. May as well draw it from the base + // Cannot increase the bounds of the subset, can only decrease it + public override SortedSet GetViewBetween(T lowerValue, T upperValue) + { + if (lBoundActive && Comparer.Compare(min, lowerValue) > 0) + { + // lBound = min; + throw new ArgumentOutOfRangeException("lowerValue"); + } + if (uBoundActive && Comparer.Compare(max, upperValue) < 0) + { + // uBound = max; + throw new ArgumentOutOfRangeException("upperValue"); + } + TreeSubSet ret = (TreeSubSet)underlying.GetViewBetween(lowerValue, upperValue); + return ret; + } + + internal override void IntersectWithEnumerable(IEnumerable other) + { + List toSave = new(this.Count); + foreach (T item in other) + { + if (this.Contains(item)) + { + toSave.Add(item); + this.Remove(item); + } + } + this.Clear(); + this.AddAllElements(toSave); +#if DEBUG + Debug.Assert(this.VersionUpToDate() && this.root == this.underlying.FindRange(min, max)); +#endif + } + +#if !FEATURE_NETCORE + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + GetObjectData(info, context); + } + + protected override void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.info); + } + info.AddValue(maxName, max, typeof(T)); + info.AddValue(minName, min, typeof(T)); + info.AddValue(lBoundActiveName, lBoundActive); + info.AddValue(uBoundActiveName, uBoundActive); + base.GetObjectData(info, context); + } + + void IDeserializationCallback.OnDeserialization(Object sender) + { + // don't do anything here as its already been done by the constructor + // OnDeserialization(sender); + } + + protected override void OnDeserialization(Object sender) + { + OnDeserializationImpl(sender); + } + + private void OnDeserializationImpl(Object sender) + { + if (siInfo == null) + { + ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_InvalidOnDeser); + } + + comparer = (IComparer)siInfo.GetValue(ComparerName, typeof(IComparer)); + int savedCount = siInfo.GetInt32(CountName); + max = (T)siInfo.GetValue(maxName, typeof(T)); + min = (T)siInfo.GetValue(minName, typeof(T)); + lBoundActive = siInfo.GetBoolean(lBoundActiveName); + uBoundActive = siInfo.GetBoolean(uBoundActiveName); + underlying = new SortedSet(); + + if (savedCount != 0) + { + T[] items = (T[])siInfo.GetValue(ItemsName, typeof(T[])); + + if (items == null) + { + ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_MissingValues); + } + + for (int i = 0; i < items.Length; i++) + { + underlying.Add(items[i]); + } + } + underlying.version = siInfo.GetInt32(VersionName); + count = underlying.count; + version = underlying.version - 1; + VersionCheck(); // this should update the count to be right and update root to be right + + if (count != savedCount) + { + ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_MismatchedCount); + } + siInfo = null; + } +#endif // !FEATURE_NETCORE + } + + + #endregion + + #region Serialization methods + +#if !FEATURE_NETCORE + // LinkDemand here is unnecessary as this is a methodimpl and linkdemand from the interface should suffice + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + GetObjectData(info, context); + } + + protected virtual void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.info); + } + + info.AddValue(CountName, count); // This is the length of the bucket array. + info.AddValue(ComparerName, comparer, typeof(IComparer)); + info.AddValue(VersionName, version); + + if (root != null) + { + T[] items = new T[Count]; + CopyTo(items, 0); + info.AddValue(ItemsName, items, typeof(T[])); + } + } + + void IDeserializationCallback.OnDeserialization(Object sender) + { + OnDeserialization(sender); + } + + protected virtual void OnDeserialization(Object sender) + { + if (comparer != null) + { + return; // Somebody had a dependency on this class and fixed us up before the ObjectManager got to it. + } + + if (siInfo == null) + { + ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_InvalidOnDeser); + } + + comparer = (IComparer)siInfo.GetValue(ComparerName, typeof(IComparer)); + int savedCount = siInfo.GetInt32(CountName); + + if (savedCount != 0) + { + T[] items = (T[])siInfo.GetValue(ItemsName, typeof(T[])); + + if (items == null) + { + ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_MissingValues); + } + + for (int i = 0; i < items.Length; i++) + { + Add(items[i]); + } + } + + version = siInfo.GetInt32(VersionName); + if (count != savedCount) + { + ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_MismatchedCount); + } + siInfo = null; + } +#endif // !FEATURE_NETCORE + #endregion + + #region Helper Classes + internal class Node + { + public bool IsRed; + public T Item; + public Node Left; + public Node Right; + + public Node(T item) + { + // The default color will be red, we never need to create a black node directly. + this.Item = item; + IsRed = true; + } + + public Node(T item, bool isRed) + { + // The default color will be red, we never need to create a black node directly. + this.Item = item; + this.IsRed = isRed; + } + } + + [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "not an expected scenario")] +#if !FEATURE_NETCORE + [Serializable] + public struct Enumerator : IEnumerator, IEnumerator, ISerializable, IDeserializationCallback + { +#else + public struct Enumerator : IEnumerator, IEnumerator { +#endif + private SortedSet tree; + private int version; + + + private Stack.Node> stack; + private SortedSet.Node current; + static readonly SortedSet.Node dummyNode = new(default); + + private bool reverse; + +#if !FEATURE_NETCORE + private readonly SerializationInfo siInfo; +#endif + internal Enumerator(SortedSet set) + { + tree = set; + // this is a hack to make sure that the underlying subset has not been changed since + // + tree.VersionCheck(); + + version = tree.version; + + // 2lg(n + 1) is the maximum height + stack = new Stack.Node>(2 * (int)SortedSet.Log2(set.Count + 1)); + current = null; + reverse = false; +#if !FEATURE_NETCORE + siInfo = null; +#endif + Intialize(); + } + + internal Enumerator(SortedSet set, bool reverse) + { + tree = set; + // this is a hack to make sure that the underlying subset has not been changed since + // + tree.VersionCheck(); + version = tree.version; + + // 2lg(n + 1) is the maximum height + stack = new Stack.Node>(2 * (int)SortedSet.Log2(set.Count + 1)); + current = null; + this.reverse = reverse; +#if !FEATURE_NETCORE + siInfo = null; +#endif + Intialize(); + } + +#if !FEATURE_NETCORE + private Enumerator(SerializationInfo info, StreamingContext context) + { + tree = null; + version = -1; + current = null; + reverse = false; + stack = null; + this.siInfo = info; + } + + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + GetObjectData(info, context); + } + + private void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.info); + } + info.AddValue(TreeName, tree, typeof(SortedSet)); + info.AddValue(EnumVersionName, version); + info.AddValue(ReverseName, reverse); + info.AddValue(EnumStartName, !NotStartedOrEnded); + info.AddValue(NodeValueName, (current == null ? dummyNode.Item : current.Item), typeof(T)); + } + + void IDeserializationCallback.OnDeserialization(Object sender) + { + OnDeserialization(sender); + } + + private void OnDeserialization(Object sender) + { + if (siInfo == null) + { + ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_InvalidOnDeser); + } + + tree = (SortedSet)siInfo.GetValue(TreeName, typeof(SortedSet)); + version = siInfo.GetInt32(EnumVersionName); + reverse = siInfo.GetBoolean(ReverseName); + bool EnumStarted = siInfo.GetBoolean(EnumStartName); + stack = new Stack.Node>(2 * (int)SortedSet.Log2(tree.Count + 1)); + current = null; + if (EnumStarted) + { + T item = (T)siInfo.GetValue(NodeValueName, typeof(T)); + Intialize(); + // go until it reaches the value we want + while (this.MoveNext()) + { + if (tree.Comparer.Compare(this.Current, item) == 0) + break; + } + } + } +#endif // !FEATURE_NETCORE + + + private void Intialize() + { + current = null; + SortedSet.Node node = tree.root; + Node next = null, other = null; + while (node != null) + { + next = (reverse ? node.Right : node.Left); + other = (reverse ? node.Left : node.Right); + if (tree.IsWithinRange(node.Item)) + { + stack.Push(node); + node = next; + } + else if (next == null || !tree.IsWithinRange(next.Item)) + { + node = other; + } + else + { + node = next; + } + } + } + + public bool MoveNext() + { + // this is a hack to make sure that the underlying subset has not been changed since + // + tree.VersionCheck(); + + if (version != tree.version) + { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion); + } + + if (stack.Count == 0) + { + current = null; + return false; + } + + current = stack.Pop(); + SortedSet.Node node = (reverse ? current.Left : current.Right); + + Node next = null, other = null; + while (node != null) + { + next = (reverse ? node.Right : node.Left); + other = (reverse ? node.Left : node.Right); + if (tree.IsWithinRange(node.Item)) + { + stack.Push(node); + node = next; + } + else if (other == null || !tree.IsWithinRange(other.Item)) + { + node = next; + } + else + { + node = other; + } + } + return true; + } + + public void Dispose() + { + } + + public T Current + { + get + { + if (current != null) + { + return current.Item; + } + return default; + } + } + + object IEnumerator.Current + { + get + { + if (current == null) + { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen); + } + + return current.Item; + } + } + + internal bool NotStartedOrEnded + { + get + { + return current == null; + } + } + + internal void Reset() + { + if (version != tree.version) + { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion); + } + + stack.Clear(); + Intialize(); + } + + void IEnumerator.Reset() + { + Reset(); + } + } + + + + internal struct ElementCount + { + internal int uniqueCount; + internal int unfoundCount; + } + #endregion + + #region misc + + /// + /// Searches the set for a given value and returns the equal value it finds, if any. + /// + /// The value to search for. + /// The value from the set that the search found, or the default value of when the search yielded no match. + /// A value indicating whether the search was successful. + /// + /// This can be useful when you want to reuse a previously stored reference instead of + /// a newly constructed one (so that more sharing of references can occur) or to look up + /// a value that has more complete data than the value you currently have, although their + /// comparer functions indicate they are equal. + /// + public bool TryGetValue(T equalValue, out T actualValue) + { + Node node = FindNode(equalValue); + if (node != null) + { + actualValue = node.Item; + return true; + } + actualValue = default; + return false; + } + + // used for set checking operations (using enumerables) that rely on counting + private static int Log2(int value) + { + // Contract.Requires(value>0) + int c = 0; + while (value > 0) + { + c++; + value >>= 1; + } + return c; + } + #endregion + } + + /// + /// A class that generates an IEqualityComparer for this SortedSet. Requires that the definition of + /// equality defined by the IComparer for this SortedSet be consistent with the default IEqualityComparer + /// for the type T. If not, such an IEqualityComparer should be provided through the constructor. + /// + internal class SortedSetEqualityComparer : IEqualityComparer> + { + private readonly IComparer comparer; + private readonly IEqualityComparer e_comparer; + + public SortedSetEqualityComparer() : this(null, null) + { + } + + public SortedSetEqualityComparer(IComparer comparer) : this(comparer, null) + { + } + + public SortedSetEqualityComparer(IEqualityComparer memberEqualityComparer) : this(null, memberEqualityComparer) + { + } + + /// + /// Create a new SetEqualityComparer, given a comparer for member order and another for member equality (these + /// must be consistent in their definition of equality) + /// + public SortedSetEqualityComparer(IComparer comparer, IEqualityComparer memberEqualityComparer) + { + if (comparer == null) + this.comparer = Comparer.Default; + else + this.comparer = comparer; + if (memberEqualityComparer == null) + e_comparer = EqualityComparer.Default; + else + e_comparer = memberEqualityComparer; + } + + + // using comparer to keep equals properties in tact; don't want to choose one of the comparers + public bool Equals(SortedSet x, SortedSet y) + { + return SortedSet.SortedSetEquals(x, y, comparer); + } + // IMPORTANT: this part uses the fact that GetHashCode() is consistent with the notion of equality in + // the set + public int GetHashCode(SortedSet obj) + { + int hashCode = 0; + if (obj != null) + { + foreach (T t in obj) + { + hashCode ^= (e_comparer.GetHashCode(t) & 0x7FFFFFFF); + } + } // else returns hashcode of 0 for null HashSets + return hashCode; + } + + // Equals method for the comparer itself. + public override bool Equals(Object obj) + { + if (obj is not SortedSetEqualityComparer comparer) + { + return false; + } + return (this.comparer == comparer.comparer); + } + + public override int GetHashCode() + { + return comparer.GetHashCode() ^ e_comparer.GetHashCode(); + } + } +} + + + +#pragma warning restore CS8604 // 引用类型参数可能为 null。 +#pragma warning restore CS8602 // 解引用可能出现空引用。 +#pragma warning restore CS8600 // 将 null 字面量或可能为 null 的值转换为非 null 类型。 +#pragma warning restore IDE0059 // 不需要赋值 +#pragma warning restore CS8625 // 无法将 null 字面量转换为非 null 的引用类型。 +#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。 +#pragma warning restore CS8601 // 引用类型赋值可能为 null。 +#pragma warning restore CS8603 // 可能返回 null 引用。 +#endif \ No newline at end of file diff --git a/src/Basal/IFox.Basal.Shared/Sortedset/ThrowHelper.cs b/src/Basal/IFox.Basal.Shared/Sortedset/ThrowHelper.cs new file mode 100644 index 0000000000000000000000000000000000000000..ebb2f410b502a822cb15b51bdcdf468ea60ee07c --- /dev/null +++ b/src/Basal/IFox.Basal.Shared/Sortedset/ThrowHelper.cs @@ -0,0 +1,381 @@ +#if NET35 +#pragma warning disable IDE0059 // 不需要赋值 +#pragma warning disable CS8600 // 将 null 字面量或可能为 null 的值转换为非 null 类型。 + +namespace System +{ + // This file defines an internal class used to throw exceptions in BCL code. + // The main purpose is to reduce code size. + // + // The old way to throw an exception generates quite a lot IL code and assembly code. + // Following is an example: + // C# source + // throw new ArgumentNullException("key", SR.GetString("ArgumentNull_Key")); + // IL code: + // IL_0003: ldstr "key" + // IL_0008: ldstr "ArgumentNull_Key" + // IL_000d: call string System.Environment::GetResourceString(string) + // IL_0012: newobj instance void System.ArgumentNullException::.ctor(string,string) + // IL_0017: throw + // which is 21bytes in IL. + // + // So we want to get rid of the ldstr and call to Environment.GetResource in IL. + // In order to do that, I created two enums: ExceptionResource, ExceptionArgument to represent the + // argument name and resource name in a small integer. The source code will be changed to + // ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key, ExceptionResource.ArgumentNull_Key); + // + // The IL code will be 7 bytes. + // IL_0008: ldc.i4.4 + // IL_0009: ldc.i4.4 + // IL_000a: call void System.ThrowHelper::ThrowArgumentNullException(valuetype System.ExceptionArgument) + // IL_000f: ldarg.0 + // + // This will also reduce the Jitted code size a lot. + // + // It is very important we do this for generic classes because we can easily generate the same code + // multiple times for different instantiation. + // + // < + + + + + + + + + + +#if !SILVERLIGHT + using System.Runtime.Serialization; +#endif + + using System.Diagnostics; + internal static class ThrowHelper + { + internal static void ThrowWrongKeyTypeArgumentException(object key, Type targetType) + { + throw new ArgumentException(SR.GetString("SR.Arg_WrongType", key, targetType), "key"); + } + + internal static void ThrowWrongValueTypeArgumentException(object value, Type targetType) + { + throw new ArgumentException(SR.GetString("SR.Arg_WrongType", value, targetType), "value"); + } + + internal static void ThrowKeyNotFoundException() + { + throw new System.Collections.Generic.KeyNotFoundException(); + } + + internal static void ThrowArgumentException(ExceptionResource resource) + { + throw new ArgumentException(SR.GetString(GetResourceName(resource))); + } + + internal static void ThrowArgumentNullException(ExceptionArgument argument) + { + throw new ArgumentNullException(GetArgumentName(argument)); + } + + internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument) + { + throw new ArgumentOutOfRangeException(GetArgumentName(argument)); + } + + internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource) + { + throw new ArgumentOutOfRangeException(GetArgumentName(argument), SR.GetString(GetResourceName(resource))); + } + + internal static void ThrowInvalidOperationException(ExceptionResource resource) + { + throw new InvalidOperationException(SR.GetString(GetResourceName(resource))); + } + +#if !SILVERLIGHT + internal static void ThrowSerializationException(ExceptionResource resource) + { + throw new SerializationException(SR.GetString(GetResourceName(resource))); + } +#endif + + internal static void ThrowNotSupportedException(ExceptionResource resource) + { + throw new NotSupportedException(SR.GetString(GetResourceName(resource))); + } + + // Allow nulls for reference types and Nullable, but not for value types. + internal static void IfNullAndNullsAreIllegalThenThrow(object value, ExceptionArgument argName) + { + // Note that default(T) is not equal to null for value types except when T is Nullable. + if (value == null && !(default(T) == null)) + ThrowHelper.ThrowArgumentNullException(argName); + } + + // + // This function will convert an ExceptionArgument enum value to the argument name string. + // + internal static string GetArgumentName(ExceptionArgument argument) + { + string argumentName = null; + + switch (argument) + { + case ExceptionArgument.array: + argumentName = "array"; + break; + + case ExceptionArgument.arrayIndex: + argumentName = "arrayIndex"; + break; + + case ExceptionArgument.capacity: + argumentName = "capacity"; + break; + + case ExceptionArgument.collection: + argumentName = "collection"; + break; + + case ExceptionArgument.converter: + argumentName = "converter"; + break; + + case ExceptionArgument.count: + argumentName = "count"; + break; + + case ExceptionArgument.dictionary: + argumentName = "dictionary"; + break; + + case ExceptionArgument.index: + argumentName = "index"; + break; + + case ExceptionArgument.info: + argumentName = "info"; + break; + + case ExceptionArgument.key: + argumentName = "key"; + break; + + case ExceptionArgument.match: + argumentName = "match"; + break; + + case ExceptionArgument.obj: + argumentName = "obj"; + break; + + case ExceptionArgument.queue: + argumentName = "queue"; + break; + + case ExceptionArgument.stack: + argumentName = "stack"; + break; + + case ExceptionArgument.startIndex: + argumentName = "startIndex"; + break; + + case ExceptionArgument.value: + argumentName = "value"; + break; + + case ExceptionArgument.item: + argumentName = "item"; + break; + + default: + Debug.Assert(false, "The enum value is not defined, please checked ExceptionArgumentName Enum."); + return string.Empty; + } + + return argumentName; + } + + // + // This function will convert an ExceptionResource enum value to the resource string. + // + internal static string GetResourceName(ExceptionResource resource) + { + string resourceName = null; + + switch (resource) + { + case ExceptionResource.Argument_ImplementIComparable: + resourceName = "SR.Argument_ImplementIComparable"; + break; + + case ExceptionResource.Argument_AddingDuplicate: + resourceName = "SR.Argument_AddingDuplicate"; + break; + + case ExceptionResource.ArgumentOutOfRange_Index: + resourceName = "SR.ArgumentOutOfRange_Index"; + break; + + case ExceptionResource.ArgumentOutOfRange_NeedNonNegNum: + resourceName = "SR.ArgumentOutOfRange_NeedNonNegNum"; + break; + + case ExceptionResource.ArgumentOutOfRange_NeedNonNegNumRequired: + resourceName = "SR.ArgumentOutOfRange_NeedNonNegNumRequired"; + break; + + case ExceptionResource.ArgumentOutOfRange_SmallCapacity: + resourceName = "SR.ArgumentOutOfRange_SmallCapacity"; + break; + + case ExceptionResource.Arg_ArrayPlusOffTooSmall: + resourceName = "SR.Arg_ArrayPlusOffTooSmall"; + break; + + case ExceptionResource.Arg_RankMultiDimNotSupported: + resourceName = "SR.Arg_MultiRank"; + break; + + case ExceptionResource.Arg_NonZeroLowerBound: + resourceName = "SR.Arg_NonZeroLowerBound"; + break; + + case ExceptionResource.Argument_InvalidArrayType: + resourceName = "SR.Invalid_Array_Type"; + break; + + case ExceptionResource.Argument_InvalidOffLen: + resourceName = "SR.Argument_InvalidOffLen"; + break; + + case ExceptionResource.InvalidOperation_CannotRemoveFromStackOrQueue: + resourceName = "SR.InvalidOperation_CannotRemoveFromStackOrQueue"; + break; + + case ExceptionResource.InvalidOperation_EmptyCollection: + resourceName = "SR.InvalidOperation_EmptyCollection"; + break; + + case ExceptionResource.InvalidOperation_EmptyQueue: + resourceName = "SR.InvalidOperation_EmptyQueue"; + break; + + case ExceptionResource.InvalidOperation_EnumOpCantHappen: + resourceName = "SR.InvalidOperation_EnumOpCantHappen"; + break; + + case ExceptionResource.InvalidOperation_EnumFailedVersion: + resourceName = "SR.InvalidOperation_EnumFailedVersion"; + break; + + case ExceptionResource.InvalidOperation_EmptyStack: + resourceName = "SR.InvalidOperation_EmptyStack"; + break; + + case ExceptionResource.InvalidOperation_EnumNotStarted: + resourceName = "SR.InvalidOperation_EnumNotStarted"; + break; + + case ExceptionResource.InvalidOperation_EnumEnded: + resourceName = "SR.InvalidOperation_EnumEnded"; + break; + + case ExceptionResource.NotSupported_KeyCollectionSet: + resourceName = "SR.NotSupported_KeyCollectionSet"; + break; + + case ExceptionResource.NotSupported_SortedListNestedWrite: + resourceName = "SR.NotSupported_SortedListNestedWrite"; + break; + +#if !SILVERLIGHT + case ExceptionResource.Serialization_InvalidOnDeser: + resourceName = "SR.Serialization_InvalidOnDeser"; + break; + + case ExceptionResource.Serialization_MissingValues: + resourceName = "SR.Serialization_MissingValues"; + break; + + case ExceptionResource.Serialization_MismatchedCount: + resourceName = "SR.Serialization_MismatchedCount"; + break; +#endif + + case ExceptionResource.NotSupported_ValueCollectionSet: + resourceName = "SR.NotSupported_ValueCollectionSet"; + break; + + default: + Debug.Assert(false, "The enum value is not defined, please checked ExceptionArgumentName Enum."); + return string.Empty; + } + + return resourceName; + } + } + + // + // The convention for this enum is using the argument name as the enum name + // + internal enum ExceptionArgument + { + obj, + dictionary, + array, + info, + key, + collection, + match, + converter, + queue, + stack, + capacity, + index, + startIndex, + value, + count, + arrayIndex, + item, + } + + // + // The convention for this enum is using the resource name as the enum name + // + internal enum ExceptionResource + { + Argument_ImplementIComparable, + ArgumentOutOfRange_NeedNonNegNum, + ArgumentOutOfRange_NeedNonNegNumRequired, + Arg_ArrayPlusOffTooSmall, + Argument_AddingDuplicate, + Serialization_InvalidOnDeser, + Serialization_MismatchedCount, + Serialization_MissingValues, + Arg_RankMultiDimNotSupported, + Arg_NonZeroLowerBound, + Argument_InvalidArrayType, + NotSupported_KeyCollectionSet, + ArgumentOutOfRange_SmallCapacity, + ArgumentOutOfRange_Index, + Argument_InvalidOffLen, + NotSupported_ReadOnlyCollection, + InvalidOperation_CannotRemoveFromStackOrQueue, + InvalidOperation_EmptyCollection, + InvalidOperation_EmptyQueue, + InvalidOperation_EnumOpCantHappen, + InvalidOperation_EnumFailedVersion, + InvalidOperation_EmptyStack, + InvalidOperation_EnumNotStarted, + InvalidOperation_EnumEnded, + NotSupported_SortedListNestedWrite, + NotSupported_ValueCollectionSet, + } +} + +#pragma warning restore CS8600 // 将 null 字面量或可能为 null 的值转换为非 null 类型。 +#pragma warning restore IDE0059 // 不需要赋值 +#endif \ No newline at end of file diff --git a/src/Basal/IFox.Basal.Shared/Sortedset/bithelper.cs b/src/Basal/IFox.Basal.Shared/Sortedset/bithelper.cs new file mode 100644 index 0000000000000000000000000000000000000000..665b435773868605715b476dc681a1647f43e7a3 --- /dev/null +++ b/src/Basal/IFox.Basal.Shared/Sortedset/bithelper.cs @@ -0,0 +1,170 @@ +#if NET35 +#pragma warning disable CS8603 // 可能返回 null 引用。 +#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。 + + +using System; +using System.Collections; +using System.Text; + +namespace System.Collections.Generic +{ + /// + /// ABOUT: + /// Helps with operations that rely on bit marking to indicate whether an item in the + /// collection should be added, removed, visited already, etc. + /// + /// BitHelper doesn't allocate the array; you must pass in an array or ints allocated on the + /// stack or heap. ToIntArrayLength() tells you the int array size you must allocate. + /// + /// USAGE: + /// Suppose you need to represent a bit array of length (i.e. logical bit array length) + /// BIT_ARRAY_LENGTH. Then this is the suggested way to instantiate BitHelper: + /// *************************************************************************** + /// int intArrayLength = BitHelper.ToIntArrayLength(BIT_ARRAY_LENGTH); + /// BitHelper bitHelper; + /// if (intArrayLength less than stack alloc threshold) + /// int* m_arrayPtr = stackalloc int[intArrayLength]; + /// bitHelper = new BitHelper(m_arrayPtr, intArrayLength); + /// else + /// int[] m_arrayPtr = new int[intArrayLength]; + /// bitHelper = new BitHelper(m_arrayPtr, intArrayLength); + /// *************************************************************************** + /// + /// IMPORTANT: + /// The second ctor args, length, should be specified as the length of the int array, not + /// the logical bit array. Because length is used for bounds checking into the int array, + /// it's especially important to get this correct for the stackalloc version. See the code + /// samples above; this is the value gotten from ToIntArrayLength(). + /// + /// The length ctor argument is the only exception; for other methods -- MarkBit and + /// IsMarked -- pass in values as indices into the logical bit array, and it will be mapped + /// to the position within the array of ints. + /// + /// + + + + + unsafe internal class BitHelper + { // should not be serialized + private const byte MarkedBitFlag = 1; + private const byte IntSize = 32; + + // m_length of underlying int array (not logical bit array) + private int m_length; + + // ptr to stack alloc'd array of ints + [System.Security.SecurityCritical] + private int* m_arrayPtr; + + // array of ints + private int[] m_array; + + // whether to operate on stack alloc'd or heap alloc'd array + private bool useStackAlloc; + + /// + /// Instantiates a BitHelper with a heap alloc'd array of ints + /// + /// int array to hold bits + /// length of int array + // + // + // + // + [System.Security.SecurityCritical] + internal BitHelper(int* bitArrayPtr, int length) + { + this.m_arrayPtr = bitArrayPtr; + this.m_length = length; + useStackAlloc = true; + } + + /// + /// Instantiates a BitHelper with a heap alloc'd array of ints + /// + /// int array to hold bits + /// length of int array + internal BitHelper(int[] bitArray, int length) + { + this.m_array = bitArray; + this.m_length = length; + } + + /// + /// Mark bit at specified position + /// + /// + // + // + // + [System.Security.SecurityCritical] + internal unsafe void MarkBit(int bitPosition) + { + if (useStackAlloc) + { + int bitArrayIndex = bitPosition / IntSize; + if (bitArrayIndex < m_length && bitArrayIndex >= 0) + { + m_arrayPtr[bitArrayIndex] |= (MarkedBitFlag << (bitPosition % IntSize)); + } + } + else + { + int bitArrayIndex = bitPosition / IntSize; + if (bitArrayIndex < m_length && bitArrayIndex >= 0) + { + m_array[bitArrayIndex] |= (MarkedBitFlag << (bitPosition % IntSize)); + } + } + } + + /// + /// Is bit at specified position marked? + /// + /// + /// + // + // + // + [System.Security.SecurityCritical] + internal unsafe bool IsMarked(int bitPosition) + { + if (useStackAlloc) + { + int bitArrayIndex = bitPosition / IntSize; + if (bitArrayIndex < m_length && bitArrayIndex >= 0) + { + return ((m_arrayPtr[bitArrayIndex] & (MarkedBitFlag << (bitPosition % IntSize))) != 0); + } + return false; + } + else + { + int bitArrayIndex = bitPosition / IntSize; + if (bitArrayIndex < m_length && bitArrayIndex >= 0) + { + return ((m_array[bitArrayIndex] & (MarkedBitFlag << (bitPosition % IntSize))) != 0); + } + return false; + } + } + + /// + /// How many ints must be allocated to represent n bits. Returns (n+31)/32, but + /// avoids overflow + /// + /// + /// + internal static int ToIntArrayLength(int n) + { + return n > 0 ? ((n - 1) / IntSize + 1) : 0; + } + } +} + +#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。 +#pragma warning restore CS8603 // 可能返回 null 引用。 + +#endif \ No newline at end of file diff --git a/src/Basal/IFox.Basal.Shared/WindowsAPI/Enums.cs b/src/Basal/IFox.Basal.Shared/WindowsAPI/Enums.cs new file mode 100644 index 0000000000000000000000000000000000000000..ed76fd07940514ca3ca50af663aa30bf9862495f --- /dev/null +++ b/src/Basal/IFox.Basal.Shared/WindowsAPI/Enums.cs @@ -0,0 +1,1071 @@ +#if true +namespace IFoxCAD.Basal; + +// https://blog.csdn.net/qq_43812868/article/details/108587936 +[Flags] +public enum TH32CS : uint +{ + /// + /// 原因在于如果不采用改参数的话,有可能快照会占用整个堆的空间 + /// + TH32CS_SNAPNOHEAPS = 0x40000000, + /// + /// 声明快照句柄是可继承的 + /// + TH32CS_INHERIT = 0x80000000, + /// + /// 在快照中包含在th32ProcessID中指定的进程的所有的堆 + /// + TH32CS_SNAPHEAPLIST = 0x00000001, + /// + /// 在快照中包含系统中所有的进程 + /// + TH32CS_SNAPPROCESS = 0x00000002, + /// + /// 在快照中包含系统中所有的线程 + /// + TH32CS_SNAPTHREAD = 0x00000004, + /// + /// 在快照中包含在th32ProcessID中指定的进程的所有的模块 + /// + TH32CS_SNAPMODULE = 0x00000008, + /// + /// 在快照中包含系统中所有的进程和线程 + /// + TH32CS_SNAPALL = TH32CS_SNAPHEAPLIST | TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD | TH32CS_SNAPMODULE, +} + +/// +/// 设置的钩子类型 +/// +[Flags] +public enum HookType : int +{ + /// + /// WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以监视菜单,滚动 + /// 条,消息框,对话框消息并且发现用户使用ALT+TAB or ALT+ESC 组合键切换窗口。 + /// WH_MSGFILTER Hook只能监视传递到菜单,滚动条,消息框的消息,以及传递到通 + /// 过安装了Hook子过程的应用程序建立的对话框的消息。WH_SYSMSGFILTER Hook + /// 监视所有应用程序消息。 + /// + /// WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以在模式循环期间 + /// 过滤消息,这等价于在主消息循环中过滤消息。 + /// + /// 通过调用CallMsgFilter function可以直接的调用WH_MSGFILTER Hook。通过使用这 + /// 个函数,应用程序能够在模式循环期间使用相同的代码去过滤消息,如同在主消息循 + /// 环里一样 + /// + WH_MSGFILTER = -1, + /// + /// WH_JOURNALRECORD Hook用来监视和记录输入事件。典型的,可以使用这 + /// 个Hook记录连续的鼠标和键盘事件,然后通过使用WH_JOURNALPLAYBACK Hook + /// 来回放。WH_JOURNALRECORD Hook是全局Hook,它不能象线程特定Hook一样 + /// 使用。WH_JOURNALRECORD是system-wide local hooks,它们不会被注射到任何行 + /// 程地址空间 + /// + WH_JOURNALRECORD = 0, + /// + /// WH_JOURNALPLAYBACK Hook使应用程序可以插入消息到系统消息队列。可 + /// 以使用这个Hook回放通过使用WH_JOURNALRECORD Hook记录下来的连续的鼠 + /// 标和键盘事件。只要WH_JOURNALPLAYBACK Hook已经安装,正常的鼠标和键盘 + /// 事件就是无效的。WH_JOURNALPLAYBACK Hook是全局Hook,它不能象线程特定 + /// Hook一样使用。WH_JOURNALPLAYBACK Hook返回超时值,这个值告诉系统在处 + /// 理来自回放Hook当前消息之前需要等待多长时间(毫秒)。这就使Hook可以控制实 + /// 时事件的回放。WH_JOURNALPLAYBACK是system-wide local hooks,它们不会被 + /// 注射到任何行程地址空间 + /// + WH_JOURNALPLAYBACK = 1, + /// + /// 在应用程序中,WH_KEYBOARD Hook用来监视WM_KEYDOWN and + /// WM_KEYUP消息,这些消息通过GetMessage or PeekMessage function返回。可以使 + /// 用这个Hook来监视输入到消息队列中的键盘消息 + /// + WH_KEYBOARD = 2, + /// + /// 应用程序使用WH_GETMESSAGE Hook来监视从GetMessage or PeekMessage函 + /// 数返回的消息。你可以使用WH_GETMESSAGE Hook去监视鼠标和键盘输入,以及 + /// 其它发送到消息队列中的消息 + /// + WH_GETMESSAGE = 3, + /// + /// 监视发送到窗口过程的消息,系统在消息发送到接收窗口过程之前调用 + /// + WH_CALLWNDPROC = 4, + /// + /// 在以下事件之前,系统都会调用WH_CBT Hook子过程,这些事件包括: + /// 1. 激活,建立,销毁,最小化,最大化,移动,改变尺寸等窗口事件; + /// 2. 完成系统指令; + /// 3. 来自系统消息队列中的移动鼠标,键盘事件; + /// 4. 设置输入焦点事件; + /// 5. 同步系统消息队列事件。 + /// Hook子过程的返回值确定系统是否允许或者防止这些操作中的一个 + /// + WH_CBT = 5, + /// + /// WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以监视菜单,滚动 + /// 条,消息框,对话框消息并且发现用户使用ALT+TAB or ALT+ESC 组合键切换窗口。 + /// WH_MSGFILTER Hook只能监视传递到菜单,滚动条,消息框的消息,以及传递到通 + /// 过安装了Hook子过程的应用程序建立的对话框的消息。WH_SYSMSGFILTER Hook + /// 监视所有应用程序消息。 + /// + /// WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以在模式循环期间 + /// 过滤消息,这等价于在主消息循环中过滤消息。 + /// + /// 通过调用CallMsgFilter function可以直接的调用WH_MSGFILTER Hook。通过使用这 + /// 个函数,应用程序能够在模式循环期间使用相同的代码去过滤消息,如同在主消息循 + /// 环里一样 + /// + WH_SYSMSGFILTER = 6, + /// + /// WH_MOUSE Hook监视从GetMessage 或者 PeekMessage 函数返回的鼠标消息。 + /// 使用这个Hook监视输入到消息队列中的鼠标消息 + /// + WH_MOUSE = 7, + /// + /// 当调用GetMessage 或 PeekMessage 来从消息队列种查询非鼠标、键盘消息时 + /// + WH_HARDWARE = 8, + /// + /// 在系统调用系统中与其它Hook关联的Hook子过程之前,系统会调用 + /// WH_DEBUG Hook子过程。你可以使用这个Hook来决定是否允许系统调用与其它 + /// Hook关联的Hook子过程 + /// + WH_DEBUG = 9, + /// + /// 外壳应用程序可以使用WH_SHELL Hook去接收重要的通知。当外壳应用程序是 + /// 激活的并且当顶层窗口建立或者销毁时,系统调用WH_SHELL Hook子过程。 + /// WH_SHELL 共有5钟情况: + /// 1. 只要有个top-level、unowned 窗口被产生、起作用、或是被摧毁; + /// 2. 当Taskbar需要重画某个按钮; + /// 3. 当系统需要显示关于Taskbar的一个程序的最小化形式; + /// 4. 当目前的键盘布局状态改变; + /// 5. 当使用者按Ctrl+Esc去执行Task Manager(或相同级别的程序)。 + /// + /// 按照惯例,外壳应用程序都不接收WH_SHELL消息。所以,在应用程序能够接 + /// 收WH_SHELL消息之前,应用程序必须调用SystemParametersInfo function注册它自 + /// 己 + /// + WH_SHELL = 10, + /// + /// 当应用程序的前台线程处于空闲状态时,可以使用WH_FOREGROUNDIDLE + /// Hook执行低优先级的任务。当应用程序的前台线程大概要变成空闲状态时,系统就 + /// 会调用WH_FOREGROUNDIDLE Hook子过程 + /// + WH_FOREGROUNDIDLE = 11, + /// + /// 监视发送到窗口过程的消息,系统在消息发送到接收窗口过程之后调用 + /// + WH_CALLWNDPROCRET = 12, + /// + /// 监视键盘消息 + /// + WH_KEYBOARD_LL = 13, + /// + /// 监视鼠标消息 + /// + WH_MOUSE_LL = 14 +} + +/// +/// 消息类型 +/// 作为SendMessage和PostMessage的参数 +/// +[Flags] +public enum WM : uint +{ + /// + /// 创建一个窗口 + /// + WM_CREATE = 0x01, + /// + /// 当一个窗口被破坏时发送 + /// + WM_DESTROY = 0x02, + /// + /// 移动一个窗口 + /// + WM_MOVE = 0x03, + /// + /// 改变一个窗口的大小 + /// + WM_SIZE = 0x05, + /// + /// 一个窗口被激活或失去激活状态 + /// + WM_ACTIVATE = 0x06, + /// + /// 一个窗口获得焦点 + /// + WM_SETFOCUS = 0x07, + /// + /// 一个窗口失去焦点 + /// + WM_KILLFOCUS = 0x08, + /// + /// 一个窗口改变成Enable状态 + /// + WM_ENABLE = 0x0A, + /// + /// 设置窗口是否能重画 + /// + WM_SETREDRAW = 0x0B, + /// + /// 应用程序发送此消息来设置一个窗口的文本 + /// + WM_SETTEXT = 0x0C, + /// + /// 应用程序发送此消息来复制对应窗口的文本到缓冲区 + /// + WM_GETTEXT = 0x0D, + /// + /// 得到与一个窗口有关的文本的长度(不包含空字符) + /// + WM_GETTEXTLENGTH = 0x0E, + /// + /// 要求一个窗口重画自己 + /// + WM_PAINT = 0x0F, + /// + /// 当一个窗口或应用程序要关闭时发送一个信号 + /// + WM_CLOSE = 0x10, + /// + /// 当用户选择结束对话框或程序自己调用ExitWindows函数 + /// + WM_QUERYENDSESSION = 0x11, + /// + /// 用来结束程序运行 + /// + WM_QUIT = 0x12, + /// + /// 当用户窗口恢复以前的大小位置时,把此消息发送给某个图标 + /// + WM_QUERYOPEN = 0x13, + /// + /// 当窗口背景必须被擦除时(例在窗口改变大小时) + /// + WM_ERASEBKGND = 0x14, + /// + /// 当系统颜色改变时,发送此消息给所有顶级窗口 + /// + WM_SYSCOLORCHANGE = 0x15, + /// + /// 当系统进程发出WM_QUERYENDSESSION消息后,此消息发送给应用程序,通知它对话是否结束 + /// + WM_ENDSESSION = 0x16, + /// + /// 当隐藏或显示窗口是发送此消息给这个窗口 + /// + WM_SHOWWINDOW = 0x18, + /// + /// 发此消息给应用程序哪个窗口是激活的,哪个是非激活的 + /// + WM_ACTIVATEAPP = 0x1C, + /// + /// 当系统的字体资源库变化时发送此消息给所有顶级窗口 + /// + WM_FONTCHANGE = 0x1D, + /// + /// 当系统的时间变化时发送此消息给所有顶级窗口 + /// + WM_TIMECHANGE = 0x1E, + /// + /// 发送此消息来取消某种正在进行的摸态(操作) + /// + WM_CANCELMODE = 0x1F, + /// + /// 如果鼠标引起光标在某个窗口中移动且鼠标输入没有被捕获时,就发消息给某个窗口 + /// + WM_SETCURSOR = 0x20, + /// + /// 当光标在某个非激活的窗口中而用户正按着鼠标的某个键发送此消息给当前窗口 + /// + WM_MOUSEACTIVATE = 0x21, + /// + /// 发送此消息给MDI子窗口当用户点击此窗口的标题栏或当窗口被激活,移动,改变大小 + /// + WM_CHILDACTIVATE = 0x22, + /// + /// 此消息由基于计算机的训练程序发送,通过WH_JOURNALPALYBACK的hook程序分离出用户输入消息 + /// + WM_QUEUESYNC = 0x23, + /// + /// 此消息发送给窗口当它将要改变大小或位置 + /// + WM_GETMINMAXINFO = 0x24, + /// + /// 发送给最小化窗口当它图标将要被重画 + /// + WM_PAINTICON = 0x26, + /// + /// 此消息发送给某个最小化窗口,仅当它在画图标前它的背景必须被重画 + /// + WM_ICONERASEBKGND = 0x27, + /// + /// 发送此消息给一个对话框程序去更改焦点位置 + /// + WM_NEXTDLGCTL = 0x28, + /// + /// 每当打印管理列队增加或减少一条作业时发出此消息 + /// + WM_SPOOLERSTATUS = 0x2A, + /// + /// 当button,combobox,listbox,menu的可视外观改变时发送 + /// + WM_DRAWITEM = 0x2B, + /// + /// 当button, combo box, list box, list view control, or menu item 被创建时 + /// + WM_MEASUREITEM = 0x2C, + /// + /// 此消息有一个LBS_WANTKEYBOARDINPUT风格的发出给它的所有者来响应WM_KEYDOWN消息 + /// + WM_VKEYTOITEM = 0x2E, + /// + /// 此消息由一个LBS_WANTKEYBOARDINPUT风格的列表框发送给他的所有者来响应WM_CHAR消息 + /// + WM_CHARTOITEM = 0x2F, + /// + /// 当绘制文本时程序发送此消息得到控件要用的颜色 + /// + WM_SETFONT = 0x30, + /// + /// 应用程序发送此消息得到当前控件绘制文本的字体 + /// + WM_GETFONT = 0x31, + /// + /// 应用程序发送此消息让一个窗口与一个热键相关连 + /// + WM_SETHOTKEY = 0x32, + /// + /// 应用程序发送此消息来判断热键与某个窗口是否有关联 + /// + WM_GETHOTKEY = 0x33, + /// + /// 此消息发送给最小化窗口,当此窗口将要被拖放而它的类中没有定义图标,应用程序能返回一个图标或光标的句柄,当用户拖放图标时系统显示这个图标或光标 + /// + WM_QUERYDRAGICON = 0x37, + /// + /// 发送此消息来判定combobox或listbox新增加的项的相对位置 + /// + WM_COMPAREITEM = 0x39, + /// + /// 显示内存已经很少了 + /// + WM_COMPACTING = 0x41, + /// + /// 窗口大小和位置将要被改变时,来调用Setwindowpos函数或其它窗口管理函数 + /// + WM_WINDOWPOSCHANGING = 0x46, + /// + /// 窗口大小和位置已经被改变后,来调用Setwindowpos函数或其它窗口管理函数 + /// + WM_WINDOWPOSCHANGED = 0x47, + /// + /// 当系统将要进入暂停状态时发送此消息 + /// + WM_POWER = 0x48, + /// + /// 当一个应用程序传递数据给另一个应用程序时发送此消息 + /// + WM_COPYDATA = 0x4A, + /// + /// 当某个用户取消程序日志激活状态,提交此消息给程序 + /// + WM_CANCELJOURNA = 0x4B, + /// + /// 当某个控件的某个事件已经发生或这个控件需要得到一些信息时,发送此消息给它的父窗口 + /// + WM_NOTIFY = 0x4E, + /// + /// 当用户选择某种输入语言,或输入语言的热键改变 + /// + WM_INPUTLANGCHANGEREQUEST = 0x50, + /// + /// 当平台现场已经被改变后发送此消息给受影响的最顶级窗口 + /// + WM_INPUTLANGCHANGE = 0x51, + /// + /// 当程序已经初始化windows帮助例程时发送此消息给应用程序 + /// + WM_TCARD = 0x52, + /// + /// 此消息显示用户按下了F1,如果某个菜单是激活的,就发送此消息个此窗口关联的菜单,否则就发送给有焦点的窗口,如果当前都没有焦点,就把此消息发送给当前激活的窗口 + /// + WM_HELP = 0x53, + /// + /// 当用户已经登入或退出后发送此消息给所有的窗口,当用户登入或退出时系统更新用户的具体设置信息,在用户更新设置时系统马上发送此消息 + /// + WM_USERCHANGED = 0x54, + /// + /// 公用控件,自定义控件和他们的父窗口通过此消息来判断控件是使用ANSI还是UNICODE结构 + /// + WM_NOTIFYFORMAT = 0x55, + /// + /// 当用户某个窗口中点击了一下右键就发送此消息给这个窗口 + /// + WM_CONTEXTMENU = 0x7B, + /// + /// 当调用SETWINDOWLONG函数将要改变一个或多个窗口的风格时发送此消息给那个窗口 + /// + WM_STYLECHANGING = 0x7C, + /// + /// 当调用SETWINDOWLONG函数一个或多个窗口的风格后发送此消息给那个窗口 + /// + WM_STYLECHANGED = 0x7D, + /// + /// 当显示器的分辨率改变后发送此消息给所有的窗口 + /// + WM_DISPLAYCHANGE = 0x7E, + /// + /// 此消息发送给某个窗口来返回与某个窗口有关连的大图标或小图标的句柄 + /// + WM_GETICON = 0x7F, + /// + /// 程序发送此消息让一个新的大图标或小图标与某个窗口关联 + /// + WM_SETICON = 0x80, + /// + /// 当某个窗口第一次被创建时,此消息在WM_CREATE消息发送前发送 + /// + WM_NCCREATE = 0x81, + /// + /// 此消息通知某个窗口,非客户区正在销毁 + /// + WM_NCDESTROY = 0x82, + /// + /// 当某个窗口的客户区域必须被核算时发送此消息 + /// + WM_NCCALCSIZE = 0x83, + /// + /// 移动鼠标/按住/释放鼠标时 + /// + WM_NCHITTEST = 0x84, + /// + /// 程序发送此消息给某个窗口当它(窗口)的框架必须被绘制时 + /// + WM_NCPAINT = 0x85, + /// + /// 此消息发送给某个窗口仅当它的非客户区需要被改变来显示是激活还是非激活状态 + /// + WM_NCACTIVATE = 0x86, + /// + /// 发送此消息给某个与对话框程序关联的控件,widdows控制方位键和TAB键使输入进入此控件通过应 + /// + WM_GETDLGCODE = 0x87, + /// + /// 当光标在一个窗口的非客户区内移动时发送此消息给这个窗口 非客户区为:窗体的标题栏及窗的边框体 + /// + WM_NCMOUSEMOVE = 0xA0, + /// + /// 当光标在一个窗口的非客户区同时按下鼠标左键时提交此消息 + /// + WM_NCLBUTTONDOWN = 0xA1, + /// + /// 当用户释放鼠标左键同时光标某个窗口在非客户区时发送此消息 + /// + WM_NCLBUTTONUP = 0xA2, + /// + /// 当用户双击鼠标左键同时光标某个窗口在非客户区时发送此消息 + /// + WM_NCLBUTTONDBLCLK = 0xA3, + /// + /// 当用户按下鼠标右键同时光标又在窗口的非客户区时发送此消息 + /// + WM_NCRBUTTONDOWN = 0xA4, + /// + /// 当用户释放鼠标右键同时光标又在窗口的非客户区时发送此消息 + /// + WM_NCRBUTTONUP = 0xA5, + /// + /// 当用户双击鼠标右键同时光标某个窗口在非客户区时发送此消息 + /// + WM_NCRBUTTONDBLCLK = 0xA6, + /// + /// 当用户按下鼠标中键同时光标又在窗口的非客户区时发送此消息 + /// + WM_NCMBUTTONDOWN = 0xA7, + /// + /// 当用户释放鼠标中键同时光标又在窗口的非客户区时发送此消息 + /// + WM_NCMBUTTONUP = 0xA8, + /// + /// 当用户双击鼠标中键同时光标又在窗口的非客户区时发送此消息 + /// + WM_NCMBUTTONDBLCLK = 0xA9, + + // 所有的键盘消息只有中间的八种,也就是 WM_KEYDOWN 到 WM_SYSDEADCHAR + /// + /// 按下一个键 == WM_KEYDOWN + /// + WM_KEYFIRST = 0x0100, + /// + /// 按下一个键 + /// + WM_KEYDOWN = 0x0100, + /// + /// 释放一个键 + /// + WM_KEYUP = 0x0101, + /// + /// 按下某键,并已发出WM_KEYDOWN, WM_KEYUP消息 + /// + WM_CHAR = 0x102, + /// + /// 当用translatemessage函数翻译WM_KEYUP消息时发送此消息给拥有焦点的窗口 + /// + WM_DEADCHAR = 0x103, + /// + /// 当用户按住ALT键同时按下其它键时提交此消息给拥有焦点的窗口 + /// + WM_SYSKEYDOWN = 0x104, + /// + /// 当用户释放一个键同时ALT 键还按着时提交此消息给拥有焦点的窗口 + /// + WM_SYSKEYUP = 0x105, + /// + /// 当WM_SYSKEYDOWN消息被TRANSLATEMESSAGE函数翻译后提交此消息给拥有焦点的窗口 + /// + WM_SYSCHAR = 0x106, + /// + /// 当WM_SYSKEYDOWN消息被TRANSLATEMESSAGE函数翻译后发送此消息给拥有焦点的窗口 + /// + WM_SYSDEADCHAR = 0x107, + /// + /// 在一个对话框程序被显示前发送此消息给它,通常用此消息初始化控件和执行其它任务 + /// + WM_INITDIALOG = 0x110, + /// + /// 当用户选择一条菜单命令项或当某个控件发送一条消息给它的父窗口 + /// + WM_COMMAND = 0x111, + + /// + /// 当用户选择窗口菜单的一条命令或最大化最小化时窗口前会收到此消息 + /// + WM_SYSCOMMAND = 0x112, + /// + /// 发生了定时器事件 + /// + WM_TIMER = 0x113, + /// + /// 当一个窗口标准水平滚动条产生一个滚动事件时发送此消息给那个窗口,也发送给拥有它的控件 + /// + WM_HSCROLL = 0x114, + /// + /// 当一个窗口标准垂直滚动条产生一个滚动事件时发送此消息给那个窗口也,发送给拥有它的控件 + /// + WM_VSCROLL = 0x115, + /// + /// 当一个菜单将要被激活时发送此消息,它发生在用户菜单条中的某项或按下某个菜单键,它允许程序在显示前更改菜单 + /// + WM_INITMENU = 0x116, + /// + /// 当一个下拉菜单或子菜单将要被激活时发送此消息,它允许程序在它显示前更改菜单,而不要改变全部 + /// + WM_INITMENUPOPUP = 0x117, + /// + /// 当用户选择一条菜单项时发送此消息给菜单的所有者(一般是窗口) + /// + WM_MENUSELECT = 0x11F, + /// + /// 当菜单已被激活用户按下了某个键(不同于加速键),发送此消息给菜单的所有者 + /// + WM_MENUCHAR = 0x120, + /// + /// 当一个模态对话框或菜单进入空载状态时发送此消息给它的所有者,一个模态对话框或菜单进入空载状态就是在处理完一条或几条先前的消息后没有消息它的列队中等待 + /// + WM_ENTERIDLE = 0x121, + /// + /// 在windows绘制消息框前发送此消息给消息框的所有者窗口,通过响应这条消息,所有者窗口可以通过使用给定的相关显示设备的句柄来设置消息框的文本和背景颜色 + /// + WM_CTLCOLORMSGBOX = 0x132, + /// + /// 当一个编辑型控件将要被绘制时发送此消息给它的父窗口通过响应这条消息,所有者窗口可以通过使用给定的相关显示设备的句柄来设置编辑框的文本和背景颜色 + /// + WM_CTLCOLOREDIT = 0x133, + + /// + /// 当一个列表框控件将要被绘制前发送此消息给它的父窗口通过响应这条消息,所有者窗口可以通过使用给定的相关显示设备的句柄来设置列表框的文本和背景颜色 + /// + WM_CTLCOLORLISTBOX = 0x134, + /// + /// 当一个按钮控件将要被绘制时发送此消息给它的父窗口通过响应这条消息,所有者窗口可以通过使用给定的相关显示设备的句柄来设置按纽的文本和背景颜色 + /// + WM_CTLCOLORBTN = 0x135, + /// + /// 当一个对话框控件将要被绘制前发送此消息给它的父窗口通过响应这条消息,所有者窗口可以通过使用给定的相关显示设备的句柄来设置对话框的文本背景颜色 + /// + WM_CTLCOLORDLG = 0x136, + /// + /// 当一个滚动条控件将要被绘制时发送此消息给它的父窗口通过响应这条消息,所有者窗口可以通过使用给定的相关显示设备的句柄来设置滚动条的背景颜色 + /// + WM_CTLCOLORSCROLLBAR = 0x137, + /// + /// 当一个静态控件将要被绘制时发送此消息给它的父窗口通过响应这条消息,所有者窗口可以通过使用给定的相关显示设备的句柄来设置静态控件的文本和背景颜色 + /// + WM_CTLCOLORSTATIC = 0x138, + + /// + /// 当鼠标轮子转动时发送此消息个当前有焦点的控件 + /// + WM_MOUSEWHEEL = 0x20A, + /// + /// 双击鼠标中键 + /// + WM_MBUTTONDBLCLK = 0x209, + /// + /// 释放鼠标中键 + /// + WM_MBUTTONUP = 0x208, + /// + /// 移动鼠标时发生,同WM_MOUSEFIRST + /// + WM_MOUSEMOVE = 0x200, + /// + /// 按下鼠标左键 + /// + WM_LBUTTONDOWN = 0x201, + /// + /// 释放鼠标左键 + /// + WM_LBUTTONUP = 0x202, + /// + /// 双击鼠标左键 + /// + WM_LBUTTONDBLCLK = 0x203, + /// + /// 按下鼠标右键 + /// + WM_RBUTTONDOWN = 0x204, + /// + /// 释放鼠标右键 + /// + WM_RBUTTONUP = 0x205, + /// + /// 双击鼠标右键 + /// + WM_RBUTTONDBLCLK = 0x206, + /// + /// 按下鼠标中键 + /// + WM_MBUTTONDOWN = 0x207, + + WM_USER = 0x0400, + + /// + /// 执行复制成功 + /// + WM_CLIPBOARDUPDATE = 0x031D, +} + +// https://blog.csdn.net/biyusr/article/details/108376195 +public enum MOUSEEVENTF : int +{ + /// + /// 移动鼠标 + /// + MOVE = 0x0001, + /// + /// 模拟鼠标左键按下 + /// + LEFTDOWN = 0x0002, + /// + /// 模拟鼠标左键抬起 + /// + LEFTUP = 0x0004, + /// + /// 模拟鼠标右键按下 + /// + RIGHTDOWN = 0x0008, + /// + /// 模拟鼠标右键抬起 + /// + RIGHTUP = 0x0010, + /// + /// 模拟鼠标中键按下 + /// + MIDDLEDOWN = 0x0020, + /// + /// 模拟鼠标中键抬起 + /// + MIDDLEUP = 0x0040, + /// + /// 标示是否采用绝对坐标 + /// + ABSOLUTE = 0x8000, + /// + /// 模拟鼠标滚轮滚动操作,必须配合dwData参数 + /// + WHEEL = 0x0800, +} + +// C#使用SendMessage发送组合键 +// https://www.cnblogs.com/johnsonton/articles/2331430.html +// Windows 使用的256个虚拟键码 +[Flags]// 打印的时候可以有名称输出,而不是值输出 +public enum VK : int +{ + VK_LBUTTON = 0x1, + VK_RBUTTON = 0x2, + VK_CANCEL = 0x3, + VK_MBUTTON = 0x4, + VK_BACK = 0x8, + VK_TAB = 0x9, + VK_CLEAR = 0xC, + VK_RETURN = 0xD, + VK_SHIFT = 0x10, + VK_CONTROL = 0x11, + VK_MENU = 0x12,// VK_ALT + VK_ALT = 0x12, + VK_PAUSE = 0x13, + VK_CAPITAL = 0x14, + VK_ESCAPE = 0x1B, + VK_SPACE = 0x20, + VK_PRIOR = 0x21, + VK_NEXT = 0x22, + VK_END = 0x23, + VK_HOME = 0x24, + VK_LEFT = 0x25, + VK_UP = 0x26, + VK_RIGHT = 0x27, + VK_DOWN = 0x28, + VK_Select = 0x29, + VK_PRINT = 0x2A, + VK_EXECUTE = 0x2B, + VK_SNAPSHOT = 0x2C, + VK_Insert = 0x2D, + VK_Delete = 0x2E, + VK_HELP = 0x2F, + VK_0 = 0x30, + VK_1 = 0x31, + VK_2 = 0x32, + VK_3 = 0x33, + VK_4 = 0x34, + VK_5 = 0x35, + VK_6 = 0x36, + VK_7 = 0x37, + VK_8 = 0x38, + VK_9 = 0x39, + VK_A = 0x41, + VK_B = 0x42, + VK_C = 0x43, + VK_D = 0x44, + VK_E = 0x45, + VK_F = 0x46, + VK_G = 0x47, + VK_H = 0x48, + VK_I = 0x49, + VK_J = 0x4A, + VK_K = 0x4B, + VK_L = 0x4C, + VK_M = 0x4D, + VK_N = 0x4E, + VK_O = 0x4F, + VK_P = 0x50, + VK_Q = 0x51, + VK_R = 0x52, + VK_S = 0x53, + VK_T = 0x54, + VK_U = 0x55, + VK_V = 0x56, + VK_W = 0x57, + VK_X = 0x58, + VK_Y = 0x59, + VK_Z = 0x5A, + VK_STARTKEY = 0x5B, + VK_CONTEXTKEY = 0x5D, + VK_NUMPAD0 = 0x60, + VK_NUMPAD1 = 0x61, + VK_NUMPAD2 = 0x62, + VK_NUMPAD3 = 0x63, + VK_NUMPAD4 = 0x64, + VK_NUMPAD5 = 0x65, + VK_NUMPAD6 = 0x66, + VK_NUMPAD7 = 0x67, + VK_NUMPAD8 = 0x68, + VK_NUMPAD9 = 0x69, + VK_MULTIPLY = 0x6A, + VK_ADD = 0x6B, + VK_SEPARATOR = 0x6C, + VK_SUBTRACT = 0x6D, + VK_DECIMAL = 0x6E, + VK_DIVIDE = 0x6F, + VK_F1 = 0x70, + VK_F2 = 0x71, + VK_F3 = 0x72, + VK_F4 = 0x73, + VK_F5 = 0x74, + VK_F6 = 0x75, + VK_F7 = 0x76, + VK_F8 = 0x77, + VK_F9 = 0x78, + VK_F10 = 0x79, + VK_F11 = 0x7A, + VK_F12 = 0x7B, + VK_F13 = 0x7C, + VK_F14 = 0x7D, + VK_F15 = 0x7E, + VK_F16 = 0x7F, + VK_F17 = 0x80, + VK_F18 = 0x81, + VK_F19 = 0x82, + VK_F20 = 0x83, + VK_F21 = 0x84, + VK_F22 = 0x85, + VK_F23 = 0x86, + VK_F24 = 0x87, + VK_NUMLOCK = 0x90, + VK_OEM_SCROLL = 0x91, + VK_OEM_1 = 0xBA, + VK_OEM_PLUS = 0xBB, + VK_OEM_COMMA = 0xBC, + VK_OEM_MINUS = 0xBD, + VK_OEM_PERIOD = 0xBE, + VK_OEM_2 = 0xBF, + VK_OEM_3 = 0xC0, + VK_OEM_4 = 0xDB, + VK_OEM_5 = 0xDC, + VK_OEM_6 = 0xDD, + VK_OEM_7 = 0xDE, + VK_OEM_8 = 0xDF, + VK_ICO_F17 = 0xE0, + VK_ICO_F18 = 0xE1, + VK_OEM102 = 0xE2, + VK_ICO_HELP = 0xE3, + VK_ICO_00 = 0xE4, + VK_ICO_CLEAR = 0xE6, + VK_OEM_RESET = 0xE9, + VK_OEM_JUMP = 0xEA, + VK_OEM_PA1 = 0xEB, + VK_OEM_PA2 = 0xEC, + VK_OEM_PA3 = 0xED, + VK_OEM_WSCTRL = 0xEE, + VK_OEM_CUSEL = 0xEF, + VK_OEM_ATTN = 0xF0, + VK_OEM_FINNISH = 0xF1, + VK_OEM_COPY = 0xF2, + VK_OEM_AUTO = 0xF3, + VK_OEM_ENLW = 0xF4, + VK_OEM_BACKTAB = 0xF5, + VK_ATTN = 0xF6, + VK_CRSEL = 0xF7, + VK_EXSEL = 0xF8, + VK_EREOF = 0xF9, + VK_PLAY = 0xFA, + VK_ZOOM = 0xFB, + VK_NONAME = 0xFC, + VK_PA1 = 0xFD, + VK_OEM_CLEAR = 0xFE, +} + +[Flags] +public enum SC : uint +{ + // 窗体关闭消息 + SC_CLOSE = 0xf060, + // 窗体最小化消息 + SC_MINIMIZE = 0xf020, + // 窗体最大化消息 + SC_MAXIMIZE = 0xf030, + // 窗体正常态消息 SC_RESTORE = 0xf120, + SC_NOMAL = 0xf120, +} + +[Flags] +public enum NCmdShow : uint +{ + /// + /// 隐藏窗口并激活其他窗口。nCmdShow + /// + SW_HIDE = 0, + /// + /// 正常态的窗口(非最大化和非最小化) + /// 激活并显示一个窗口。如果窗口被最小化或最大化,系统将其恢复到原来的尺寸和大小。应用程序在第一次显示窗口的时候应该指定此标志 + /// + SW_SHOWNORMAL = 1, + /// + /// 激活窗口并将其最小化 + /// + SW_SHOWMINIMIZED = 2, + /// + /// 激活窗口并将其最大化 + /// + SW_SHOWMAXIMIZED = 3, + /// + /// 最大化指定的窗口 + /// + SW_MAXIMIZE = 3, + /// + /// 以窗口最近一次的大小和状态显示窗口。激活窗口仍然维持激活状态 + /// + SW_SHOWNOACTIVATE = 4, + /// + /// 在窗口原来的位置以原来的尺寸激活和显示窗口 + /// + SW_SHOW = 5, + /// + /// 最小化指定的窗口并且激活在Z序中的下一个顶层窗口 + /// + SW_MINIMIZE = 6, + /// + /// 窗口最小化,激活窗口仍然维持激活状态 + /// + SW_SHOWMINNOACTIVE = 7, + /// + /// 以窗口原来的状态显示窗口。激活窗口仍然维持激活状态 + /// + SW_SHOWNA = 8, + /// + /// 激活并显示窗口。如果窗口最小化或最大化,则系统将窗口恢复到原来的尺寸和位置。在恢复最小化窗口时,应用程序应该指定这个标志 + /// + SW_RESTORE = 9, + /// + /// 依据在STARTUPINFO结构中指定的SW_FLAG标志设定显示状态,STARTUPINFO 结构是由启动应用程序的程序传递给CreateProcess函数的 + /// + SW_SHOWDEFAULT = 10, + /// + /// 在WindowNT5.0中最小化窗口,即使拥有窗口的线程被挂起也会最小化。在从其他线程最小化窗口时才使用这个参数 + /// + SW_FORCEMINIMIZE = 11, +} + +public enum WS : uint +{ + // 窗口风格 + WS_CAPTION = 0xC00000, // 带标题栏的窗口 + WS_MAXIMIZEBOX = 0x10000, // 带最大化按钮的窗口 + WS_MINIMIZEBOX = 0x20000, // 带最小化按钮的窗口 + WS_SYSMENU = 0x80000, // 带系统菜单的窗口 + WS_CLIPSIBLINGS = 0x4000000, // 不重绘层叠子窗口 + WS_CLIPCHILDREN = 0x2000000, // 绘图时排子窗口区域 + WS_OVERLAPPED = 0x0, // 具有标题栏和边框的层叠窗口 + WS_THICKFRAME = 0x40000, // 具有可调边框 + + // 具有标题栏、窗口菜单、可调边框和最大化、最小化按钮的窗口 + WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX, + + WS_GROUP = 0x20000, // 指定一组控制的第一个控制 + WS_POPUP = 0x80000000, // 弹出式窗口 + WS_BORDER = 0x800000, // 单边框窗口 + WS_POPUPWINDOW = WS_POPUP | WS_BORDER | WS_SYSMENU, // 具有单边框、标题栏菜单的弹出式窗口 + WS_MINIMIZE = 0x20000000, // 窗口最小化 + WS_VISIBLE = 0x10000000, // 窗口可见 + WS_DISABLED = 0x8000000, // 窗口被禁用 + WS_MAXIMIZE = 0x1000000, // 窗口最大化 + WS_DLGFRAME = 0x400000, // 对话框边框风格 + WS_VSCROLL = 0x200000, // 具有垂直滚动条 + WS_HSCROLL = 0x100000, // 具有水平滚动条 + WS_TABSTOP = 0x10000, // 具有TAB键控制 + WS_CHILD = 0x40000000, // 设置窗口属性为child 多文档界面的子窗体 + WS_CHILDWINDOW = WS_CHILD, // 具有子窗口 + + // 扩展风格 + WS_EX_WINDOWEDGE = 0x100, // 窗口具有凸起的3D边框 + WS_EX_CLIENTEDGE = 0x200, // 窗口具有阴影边界 + WS_EX_TOOLWINDOW = 0x80, // 小标题工具窗口 + WS_EX_TOPMOST = 0x8, // 窗口总在顶层 const int WS_EX_TOPMOST = 0x00000008; + WS_EX_OVERLAPPEDWINDOW = WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE, // WS_EX-CLIENTEDGE和WS_EX_WINDOWEDGE的组合 + WS_EX_PALETTEWINDOW = WS_EX_WINDOWEDGE | WS_EX_TOOLWINDOW | WS_EX_TOPMOST, // WS_EX_WINDOWEDGE和WS_EX_TOOLWINDOW和WS_EX_TOPMOST的组合 + WS_EX_DLGMODALFRAME = 0x1, // 带双边的窗口 + WS_EX_NOPARENTNOTIFY = 0x4, // 窗口在创建和销毁时不向父窗口发送WM_PARENTNOTIFY消息 + WS_EX_TRANSPARENT = 0x20, // 窗口透眀 + WS_EX_MDICHILD = 0x40, // MDI子窗口 + WS_EX_CONTEXTHELP = 0x400, // 标题栏包含问号联机帮助按钮 + WS_EX_RIGHT = 0x1000, // 窗口具有右对齐属性 + WS_EX_RTLREADING = 0x2000, // 窗口文本自右向左显示 + WS_EX_LEFTSCROLLBAR = 0x4000, // 标题栏在客户区的左边 + WS_EX_CONTROLPARENT = 0x10000, // 允许用户使用Tab键在窗口的子窗口间搜索 + WS_EX_STATICEDGE = 0x20000, // 为不接受用户输入的项创建一个三维边界风格 + WS_EX_APPWINDOW = 0x40000, // 在任务栏上显示顶层窗口的标题按钮 + WS_EX_LAYERED = 0x80000, // 窗口具有透眀属性(Win2000)以上 + WS_EX_NOINHERITLAYOUT = 0x100000, // 窗口布局不传递给子窗口(Win2000)以上 + WS_EX_LAYOUTRTL = 0x400000, // 水平起点在右边的窗口 + WS_EX_NOACTIVATE = 0x8000000, // 窗口不会变成前台窗口(Win2000)以上 + WS_EX_LEFT = 0x0, // 窗口具有左对齐属性 + WS_EX_LTRREADING = 0x0, // 窗口文本自左向右显示 + WS_EX_RIGHTSCROLLBAR = 0x0, // 垂直滚动条在窗口的右边界 + WS_EX_ACCEPTFILES = 0x10, // 接受文件拖曳 + WS_EX_COMPOSITED = 0x2000000, // 窗体所有子窗口使用双缓冲从低到高绘制(XP) +} + +public enum GWL : int +{ + /// + /// 获取、设置窗口过程的地址 + /// + GWL_WNDPROC = -4, + /// + /// 获取应用程序的实例句柄 + /// + GWL_HINSTANCE = -6, + /// + /// 获取父窗口句柄 + /// + GWL_HWNDPARENT = -8, + /// + /// 获取窗口标识 + /// + GWL_ID = -12, + /// + /// 获取、设置窗口样式 + /// + GWL_STYLE = -16, + /// + /// 获取、设置窗口扩展样式 + /// + GWL_EXSTYLE = -20, + /// + /// 获取、设置与窗口关联的自定义数据 + /// + GWL_USERDATA = -21, +} + +public enum GetWindowCmd : uint +{ + /// + /// 返回的句柄标识了在Z序最高端的相同类型的窗口。 + /// 如果指定窗口是最高端窗口,则该句柄标识了在Z序最高端的最高端窗口; + /// 如果指定窗口是顶层窗口,则该句柄标识了在z序最高端的顶层窗口: + /// 如果指定窗口是子窗口,则句柄标识了在Z序最高端的同属窗口。 + /// + GW_HWNDFIRST = 0, + /// + /// 返回的句柄标识了在z序最低端的相同类型的窗口。 + /// 如果指定窗口是最高端窗口,则该柄标识了在z序最低端的最高端窗口: + /// 如果指定窗口是顶层窗口,则该句柄标识了在z序最低端的顶层窗口; + /// 如果指定窗口是子窗口,则句柄标识了在Z序最低端的同属窗口。 + /// + GW_HWNDLAST = 1, + /// + /// 返回的句柄标识了在Z序中指定窗口下的相同类型的窗口。 + /// 如果指定窗口是最高端窗口,则该句柄标识了在指定窗口下的最高端窗口: + /// 如果指定窗口是顶层窗口,则该句柄标识了在指定窗口下的顶层窗口; + /// 如果指定窗口是子窗口,则句柄标识了在指定窗口下的同属窗口。 + /// + GW_HWNDNEXT = 2, + /// + /// 返回的句柄标识了在Z序中指定窗口上的相同类型的窗口。 + /// 如果指定窗口是最高端窗口,则该句柄标识了在指定窗口上的最高端窗口; + /// 如果指定窗口是顶层窗口,则该句柄标识了在指定窗口上的顶层窗口; + /// 如果指定窗口是子窗口,则句柄标识了在指定窗口上的同属窗口。 + /// + GW_HWNDPREV = 3, + /// + /// 返回的句柄标识了指定窗口的所有者窗口(如果存在)。 + /// GW_OWNER与GW_CHILD不是相对的参数,没有父窗口的含义,如果想得到父窗口请使用GetParent()。 + /// 例如:例如有时对话框的控件的GW_OWNER,是不存在的。 + /// + GW_OWNER = 4, + /// + /// 如果指定窗口是父窗口,则获得的是在Tab序顶端的子窗口的句柄,否则为NULL。 + /// 函数仅检查指定父窗口的子窗口,不检查继承窗口。 + /// + GW_CHILD = 5, + /// + /// (WindowsNT 5.0)返回的句柄标识了属于指定窗口的处于使能状态弹出式窗口(检索使用第一个由GW_HWNDNEXT 查找到的满足前述条件的窗口); + /// 如果无使能窗口,则获得的句柄与指定窗口相同。 + /// + GW_ENABLEDPOPUP = 6 +} +#endif \ No newline at end of file diff --git a/src/Basal/IFox.Basal.Shared/WindowsAPI/MouseHook.cs b/src/Basal/IFox.Basal.Shared/WindowsAPI/MouseHook.cs new file mode 100644 index 0000000000000000000000000000000000000000..400e4a792998e0d143e0b0dbb76e11b4695eeecb --- /dev/null +++ b/src/Basal/IFox.Basal.Shared/WindowsAPI/MouseHook.cs @@ -0,0 +1,287 @@ +namespace IFoxCAD.Basal; + +public class MouseHook +{ + /// + /// 鼠标按下事件 + /// + public event MouseEventHandler? MouseDown; + /// + /// 松开鼠标事件 + /// + public event MouseEventHandler? MouseUp; + /// + /// 鼠标移动事件 + /// + public event MouseEventHandler? MouseMove; + /// + /// 鼠标滚轮事件 + /// + public event MouseEventHandler? MouseWheel; + /// + /// 鼠标单击事件 + /// + public event EventHandler? Click; + /// + /// 鼠标双击事件 + /// + public event EventHandler? DoubleClick; + + + bool _isHookBreak = false; + /// + /// 否决本次输入:设置不向下回调 + /// + public void Vote() + { + _isHookBreak = true; + } + + /// 不要试图省略此变量,否则将会导致GC变量池满后释放
+ /// 提示:激活 CallbackOnCollectedDelegate 托管调试助手(MDA) + internal static WindowsAPI.CallBack? HookProc; + internal static IntPtr _NextHookProc;//挂载成功的标记 + public readonly Process Process; + + + [DllImport("user32.dll", EntryPoint = "GetDoubleClickTime")] + public extern static int GetDoubleClickTime(); + static readonly Stopwatch _watch = new(); + + /// + /// 安装鼠标钩子 + /// + /// 低级钩子超时时间 + public MouseHook(int setLowLevel = 25000) + { + _NextHookProc = IntPtr.Zero; + Process = Process.GetCurrentProcess(); + WindowsAPI.CheckLowLevelHooksTimeout(setLowLevel); + _watch.Start(); + } + + void UnHook() + { + if (_NextHookProc != IntPtr.Zero) + { + WindowsAPI.UnhookWindowsHookEx(_NextHookProc); + _NextHookProc = IntPtr.Zero; + } + } + + /// + /// 设置钩子 + /// + /// false进程钩子,true全局钩子 + public void SetHook(bool processHook = false) + { + UnHook(); + if (_NextHookProc != IntPtr.Zero) + return; + + if (processHook) + { + HookProc = (nCode, wParam, lParam) => { + if (nCode >= 0 && HookTask(nCode, wParam, lParam)) + return (IntPtr)1; + return WindowsAPI.CallNextHookEx(_NextHookProc, nCode, wParam, lParam); + }; + _NextHookProc = WindowsAPI.SetWindowsHookEx(HookType.WH_MOUSE, HookProc, + IntPtr.Zero, WindowsAPI.GetCurrentThreadId()); + } + else + { + var moduleHandle = WindowsAPI.GetModuleHandle(Process.MainModule.ModuleName); + HookProc = (nCode, wParam, lParam) => { + if (nCode >= 0 && HookTask(nCode, wParam, lParam)) + return (IntPtr)1; + return WindowsAPI.CallNextHookEx(_NextHookProc, nCode, wParam, lParam); + }; + _NextHookProc = WindowsAPI.SetWindowsHookEx(HookType.WH_MOUSE_LL, HookProc, + moduleHandle, 0); + } + } + + + + MouseButtons _button; + int _clickCount = 0; + bool _down = false; + bool _up = false; + bool _ck = false; + + /// + /// 钩子的消息处理 + /// + /// + /// + /// + /// false不终止回调,true终止回调 + bool HookTask(int nCode, int wParam, IntPtr lParam) + { + if (MouseDown is null + && MouseUp is null + && MouseMove is null + && MouseWheel is null + && Click is null + && DoubleClick is null) + return false; + + _button = MouseButtons.None; + _clickCount = 0; + _down = false; + _up = false; + _ck = false; + + switch ((WM)wParam) + { + case WM.WM_LBUTTONDOWN: + _button = MouseButtons.Left; + _clickCount = 1; + _down = true; + _ck = true; + break; + case WM.WM_LBUTTONUP: + _button = MouseButtons.Left; + _clickCount = 1; + _up = true; + break; + case WM.WM_LBUTTONDBLCLK: + _button = MouseButtons.Left; + _clickCount = 2; + _ck = true; + break; + case WM.WM_RBUTTONDOWN: + _button = MouseButtons.Right; + _clickCount = 1; + _down = true; + _ck = true; + break; + case WM.WM_RBUTTONUP: + _button = MouseButtons.Right; + _clickCount = 1; + _up = true; + break; + case WM.WM_RBUTTONDBLCLK: + _button = MouseButtons.Right; + _clickCount = 2; + _ck = true; + break; + case WM.WM_MBUTTONDOWN: + _button = MouseButtons.Middle; + _clickCount = 1; + _ck = true; + break; + case WM.WM_MOUSEWHEEL: + // 滚轮 + break; + case WM.WM_MOUSEMOVE: + // 移动 + // 假设想要限制鼠标在屏幕中的移动区域能够在此处设置 + // 后期须要考虑实际的x y的容差 + // if (!Screen.PrimaryScreen.Bounds.Contains(e.X, e.Y)) + // // return 1; + // if (button == MouseButtons.Left) + // { + // GetCursorPos(out POINT pt); + // // 防止频繁获取导致出错 + // if (pt0ld.Leng(pt) > 20) + // pt0ld = pt; + // } + break; + } + + // 从回调函数中得到鼠标的信息 + var mouseMsg = MouseHookStruct.Create(lParam); + MouseEventArgs e = new(_button, _clickCount, mouseMsg.Point.X, mouseMsg.Point.Y, 0); + if (_down) + MouseDown?.Invoke(this, e); + if (_up) + MouseUp?.Invoke(this, e); + if (_ck) + Click?.Invoke(this, e); + if (_clickCount == 2) + { + // 如果不用时间控制,那么双击会执行两次 + if (_watch.Elapsed.TotalMilliseconds > GetDoubleClickTime()) + { + DoubleClick?.Invoke(this, e); + _watch.Reset(); + _watch.Start(); + } + } + MouseMove?.Invoke(this, e); + MouseWheel?.Invoke(this, e); + + // 屏蔽此输入 + if (_isHookBreak) + return true; + + return false; + } + + + /// + /// Hook鼠标数据结构 + /// + [StructLayout(LayoutKind.Sequential)] + public struct MouseHookStruct + { + /// + /// 鼠标在屏幕上的x,y坐标 + /// + public Point Point; + /// + /// 点击窗体的句柄 + /// + public IntPtr hWnd; + /// + /// 消息 + /// + public int wHitTestCode; + /// + /// 扩展信息,可以使用GetMessageExtraInfo的返回值 + /// + public int dwExtraInfo; + + public static MouseHookStruct Create(IntPtr lParam) + { + return (MouseHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseHookStruct)); + } + + public void ToPtr(IntPtr lParam) + { + Marshal.StructureToPtr(this, lParam, true); + } + } + + + #region IDisposable接口相关函数 + public bool IsDisposed { get; private set; } = false; + + /// + /// 手动调用释放 + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// 析构函数调用释放 + /// + ~MouseHook() + { + Dispose(false); + } + + protected virtual void Dispose(bool disposing) + { + // 不重复释放,并设置已经释放 + if (IsDisposed) return; + IsDisposed = true; + UnHook(); + } + #endregion +} \ No newline at end of file diff --git a/src/Basal/IFox.Basal.Shared/WindowsAPI/WindowsAPI.cs b/src/Basal/IFox.Basal.Shared/WindowsAPI/WindowsAPI.cs new file mode 100644 index 0000000000000000000000000000000000000000..6f8ac91489655ed2c03be1ad55c1f77ac364fe1c --- /dev/null +++ b/src/Basal/IFox.Basal.Shared/WindowsAPI/WindowsAPI.cs @@ -0,0 +1,657 @@ +#define Marshal + +namespace IFoxCAD.Basal; +public partial class WindowsAPI +{ + #region kernel32 + // https://blog.csdn.net/haelang/article/details/45147121 + [DllImport("kernel32.dll")] + public extern static uint GetLastError(); + + [DllImport("kernel32.dll")] + public static extern long GetHandleInformation(long hObject, ref long lpdwFlags); + + [DllImport("kernel32.dll")] + public static extern IntPtr GetModuleHandle(string ModuleName); + + [DllImport("kernel32.dll")] + public static extern int GetCurrentThreadId(); + + /// + /// 获取要引入的函数,将符号名或标识号转换为DLL内部地址 + /// + /// exe/dll句柄 + /// 接口名 + /// + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true)] + public static extern IntPtr GetProcAddress(IntPtr hModule, string procName); + + /// + /// 锁定内存 + /// + /// + /// + [DllImport("kernel32.dll", SetLastError = true)] + static extern IntPtr GlobalLock(IntPtr hMem); + /// + /// 解锁内存 + /// + /// + /// + [DllImport("kernel32.dll", SetLastError = true)] + static extern bool GlobalUnlock(IntPtr hMem); +#if !Marshal + /* + const int GMEM_MOVEABLE = 0x0002; + IntPtr newPtr = WindowsAPI.GlobalAlloc(GMEM_MOVEABLE, Marshal.SizeOf(structObj)); + */ + /// + /// 从堆中分配内存 + /// 被代替: Marshal.AllocHGlobal + /// + /// 分配方式 + /// 分配的字节数 + /// + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr GlobalAlloc(uint uFlags, int dwBytes); + /// + /// 释放堆内存 + /// 被代替: Marshal.FreeHGlobal + /// + /// 产生的句柄 + /// + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr GlobalFree(IntPtr hMem); +#endif + /// + /// 获取内存块大小 + /// + /// + /// + [DllImport("kernel32.dll", SetLastError = true)] + public static extern uint GlobalSize(IntPtr hMem); + + /// + /// 锁定和释放内存 + /// + /// 锁定数据对象指针 + /// 返回锁定的内存片段指针,锁定期间执行任务 + /// 是否锁定成功 + /// + public static bool GlobalLockTask(IntPtr data, Action task) + { + //if (task == null) + // throw new ArgumentNullException(nameof(task)); + task.NotNull(nameof(task)); + if (data == IntPtr.Zero) + return false; + + try + { + var ptr = GlobalLock(data); + // 有几率导致无效锁定: + // 重复复制同一个图元时,第二次是 IntPtr.Zero, + // 第三次就又可以复制了 + if (ptr == IntPtr.Zero) + return false; + task.Invoke(ptr); + } + finally { GlobalUnlock(data); } + return true; + } + + /// + /// byte数组转结构体 + /// + /// byte数组 + /// 返回的结构大小 + /// 返回的结构体 + [Obsolete("效率太低", true)] + public static T? BytesToStruct(byte[] bytes, out int typeSize) + { + var structType = typeof(T); + typeSize = Marshal.SizeOf(structType); + if (typeSize > bytes.Length) + return default; + + // 安全写法效率太低了 + // 分配结构体大小的内存空间 + IntPtr structPtr = Marshal.AllocHGlobal(typeSize); + + // 将byte数组拷到分配好的内存空间 + Marshal.Copy(bytes, 0, structPtr, typeSize); + // 将内存空间转换为目标结构体; + // 转类型的时候会拷贝一次,看它们地址验证 &result != &structPtr + var result = (T)Marshal.PtrToStructure(structPtr, structType); + + // 释放内存空间 + Marshal.FreeHGlobal(structPtr); + return result; + } + + /// + /// byte数组转结构体 + /// + /// byte数组 + /// 返回的结构体 + [MethodImpl] + public static T? BytesToStruct(byte[] bytes) + { + T? result = default; + unsafe + { + // 安全指针方法 + // var pB = Marshal.UnsafeAddrOfPinnedArrayElement(bytes, 0); + // 不安全指针方法 + fixed (byte* pB = &bytes[0]) + { + result = (T?)Marshal.PtrToStructure(new IntPtr(pB), typeof(T)); + } + } + return result; + } + + /// + /// 结构体转byte数组 + /// unmanaged + /// + /// 要转换的结构体 + [MethodImpl] + public static byte[] StructToBytes(T structObj) where T : unmanaged/*非托管的T从来不为空*/ + { + // 得到结构体的大小 + var typeSize = Marshal.SizeOf(structObj); + // 从内存空间拷到byte数组 + var bytes = new byte[typeSize]; + unsafe + { + Marshal.Copy(new IntPtr(&structObj), bytes, 0, typeSize); + } +#if true20221030 + // 安全写法效率太低了 + StructToPtr(structObj, structPtr => { + Marshal.Copy(structPtr, bytes, 0, typeSize); + }); +#endif + return bytes; + } + +#if true20221030 + /// + /// 结构体转指针 + /// + /// 要转换的结构体 + /// 输出指针 + /// 释放申请的内存 + /// 是否锁定内存 + /// + public static void StructToPtr(T structObj, + Action? task = null, + bool freeHGlobal = true, + bool lockPrt = true) + { + IntPtr newPtr = Marshal.AllocHGlobal(Marshal.SizeOf(structObj)); + if (newPtr == IntPtr.Zero) + throw new ArgumentException(nameof(newPtr)); + + try + { + // 剪贴板写入的时候不允许锁定内存,否则在频繁触发剪贴板将导致卡死程序 + if (lockPrt) + { + GlobalLockTask(newPtr, ptr => { + // 将结构体拷到分配好的内存空间 + Marshal.StructureToPtr(structObj, newPtr, true); + task?.Invoke(newPtr); + }); + } + else + { + // 将结构体拷到分配好的内存空间 + Marshal.StructureToPtr(structObj, newPtr, true); + task?.Invoke(newPtr); + } + } + catch (Exception e) + { + Debugger.Break(); + Debugx.Printl(e.Message); + } + finally + { + if (freeHGlobal && newPtr != IntPtr.Zero) + Marshal.FreeHGlobal(newPtr); + } + } +#endif + #endregion +} + +public partial class WindowsAPI +{ + #region imm32 + /// + /// 获取输入法的虚拟键码 + /// + /// + /// + [DllImport("imm32.dll")] + public static extern IntPtr ImmGetVirtualKey(IntPtr hWnd); + /// + /// 获取输入法状态 + /// + /// 输入法标识符 + /// 输入模式 + /// 指向函数在其中检索句子模式值的变量的指针 + /// + [DllImport("imm32.dll")] + public static extern bool ImmGetConversionStatus(IntPtr himc, out int lpdw, out int lpdw2); + + /// + /// 获取指定窗口的输入法状态 + /// + /// 窗口句柄 + /// + [DllImport("imm32.dll")] + public static extern IntPtr ImmGetContext(IntPtr hwnd); + /// + /// 设置输入法的当前状态 + /// + /// 窗口句柄 + /// + /// + [DllImport("imm32.dll")] + public static extern bool ImmSetOpenStatus(IntPtr hwnd, bool fOpen); + /// + /// 输入法打开状态 + /// + /// + /// 非0打开,0关闭;(true中文,false英文) + [DllImport("imm32.dll")] + public static extern bool ImmGetOpenStatus(IntPtr hwnd); + #endregion +} + +public partial class WindowsAPI +{ + #region user32 + + /// + /// 获取窗口客户区的大小,客户区为窗口中除标题栏,菜单栏之外的地方 + /// + /// + /// + /// + [DllImport("user32.dll", CharSet = CharSet.Auto, EntryPoint = "GetClientRect")] + public static extern bool GetClientRect(IntPtr hwnd, out IntRect lpRect); + + /// + /// 查找主线程
+ /// 代替
+ /// 托管线程和他们不一样: + ///
+ /// 主窗口 + /// 进程ID + /// 线程ID + [DllImport("user32.dll", SetLastError = true)] + public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); + + /// + /// 设置焦点 + /// + /// + /// + [DllImport("user32.dll")] + public static extern IntPtr SetFocus(IntPtr hWnd); + + /// + /// 获取当前窗口 + /// + /// 当前窗口标识符 + [DllImport("user32.dll")] + public static extern IntPtr GetForegroundWindow(); + /// + /// 将一个消息的组成部分合成一个消息并放入对应线程消息队列的方法 + /// + /// 控件句柄 + /// 消息是什么。键盘按键、鼠标点击还是其他 + /// + /// + /// + [DllImport("user32.dll")] + public static extern bool PostMessage(IntPtr hhwnd, int msg, IntPtr wparam, IntPtr lparam); + /// + /// 发送击键 + /// + /// + /// + /// + /// + [DllImport("user32.dll", EntryPoint = "keybd_event")] + public static extern void KeybdEvent(byte bVk, byte bScan, int dwFlags, int dwExtraInfo); + /// + /// 获取窗口文字的长度 + /// + /// 窗口标识符 + /// 文字长度 + [DllImport("user32.dll")] + public static extern int GetWindowTextLength(IntPtr hWnd); + /// + /// 获取窗口的标题 + /// + /// 窗口标识符 + /// 窗口文字 + /// 文字长度 + /// + [DllImport("User32.dll", CharSet = CharSet.Auto)] + public static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int nMaxCount); + + // [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + // internal static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); + + + /// + /// 获取某个线程的输入法布局 + /// + /// 线程ID + /// 布局码 + [DllImport("user32.dll")] + public static extern int GetKeyboardLayout(int threadid); + + + /// + /// 获取按键的当前状态 + /// + /// 按键虚拟代码 + /// 表示没按下>0;按下<0 + [DllImport("user32.dll")] + public static extern short GetKeyState(int nVirtKey); + /// + /// 检索指定窗口所属的类的名称。 + /// + /// 窗口标识符 + /// + /// + /// + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd); + + [DllImport("user32.DLL", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall, SetLastError = true)] + public static extern IntPtr GetTopWindow(IntPtr hWnd); + + + /// + /// 获取线程对应的窗体信息 + /// + /// 线程 + /// + /// + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + static extern bool GetGUIThreadInfo(uint idThread, ref GuiThreadInfo lpgui); + + /// + /// 获取线程对应的窗体信息 + /// + [StructLayout(LayoutKind.Sequential)] + public struct GuiThreadInfo + { + public int cbSize; + public int flags; + public IntPtr hwndActive; + public IntPtr hwndFocus; + public IntPtr hwndCapture; + public IntPtr hwndMenuOwner; + public IntPtr hwndMoveSize; + public IntPtr hwndCaret; + public System.Drawing.Rectangle rcCaret; + + public static GuiThreadInfo Create(uint windowThreadProcessId) + { + if (windowThreadProcessId == 0) + throw new ArgumentNullException(nameof(windowThreadProcessId)); + + GuiThreadInfo gti = new(); + gti.cbSize = Marshal.SizeOf(gti); + GetGUIThreadInfo(windowThreadProcessId, ref gti); + return gti; + } + } + + [DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr GetFocus(); + + [DllImport("user32.dll")] + public static extern IntPtr SendMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam); + + [DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr GetParent(IntPtr hWnd); + + [DllImport("user32.dll")] + public static extern int ToAscii(int uVirtKey, int uScancode, byte[] lpdKeyState, byte[] lpwTransKey, int fuState); + + [DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr GetActiveWindow(); + + [DllImport("user32.dll", SetLastError = true)] + public static extern long GetWindowThreadProcessId(IntPtr hwnd, ref int lpdwProcessId); + + [DllImport("user32.dll", SetLastError = true)] + public static extern bool IsIconic(int hWnd); + + [DllImport("user32.dll")] + public static extern bool IsWindowEnabled(IntPtr hWnd); + #endregion + + #region 键盘钩子 + public delegate IntPtr CallBack(int nCode, int wParam, IntPtr lParam); + [DllImport("user32.dll")] + public static extern IntPtr SetWindowsHookEx(HookType idHook, CallBack lpfn, IntPtr hmod, int dwThreadId); + [DllImport("user32.dll")] + public static extern IntPtr UnhookWindowsHookEx(IntPtr hHook); + [DllImport("user32.dll")] + public static extern IntPtr CallNextHookEx(IntPtr hHook, int ncode, int wParam, IntPtr lParam); + /// + /// Hook键盘数据结构 + /// + [ComVisible(true)] + [Serializable] + //[DebuggerDisplay("{DebuggerDisplay,nq}")] + //[DebuggerTypeProxy(typeof(KeyboardHookStruct))] + [StructLayout(LayoutKind.Sequential)] + public struct KeyboardHookStruct + { + public int VkCode; // 键码,该代码必须有一个价值的范围1至254 + public int ScanCode; // 指定的硬件扫描码的关键 + public int Flags; // 键标志 + public int Time; // 指定的时间戳记的这个讯息 + public int DwExtraInfo; // 指定额外信息相关的信息 + + public static KeyboardHookStruct Create(IntPtr lParam) + { + return (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct)); + } + public void ToPtr(IntPtr lParam) + { + Marshal.StructureToPtr(this, lParam, true); + } + } + /// + /// 注册表增加低级钩子超时处理,防止系统不允许, + /// 否则:偶发性出现 键盘钩子不能用了,而且退出时产生 1404 错误 + /// https://www.cnblogs.com/songr/p/5131655.html + /// + public static void CheckLowLevelHooksTimeout(int setLowLevel = 25000) + { + const string llh = "LowLevelHooksTimeout"; + using var registryKey = Registry.CurrentUser.OpenSubKey("Control Panel\\Desktop", true); + if ((int)registryKey.GetValue(llh, 0) < setLowLevel) + registryKey.SetValue(llh, setLowLevel, RegistryValueKind.DWord); + } + #endregion +} + +public partial class WindowsAPI +{ + [DllImport("user32.dll")] + public static extern bool GetWindowRect(IntPtr hwnd, ref IntRect lpRect); + + [ComVisible(true)] + [Serializable] + [StructLayout(LayoutKind.Sequential)] + [DebuggerDisplay("{DebuggerDisplay,nq}")] + [DebuggerTypeProxy(typeof(IntRect))] + public struct IntRect + { + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private string DebuggerDisplay => $"(Left:{_Left},Top:{_Top},Right:{_Right},Bottom:{_Bottom})"; + + int _Left; + int _Top; + int _Right; + int _Bottom; + public int Left => _Left; + public int Top => _Top; + public int Right => _Right; + public int Bottom => _Bottom; + public int Width => checked(Right - Left); + public int Height => checked(Bottom - Top); + + public IntRect(int left, int top, int right, int bottom) + { + _Left = left; + _Top = top; + _Right = right; + _Bottom = bottom; + } + + static readonly IntRect _Zero = new(0, 0, 0, 0); + public static IntRect Zero => _Zero; + + public override string ToString() => $"({_Left},{_Top},{_Right},{_Bottom})"; + + #region 重载运算符_比较 + public bool Equals(IntRect other) + { + return + _Left == other._Left && + _Top == other._Top && + _Right == other._Right && + _Bottom == other._Bottom; + } + public static bool operator !=(IntRect a, IntRect b) + { + return !(a == b); + } + public static bool operator ==(IntRect a, IntRect b) + { + return a.Equals(b); + } + public override bool Equals(object obj) + { + return obj is IntRect d && Equals(d); + } + public override int GetHashCode() + { + return ((_Left, _Top).GetHashCode(), _Right).GetHashCode() ^ _Bottom.GetHashCode(); + } + + public IntRect Clone() + { + return (IntRect)MemberwiseClone(); + } + #endregion + } + + [ComVisible(true)] + [Serializable] + [DebuggerDisplay("{DebuggerDisplay,nq}")] + [DebuggerTypeProxy(typeof(IntSize))] + [StructLayout(LayoutKind.Sequential)] + public struct IntSize + { + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private string DebuggerDisplay => $"(Hight:{Hight},Width:{Width})"; + public int Hight; + public int Width; + + public IntSize(int cx, int cy) + { + Hight = cx; + Width = cy; + } + public override string ToString() => $"({Hight},{Width})"; + } + + [ComVisible(true)] + [Serializable] + [DebuggerDisplay("{DebuggerDisplay,nq}")] + [DebuggerTypeProxy(typeof(Point3D))] + [StructLayout(LayoutKind.Sequential)] + public struct Point3D : IEquatable + { + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private string DebuggerDisplay => $"(X:{X},Y:{Y},Z:{Z})"; + + /* 由于此类是用来优化,从而实现字段修改,因此直接暴露字段减少栈帧 */ + public double X; + public double Y; + public double Z; + + public Point3D(double x, double y, double z) + { + X = x; + Y = y; + Z = z; + } + //public static implicit operator Point3D(Point3d pt) + //{ + // return new Point3D(pt.X, pt.Y, pt.Z); + //} + //public static implicit operator Point3d(Point3D pt) + //{ + // return new Point3d(pt.X, pt.Y, pt.Z); + //} + public override string ToString() => $"({X},{Y},{Z})"; + + public static Point3D Create(IntPtr lParam) + { + return (Point3D)Marshal.PtrToStructure(lParam, typeof(Point3D)); + } + + public void ToPtr(IntPtr lParam) + { + Marshal.StructureToPtr(this, lParam, true); + } + + + #region 重载运算符_比较 + public bool Equals(Point3D other) + { + return + X == other.X && + Y == other.Y && + Z == other.Z; + } + public static bool operator !=(Point3D a, Point3D b) + { + return !(a == b); + } + public static bool operator ==(Point3D a, Point3D b) + { + return a.Equals(b); + } + public override bool Equals(object obj) + { + return obj is Point3D d && Equals(d); + } + public override int GetHashCode() + { + return (X, Y).GetHashCode() ^ Z.GetHashCode(); + } + #endregion + } +} \ No newline at end of file diff --git a/src/Basal/IFox.Basal.Source/IFox.Basal.Source.csproj b/src/Basal/IFox.Basal.Source/IFox.Basal.Source.csproj new file mode 100644 index 0000000000000000000000000000000000000000..4a5496ddc3770d91d3adc5dafa32ab4850a3ec52 --- /dev/null +++ b/src/Basal/IFox.Basal.Source/IFox.Basal.Source.csproj @@ -0,0 +1,49 @@ + + + + + + netstandard1.0 + true + $(AssemblyName) + $(Version) + true + + + + CS8021 + true + false + contentFiles + true + false + false + true + + + + + true + $(ContentTargetFolders)\cs\any\$(PackageId)\ + false + + + true + + + + true + $(ContentTargetFolders)\any\any\$(PackageId)\ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Basal/IFox.Basal/GlobalUsings.cs b/src/Basal/IFox.Basal/GlobalUsings.cs new file mode 100644 index 0000000000000000000000000000000000000000..35cc1cd67e92eaf57e0061ef6762df7472b42d5b --- /dev/null +++ b/src/Basal/IFox.Basal/GlobalUsings.cs @@ -0,0 +1,23 @@ +// 系统引用 +global using System; +global using System.Collections; +global using System.Collections.Generic; +global using System.IO; +global using System.Linq; +global using System.Text; +global using System.Reflection; +global using System.ComponentModel; +global using System.Runtime.InteropServices; +global using System.Diagnostics; +global using System.Drawing; +global using System.Windows.Forms; +global using Microsoft.Win32; +global using System.Runtime.CompilerServices; +global using System.Threading; +global using System.Linq.Expressions; +global using System.Collections.ObjectModel; + + +#if NET45 +global using Microsoft.CSharp.RuntimeBinder; +#endif \ No newline at end of file diff --git a/src/Basal/IFox.Basal/IFox.Basal.csproj b/src/Basal/IFox.Basal/IFox.Basal.csproj new file mode 100644 index 0000000000000000000000000000000000000000..db74995c7f83db9baef373bd0b064fee99c77449 --- /dev/null +++ b/src/Basal/IFox.Basal/IFox.Basal.csproj @@ -0,0 +1,19 @@ + + + + NET35;NET40;NET45 + true + true + MSB3270 + + + + + 4.7.0 + + + + + + + diff --git a/src/CAD/Directory.Build.props b/src/CAD/Directory.Build.props new file mode 100644 index 0000000000000000000000000000000000000000..84265c22fa5b98facccfc1186d83eba545c9b5c4 --- /dev/null +++ b/src/CAD/Directory.Build.props @@ -0,0 +1,34 @@ + + + + 0.5.2.4 + 补充2023的信息 + + + + preview + enable + true + ..\..\..\bin\$(Configuration)\ + true + true + True + True + + + + + InspireFunction + xsfhlzh;vicwjb;liuqihong + InspireFunction + 基于.NET的二次开发基本类库. + MIT + true + https://gitee.com/inspirefunction/ifoxcad + https://gitee.com/inspirefunction/ifoxcad.git + git + IFox;CAD;AutoCad;C#;NET;GStarCAD;ZWCAD + + + + \ No newline at end of file diff --git a/src/CAD/IFox.CAD.ACAD/GlobalUsings.cs b/src/CAD/IFox.CAD.ACAD/GlobalUsings.cs new file mode 100644 index 0000000000000000000000000000000000000000..dadfe53fe00dea58372bc2f77740aedf67c137a9 --- /dev/null +++ b/src/CAD/IFox.CAD.ACAD/GlobalUsings.cs @@ -0,0 +1,49 @@ +// 系统引用 +global using System; +global using System.Collections; +global using System.Collections.Generic; +global using System.IO; +global using System.Linq; +global using System.Text; +global using System.Reflection; +global using System.Text.RegularExpressions; +global using Microsoft.Win32; +global using System.ComponentModel; +global using System.Runtime.InteropServices; +global using System.Collections.Specialized; + +global using Exception = System.Exception; + +global using Registry = Microsoft.Win32.Registry; +global using RegistryKey = Microsoft.Win32.RegistryKey; + +// cad 引用 +global using Autodesk.AutoCAD.ApplicationServices; +global using Autodesk.AutoCAD.EditorInput; +global using Autodesk.AutoCAD.Colors; +global using Autodesk.AutoCAD.DatabaseServices; +global using Autodesk.AutoCAD.Geometry; +global using Autodesk.AutoCAD.Runtime; +global using Acap = Autodesk.AutoCAD.ApplicationServices.Application; +global using Acgi = Autodesk.AutoCAD.GraphicsInterface; + +global using Autodesk.AutoCAD.DatabaseServices.Filters; +global using Autodesk.AutoCAD; + +// jig命名空间会引起Viewport/Polyline等等重义,最好逐个引入 using Autodesk.AutoCAD.GraphicsInterface +global using Autodesk.AutoCAD.GraphicsInterface; +global using WorldDraw = Autodesk.AutoCAD.GraphicsInterface.WorldDraw; +global using Manager = Autodesk.AutoCAD.GraphicsSystem.Manager; +global using Group = Autodesk.AutoCAD.DatabaseServices.Group; +global using Viewport = Autodesk.AutoCAD.DatabaseServices.Viewport; +global using Polyline = Autodesk.AutoCAD.DatabaseServices.Polyline; +global using Cad_DwgFiler = Autodesk.AutoCAD.DatabaseServices.DwgFiler; +global using Cad_DxfFiler = Autodesk.AutoCAD.DatabaseServices.DxfFiler; +global using Cad_ErrorStatus = Autodesk.AutoCAD.Runtime.ErrorStatus; + +// ifoxcad.basal 引用 +global using IFoxCAD.Basal; + +#if !NewtonsoftJson +global using System.Web.Script.Serialization; +#endif \ No newline at end of file diff --git a/src/CAD/IFox.CAD.ACAD/IFox.CAD.ACAD.csproj b/src/CAD/IFox.CAD.ACAD/IFox.CAD.ACAD.csproj new file mode 100644 index 0000000000000000000000000000000000000000..4b999214c35f4585bed44e5a4102ba9e43324099 --- /dev/null +++ b/src/CAD/IFox.CAD.ACAD/IFox.CAD.ACAD.csproj @@ -0,0 +1,54 @@ + + + + NET35;NET40;NET45 + true + true + MSB3270 + + + + DEBUG + + + $(Configuration);acad;ac2009 + + + $(Configuration);acad;ac2013 + + + $(Configuration);acad;ac2015 + + + + + + + + + + + + + + + + True + + + + + + + + + + + + + + + + + + diff --git a/src/CAD/IFox.CAD.GCAD/GlobalUsings.cs b/src/CAD/IFox.CAD.GCAD/GlobalUsings.cs new file mode 100644 index 0000000000000000000000000000000000000000..08de9aa6def451afa40d8019aa538ef8678972ed --- /dev/null +++ b/src/CAD/IFox.CAD.GCAD/GlobalUsings.cs @@ -0,0 +1,51 @@ +/// 系统引用 +global using System; +global using System.Collections; +global using System.Collections.Generic; +global using System.IO; +global using System.Linq; +global using System.Text; +global using System.Reflection; +global using System.Text.RegularExpressions; +global using Microsoft.Win32; +global using System.ComponentModel; +global using System.Runtime.InteropServices; +global using System.Collections.Specialized; + +global using Exception = System.Exception; + +global using Registry = Microsoft.Win32.Registry; +global using RegistryKey = Microsoft.Win32.RegistryKey; + +/// cad 引用 +global using GrxCAD.ApplicationServices; +global using GrxCAD.EditorInput; +global using GrxCAD.Colors; +global using GrxCAD.DatabaseServices; +global using GrxCAD.Geometry; +global using GrxCAD.Runtime; +global using Acap = GrxCAD.ApplicationServices.Application; +global using Acgi = GrxCAD.GraphicsInterface; + +global using GrxCAD.DatabaseServices.Filters; +global using GrxCAD; + +global using GrxCAD.Internal; + +// jig命名空间会引起Viewport/Polyline等等重义,最好逐个引入 using Autodesk.AutoCAD.GraphicsInterface +global using GrxCAD.GraphicsInterface; +global using WorldDraw = GrxCAD.GraphicsInterface.WorldDraw; +global using Manager = GrxCAD.GraphicsSystem.Manager; +global using Group = GrxCAD.DatabaseServices.Group; +global using Viewport = GrxCAD.DatabaseServices.Viewport; +global using Polyline = GrxCAD.DatabaseServices.Polyline; +global using Cad_DwgFiler = GrxCAD.DatabaseServices.DwgFiler; +global using Cad_DxfFiler = GrxCAD.DatabaseServices.DxfFiler; +global using Cad_ErrorStatus = GrxCAD.Runtime.ErrorStatus; + +/// ifoxcad.basal 引用 +global using IFoxCAD.Basal; + +#if !NewtonsoftJson +global using System.Web.Script.Serialization; +#endif \ No newline at end of file diff --git a/src/CAD/IFox.CAD.GCAD/IFox.CAD.GCAD.csproj b/src/CAD/IFox.CAD.GCAD/IFox.CAD.GCAD.csproj new file mode 100644 index 0000000000000000000000000000000000000000..506d96e51864e52787ab91b6191c187e3ac076cb --- /dev/null +++ b/src/CAD/IFox.CAD.GCAD/IFox.CAD.GCAD.csproj @@ -0,0 +1,42 @@ + + + + NET48 + + true + true + false + MSB3270 + + + + DEBUG + + + $(Configuration);gcad + + + + + runtime + + + + + + True + + + + + + + + + + + + + + + diff --git a/src/CAD/IFox.CAD.Shared/Algorithms/Graph/Graph.cs b/src/CAD/IFox.CAD.Shared/Algorithms/Graph/Graph.cs new file mode 100644 index 0000000000000000000000000000000000000000..30a76f8d70ac7ba35fca36ce403c82f4bc2d1de5 --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/Algorithms/Graph/Graph.cs @@ -0,0 +1,739 @@ +namespace IFoxCAD.Cad; + +using Exception = System.Exception; + +/// +/// 无权无向图实现 +/// IEnumerable 枚举所有顶点; +/// +public sealed class Graph : IGraph, IEnumerable +{ + #region 字段及属性 + /// + /// 存储所有节点的字典,key为顶点的类型,value为邻接表,类型是hashset,不可重复添加点 + /// + /// + readonly Dictionary> vertices = new(); + /// + /// 邻接边表,key为顶点的类型,value为邻接边表,类型是hashset,不可重复添加边 + /// + readonly Dictionary> edges = new(); + /// + /// 为加快索引,引入hash检索 + /// + readonly Dictionary vertexs = new(); + /// + /// 节点数量 + /// + public int VerticesCount => vertices.Count; + + /// + /// Returns a reference vertex. + /// Time complexity: O(1). + /// + private IGraphVertex? ReferenceVertex + { + get + { + using (var enumerator = vertexs.GetEnumerator()) + { + if (enumerator.MoveNext()) + { + return enumerator.Current.Value; + } + } + + return null; + } + } + IGraphVertex? IGraph.ReferenceVertex => ReferenceVertex; + /// + /// 目前点增加点的顺序号,这个点号不随删点而减少的 + /// + private int insertCount; + #endregion + + #region 构造函数 + /// + /// + /// + public Graph() + { + insertCount = 0; // 每次新建对象就将顶点顺序号归零 + } + #endregion + + #region 顶点及边_增 + /// + /// 向该图添加一个新顶点,但是无边; + /// + /// 点 + /// 创建的顶点 + public IGraphVertex AddVertex(Point3d pt) + { + var str = pt.GetHashString(); + if (vertexs.ContainsKey(str)) + return vertexs[str]; + + var vertex = new GraphVertex(pt, insertCount++); + vertices.Add(vertex, new HashSet()); + edges.Add(vertex, new HashSet()); + + vertexs[str] = vertex; + + return vertex; + } + + /// + /// 向该图添加一个边; + /// + /// + public void AddEdge(Curve3d curve) + { + //if (curve == null) + // throw new ArgumentNullException(nameof(curve)); + + curve.NotNull(nameof(curve)); + + var start = AddVertex(curve.StartPoint); + var end = AddVertex(curve.EndPoint); + + // 添加起点的邻接表和邻接边 + vertices[start].Add(end); + edges[start].Add(new GraphEdge(end, curve)); + + // 为了保证点顺序,每个点的邻接边必须按起点-终点,所以添加曲线终点时,将添加一个方向的曲线 + var curtmp = (Curve3d)curve.Clone(); + curtmp = curtmp.GetReverseParameterCurve(); + + // 添加终点的邻接表和邻接边 + vertices[end].Add(start); + edges[end].Add(new GraphEdge(start, curtmp)); + } + #endregion + + #region 顶点及边_删 + /// + /// 从此图中删除现有顶点; + /// + /// 点 + public void RemoveVertex(Point3d pt) + { + var str = pt.GetHashString(); + if (vertexs.ContainsKey(str)) + { + var vertex = vertexs[str]; + + // 删除邻接表里的vertex点,先删除后面的遍历可以少一轮 + vertices.Remove(vertex!); + + // 删除其他顶点的邻接表里的vertex点 + foreach (var item in vertices.Values) + item.Remove(vertex!); + + // 删除邻接边表里的vertex点,先删除后面的遍历可以少一轮 + edges.Remove(vertex!); + + // 删除其他顶点的邻接边表的指向vertex的边 + foreach (var item in edges.Values) + { + item.RemoveWhere(x => vertex.Equals(x.TargetVertex)); + // foreach (var edge in item) + // { + // if (vertex.Equals(edge.TargetVertex)) + // item.Remove(edge); + // } + } + vertexs.Remove(str); + } + } + + /// + /// 从此图中删除一条边; + /// + /// 曲线 + public void RemoveEdge(Curve3d curve) + { + //if (curve == null) + // throw new ArgumentNullException(nameof(curve)); + curve.NotNull(nameof(curve)); + + RemoveVertex(curve.StartPoint); + RemoveVertex(curve.EndPoint); + } + #endregion + + #region 顶点和边_查 + /// + /// 我们在给定的来源和目的地之间是否有边? + /// + /// 起点 + /// 终点 + /// 有边返回 ,反之返回 + public bool HasEdge(IGraphVertex source, IGraphVertex dest) + { + if (!vertices.ContainsKey(source) || !vertices.ContainsKey(dest)) + throw new ArgumentException("源或目标不在此图中;"); + + foreach (var item in edges[source]) + { + if (item.TargetVertex == dest) + return true; + } + return false; + } + + /// + /// 获取边 + /// + /// 起点 + /// 终点 + /// + /// 传入的点不在图中时抛出参数异常 + public IEdge? GetEdge(IGraphVertex source, IGraphVertex dest) + { + if (!vertices.ContainsKey(source) || !vertices.ContainsKey(dest)) + throw new ArgumentException("源或目标不在此图中;"); + + foreach (var item in edges[source]) + { + if (item.TargetVertex.Equals(dest)) + return item; + } + return null; + } + + /// + /// 是否存在顶点,此函数目前未发现有啥用 + /// + /// 顶点 + /// 存在顶点返回 ,反之返回 + public bool ContainsVertex(IGraphVertex value) + { + return vertices.ContainsKey(value); + } + #endregion + + #region 获取邻接表和曲线 + /// + /// 获取顶点的邻接表 + /// + /// 顶点 + /// 邻接表 + public HashSet GetAdjacencyList(IGraphVertex vertex) + { + return vertices[vertex]; + } + + /// + /// 获取顶点的邻接边表 + /// + /// 顶点 + /// 邻接边表 + public HashSet GetAdjacencyEdge(IGraphVertex vertex) + { + return edges[vertex]; + } + + /// + /// 根据顶点表获取曲线集合 + /// + /// 顶点表 + /// 曲线表 + public List GetCurves(List graphVertices) + { + var curves = new List(); + for (int i = 0; i < graphVertices.Count - 1; i++) + { + var cur = graphVertices[i]; + var next = graphVertices[i + 1]; + var edge = GetEdge(cur, next); + if (edge is not null) + curves.Add(edge.TargetEdge); + } + var lastedge = GetEdge(graphVertices[^1], graphVertices[0]); + if (lastedge is not null) + curves.Add(lastedge.TargetEdge); + + return curves; + } + #endregion + + #region 克隆及接口实现 + /// + /// 克隆此图;目测是深克隆 + /// + [System.Diagnostics.DebuggerStepThrough] + public Graph Clone() + { + var newGraph = new Graph(); + + foreach (var vertex in edges.Values) + foreach (var item in vertex) + newGraph.AddEdge(item.TargetEdge); + + return newGraph; + } + + IGraph IGraph.Clone() + { + return Clone(); + } + + /// + /// 节点迭代器 + /// + /// + [System.Diagnostics.DebuggerStepThrough] + public IEnumerator GetEnumerator() + { + return VerticesAsEnumberable.GetEnumerator(); + } + + [System.Diagnostics.DebuggerStepThrough] + IEnumerator? IEnumerable.GetEnumerator() + { + return GetEnumerator() as IEnumerator; + } + /// + /// 节点迭代器 + /// + public IEnumerable VerticesAsEnumberable => + vertices.Select(x => x.Key); + #endregion + + #region 方法 + /// + /// 输出点的邻接表的可读字符串 + /// + /// + public string ToReadable() + { + int i = 1; + string output = string.Empty; + foreach (var node in vertices) + { + var adjacents = string.Empty; + + output = string.Format("{1}\r\n{0}-{2}: [", i, output, node.Key.Data.ToString()); + + foreach (var adjacentNode in node.Value) + adjacents = string.Format("{0}{1},", adjacents, adjacentNode.Data.ToString()); + + if (adjacents.Length > 0) + adjacents = adjacents.TrimEnd(new char[] { ',', ' ' }); + + output = string.Format("{0}{1}]", output, adjacents); + i++; + } + return output; + } + #endregion +} + + +/// +/// 邻接表图实现的顶点; +/// IEnumerable 枚举所有邻接点; +/// +public sealed class GraphVertex : IGraphVertex, IEquatable, IComparable, IComparable +{ + #region 属性 + /// + /// 数据 + /// + public Point3d Data { get; set; } + /// + /// 索引 + /// + public int Index { get; set; } + #endregion + + #region 构造 + /// + /// 邻接表图实现的顶点 + /// + /// 点 + /// 所在节点索引 + public GraphVertex(Point3d value, int index) + { + Data = value; + Index = index; + } + #endregion + + #region 重载运算符_比较 + /// + /// 是否相等 + /// + /// + /// + public bool Equals(IGraphVertex other) + { + return Index == other.Index; + } + /// + /// 是否相等 + /// + /// + /// + public override bool Equals(object obj) + { + if (obj is null) + return false; + if (obj is not IGraphVertex vertex) + return false; + else + return Equals(vertex); + } + /// + /// 计算hashcode + /// + /// + public override int GetHashCode() + { + return Index; + } + /// + /// 比较大小 + /// + /// + /// + public int CompareTo(IGraphVertex other) + { + if (Equals(other)) + return 0; + + if (Index < other.Index) + return -1; + else + return 1; + } + + int IComparable.CompareTo(IGraphVertex other) + { + return CompareTo(other); + } + /// + /// 比较大小 + /// + /// + /// + /// + public int CompareTo(object obj) + { + if (obj is null) + return 1; + + try + { + var other = (GraphVertex)obj; + return CompareTo(other); + } + catch (Exception) + { + throw new ArgumentException("Object is not a IGraphVertex"); + } + } + /// + /// 相等 + /// + /// + /// + /// + public static bool operator ==(GraphVertex person1, GraphVertex person2) + { + if (person1 is null || person2 is null) + return Equals(person1, person2); + + return person1.Equals(person2); + } + /// + /// 不相等 + /// + /// + /// + /// + public static bool operator !=(GraphVertex person1, GraphVertex person2) + { + if (person1 is null || person2 is null) + return !Equals(person1, person2); + + return !person1.Equals(person2); + } + #endregion +} + + +/// +/// 无向图中边的定义 +/// +public sealed class GraphEdge : IEdge, IEquatable +{ + #region 属性 + /// + /// 顶点 + /// + public IGraphVertex TargetVertex { get; set; } + /// + /// 边 + /// + public Curve3d TargetEdge { get; set; } + #endregion + + #region 构造 + /// + /// 无向图中边的定义 + /// + /// 下一点 + /// 下一点之间的曲线 + public GraphEdge(IGraphVertex target, Curve3d edge) + { + TargetVertex = target; + TargetEdge = edge; + } + #endregion + + #region 重载运算符_比较 + /// + /// 是否相等 + /// + /// + /// + public bool Equals(GraphEdge other) + { + if (other is null) + return false; + return TargetVertex == other.TargetVertex && + TargetEdge == other.TargetEdge; + } + /// + /// 是否相等 + /// + /// + /// + public override bool Equals(object obj) + { + if (obj is null) + return false; + if (obj is not GraphEdge personObj) + return false; + else + return Equals(personObj); + } + /// + /// 获取hashcode + /// + /// + public override int GetHashCode() + { + return (TargetVertex.GetHashCode(), TargetEdge.GetHashCode()).GetHashCode(); + } + /// + /// 相等 + /// + /// + /// + /// + public static bool operator ==(GraphEdge person1, GraphEdge person2) + { + if (person1 is null || person2 is null) + return Equals(person1, person2); + + return person1.Equals(person2); + } + /// + /// 不相等 + /// + /// + /// + /// + public static bool operator !=(GraphEdge person1, GraphEdge person2) + { + if (person1 is null || person2 is null) + return !Equals(person1, person2); + + return !person1.Equals(person2); + } + #endregion +} + + +/// +/// 深度优先搜索; +/// +public sealed class DepthFirst +{ + #region 公共方法 + /// + /// 存储所有的边 + /// +#if true + public List> Curve3ds { get; } = new(); +#else + public List> Curve3ds { get; } = new(); +#endif + private HashSet Curved { get; } = new(); + + + /// + /// 找出所有的路径 + /// + /// 图 + public void FindAll(IGraph graph) + { + var total = new HashSet(); + // var graphtmp = graph.Clone(); + foreach (var item in graph.VerticesAsEnumberable) + { + Dfs(graph, new LinkedHashSet { item }, total); + total.Add(item); + } + } + #endregion + + #region 内部方法 + /// + /// 递归 DFS; + /// + /// 图 + /// 已经遍历的路径 +#if true + void Dfs(IGraph graph, LinkedHashSet visited, HashSet totalVisited) + { + var adjlist = graph.GetAdjacencyList(/*startNode*/ visited.First!.Value); // O(1) + foreach (var nextNode in adjlist) // O(n) + { + if (totalVisited.Contains(nextNode)) + { + continue; + } + // 如果下一个点未遍历过 + if (!visited.Contains(nextNode)) // O(1) + { + // 将下一点加入路径集合,并进行下一次递归 + var sub = new LinkedHashSet { nextNode }; + sub.AddRange(visited); // O(n) + Dfs(graph, sub, totalVisited); + } + // 如果下一点遍历过,并且路径大于2,说明已经找到起点 + else if (visited.Count > 2 && nextNode.Equals(visited.Last!.Value)) + { + // 将重复的路径进行过滤,并把新的路径存入结果 + var curstr = Gethashstring(visited); // O(n) + if (Isnew(curstr)) // O(1) + { + Curve3ds.Add(visited); + Curved.Add(curstr.Item1); + } + } + } + } + + + + +#else + + void Dfs(IGraph graph, List visited) + { + var startNode = visited[0]; + IGraphVertex nextNode; + List sub; + + var adjlist = graph.GetAdjacencyList(startNode).ToList(); // O(n) + for (int i = 0; i < adjlist.Count; i++) // O(n) + { + nextNode = adjlist[i]; + + // 如果下一个点未遍历过 + if (!visited.Contains(nextNode)) // O(n) + { + // 将下一点加入路径集合,并进行下一次递归 + sub = new List { nextNode }; + sub.AddRange(visited); // O(n) + Dfs(graph, sub); + } + + // 如果下一点遍历过,并且路径大于2,说明已经找到起点 + else if (visited.Count > 2 && nextNode.Equals(visited[^1])) + { + // 将重复的路径进行过滤,并把新的路径存入结果 + var cur = RotateToSmallest(visited); // O(n) + var inv = Invert(cur,cur[0]); // O(n) + + var curstr = Gethashstring(cur,inv); + // Env.Print(curstr); + if (Isnew(curstr)) + { + Curve3ds.Add(cur); + Curved.Add(curstr.Item1); + } + } + } + } +#endif + + + + + + + + + /// + /// 将列表旋转到最小的值为列表起点 + /// + /// + /// + static List RotateToSmallest(List lst) + { + var index = lst.IndexOf(lst.Min()); + return lst.Skip(index).Concat(lst.Take(index)).ToList(); + } + + /// + /// 将列表反向,并旋转到起点为最小值 + /// + /// + /// + static List Invert(List lst, IGraphVertex vertex) + { + var tmp = lst.ToList(); + tmp.Reverse(); + var index = tmp.IndexOf(vertex); + return tmp.Skip(index).Concat(lst.Take(index)).ToList(); + } + + static (string, string) Gethashstring(List pathone, List pathtwo) + { + var one = new string[pathone.Count]; + var two = new string[pathtwo.Count]; + for (int i = 0; i < pathone.Count; i++) + { + one[i] = pathone[i].Index.ToString(); + two[i] = pathtwo[i].Index.ToString(); + } + return (string.Join("-", one), string.Join("-", two)); + } + + static (string, string) Gethashstring(LinkedHashSet path) + { + var one = new string[path.Count]; + var two = new string[path.Count]; + path.For(path.MinNode!, (i, ver1, ver2) => { + one[i] = ver1.Index.ToString(); + two[i] = ver2.Index.ToString(); + }); + return (string.Join("-", one), string.Join("-", two)); + } + + + bool Isnew((string, string) path) + { + return !Curved.Contains(path.Item1) && !Curved.Contains(path.Item2); + } + + + #endregion +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/Algorithms/Graph/IGraph.cs b/src/CAD/IFox.CAD.Shared/Algorithms/Graph/IGraph.cs new file mode 100644 index 0000000000000000000000000000000000000000..3a7eec4091d642bd79cd8e7b96e295d2f3070baf --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/Algorithms/Graph/IGraph.cs @@ -0,0 +1,108 @@ +namespace IFoxCAD.Cad; + +/// +/// 无向图 +/// +public interface IGraph +{ + /// + /// 顶点的数量 + /// + /// + int VerticesCount { get; } + + /// + /// 是否存在顶点 + /// + /// 顶点键 + /// + bool ContainsVertex(IGraphVertex key); + + /// + /// 顶点的迭代器 + /// + /// + IEnumerable VerticesAsEnumberable { get; } + + /// + /// 是否有边 + /// + /// 源顶点 + /// 目的顶点 + /// + bool HasEdge(IGraphVertex source, IGraphVertex destination); + /// + /// 图克隆函数 + /// + /// + IGraph Clone(); + /// + /// 获取边 + /// + /// + /// + /// + IEdge? GetEdge(IGraphVertex source, IGraphVertex dest); + /// + /// 邻接表 + /// + /// + /// + HashSet GetAdjacencyList(IGraphVertex vertex); + /// + /// 邻接边表 + /// + /// + /// + HashSet GetAdjacencyEdge(IGraphVertex vertex); + /// + /// 当前的节点 + /// + IGraphVertex? ReferenceVertex { get; } + /// + /// 删除节点 + /// + /// 节点的坐标 + void RemoveVertex(Point3d pt); + /// + /// 删除边 + /// + /// 曲线 + void RemoveEdge(Curve3d curve); + +} + +/// +/// 无向图顶点 +/// +/// 顶点数据类型 +public interface IGraphVertex : IComparable +{ + /// + /// 顶点的键 + /// + /// + int Index { get; set; } + + /// + /// 顶点的数据 + /// + Point3d Data { get; } +} +/// +/// 无向图边 +/// +public interface IEdge +{ + /// + /// 边 + /// + Curve3d TargetEdge { get; } + /// + /// 目标顶点 + /// + IGraphVertex TargetVertex { get; } +} + + + diff --git a/src/CAD/IFox.CAD.Shared/Algorithms/QuadTree/QuadEntity.cs b/src/CAD/IFox.CAD.Shared/Algorithms/QuadTree/QuadEntity.cs new file mode 100644 index 0000000000000000000000000000000000000000..c5f7f11068f04ab5e0f797ff44e5be57a30637ee --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/Algorithms/QuadTree/QuadEntity.cs @@ -0,0 +1,25 @@ +namespace IFoxCAD.Cad; + +/* + * 这个类存在的意义是为了不暴露Rect类字段 + * 同时利用了Rect类字段的快速 + * 提供到外面去再继承 + */ + +/// +/// 四叉树图元 +/// +public class QuadEntity : Rect +{ + /// + /// 四叉树图元 + /// + /// 包围盒 + public QuadEntity(Rect box) + { + _X = box._X; + _Y = box._Y; + _Top = box._Top; + _Right = box._Right; + } +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/Algorithms/QuadTree/QuadTree.cs b/src/CAD/IFox.CAD.Shared/Algorithms/QuadTree/QuadTree.cs new file mode 100644 index 0000000000000000000000000000000000000000..538a6207d96679f78b1e3006bebe85dd372c26d6 --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/Algorithms/QuadTree/QuadTree.cs @@ -0,0 +1,260 @@ +/* + * 四叉树维基百科 http://en.wikipedia.org/wiki/Quadtree + * 四叉树是一种分区空间的算法,更快找出内部或外部给定区域. + * 通过一个正交矩形边界进行中心点分裂四个正交矩形, + * 插入时候会一直分裂四个正交矩形, + * 当分裂四个节点都无法单独拥有 图元包围盒 就停止分裂,并且你属于这四个节点的父亲. + * (不包含就是面积少了,就这么一句话看代码看半天), + * 还可以通过限制树的深度实现加速. + * + * 第一版: https://www.codeproject.com/script/Articles/ViewDownloads.aspx?aid=30535 + * + * 第二版: 找邻居 + * https://blog.csdn.net/dive_shallow/article/details/112438050 + * https://geidav.wordpress.com/2017/12/02/advanced-octrees-4-finding-neighbor-nodes/ + * + * 1.根节点:控制根节点从而控制所有节点 + * 2.子节点:包含自身根节点,插入矩形的时候进行递归分裂自身,和实现查找. + * 3.接口:约束都要有正交矩形,否则无法调用"包含"方法 + * 4.选择模式:模仿cad的窗选和框选 + */ +namespace IFoxCAD.Cad; + +/// +/// 根节点控制器 +/// +/// 类型接口约束必须有正交矩形 +public class QuadTree where TEntity : QuadEntity +{ + #region 成员 + /// + /// 根节点 + /// + QuadTreeNode _rootNode; + + /// + /// 四叉树节点的数目 + /// + public int Count { get => _rootNode.CountSubTree; } + + /// + /// 点容器(红黑树) + /// + SortedSet _points; + #endregion + + #region 构造 + /// + /// 四叉树根节点控制器 + /// + /// 四叉树矩形范围 + public QuadTree(Rect rect) + { + _rootNode = new QuadTreeNode(rect, null, 0);// 初始化根节点 + _points = new(); + } + #endregion + + #region 方法 + /// + /// 通过根节点插入数据项 + /// + /// + public void Insert(TEntity ent) + { + /* + * 图元点 是不分裂空间的,加入一个红黑树内部. + */ + if (ent.IsPoint) + { + _points.Add(ent); + return; + } + + while (!_rootNode.Contains(ent)) + { + /* + * 四叉树插入时候,如果超出根边界,就需要扩展 + * 扩展时候有一个要求,当前边界要作为扩展边界的一个象限,也就是反向分裂 + * + * 创建新根,计算原根在新根的位置, + * 替换指针:获取新分裂的节点的父节点,判断它哪个儿子是它, + * 替换之后可能仍然不包含图元边界,再循环计算. + */ + var sq_Left = _rootNode._X; + var sq_Botton = _rootNode._Y; + var sq_Right = _rootNode._Right; + var sq_Top = _rootNode._Top; + if (ent._Y >= _rootNode._Y)// 上↑增殖 + { + if (ent._X >= _rootNode._X) + { + // 右上↗增殖 + sq_Right += _rootNode.Width; + sq_Top += _rootNode.Height; + } + else + { + // 左上↖增殖 + sq_Left -= _rootNode.Width; + sq_Top += _rootNode.Height; + } + } + else// 在下↓ + { + if (ent._X >= _rootNode._X) + { + // 右下↘增殖 + sq_Right += _rootNode.Width; + sq_Botton -= _rootNode.Height; + } + else + { + // 左下↙增殖 + sq_Left -= _rootNode.Width; + sq_Botton -= _rootNode.Height; + } + } + // 扩大2次方 + var rectSquare = new Rect(sq_Left, sq_Botton, sq_Right, sq_Top); + + // 四叉树的旧根要作为四分之一插入 + // 新根中计算原根 + // 把 旧根节点 连接到 新根节点 上面,然后新根成为根 + var newRoot = new QuadTreeNode(rectSquare, null, 0); + var insert = newRoot.Insert(_rootNode); + if (insert is null) + throw new("四叉树:新根尺寸不对"); + if (!insert.Equals(_rootNode)) + throw new("四叉树:新旧节点大小不一致,无法连接"); + + var insPar = insert.Parent; + _rootNode.Parent = insPar; + if (insPar is null) + return; + + if (_rootNode.Equals(insPar.RightTopTree)) + insPar.RightTopTree = _rootNode; + else if (_rootNode.Equals(insPar.RightBottomTree)) + insPar.RightBottomTree = _rootNode; + else if (_rootNode.Equals(insPar.LeftBottomTree)) + insPar.LeftBottomTree = _rootNode; + else if (_rootNode.Equals(insPar.LeftTopTree)) + insPar.LeftTopTree = _rootNode; + else + throw new("四叉树:新节点不对,无法连接"); + + // 其后的子节点层数全部增加层数, + // 要加多少层取决于当前根边界属于新根边界的所在层 + var depth = insert.Depth; + if (depth == 0) + throw new("四叉树:插入节点是0,造成错误"); + _rootNode.ForEach(node => { + node.Depth += depth; + return false; + }); + + // 交换根控制 + _rootNode = newRoot; + } + + _rootNode.Insert(ent); + } + + + /// + /// 查询四叉树,返回给定区域的数据项 + /// + /// 矩形选区查询 + /// 查询模式 + /// 查询结果列表 + public List Query(Rect rect, QuadTreeSelectMode selectMode = QuadTreeSelectMode.IntersectsWith) + { + QuadTreeEvn.SelectMode = selectMode; + + var results = new List(); + // 选择图元 + _rootNode.Query(rect, results); + // 选择点 + var ptge = _points.GetEnumerator(); + switch (selectMode) + { + case QuadTreeSelectMode.IntersectsWith: + case QuadTreeSelectMode.Contains: + /* 由于红黑树的方法 _points.GetViewBetween() + * 过滤只能过滤X区间,Y区间还是要过滤, + * 那么我就只能用这样的方法加速了 + * + * 而更好的方式是不用红黑树,去加入一个点云数据来进行,可谓是编程无极限.... + */ + while (ptge.MoveNext()) + { + var ptEnt = ptge.Current; + if (rect._X <= ptEnt._X && ptEnt._X <= rect._Right) + { + if (rect._Y <= ptEnt._Y && ptEnt._Y <= rect.Top) + results.Add(ptEnt); + } + else if (ptEnt._X > rect._Right) + break;// 超过后面范围就break,因为红黑树已经排序 + } + break; + default: + throw new("四叉树:" + nameof(selectMode)); + } + return results; + } + + /// + /// 删除子节点 + /// + /// 根据范围删除 + public void Remove(Rect rect) + { + _rootNode.Remove(rect); + } + + /// + /// 删除子节点 + /// + /// 根据图元删除 + public void Remove(TEntity ent) + { + _rootNode.Remove(ent); + } + + /// + /// 找到附近节点图元 + /// + [Obsolete("找附近节点的并不是最近的图元")] + public TEntity? FindNeibor(Rect rect, QuadTreeFindMode findMode) + { + return _rootNode.FindNeibor(rect, findMode); + } + + /// + /// 找到附近图元 + /// + /// + /// + public TEntity? FindNearEntity(Rect rect) + { + return _rootNode.FindNearEntity(rect); + } + + /// + /// 执行四叉树中特定的行为 + /// + /// + public void ForEach(QTAction action) + { + _rootNode.ForEach(action); + } + + /// + /// 委托:四叉树节点上执行一个操作 + /// + /// + public delegate bool QTAction(QuadTreeNode obj); + #endregion +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/Algorithms/QuadTree/QuadTreeEvn.cs b/src/CAD/IFox.CAD.Shared/Algorithms/QuadTree/QuadTreeEvn.cs new file mode 100644 index 0000000000000000000000000000000000000000..f077a02a4057ae10577d785727ffcf6956e7baac --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/Algorithms/QuadTree/QuadTreeEvn.cs @@ -0,0 +1,28 @@ +#pragma warning disable CA2211 // 非常量字段应当不可见 +namespace IFoxCAD.Cad; +/// +/// 四叉树环境变量 +/// +public class QuadTreeEvn +{ + /// + /// 最小的节点有一个面积(一定要大于0) + /// + public static double MinArea = 1e-6; + + /// + /// 选择模式 + /// + public static QuadTreeSelectMode SelectMode; + + /// + /// 最大深度 + /// + public static int QuadTreeMaximumDepth = 2046; + + /// + /// 节点内容超过就分裂 + /// + public static int QuadTreeContentsCountSplit = 20; +} +#pragma warning restore CA2211 // 非常量字段应当不可见 \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/Algorithms/QuadTree/QuadTreeNode.cs b/src/CAD/IFox.CAD.Shared/Algorithms/QuadTree/QuadTreeNode.cs new file mode 100644 index 0000000000000000000000000000000000000000..06bf47266e97a3c0074d6df09760770f028e59ed --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/Algorithms/QuadTree/QuadTreeNode.cs @@ -0,0 +1,816 @@ +namespace IFoxCAD.Cad; + +/// +/// 子节点 +/// +/// +public class QuadTreeNode + : Rect + where TEntity : QuadEntity +{ + #region 成员 + /// + /// 子节点:第一象限:右上↗ + /// + public QuadTreeNode? RightTopTree; + /// + /// 子节点:第二象限:左上↖ + /// + public QuadTreeNode? LeftTopTree; + /// + /// 子节点:第三象限:左下↙ + /// + public QuadTreeNode? LeftBottomTree; + /// + /// 子节点:第四象限:右下↘ + /// + public QuadTreeNode? RightBottomTree; + /// + /// 所有子节点 + /// + QuadTreeNode[] Nodes + { + get + { + return new QuadTreeNode[] + { + RightTopTree!, + LeftTopTree!, + LeftBottomTree!, + RightBottomTree!, + }; + } + } + /// + /// 所有子节点是空的 + /// + bool NodesIsEmpty => RightTopTree is null && LeftTopTree is null && LeftBottomTree is null && RightBottomTree is null; + + /// + /// 父节点 + /// + public QuadTreeNode? Parent; + /// + /// 节点的在四叉树的深度 + /// + public int Depth; + + // 注意,内容没有限制:这不是 impement 四叉树的标准方法 + /// (节点图元是交叉线压着的,并不是矩形范围内全部,因为这是四叉树的特性决定) + /// + /// 本节点:内容 + /// + public List Contents; + + /// + /// 本节点和旗下所有子节点:内容群 + /// + public void ContentsSubTree(List results) + { + if (Contents is null) + return; + results.AddRange(Contents); + var nodes = Nodes; + for (int i = 0; i < nodes.Length; i++) + nodes[i]?.ContentsSubTree(results); + } + + /// + /// 本节点和旗下所有子节点:内容群数量 + /// + public int CountSubTree + { + get + { + if (Contents is null) + return 0; + int count = Contents.Count; + + var nodes = Nodes; + for (int i = 0; i < nodes.Length; i++) + { + var node = nodes[i]; + if (node is null) + continue; + count += node.CountSubTree; + } + return count; + } + } + #endregion + + #region 构造 + /// + /// 四叉树节点 + /// + /// 当前节点边界 + /// 父节点 + /// 节点深度 + public QuadTreeNode(Rect box, QuadTreeNode? parent, int depth) + { + _X = box._X; + _Y = box._Y; + _Right = box._Right; + _Top = box._Top; + + Parent = parent; + Depth = depth; + Contents = new(); + } + #endregion + + #region 增 + /// + /// 将原有节点插入用 + /// + /// + internal QuadTreeNode? Insert(Rect rect) + { + if (!Contains(rect)) + return null; + + // 四叉树分裂,将当前节点分为四个子节点 + if (NodesIsEmpty) + CreateChildren(); + + // 当前节点边界 包含 图元包围盒 就插入 + // 退出递归:4个节点都不完全包含 + // 4个节点的上层 + var nodes = Nodes; + for (int i = 0; i < nodes.Length; i++) + { + var node = nodes[i]; + if (node is null) + continue; + + if (node.Equals(rect)) + { + rect = node; + return node.Insert(rect); + } + } + return this; + } + + /// + /// 将数据项递归插入四叉树 + /// + /// + public QuadTreeNode? Insert(TEntity ent) + { + if (!Contains(ent)) + { + // Debugx.Printl("不在四叉树边界范围"); + // Trace.WriteLine("不在四叉树边界范围"); + return null; + } + + // if (ent.IsPoint) + // { + // // 找到最后一层包含它的节点,然后加入它 + // // 因此是跳过分裂矩形的,以免造成无限递归 + // var minNode = GetMinNode(ent); + // minNode.Contents.Add(ent); + // return minNode; + // } + +#if true2 + // 方案二: + // 内容数超过才分裂,防止树深度过高,但是多选过滤时候慢一点 + if (Contents.Count > QuadTreeEvn.QuadTreeContentsCountSplit) + { + // 分裂出四个子节点 + if (_nodesIsEmpty) + { + CreateChildren(); + // 分裂之后将当前层的内容扔到四个子节点, + // 如果被压着,那么就不会扔到下面 + for (int i = Contents.Count - 1; i >= 0; i--) + { + var minNode = GetMinNode(Contents[i].Box); + minNode.Contents.Add(Contents[i]); + Contents.RemoveAt(i); + } + } + else + { + // 没有分裂的话,就递归 + // 退出递归:4个节点都不完全包含,内容就是他们的父亲 + var nodes = _Nodes; + for (int i = 0; i < nodes.Length; i++) + { + var node = nodes[i]; + if (node is null) + continue; + + // 这里需要中断.(匿名方法ForEach无法中断,会造成父节点加入内容) + if (node.Contains(ent)) + return node.Insert(ent); + } + } + } +#else + // 方案一:分裂到最细节点 + + // 分裂出四个子节点 + if (NodesIsEmpty) + CreateChildren(); + + // 4个子节点开始递归 + // 退出递归:4个节点都不完全包含,内容就是他们的父亲 + var nodes = Nodes; + for (int i = 0; i < nodes.Length; i++) + { + var node = nodes[i]; + if (node is null) + continue; + // 这里需要中断.(匿名方法ForEach无法中断,会造成父节点加入内容) + if (node.Contains(ent)) + return node.Insert(ent); + } +#endif + + // 为什么要用容器? + // 相同包围盒或者四叉树分割线压着多个. + this.Contents.Add(ent); + return this; + } + + /// + /// 创建子节点 + /// + void CreateChildren() + { + // 最小面积控制节点深度,但是这样可能导致树分成高,引起爆栈 + if (Depth > QuadTreeEvn.QuadTreeMaximumDepth) + return; + var recs = RectSplit(this); + var de = Depth + 1; + RightTopTree = new QuadTreeNode(recs[0], this, de); + LeftTopTree = new QuadTreeNode(recs[1], this, de); + LeftBottomTree = new QuadTreeNode(recs[2], this, de); + RightBottomTree = new QuadTreeNode(recs[3], this, de); + } + + /// + /// 矩形分裂为四个 + /// + /// + /// + static Rect[] RectSplit(Rect box) + { + var halfWidth = box.Width / 2.0; + var halfHeight = box.Height / 2.0; + + var upperRight = new Rect(box._X + halfWidth, box._Y + halfHeight, box._Right, box._Top); + var upperLeft = new Rect(box._X, box._Y + halfHeight, box._Right - halfWidth, box._Top); + var lowerleft = new Rect(box._X, box._Y, box._Right - halfWidth, box._Top - halfHeight);// 基础 + var lowerRight = new Rect(box._X + halfWidth, box._Y, box._Right, box._Top - halfHeight); + + // 依照象限顺序输出 + return new Rect[] { upperRight, upperLeft, lowerleft, lowerRight }; + } + #endregion + + #region 删 + /// + /// 删除图元 + /// + /// 根据图元删除 + public bool Remove(TEntity easeEnt) + { + // 通过图元id删除无疑是非常低效的, + // 1.相当于在所有的容器查找它,但是移除只会移除一次, + // 因此必须要求图元只会加入一次,才能中断检索剩余分支. + // 2.这个代价还是太高,因此我们还是要默认条件,图元载入一次之后,不再改动. + // 3.不再改动也不太合理,因为cad图元还是可以修改的 + + // 1.处理内容 + if (Contents.Remove(easeEnt)) + { + if (CountSubTree == 0) + this.Clear(this); + return true; + } + + // 2.递归子节点移除 + var nodes = Nodes; + for (int i = 0; i < nodes.Length; i++) + { + var node = nodes[i]; + if (node is null) + continue; + if (node.Remove(easeEnt)) // 递归进入子节点删除内容 + return true; // 删除成功就中断其他节点的搜索 + } + return false; + } + + /// + /// 递归进入最下层节点,然后开始清理 + /// + /// + void Clear(QuadTreeNode node) + { + var nodes = Nodes; + for (int i = 0; i < nodes.Length; i++) + nodes[i]?.Clear(nodes[i]); + + node.Contents.Clear(); + // node.Contents = null;// 重复加入时候会出错 + node.RightTopTree = null; + node.LeftTopTree = null; + node.LeftBottomTree = null; + node.RightBottomTree = null; + node.Parent = null; + // node.Box = zoreRect; + } + + /// + /// 删除子节点内容 + /// + /// 根据范围删除 + public void Remove(Rect queryArea) + { + // 本节点内容移除 + if (Contents is not null && Contents.Count > 0)// 从最上层的根节点开始进入 + { + for (int i = Contents.Count - 1; i >= 0; i--) + { + var ent = Contents[i]; + // 移除之后,如果容器是0,那么这里不能直接 Contents=null, + // 因为此节点下面可能还有节点, + // 需要判断了其后数量0才可以清理. + // 否则其后还有内容,那么此节点就是仍然可以用的. + if (queryArea.Contains(ent)) + Contents.Remove(ent); + } + } + + // 同插入一样 + // 跳到指定节点再搜索这个节点下面的图元 + var nodes = Nodes; + for (int i = 0; i < nodes.Length; i++) + { + var node = nodes[i]; + if (node is null) + continue; + if (node.NodesIsEmpty) + continue; + + // 此节点边界 完全包含 查询区域,则转到该节点,并跳过其余节点(打断此循环) + if (node.Contains(queryArea)) + { + node.Remove(queryArea); + break; + } + // 查询区域 完全包含 此节点边界,提取此节点全部内容 + // 跳过分析碰撞,并继续循环搜索其他节点 + if (queryArea.Contains(node)) + { + node.Clear(node); + continue; + } + // 查询区域 与 此节点四边形边线碰撞 查询该四边形中,并继续循环搜索其他节点 + // 1,角点碰撞 2,边界碰撞 + if (node.IntersectsWith(queryArea)) + node.Remove(queryArea); + } + + // 本节点内容移除之后,旗下还有内容的话, + // 会跳过此处,再进入子节点进行递归,直到最后一个节点 + if (CountSubTree == 0) + Clear(this); + } + #endregion + + #region 查 + /// + /// 查询范围内的实体 + /// + /// 查询矩形 + /// 查询结果 + public void Query(Rect queryArea, List results) + { + GetCurrentContents(queryArea, results); + + // 遍历子节点 + var nodes = Nodes; + for (int i = 0; i < nodes.Length; i++) + { + var node = nodes[i]; + if (node is null) + continue; + // 子节点的4个子节点都是空的, + // 那么表示元素会在子节点这一层啊... + if (node.NodesIsEmpty) + continue; + + // 此节点边界 完全包含 查询区域,则转到该节点,并跳过其余节点(打断此循环) + if (node.Contains(queryArea)) + { + node.Query(queryArea, results); + break; + } + // 查询区域 完全包含 此节点边界,提取此节点全部内容 + // 跳过分析碰撞,并继续循环搜索其他节点 + if (queryArea.Contains(node)) + { + node.ContentsSubTree(results); + continue; + } + // 查询区域 与 此节点四边形边线碰撞 查询该四边形中,并继续循环搜索其他节点 + // 1,角点碰撞 2,边界碰撞 + if (node.IntersectsWith(queryArea)) + node.Query(queryArea, results); + } + } + + /// + /// 获取本节点内容 + /// + /// + /// + void GetCurrentContents(Rect queryArea, List results) + { + // 遍历当前节点内容,加入方式取决于碰撞模式 + if (QuadTreeEvn.SelectMode == QuadTreeSelectMode.IntersectsWith) + { + for (int i = 0; i < Contents.Count; i++) + { + var ent = Contents[i]; + if (queryArea.IntersectsWith(ent)) + results.Add(ent); + } + } + else + { + for (int i = 0; i < Contents.Count; i++) + { + var ent = Contents[i]; + if (queryArea.Contains(ent)) + results.Add(ent); + } + } + } + + /// + /// 找临近图元 + /// + /// 查找矩形 + /// + public TEntity? FindNearEntity(Rect queryArea) + { + TEntity? resultEntity = default; + // 1.找到 查找矩形 所在的节点,利用此节点的矩形. + var queryNode = GetMinNode(queryArea); + var queryAreaCenter = queryArea.CenterPoint; + + // 2.从根开始搜索 + // 如果搜索父亲的父亲的...内容群,它不是距离最近的,只是节点(亲属关系)最近 + // 储存找过的<图元,距离> + var entDic = new Dictionary(); + + var old = QuadTreeEvn.SelectMode; + QuadTreeEvn.SelectMode = QuadTreeSelectMode.IntersectsWith; + while (true) + { + // 循环找父节点大小 + var hw = queryNode.Width / 2.0; + var hh = queryNode.Height / 2.0; + // 3.利用选区中心扩展一个节点边界大小的矩形.从而选择图元 + // 再判断图元的与目标的距离,找到最小距离,即为最近 + var minPt = new Point2d(queryAreaCenter.X - hw, queryAreaCenter.Y - hh); + var maxPt = new Point2d(queryAreaCenter.X + hw, queryAreaCenter.Y + hh); + var ents = new List(); + Query(new Rect(minPt, maxPt), ents); + for (int i = 0; i < ents.Count; i++) + { + var ent = ents[i]; + if (entDic.ContainsKey(ent)) + continue; + var dis = ent.CenterPoint.GetDistanceTo(queryAreaCenter); + if (dis > 1e-6)// 剔除本身 + entDic.Add(ent, dis); + } + if (entDic.Count > 0) + { + resultEntity = entDic.OrderBy(a => a.Value).First().Key; + break; + } + if (queryNode.Parent is null)// 最顶层就退出 + break; + queryNode = queryNode.Parent;// 利用父节点矩形进行变大选区 + } + QuadTreeEvn.SelectMode = old; + return resultEntity; + } + + /// + /// 找临近节点的图元 + /// + /// 查找矩形 + /// 查找什么方向 + /// + [Obsolete("找附近节点的并不是最近的图元")] + public TEntity? FindNeibor(Rect queryArea, QuadTreeFindMode findMode) + { + TEntity? resultEntity = default; + // 1.找到 查找矩形 所在的节点,利用此节点的矩形. + // 2.利用节点矩形是分裂的特点,边和边必然贴合. + // 3.找到方向 findMode 拥有的节点,然后查找节点的内容 + var queryNode = GetMinNode(queryArea); + + bool whileFlag = true; + // 同一个节点可能包含邻居,因为四叉树的加入是图元压线, + // 那么就在这里搜就得了,用中心点决定空间位置 + // 但是本空间的图元可能都比它矮,无法满足条件 + if (queryNode.CountSubTree > 1) + { + resultEntity = GetNearestNeighbor(queryNode, findMode, queryArea); + if (resultEntity is null || resultEntity.CenterPoint == queryArea.CenterPoint) + whileFlag = true; + else + whileFlag = false; + } + + while (whileFlag) + { + // 同一个父节点是临近的,优先获取 兄弟节点 的内容. + // 循环了第二次是北方兄弟的节点, + // 但是这不是一个找到临近图元的方法, + // 因为临近的可能是父亲的父亲的父亲...另一个函数 FindNearEntity 写 + // 本方案也仅仅作为找北方节点 + var parent = queryNode.Parent; + if (parent is not null) + { + switch (findMode) + { + case QuadTreeFindMode.Top: + { + // 下格才获取上格,否则导致做了无用功,上格就直接获取邻居了 + if (parent.LeftBottomTree == queryNode || parent.RightBottomTree == queryNode) + resultEntity = GetNearestNeighbor(parent, findMode, queryArea); + } + break; + case QuadTreeFindMode.Bottom: + { + if (parent.LeftTopTree == queryNode || parent.RightTopTree == queryNode) + resultEntity = GetNearestNeighbor(parent, findMode, queryArea); + } + break; + case QuadTreeFindMode.Left: + { + if (parent.RightTopTree == queryNode || parent.RightBottomTree == queryNode) + resultEntity = GetNearestNeighbor(parent, findMode, queryArea); + } + break; + case QuadTreeFindMode.Right: + { + if (parent.LeftTopTree == queryNode || parent.LeftBottomTree == queryNode) + resultEntity = GetNearestNeighbor(parent, findMode, queryArea); + } + break; + } + } + if (resultEntity is not null) + break; + + // 通过 所在节点 找 邻居节点, + // 拿到 邻居节点 下面的所有内容(图元) + // 内容可能是空的,再从邻居那往北找...如果找到了四叉树最外层,仍然没有内容,退出循环 + var neiborNode = FindNeiborNode(queryNode, findMode); + if (neiborNode is null) + continue; + if (neiborNode.CountSubTree > 0) + { + resultEntity = GetNearestNeighbor(neiborNode, findMode, queryArea); + break; + } + if (neiborNode.Parent is null)// 如果找到了四叉树最外层,仍然没有内容,退出循环 + break; + queryNode = neiborNode; + } + + return resultEntity; + } + + /// + /// 查找节点的(本内容和子内容)与(查找面积)矩形中点对比,找到最近一个内容 + /// + /// 查找面积 + /// 查找方向 + /// 查找节点 + /// + static TEntity? GetNearestNeighbor(QuadTreeNode queryNode, + QuadTreeFindMode findMode, + Rect queryArea) + { + TEntity? results = default; + + var lst = new List(); + var qcent = queryArea.CenterPoint; + + switch (findMode) + { + case QuadTreeFindMode.Top: + { + // 取出Y比queryArea的还大的一个,是最近的那个 + var qy = qcent.Y; + queryNode.ContentsSubTree(lst); + lst.ForEach(ent => { + if (ent.CenterPoint.Y > qy) + lst.Add(ent); + }); + lst = lst.OrderBy(ent => ent.CenterPoint.Y).ToList(); + } + break; + case QuadTreeFindMode.Bottom: + { + var qy = qcent.Y; + queryNode.ContentsSubTree(lst); + lst.ForEach(ent => { + if (ent.CenterPoint.Y < qy) + lst.Add(ent); + }); + lst = lst.OrderByDescending(ent => ent.CenterPoint.Y).ToList(); + } + break; + case QuadTreeFindMode.Left: + { + var qx = qcent.Y; + queryNode.ContentsSubTree(lst); + lst.ForEach(ent => { + if (ent.CenterPoint.X > qx) + lst.Add(ent); + }); + lst = lst.OrderBy(ent => ent.CenterPoint.X).ToList(); + } + break; + case QuadTreeFindMode.Right: + { + var qx = qcent.Y; + queryNode.ContentsSubTree(lst); + lst.ForEach(ent => { + if (ent.CenterPoint.X < qx) + lst.Add(ent); + }); + lst = lst.OrderByDescending(ent => ent.CenterPoint.X).ToList(); + } + break; + } + + if (lst.Count > 0) + return lst[0];// 可能就是本体重叠 + return results; + } + + /// + /// 找包含它的最小分支 + /// + /// 查询的矩形 + /// 节点 + QuadTreeNode GetMinNode(Rect queryArea) + { + var nodes = Nodes; + for (int i = 0; i < nodes.Length; i++) + { + var node = nodes[i]; + if (node is null) + continue; + + // 边界包含查询面积,那么再递归查询, + // 直到最后四个都不包含,那么上一个就是图元所在节点 + if (node.Contains(queryArea)) + return node.GetMinNode(queryArea);// 中断后面的范围,才可以返回正确的 + } + return this; + } + + /// + /// 四叉树找邻居节点(相同或更大) + /// + /// 源节点 + /// 方向 + /// + QuadTreeNode? FindNeiborNode(QuadTreeNode tar, QuadTreeFindMode findMode) + { + var parent = tar.Parent; + if (parent is null) + return null; + switch (findMode) + { + case QuadTreeFindMode.Top: + { + // 判断当前节点在父节点的位置,如果是在 下格 就取对应的 上格 + if (tar == parent.LeftBottomTree) + return parent.LeftTopTree; + if (tar == parent.RightBottomTree) + return parent.RightTopTree; + // 否则就是上格 + // 找父节点的北方邻居..也就是在爷节点上面找 + // 递归,此时必然是 下格,就必然返回 上格,然后退出递归 + var parentNeibor = FindNeiborNode(parent, QuadTreeFindMode.Top); + // 父节点的北方邻居 无 子节点 + if (parentNeibor is null || parentNeibor.RightTopTree is null) + return parentNeibor;// 返回父节点的北方邻居,比较大 + // 父节点的北方邻居 有 子节点,剩下条件就只有这两 + + // 如果直接返回,那么是(相同或更大), + // 而找邻近图元需要的是这个(相同或更大)下面的图元,在外面对这个格子内图元用坐标分析就好了 + if (tar == parent.LeftTopTree) + return parentNeibor.LeftBottomTree; + return parentNeibor.RightBottomTree; + } + case QuadTreeFindMode.Bottom: + { + if (tar == parent.LeftTopTree) + return parent.LeftBottomTree; + if (tar == parent.RightTopTree) + return parent.RightBottomTree; + var parentNeibor = FindNeiborNode(parent, QuadTreeFindMode.Bottom); + if (parentNeibor is null || parentNeibor.RightTopTree is null) + return parentNeibor; + if (tar == parent.LeftBottomTree) + return parentNeibor.LeftTopTree; + return parentNeibor.RightTopTree; + } + case QuadTreeFindMode.Right: + { + if (tar == parent.LeftTopTree) + return parent.RightTopTree; + if (tar == parent.LeftBottomTree) + return parent.RightBottomTree; + var parentNeibor = FindNeiborNode(parent, QuadTreeFindMode.Right); + if (tar == parent.RightTopTree) + return parentNeibor?.LeftTopTree; + return parentNeibor?.LeftBottomTree; + } + case QuadTreeFindMode.Left: + { + if (tar == parent.RightTopTree) + return parent.LeftTopTree; + if (tar == parent.RightBottomTree) + return parent.LeftBottomTree; + var parentNeibor = FindNeiborNode(parent, QuadTreeFindMode.Left); + if (tar == parent.LeftTopTree) + return parentNeibor?.RightTopTree; + return parentNeibor?.RightBottomTree; + } + } + return null; + } + #endregion + + #region 改 + /// + /// 所有的点归类到最小包围它的空间 + /// + // public void PointsToMinNode() + // { + // ForEach(node => + // { + // for (int i = 0; i < node.Contents.Count; i++) + // { + // var ent = node.Contents[i]; + // if (ent.IsPoint) + // { + // // 如果最小包含!=当前,就是没有放在最适合的位置 + // var queryNode = GetMinNode(ent); + // if (queryNode != node) + // { + // node.Remove(ent); + // queryNode.Contents.Add(ent); + // } + // } + // } + // return false; + // }); + // } + #endregion + + #region 方法 + /// + /// 递归全部节点(提供给根用的,所以是全部) + /// + /// QTAction + public bool ForEach(QuadTree.QTAction action) + { + // 执行本节点 + if (action(this)) + return true; + + // 递归执行本节点的子节点 + var nodes = Nodes; + for (int i = 0; i < nodes.Length; i++) + { + var node = nodes[i]; + if (node is null) + continue; + if (node.ForEach(action)) + break; + } + return false; + } + #endregion +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/Runtime/AssemInfo.cs b/src/CAD/IFox.CAD.Shared/Algorithms/QuadTree/QuadTreeSelectMode.cs similarity index 38% rename from src/IFoxCAD.Cad/Runtime/AssemInfo.cs rename to src/CAD/IFox.CAD.Shared/Algorithms/QuadTree/QuadTreeSelectMode.cs index 8b88d8859d57cce7e96e21515026896e72640ef2..624d313ad43aefe905eb3fb41e2f8b135409c3f5 100644 --- a/src/IFoxCAD.Cad/Runtime/AssemInfo.cs +++ b/src/CAD/IFox.CAD.Shared/Algorithms/QuadTree/QuadTreeSelectMode.cs @@ -1,33 +1,39 @@ namespace IFoxCAD.Cad; /// -/// 程序集信息 +/// 四叉树选择模式 /// -[Serializable] -public struct AssemInfo +public enum QuadTreeSelectMode { /// - /// 注册名 + /// 碰撞到就选中 /// - public string Name { get; set; } - + IntersectsWith, /// - /// 程序集全名 + /// 全包含才选中 /// - public string Fullname { get; set; } + Contains, +} +/// +/// 四叉树查找方向 +/// +public enum QuadTreeFindMode +{ /// - /// 程序集路径 + /// 上 /// - public string Loader { get; set; } - + Top = 1, /// - /// 加载方式 + /// 下 /// - public AssemLoadType LoadType { get; set; } - + Bottom = 2, /// - /// 程序集说明 + /// 左 /// - public string Description { get; set; } -} + Left = 4, + /// + /// 右 + /// + Right = 8, +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/Algorithms/QuadTree/Rect.cs b/src/CAD/IFox.CAD.Shared/Algorithms/QuadTree/Rect.cs new file mode 100644 index 0000000000000000000000000000000000000000..a4c219dab62d9f85e1b95cd2511eb6b8d2f66ddd --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/Algorithms/QuadTree/Rect.cs @@ -0,0 +1,617 @@ +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace IFoxCAD.Cad; + +/// +/// Linq Distinct 消重比较两点在容差范围内就去除 +/// +public class TolerancePoint2d : IEqualityComparer +{ + readonly double _tolerance; + /// + /// 构造函数 + /// + /// 容差 + public TolerancePoint2d(double tolerance = 1e-6) + { + _tolerance = tolerance; + } + + public bool Equals(Point2d a, Point2d b)// Point3d是struct不会为null + { + /*默认规则是==是0容差,Eq是有容差*/ + // 方形限定 + // 在 0~1e-6 范围实现 圆形限定 则计算部分在浮点数6位后,没有啥意义 + // 在 0~1e-6 范围实现 从时间和CPU消耗来说,圆形限定 都没有 方形限定 的好 + if (_tolerance <= 1e-6) + return Math.Abs(a.X - b.X) <= _tolerance && Math.Abs(a.Y - b.Y) <= _tolerance; + + // 圆形限定 + // DistanceTo 分别对XYZ进行了一次乘法,也是总数3次乘法,然后求了一次平方根 + // (X86.CPU.FSQRT指令用的牛顿迭代法/软件层面可以使用快速平方根....我还以为CPU会采取快速平方根这样的取表操作) + return a.IsEqualTo(b, new Tolerance(_tolerance, _tolerance)); + } + + public int GetHashCode(Point2d obj) + { + // 结构体直接返回 obj.GetHashCode(); Point3d ToleranceDistinct3d + // 因为结构体是用可值叠加来判断?或者因为结构体兼备了一些享元模式的状态? + // 而类是构造的指针,所以取哈希值要改成x+y+z..s给Equals判断用,+是会溢出,所以用^ + return (int)obj.X ^ (int)obj.Y;// ^ (int)obj.Z; + } +} + + +[Serializable] +[StructLayout(LayoutKind.Sequential)] +[DebuggerDisplay("{DebuggerDisplay,nq}")] +[DebuggerTypeProxy(typeof(Rect))] +public class Rect : IEquatable, IComparable +{ + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private string DebuggerDisplay => ToString("f4"); + +#pragma warning disable CA2211 // 非常量字段应当不可见 + public static TolerancePoint2d RectTolerance = new(1e-6); + public static Tolerance CadTolerance = new(1e-6, 1e-6); +#pragma warning restore CA2211 // 非常量字段应当不可见 + + #region 字段 + // 这里的成员不要用{get}封装成属性,否则会导致跳转了一次函数, + // 10w图元将会从187毫秒变成400毫秒 + // 不用 protected 否则子类传入Rect对象进来无法用 + internal double _X; + internal double _Y; + internal double _Right; + internal double _Top; + #endregion + + #region 成员 + public double X => _X; + public double Y => _Y; + public double Left => _X; + public double Bottom => _Y; + public double Right => _Right; + public double Top => _Top; + + public double Width => _Right - _X; + public double Height => _Top - _Y; + public double Area + { + get + { + var ar = (_Right - _X) * (_Top - _Y); + return ar < 1e-10 ? 0 : ar; + } + } + + public Point2d MinPoint => LeftLower; + public Point2d MaxPoint => RightUpper; + public Point2d CenterPoint => Midst; + + /// + /// 左下Min + /// + public Point2d LeftLower => new(_X, _Y); + + /// + /// 左中 + /// + public Point2d LeftMidst => new(_X, Midst.Y); + + /// + /// 左上 + /// + public Point2d LeftUpper => new(_X, _Top); + + /// + /// 右上Max + /// + public Point2d RightUpper => new(_Right, _Top); + + /// + /// 右中 + /// + public Point2d RightMidst => new(_Right, Midst.Y); + + /// + /// 右下 + /// + public Point2d RightBottom => new(_Right, _Y); + + /// + /// 中间 + /// + public Point2d Midst => new(((_Right - _X) * 0.5) + _X, ((_Top - _Y) * 0.5) + _Y); + + /// + /// 中上 + /// + public Point2d MidstUpper => new(Midst.X, _Top); + + /// + /// 中下 + /// + public Point2d MidstBottom => new(Midst.X, _Y); + + /// + /// 是一个点 + /// 水平或垂直直线包围盒是面积是0,所以面积是0不一定是点 + /// + public bool IsPoint => Math.Abs(_X - _Right) < 1e-10 && Math.Abs(_Y - _Top) < 1e-10; + #endregion + + #region 构造 + public Rect() + { + } + + /// + /// 矩形类 + /// + /// 左 + /// 下 + /// 右 + /// 上 + public Rect(double left, double bottom, double right, double top) + { + _X = left; + _Y = bottom; + _Right = right; + _Top = top; + } + + /// + /// 构造矩形类 + /// + /// + /// + /// 是否检查大小 + public Rect(Point2d p1, Point2d p3, bool check = false) + { + if (check) + { + _X = Math.Min(p1.X, p3.X); + _Y = Math.Min(p1.Y, p3.Y); + _Right = Math.Max(p1.X, p3.X); + _Top = Math.Max(p1.Y, p3.Y); + } + else + { + _X = p1.X; + _Y = p1.Y; + _Right = p3.X; + _Top = p3.Y; + } + } + #endregion + + #region 重载运算符_比较 + public override bool Equals(object? obj) + { + return this.Equals(obj as Rect); + } + public bool Equals(Rect? b) + { + return this.Equals(b, 1e-6);/*默认规则是==是0容差,Eq是有容差*/ + } + public static bool operator !=(Rect? a, Rect? b) + { + return !(a == b); + } + public static bool operator ==(Rect? a, Rect? b) + { + // 此处地方不允许使用==null,因为此处是定义 + if (b is null) + return a is null; + else if (a is null) + return false; + if (ReferenceEquals(a, b))// 同一对象 + return true; + + return a.Equals(b, 0); + } + + /// + /// 比较核心 + /// + public bool Equals(Rect? b, double tolerance = 1e-6) + { + if (b is null) + return false; + if (ReferenceEquals(this, b)) // 同一对象 + return true; + + return Math.Abs(_X - b._X) < tolerance && + Math.Abs(_Right - b._Right) < tolerance && + Math.Abs(_Top - b._Top) < tolerance && + Math.Abs(_Y - b._Y) < tolerance; + } + + public override int GetHashCode() + { + return (((int)_X ^ (int)_Y).GetHashCode() ^ (int)_Right).GetHashCode() ^ (int)_Top; + } + #endregion + + #region 包含 + public bool Contains(Point2d Point2d) + { + return Contains(Point2d.X, Point2d.Y); + } + public bool Contains(double x, double y) + { + return _X <= x && x <= _Right && + _Y <= y && y <= _Top; + } + + /// + /// 四个点都在内部就是包含 + /// + /// + /// + public bool Contains(Rect rect) + { + return _X <= rect._X && rect._Right <= _Right && + _Y <= rect._Y && rect._Top <= _Top; + } + + /// + /// 一个点在内部就是碰撞 + /// + /// + /// true内部 + [MethodImpl] + public bool IntersectsWith(Rect rect) + { + return rect._X <= _Right && _X <= rect._Right && + rect._Top >= _Y && rect._Y <= _Top; + } + #endregion + + #region 方法 + /// + /// 获取共点 + /// + /// + public Point2d[] GetCommonPoint(Rect other) + { + return ToPoints().Intersect(other.ToPoints(), RectTolerance).ToArray(); + } + + public Point2d[] ToPoints() + { + Point2d a = MinPoint;// min + Point2d b = new(_Right, _Y); + Point2d c = MaxPoint;// max + Point2d d = new(_X, _Top); + return new Point2d[] { a, b, c, d }; + } + + public (Point2d boxMin, Point2d boxRigthDown, Point2d boxMax, Point2d boxLeftUp) ToPoints4() + { + Point2d a = MinPoint;// min + Point2d b = new(_Right, _Y); + Point2d c = MaxPoint;// max + Point2d d = new(_X, _Top); + return (a, b, c, d); + } + + /// + /// 四周膨胀 + /// + /// + public Rect Expand(double d) + { + return new Rect(_X - d, _Y - d, _Right + d, _Top + d); + } + + /// + /// 是否矩形(带角度) + /// + /// + /// + public static bool IsRectAngle(List? ptList, double tolerance = 1e-8) + { + //if (ptList == null) + // throw new ArgumentNullException(nameof(ptList)); + + ptList.NotNull(nameof(ptList)); + var pts = ptList.ToList(); + /* + * 消重,不这里设置,否则这不是一个正确的单元测试 + * // var ptList = pts.Distinct().ToList(); + * var ptList = pts.DistinctExBy((a, b) => a.DistanceTo(b) < 1e-6).ToList(); + */ + if (ptList.Count == 5) + { + // 首尾点相同移除最后 + if (pts[0].IsEqualTo(pts[^1], CadTolerance)) + pts.RemoveAt(pts.Count - 1); + } + if (pts.Count != 4) + return false; + + // 最快的方案 + // 点乘求值法:(为了处理 正梯形/平行四边形 需要三次) + // 这里的容差要在1e-8内,因为点乘的三次浮点数乘法会令精度变低 + var dot = DotProductValue(pts[0], pts[1], pts[3]); + if (Math.Abs(dot) < tolerance) + { + dot = DotProductValue(pts[1], pts[2], pts[0]); + if (Math.Abs(dot) < tolerance) + { + dot = DotProductValue(pts[2], pts[3], pts[1]); + return Math.Abs(dot) < tolerance; + } + } + return false; + } + + /// + /// 点积,求值 + /// 1.是两个向量的长度与它们夹角余弦的积 + /// 2.求四个点是否矩形使用 + /// + /// 原点 + /// 点 + /// 点 + /// 0方向相同,夹角0~90度;=0相互垂直;<0方向相反,夹角90~180度]]> + static double DotProductValue(Point2d o, Point2d a, Point2d b) + { + var oa = o.GetVectorTo(a); + var ob = o.GetVectorTo(b); + return (oa.X * ob.X) + (oa.Y * ob.Y); + } + + /// + /// 是否轴向矩形(无角度) + /// + public static bool IsRect(List? ptList, double tolerance = 1e-10) + { + //if (ptList == null) + // throw new ArgumentNullException(nameof(ptList)); + ptList.NotNull(nameof(ptList)); + var pts = ptList.ToList(); + if (ptList.Count == 5) + { + // 首尾点相同移除最后 + if (pts[0].IsEqualTo(pts[^1], CadTolerance)) + pts.RemoveAt(pts.Count - 1); + } + if (pts.Count != 4) + return false; + + return Math.Abs(pts[0].X - pts[3].X) < tolerance && + Math.Abs(pts[0].Y - pts[1].Y) < tolerance && + Math.Abs(pts[1].X - pts[2].X) < tolerance && + Math.Abs(pts[2].Y - pts[3].Y) < tolerance; + } + + /// + /// 获取点集的包围盒的最小点和最大点(无角度) + /// + /// + public static (Point2d boxMin, Point2d boxMax) GetMinMax(IEnumerable pts) + { + var xMin = double.MaxValue; + var xMax = double.MinValue; + var yMin = double.MaxValue; + var yMax = double.MinValue; + // var zMin = double.MaxValue; + // var zMax = double.MinValue; + + pts.ForEach(p => { + xMin = Math.Min(p.X, xMin); + xMax = Math.Max(p.X, xMax); + yMin = Math.Min(p.Y, yMin); + yMax = Math.Max(p.Y, yMax); + // zMin = Math.Min(p.Z, zMin); + // zMax = Math.Max(p.Z, zMax); + }); + return (new Point2d(xMin, yMin), new Point2d(xMax, yMax)); + } + + /// + /// 矩形点序逆时针排列,将min点[0],max点是[3](带角度) + /// + /// + /// + public static bool RectAnglePointOrder(List? pts) + { + //if (pts == null) + // throw new ArgumentNullException(nameof(pts)); + pts.NotNull(nameof(pts)); + if (!Rect.IsRectAngle(pts)) + return false; + + // 获取min和max点(非包围盒) + pts = pts.OrderBy(a => a.X).ThenBy(a => a.Y).ToList(); + var minPt = pts.First(); + var maxPt = pts.Last(); + var link = new LoopList(); + link.AddRange(pts); + + pts.Clear(); + // 排序这四个点,左下/右下/右上/左上 + var node = link.Find(minPt); + for (int i = 0; i < 4; i++) + { + pts.Add(node!.Value); + node = node.Next; + } + // 保证是逆时针 + var isAcw = CrossAclockwise(pts[0], pts[1], pts[2]); + if (!isAcw) + (pts[3], pts[1]) = (pts[1], pts[3]); + return true; + } + + /// + /// 叉积,二维叉乘计算 + /// + /// 传参是向量,表示原点是0,0 + /// 传参是向量,表示原点是0,0 + /// 其模为a与b构成的平行四边形面积 + static double Cross(Vector2d a, Vector2d b) + { + return a.X * b.Y - a.Y * b.X; + } + + /// + /// 叉积,二维叉乘计算 + /// + /// 原点 + /// oa向量 + /// ob向量,此为判断点 + /// 返回值有正负,表示绕原点四象限的位置变换,也就是有向面积 + static double Cross(Point2d o, Point2d a, Point2d b) + { + return Cross(o.GetVectorTo(a), o.GetVectorTo(b)); + } + + /// + /// 叉积,逆时针方向为真 + /// + /// 直线点1 + /// 直线点2 + /// 判断点 + /// b点在oa的逆时针时为 + static bool CrossAclockwise(Point2d o, Point2d a, Point2d b) + { + return Cross(o, a, b) > -1e-6;// 浮点数容差考虑 + } + +#if !WinForm + /// + /// 创建矩形范围多段线 + /// + /// 多段线对象 + public Entity ToPolyLine() + { + var bv = new List(); + var pts = ToPoints(); + Polyline pl = new(); + pl.SetDatabaseDefaults(); + pts.ForEach((vertex, state, index) => { + pl.AddVertexAt(index, vertex, 0, 0, 0); + }); + return pl; + } +#endif + + /// + /// 列扫碰撞检测(碰撞算法) + /// 比四叉树还快哦~ + /// + /// + /// 继承Rect的集合 + /// 先处理集合每一个成员;返回true就跳过后续委托 + /// 碰撞,返回两个碰撞的成员;返回true就跳过后续委托 + /// 后处理集合每一个成员 + public static void XCollision(List box, + Func firstProcessing, + Func collisionProcessing, + Action lastProcessing) where T : Rect + { + // 先排序X:不需要Y排序,因为Y的上下浮动不共X .ThenBy(a => a.Box.Y) + // 因为先排序就可以有序遍历x区间,超过就break,达到更快 + box = box.OrderBy(a => a._X).ToList(); + + // 遍历所有图元 + for (int i = 0; i < box.Count; i++) + { + var oneRect = box[i]; + if (firstProcessing(oneRect)) + continue; + + bool actionlast = true; + + // 搜索范围要在 one 的头尾中间的部分 + for (int j = i + 1; j < box.Count; j++) + { + var twoRect = box[j]; + // x碰撞:矩形2的Left 在 矩形1[Left-Right]闭区间;穿过的话,也必然有自己的Left因此不需要处理 + if (oneRect._X <= twoRect._X && twoRect._X <= oneRect._Right) + { + // y碰撞,那就是真的碰撞了 + if ((oneRect._Top >= twoRect._Top && twoRect._Top >= oneRect._Y) /*包容上边*/ + || (oneRect._Top >= twoRect._Y && twoRect._Y >= oneRect._Y) /*包容下边*/ + || (twoRect._Top >= oneRect._Top && oneRect._Y >= twoRect._Y)) /*穿过*/ + { + if (collisionProcessing(oneRect, twoRect)) + actionlast = false; + } + // 这里想中断y高过它的无意义比较, + // 但是必须排序Y,而排序Y必须同X,而这里不是同X(而是同X区间),所以不能中断 + // 而做到X区间排序,就必须创造一个集合,再排序这个集合, + // 会导致每个图元都拥有一次X区间集合,开销更巨大(因此放弃). + } + else + break;// 因为已经排序了,后续的必然超过 x碰撞区间 + } + + if (actionlast) + lastProcessing(oneRect); + } + } + + #endregion + + #region 转换类型 +#if !WinForm + // 隐式转换(相当于是重载赋值运算符) + // public static implicit operator Rect(System.Windows.Rect rect) + // { + // return new Rect(rect.Left, rect.Bottom, rect.Right, rect.Top); + // } + public static implicit operator Rect(System.Drawing.RectangleF rect) + { + return new Rect(rect.Left, rect.Bottom, rect.Right, rect.Top); + } + public static implicit operator Rect(System.Drawing.Rectangle rect) + { + return new Rect(rect.Left, rect.Bottom, rect.Right, rect.Top); + } +#endif + + #region ToString + public sealed override string ToString() + { + return ToString(null, null); + } + public string ToString(IFormatProvider? provider) + { + return ToString(null, provider); + } + public string ToString(string? format = null, IFormatProvider? formatProvider = null) + { + return $"({_X.ToString(format, formatProvider)},{_Y.ToString(format, formatProvider)})," + + $"({_Right.ToString(format, formatProvider)},{_Top.ToString(format, formatProvider)})"; + + // return $"X={_X.ToString(format, formatProvider)}," + + // $"Y={_Y.ToString(format, formatProvider)}," + + // $"Right={_Right.ToString(format, formatProvider)}," + + // $"Top={_Top.ToString(format, formatProvider)}"; + } + + /*为了红黑树,加入这个*/ + public int CompareTo(Rect rect) + { + if (rect == null) + return -1; + if (_X < rect._X) + return -1; + else if (_X > rect._X) + return 1; + else if (_Y < rect._Y)/*x是一样的*/ + return -1; + else if (_Y > rect._Y) + return 1; + return 0;/*全部一样*/ + } + #endregion + + #endregion +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/Copyclip/BitmapTool.cs b/src/CAD/IFox.CAD.Shared/Copyclip/BitmapTool.cs new file mode 100644 index 0000000000000000000000000000000000000000..444acae7cb766ffcff120285ea938b00da53abd0 --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/Copyclip/BitmapTool.cs @@ -0,0 +1,165 @@ +namespace IFoxCAD.Cad; + +using System; +/// +/// bitmap工具类 +/// +public class BitmapTool +{ + // https://blog.csdn.net/shellching/article/details/18405185 + /// Windows不允许程序员直接访问硬件, + /// 它对屏幕的操作是通过环境设备,也就是DC来完成的 + /// 屏幕上的每一个窗口都对应一个DC,可以把DC想象成一个视频缓冲区, + /// 对这这个缓冲区的操作,会表现在这个缓冲区对应的屏幕窗口上. + /// 在窗口的DC之外,可以建立自己的DC,就是说它不对应窗口, + /// 这个方法就是 CreateCompatibleDC,这个DC就是一个内存缓冲区, + /// 通过这个DC你可以把和它兼容的窗口DC保存到这个DC中, + /// 就是说你可以通过它在不同的DC之间拷贝数据. + /// 例如:你先在这个DC中建立好数据,然后在拷贝到窗口的DC就是完成了这个窗口的刷新 + + /// + /// 检索指定窗口的工作区的显示设备上下文(DC)的句柄
+ /// 显示设备上下文可以在随后的图形显示界面(GDI)函数中使用,
+ /// 以在窗口的工作区中绘制
+ ///
+ /// + /// + [DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr GetDC(IntPtr hWnd); + /// + /// + /// + /// + /// + /// + [DllImport("user32.dll")] + public static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDC); + + /// + /// 创建DC + /// + /// + /// + [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleDC", SetLastError = true)] + public static extern IntPtr CreateCompatibleDC([In] IntPtr hdc); + + /// + /// Creates a bitmap compatible with the device that is associated with the specified device context. + /// + /// A handle to a device context. + /// The bitmap width, in pixels. + /// The bitmap height, in pixels. + /// If the function succeeds, the return value is a handle to the compatible bitmap (DDB). If the function fails, the return value is . + [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleBitmap")] + public static extern IntPtr CreateCompatibleBitmap([In] IntPtr hdc, int nWidth, int nHeight); + + /// Selects an object into the specified device context (DC). The new object replaces the previous object of the same type. + /// A handle to the DC. + /// A handle to the object to be selected. + /// + /// If the selected object is not a region and the function succeeds, the return value is a handle to the object being replaced. If the selected object is a region and the function succeeds, the return value is one of the following values. + /// SIMPLEREGION - Region consists of a single rectangle. + /// COMPLEXREGION - Region consists of more than one rectangle. + /// NULLREGION - Region is empty. + /// If an error occurs and the selected object is not a region, the return value is NULL. Otherwise, it is HGDI_ERROR. + /// + /// + /// This function returns the previously selected object of the specified type. An application should always replace a new object with the original, default object after it has finished drawing with the new object. + /// An application cannot select a single bitmap into more than one DC at a time. + /// ICM: If the object being selected is a brush or a pen, color management is performed. + /// + [DllImport("gdi32.dll", EntryPoint = "SelectObject")] + public static extern IntPtr SelectObject([In] IntPtr hdc, [In] IntPtr hgdiobj); + + /// Deletes a logical pen, brush, font, bitmap, region, or palette, freeing all system resources associated with the object. After the object is deleted, the specified handle is no longer valid. + /// A handle to a logical pen, brush, font, bitmap, region, or palette. + /// + /// If the function succeeds, the return value is nonzero. + /// If the specified handle is not valid or is currently selected into a DC, the return value is zero. + /// + /// + /// Do not delete a drawing object (pen or brush) while it is still selected into a DC. + /// When a pattern brush is deleted, the bitmap associated with the brush is not deleted. The bitmap must be deleted independently. + /// + [DllImport("gdi32.dll", EntryPoint = "DeleteObject")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool DeleteObject([In] IntPtr hObject); + + /// + /// 指定的源设备环境区域中的像素进行位块转换,以传送到目标设备环境 + /// + /// Handle to the destination device context. + /// The leftmost x-coordinate of the destination rectangle (in pixels). + /// The topmost y-coordinate of the destination rectangle (in pixels). + /// The width of the source and destination rectangles (in pixels). + /// The height of the source and the destination rectangles (in pixels). + /// Handle to the source device context. + /// The leftmost x-coordinate of the source rectangle (in pixels). + /// The topmost y-coordinate of the source rectangle (in pixels). + /// A raster-operation code. + /// + /// true if the operation succeedes, false otherwise. To get extended error information, call . + /// + [DllImport("gdi32.dll", EntryPoint = "BitBlt", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool BitBlt([In] IntPtr hdc, int nXDest, int nYDest, int nWidth, int nHeight, [In] IntPtr hdcSrc, int nXSrc, int nYSrc, TernaryRasterOperations dwRop); + /// + /// A raster-operation code enum + /// + public enum TernaryRasterOperations : uint + { + +#pragma warning disable CS1591 // 缺少对公共可见类型或成员的 XML 注释 + SRCCOPY = 0x00CC0020, + SRCPAINT = 0x00EE0086, + SRCAND = 0x008800C6, + SRCINVERT = 0x00660046, + SRCERASE = 0x00440328, + NOTSRCCOPY = 0x00330008, + NOTSRCERASE = 0x001100A6, + MERGECOPY = 0x00C000CA, + MERGEPAINT = 0x00BB0226, + PATCOPY = 0x00F00021, + PATPAINT = 0x00FB0A09, + PATINVERT = 0x005A0049, + DSTINVERT = 0x00550009, + BLACKNESS = 0x00000042, + WHITENESS = 0x00FF0062, + CAPTUREBLT = 0x40000000 //only if WinVer >= 5.0.0 (see wingdi.h) +#pragma warning restore CS1591 // 缺少对公共可见类型或成员的 XML 注释 + } + + /// + /// 截图成为BMP + /// + /// 截图的窗口 + /// 扔出BMP执行任务 + /// + public static void CaptureWndImage(IntPtr hWnd, Action action) + { + //if (action == null) + // throw new ArgumentNullException(nameof(action)); + action.NotNull(nameof(action)); + var hDC = GetDC(hWnd); + var hMemDC = CreateCompatibleDC(hDC); + if (hMemDC == IntPtr.Zero) + return; + + WindowsAPI.GetClientRect(hWnd, out WindowsAPI.IntRect rcClient); + int width = rcClient.Right - rcClient.Left; + int height = rcClient.Bottom - rcClient.Top; + + var hBitmap = CreateCompatibleBitmap(hDC, width, height); + if (hBitmap != IntPtr.Zero) + { + SelectObject(hMemDC, hBitmap); + if (BitBlt(hMemDC, 0, 0, width, height, + hDC, 0, 0, TernaryRasterOperations.SRCCOPY)) + { + action.Invoke(hBitmap); + } + DeleteObject(hBitmap); + } + DeleteObject(hMemDC); + } +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/Copyclip/EmfTool.cs b/src/CAD/IFox.CAD.Shared/Copyclip/EmfTool.cs new file mode 100644 index 0000000000000000000000000000000000000000..da8ca7d60c207bbb5cbd192cc13dab7e1db00f5c --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/Copyclip/EmfTool.cs @@ -0,0 +1,922 @@ +namespace IFoxCAD.Cad; + +using System; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Imaging; +using System.Text; +using static IFoxCAD.Basal.WindowsAPI; +using Point = System.Drawing.Point; +using Size = System.Drawing.Size; + +// DWORD == uint +// WORD == ushort +// LONG == int + +/* + * Console.WriteLine(Marshal.SizeOf(typeof(PlaceableMetaHeader))); + * Console.WriteLine(Marshal.SizeOf(typeof(WindowsMetaHeader))); + * Console.WriteLine(Marshal.SizeOf(typeof(StandardMetaRecord))); + */ + + +// https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-metafilepict +// http://www.cppblog.com/zwp/archive/2012/02/25/60225.html +[Serializable] +[StructLayout(LayoutKind.Sequential, Pack = 2)] +public struct MetaFilePict +{ + public MappingModes mm; + public int xExt; + public int yExt; + public IntPtr hMF; //内存图元文件的句柄 +} + +public enum MappingModes : int +{ + MM_TEXT = 1, + MM_LOMETRIC = 2, + MM_HIMETRIC = 3, + MM_LOENGLISH = 4,//逻辑坐标的单位为0.01英寸 + MM_HIENGLISH = 5, + MM_TWIPS = 6, + MM_ISOTROPIC = 7, + MM_ANISOTROPIC = 8, + + //Minimum and Maximum Mapping Mode values + MM_MIN = MM_TEXT, + MM_MAX = MM_ANISOTROPIC, + MM_MAX_FIXEDSCALE = MM_TWIPS, +} + + +//WMF 文件格式: +//https://blog.51cto.com/chenyanxi/803247 +//文件缩放信息:22字节 +[Serializable] +[StructLayout(LayoutKind.Sequential, Pack = 2)] +public struct PlaceableMetaHeader +{ + public uint Key; /* 固定大小以相反顺序出现 9AC6CDD7h */ + public ushort Handle; /* Metafile HANDLE number (always 0) */ + + public short Left; /* Left coordinate in metafile units */ + public short Top; /* Top coordinate in metafile units */ + public short Right; /* Right coordinate in metafile units */ + public short Bottom; /* Bottom coordinate in metafile units */ + + public ushort Inch; /* Number of metafile units per inch */ + public uint Reserved; /* Reserved (always 0) */ + public ushort Checksum; /* Checksum value for previous 10 WORDs */ + + // 微软的wmf文件分为两种一种是标准的图元文件, + // 一种是活动式图元文件,活动式图元文件 与 标准的图元文件 的主要区别是, + // 活动式图元文件包含了图像的原始大小和缩放信息. + /// + /// 是活动式图元文件 + /// + public bool IsActivity => Key == 0x9AC6CDD7; + + /// + /// wmf转为emf
+ ///
+ /// 文件路径 + /// + /// 错误: ;
+ /// 成功: 返回一个增强型图元 emf文件句柄 (位于内存中) + ///
+ /// + public static IntPtr Wmf2Emf(string wmfFile) + { + using FileStream file = new(wmfFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); // FileShare才能进c盘 + if (file.Length == 0) + throw new IOException("文件字节0长:" + file); + if (file.Length < 5) + throw new IOException("无法校验文件签名:" + file); + + var fileByte = new byte[file.Length]; + file.Read(fileByte, 0, fileByte.Length); + file.Close(); + + var sWMF = BytesToStruct(fileByte); + // 转为emf的地址 + IntPtr hEMF = IntPtr.Zero; + + // 控制输出的时候跟cad一样带有一个矩形框边界,而不是所有图元的包围盒作为边界 + var mpType = new MetaFilePict + { + mm = MappingModes.MM_ANISOTROPIC, + xExt = sWMF.Right - sWMF.Left, + yExt = sWMF.Bottom - sWMF.Top, + hMF = IntPtr.Zero + }; + + // byte[] 指针偏移 + int iOffset = 0; + if (sWMF.IsActivity) + iOffset = Marshal.SizeOf(typeof(PlaceableMetaHeader)); + + unsafe + { + // 安全指针方法 + //IntPtr fileIntPtr = Marshal.UnsafeAddrOfPinnedArrayElement(fileByte, iOffset); + // 不安全指针方法 + fixed (byte* fileIntPtr = &fileByte[iOffset]) + hEMF = EmfTool.SetWinMetaFileBits( + (uint)fileByte.Length, new IntPtr(fileIntPtr), IntPtr.Zero, new IntPtr(&mpType)); + } + return hEMF; + } +} + +public class Emf +{ + public IntPtr EmfHandle; + + /// + /// 转换wmf到emf + /// + /// + public void Wmf2Emf(string wmfFile) + { + EmfHandle = PlaceableMetaHeader.Wmf2Emf(wmfFile);//emf文件句柄 + } + + /// + /// 获取emf结构 + /// + /// + public EnhMetaHeader CreateEnhMetaHeader() + { + if (EmfHandle == IntPtr.Zero) + throw new ArgumentException(nameof(EmfHandle) + "== IntPtr.Zero"); + return EnhMetaHeader.Create(EmfHandle); + } +} + +//紧接文件缩放信息的是 WMFHEAD, 18字节 +[Serializable] +[StructLayout(LayoutKind.Sequential, Pack = 2)] +public struct WindowsMetaHeader +{ + public ushort FileType; /* Type of metafile (0=memory, 1=disk) */ + public ushort HeaderSize; /* Size of header in WORDS (always 9) */ + public ushort Version; /* Version of Microsoft Windows used */ + public uint FileSize; /* Total size of the metafile in WORDs */ + public ushort NumOfObjects; /* Number of objects in the file */ + public uint MaxRecordSize; /* The size of largest record in WORDs */ + public ushort NumOfParams;    /* Not Used (always 0) */ +} + +//紧接 WMFHEAD 的是 WMFRECORD, 14字节 +[Serializable] +[StructLayout(LayoutKind.Sequential, Pack = 2)] +public struct StandardMetaRecord +{ + public uint Size; /* Total size of the record in WORDs */ + public ushort Function; /* Function number (defined in WINDOWS.H) */ + public ushort[] Parameters;  /* Parameter values passed to function */ +} + +// 文件结构:头记录(ENHMETAHEADER),各记录(ENHMETARECORD),文件结尾(EMR_EOF) +// https://www.cnblogs.com/5iedu/p/4706327.html +[Serializable] +[StructLayout(LayoutKind.Sequential, Pack = 2)] +public struct EnhMetaHeader +{ + [Description("记录类型")] + public uint iType; + [Description("结构大小")] + public int nSize; //注意这个大小是含描述字符串的长度,即等于sizeof(ENHMETAHEADER)+nDescription*2 + [Description("外接矩形(单位是像素)")] + public IntRect rclBounds; + [Description("图片矩形(单位是 0.1 毫米)")] + public IntRect rclFrame; + [Description("文件签名")] + public uint dSignature; + [Description("文件版本")] + public uint nVersion; + [Description("文件尺寸")] + public uint nBytes; + [Description("记录数")] + public uint nRecords; + [Description("句柄数")] + public ushort nHandles; + [Description("保留")] + public ushort sReserved; + [Description("说明文本的长度")] + public uint nDescription; + [Description("说明文本的偏移量")] + public uint offDescription; + [Description("调色板的元素数")] + public uint nPalEntries; + [Description("分辨率(像素)")] + public IntSize szlDevice; + [Description("分辨率(毫米)")] + public IntSize szlMillimeters; + [Description("像素格式的尺寸")] + public uint cbPixelFormat; + [Description("像素格式的起始偏移位置")] + public uint offPixelFormat; + [Description("在不含OpenGL记录时,该值为FALSE")] + public uint bOpenGL; + [Description("参考设备的尺寸(微米)")] + public IntSize szlMicrometers; + + public override string ToString() + { + //var tp = GetType(); + //var sb = new StringBuilder(); + //sb.AppendLine(EnumEx.GetDesc(tp, nameof(iType)) + ":" + iType); + + // 输出json + // NET472 System.Text.Json + var serializer = new System.Web.Script.Serialization.JavaScriptSerializer(); + string jsonString = serializer.Serialize(this); + return jsonString; + } + + /// + /// 通过wmf创建 + /// + /// + /// + public static EnhMetaHeader Create(string wmf) + { + var emf = PlaceableMetaHeader.Wmf2Emf(wmf); + if (emf == IntPtr.Zero) + throw new ArgumentException(nameof(emf)); + return Create(emf); + } + + /// + /// 通过emf指针创建 + /// + /// 参数1的结构体首地址
+ /// 也就是的返回值 + /// + /// + public static EnhMetaHeader Create(IntPtr emf) + { + var len = EmfTool.GetEnhMetaFileHeader(emf, 0, IntPtr.Zero); + if (len == 0) + throw new ArgumentException(nameof(len)); + + IntPtr header = Marshal.AllocHGlobal((int)len); + EmfTool.GetEnhMetaFileHeader(emf, len, header);//这里是切割获取内部的bytes,存放在header + + var result = (EnhMetaHeader)Marshal.PtrToStructure(header, typeof(EnhMetaHeader)); + + Marshal.FreeHGlobal(header); + return result; + } +} + + + +public static class EmfTool +{ + /// + /// 保存 + /// + /// GetEnhMetaFileBits 参数1的结构体首地址 + /// 保存路径 + /// + public static void Save(IntPtr clipTypeData, string file) + { + // 保存emf文件 + // https://blog.csdn.net/tigertianx/article/details/7098490 + var len = EmfTool.GetEnhMetaFileBits(clipTypeData, 0, null!); + if (len != 0) + { + var bytes = new byte[len]; + _ = EmfTool.GetEnhMetaFileBits(clipTypeData, len, bytes); + + using MemoryStream ms1 = new(bytes); + using var bm = Image.FromStream(ms1);//此方法emf保存成任何版本都会变成png + bm.Save(file); + } + } + + /// + /// 返回对一个增强型图元文件的说明 + /// + /// 目标增强型图元文件的句柄 + /// lpszDescription缓冲区的长度 + /// 指定一个预先初始化好的字串缓冲区,准备随同图元文件说明载入; + /// 参考 CreateEnhMetaFile 函数,了解增强型图元文件说明字串的具体格式 + /// + [DllImport("gdi32.dll")] + static extern uint GetEnhMetaFileDescription(IntPtr hemf, uint cchBuffer, [MarshalAs(UnmanagedType.LPStr)] StringBuilder lpDescription); + + /// + /// 获取emf描述 + /// + /// 文件句柄 + /// 描述的内容 + [System.Diagnostics.DebuggerStepThrough] + [System.CodeDom.Compiler.GeneratedCode("InteropSignatureToolkit", "0.9 Beta1")]//初始化时指定生成代码的工具的名称和版本 + public static string? GetEnhMetaFileDescriptionEx(IntPtr clipTypeData) + { + var len = GetEnhMetaFileDescription(clipTypeData, 0, null!); + if (len != 0) + { + StringBuilder desc = new((int)len); + GetEnhMetaFileDescription(clipTypeData, (uint)desc.Capacity, desc); + return desc.ToString(); + } + return null; + } + + public enum DeviceCap : int + { + /// + /// Device driver version + /// + DRIVERVERSION = 0, + /// + /// Device classification + /// + TECHNOLOGY = 2, + /// + /// Horizontal size in millimeters + /// + HORZSIZE = 4, + /// + /// Vertical size in millimeters + /// + VERTSIZE = 6, + /// + /// Horizontal width in pixels + /// + HORZRES = 8, + /// + /// Vertical height in pixels + /// + VERTRES = 10, + /// + /// Number of bits per pixel + /// + BITSPIXEL = 12, + /// + /// Number of planes + /// + PLANES = 14, + /// + /// Number of brushes the device has + /// + NUMBRUSHES = 16, + /// + /// Number of pens the device has + /// + NUMPENS = 18, + /// + /// Number of markers the device has + /// + NUMMARKERS = 20, + /// + /// Number of fonts the device has + /// + NUMFONTS = 22, + /// + /// Number of colors the device supports + /// + NUMCOLORS = 24, + /// + /// Size required for device descriptor + /// + PDEVICESIZE = 26, + /// + /// Curve capabilities + /// + CURVECAPS = 28, + /// + /// Line capabilities + /// + LINECAPS = 30, + /// + /// Polygonal capabilities + /// + POLYGONALCAPS = 32, + /// + /// Text capabilities + /// + TEXTCAPS = 34, + /// + /// Clipping capabilities + /// + CLIPCAPS = 36, + /// + /// Bitblt capabilities + /// + RASTERCAPS = 38, + /// + /// Length of the X leg + /// + ASPECTX = 40, + /// + /// Length of the Y leg + /// + ASPECTY = 42, + /// + /// Length of the hypotenuse + /// + ASPECTXY = 44, + /// + /// Shading and Blending caps + /// + SHADEBLENDCAPS = 45, + + /// + /// Logical pixels inch in X + /// + LOGPIXELSX = 88, + /// + /// Logical pixels inch in Y + /// + LOGPIXELSY = 90, + + /// + /// Number of entries in physical palette + /// + SIZEPALETTE = 104, + /// + /// Number of reserved entries in palette + /// + NUMRESERVED = 106, + /// + /// Actual color resolution + /// + COLORRES = 108, + + // Printing related DeviceCaps. These replace the appropriate Escapes + /// + /// Physical Width in device units + /// + PHYSICALWIDTH = 110, + /// + /// Physical Height in device units + /// + PHYSICALHEIGHT = 111, + /// + /// Physical Printable Area x margin + /// + PHYSICALOFFSETX = 112, + /// + /// Physical Printable Area y margin + /// + PHYSICALOFFSETY = 113, + /// + /// Scaling factor x + /// + SCALINGFACTORX = 114, + /// + /// Scaling factor y + /// + SCALINGFACTORY = 115, + + /// + /// Current vertical refresh rate of the display device (for displays only) in Hz + /// + VREFRESH = 116, + /// + /// Vertical height of entire desktop in pixels + /// + DESKTOPVERTRES = 117, + /// + /// Horizontal width of entire desktop in pixels + /// + DESKTOPHORZRES = 118, + /// + /// Preferred blt alignment + /// + BLTALIGNMENT = 119 + } + + [DllImport("gdi32.dll")] + static extern int GetDeviceCaps(IntPtr hDC, DeviceCap nIndex); + + [DllImport("gdi32.dll")] + static extern int SetMapMode(IntPtr hDC, MappingModes fnMapMode); + + [DllImport("gdi32.dll")] + static extern bool SetViewportOrgEx(IntPtr hDC, int x, int y, Point[] prevPoint); + + [DllImport("gdi32.dll")] + static extern bool SetWindowOrgEx(IntPtr hDC, int x, int y, Point[] prevPoint); + + [DllImport("gdi32.dll")] + static extern bool SetViewportExtEx(IntPtr hDC, int nExtentX, int nExtentY, Size[] prevSize); + + [DllImport("gdi32.dll")] + static extern bool SetWindowExtEx(IntPtr hDC, int nExtentX, int nExtentY, Size[] prevSize); + + [DllImport("Gdi32.dll")] + public static extern int CreatePen(int nPenStyle, int nWidth, int nColor); + + [DllImport("Gdi32.dll")] + public static extern int GetStockObject(int nStockBrush); + + [DllImport("Gdi32.dll")] + public static extern int SelectObject(IntPtr hDC, int hGdiObject); + + [DllImport("Gdi32.dll")] + public static extern int DeleteObject(int hBitmap); + + [DllImport("Gdi32.dll")] + public static extern int MoveToEx(IntPtr hDC, int x, int y, int nPreviousPoint); + + [DllImport("Gdi32.dll")] + public static extern int LineTo(IntPtr hDC, int x, int y); + + [DllImport("Gdi32.dll")] + public static extern int Rectangle(IntPtr hDC, int nLeft, int nTop, int nRight, int nBottom); + + [DllImport("Gdi32.dll")] + public static extern bool DPtoLP(IntPtr hdc, [In, Out] Point[] lpPoints, int nCount); + + + /// + /// 设置emf描述 + /// + /// emf文件句柄 + /// 设置描述 + /// 新的emf指针 + /// + public static void SetEnhMetaFileDescriptionEx(ref IntPtr hMetaFile, string desc) + { + //if (hMetaFile == IntPtr.Zero) + // throw new ArgumentNullException(nameof(hMetaFile)); + hMetaFile.NotNull(nameof(hMetaFile)); + var emh = EnhMetaHeader.Create(hMetaFile);//emf结构 GetEnhMetaFileHeader + // 创建画布句柄 + IntRect intRect = emh.rclFrame; //new(0, 0, 0, 0); + var hMetaDC = EmfTool.CreateEnhMetaFile(IntPtr.Zero, null!, ref intRect, desc); + if (hMetaDC == IntPtr.Zero) + return; + //SetMapMode(hMetaDC, MappingModes.MM_ANISOTROPIC); // 默认的就是这个模式 + //SetMapMode(hMetaDC, MappingModes.MM_HIMETRIC);//逻辑单位:0.01mm + + // 设置单位 + //var size = new IntSize(0, 0); + //EmfTool.SetWindowExtEx(hMetaDC, 0, 0, ref size); + //EmfTool.SetViewportExtEx(hMetaDC, 0, 0, ref size); + //EmfTool.GetEnhMetaFilePaletteEntries() 统一调色 + //SetViewportOrgEx(hMetaDC, 0, 0, null!);//将视口原点设在左下角 + + // 旧的克隆到新的 + /* + * 第18章 图元文件_18.2 增强型图元文件(emf)(2) + * https://blog.51cto.com/u_15082403/3724715 + * 方案2——利用图像的物理尺寸 + * 通过rclFrame字段(是设备单位:0.01mm)显示出来的刻度尺,这样不管在视频显示器 + * 打印机上,显示出来的刻度尺都较为真实 + */ + //目标设备信息 + int cxMms = GetDeviceCaps(hMetaDC, DeviceCap.HORZSIZE);//宽度(单位:mm) + int cyMms = GetDeviceCaps(hMetaDC, DeviceCap.VERTSIZE);//高度(单位:mm) + var cxArea = cxMms; + var cyArea = cyMms; +#if true2 + int cxPix = GetDeviceCaps(hMetaDC, DeviceCap.HORZRES);//宽度(单位:像素) + int cyPix = GetDeviceCaps(hMetaDC, DeviceCap.VERTRES);//高度(单位:像素) + int cxImage = emh.rclFrame.Right - emh.rclFrame.Left; //单位:0.01mm + int cyImage = emh.rclFrame.Bottom - emh.rclFrame.Top; + + // 设置之后图像就没有拉伸了,但是跑偏了 + //将图元文件大小(0.01mm为单位)转换为像素大小 + cxImage = cxImage * cxPix / cxMms / 100; + cyImage = cyImage * cyPix / cyMms / 100; + + //在指定的矩形区内,水平和垂直居中显示图元文件,同时保证了区域的大小为cxImage和cyImage + int left = (cxArea - cxImage) / 2; + int right = (cxArea + cxImage) / 2; + int top = (cyArea - cyImage) / 2; + int bottom = (cyArea + cyImage) / 2; +#else + cxArea = 0; + cyArea = 0; + SetMapMode(hMetaDC, MappingModes.MM_HIMETRIC);//逻辑单位:0.01mm + SetViewportOrgEx(hMetaDC, 0, cyArea, null!);//将视口原点设在左下角 + var pt = new Point(cxArea, 0); + + int cxImage = emh.rclFrame.Right - emh.rclFrame.Left; //单位:0.01mm + int cyImage = emh.rclFrame.Bottom - emh.rclFrame.Top; + + //在指定的矩形区内,水平和垂直居中显示图元文件,同时保证了区域的大小为cxImage和cyImage + int left = (pt.X - cxImage) / 2; + int right = (pt.X + cxImage) / 2; + int top = (pt.Y + cyImage) / 2; //注意,这里与前面例子不同 + int bottom = (pt.Y - cyImage) / 2; //注意,这里与前面例子不同 +#endif + var rect = new IntRect(left, top, right, bottom); + + // 图像拉伸了 + //bool pef = EmfTool.EnumEnhMetaFile(hMetaDC, hMetaFile, IntPtr.Zero, IntPtr.Zero, ref emhValue.rclFrame);// 这个失败 + bool pef = EmfTool.PlayEnhMetaFile(hMetaDC, hMetaFile, ref rect); + if (!pef) + { + DeleteObject(hMetaDC); + Debugger.Break(); + return; + } + // 删除旧的图元文件句柄,返回新的 + var del = EmfTool.DeleteEnhMetaFile(hMetaFile); + if (del) + hMetaFile = EmfTool.CloseEnhMetaFile(hMetaDC); + } + + [DllImport("gdi32.dll", EntryPoint = "GetEnhMetaFileHeader")] + public static extern uint GetEnhMetaFileHeader(IntPtr hemf, uint cbBuffer, IntPtr /*ENHMETAHEADER*/ lpemh); + + + /// + /// 将一个标准Windows图元文件转换成增强型图元文件 + /// + /// 数组的长度 + /// + /// 数组包含了标准图元文件数据.
+ /// 常用 GetMetaFileBitsEx 或 GetWinMetaFileBits 函数获得 + /// + /// + /// 用于决定原始格式及图元文件分辨率的一个参考设备场景;
+ /// 采用显示器分辨率为: + /// + /// + /// 定义一个图元文件附加参考信息的结构
+ /// 为null时,会假定使用当前显示器的 MM_ANISOTROPIC 映射模式 + /// + /// + /// 错误: ;
+ /// 成功: 返回一个增强型图元emf文件的指针(位于内存中) + ///
+ [DllImport("gdi32.dll", EntryPoint = "SetWinMetaFileBits")] + public static extern IntPtr SetWinMetaFileBits(uint nSize, IntPtr lpMeta16Data, IntPtr hdcRef, IntPtr lpMFP); + /// + /// 获取矢量图的byte + /// + /// + /// + /// + /// + [DllImport("gdi32.dll")] + public static extern uint GetEnhMetaFileBits(IntPtr hemf, uint cbBuffer, byte[] lpbBuffer); + /// + /// byte转换矢量图 + /// + /// + /// + /// + [DllImport("gdi32.dll")] + public static extern IntPtr SetEnhMetaFileBits(uint cbBuffer, byte[] lpBuffer); + /// + /// 删除矢量图 + /// + /// + /// + [DllImport("gdi32.dll")] + public static extern bool DeleteEnhMetaFile(IntPtr hemf); + + /// + /// 创建emf
+ /// https://www.cnblogs.com/5iedu/p/4706327.html + ///
+ /// 参考设备环境,null以整个屏幕为参考 + /// 指定文件名时,创建磁盘文件(.EMF),为null时创建内存图元文件 + /// 用于描述图元文件的大小和位置(以0.01mm为单位),可用它精确定义图元文件的物理尺寸 + /// 对图元文件的一段说明.包括创建应用程序的名字、一个NULL字符、对图元文件的一段说明以及两个NULL字符. + /// 返回画布句柄DC(图元文件句柄得调用 CloseEnhMetaFile 函数) + [DllImport("gdi32.dll", SetLastError = true)] + public static extern IntPtr CreateEnhMetaFile(IntPtr hdcRef, string szFilename, ref IntRect lpRect, string lpDescription); + + [DllImport("gdi32.dll", SetLastError = true)] + public static extern bool DeleteObject(IntPtr hdcRef); + + /// + /// 在指定的设备场景中画一个增强型图元文件;
+ /// 与标准图元文件不同,完成回放后,增强型图元文件会恢复设备场景以前的状态 + ///
+ /// 画布句柄 + /// 欲描绘的emf的图元文件句柄 + /// 指定显示区域(逻辑单位)GDI会缩放图像以适应该矩形范围 + /// + [DllImport("gdi32.dll")] + public static extern bool PlayEnhMetaFile(IntPtr hdcRef, IntPtr hemf, ref IntRect lpRect); + + // https://blog.csdn.net/hongke457546235/article/details/17404715 + /// + /// 逻辑单位设置窗口单位 + /// {只能在 MM_ISOTROPIC 或 MM_ANISOTROPIC 模式下使用下面两个函数} + /// + /// 画布句柄 + /// 以逻辑单位表示的新窗口区域的高度 + /// 以逻辑单位表示的新窗口区域的宽度 + /// 保存函数调用前窗口区域尺寸的SIZE结构地址,NULL则表示忽略调用前的尺寸 + [DllImport("gdi32.dll")] + public static extern bool SetWindowExtEx(IntPtr hdcRef, int nHeight, int nWidth, ref IntSize lpSize); + + /// + /// 视口区域的定义 + /// {只能在 MM_ISOTROPIC 或 MM_ANISOTROPIC 模式下使用下面两个函数} + /// + /// + /// + /// + /// + [DllImport("gdi32.dll")] + public static extern bool SetViewportExtEx(IntPtr hdcRef, int nHeight, int nWidth, ref IntSize lpSize); + + /// + /// 旧emf绘制新的hdcEMF中(即回放) + /// + /// 画布句柄 + /// 图元文件句柄 + /// 回调函数 + /// 传给回调函数的额外参数 + /// 在指定的矩形区内显示图元文件 + /// + [DllImport("gdi32.dll", SetLastError = true)] + public static extern bool EnumEnhMetaFile(IntPtr hdcRef, IntPtr hmf, IntPtr proc, IntPtr procParam, ref IntRect lpRect); + + /// + /// 返回图元文件句柄 + /// + /// 画布句柄 + [DllImport("gdi32.dll", SetLastError = true)] + public static extern IntPtr CloseEnhMetaFile(IntPtr hdcRef); + + // https://zhidao.baidu.com/question/646739770512964165/answer/1616737219.html?qq-pf-to=pcqq.c2c + //16位的函数 + [DllImport("gdi32.dll")] + public static extern IntPtr GetMetaFile(string path); + //32位的函数 + [DllImport("gdi32.dll")] + public static extern IntPtr GetEnhMetaFile(string path); + + + + /// + /// EMF保存到文件或者路径 + /// + /// EMF要复制的增强型图元文件的句柄 + /// 指向目标文件名称的指针,为NULL则将源图元文件复制到内存中 + /// + [DllImport("gdi32.dll")] + public static extern IntPtr CopyEnhMetaFile(IntPtr hemfSrc, string? lpszFile); + + /// + /// 矢量图保存 + /// + /// + /// + public static void SaveMetaFile(this Metafile file, string emfName) + { + //MetafileHeader metafileHeader = file.GetMetafileHeader(); //这句话可要可不要 + IntPtr h = file.GetHenhmetafile(); + CopyEnhMetaFile(h, emfName); + DeleteEnhMetaFile(h); + } + + /// + /// 矢量图 转换 byte[] + /// + /// + /// + public static byte[]? ToByteArray(this Image image) + { + return ToByteArray((Metafile)image); + } + + // https://www.pinvoke.net/default.aspx/gdi32.getenhmetafile + /// + /// 矢量图 转换 byte[] + /// + /// + /// + public static byte[]? ToByteArray(this Metafile mf) + { + byte[]? arr = null; + IntPtr handle = mf.GetHenhmetafile(); + if (handle != IntPtr.Zero) + { + var size = GetEnhMetaFileBits(handle, 0, null!); + if (size != 0) + { + arr = new byte[size]; + _ = GetEnhMetaFileBits(handle, size, arr); + } + DeleteEnhMetaFile(handle); + } + return arr; + } + + /// + /// byte[] 转换 矢量图 + /// + /// + /// 返回值true删除句柄 + /// + public static void ToMetafile(byte[] data, Func task) + { + //if (task == null) + // throw new ArgumentNullException(nameof(task)); + task.NotNull(nameof(task)); + IntPtr hemf = SetEnhMetaFileBits((uint)data.Length, data); + using var mf = new Metafile(hemf, true); + if (task.Invoke(mf)) // 对图像进行操作,就不能进行删除句柄 + DeleteEnhMetaFile(hemf); + } + + +#if false + /// + /// c#获取wmf方式 + /// + /// + /// + public static IntPtr GetMetafile(string wmfFile) + { + using FileStream file = new(wmfFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); // FileShare才能进c盘 + var hEMF2 = IntPtr.Zero; + + using Metafile mf = new(file); + var hEMF = mf.GetHenhmetafile(); + if (hEMF != IntPtr.Zero) + hEMF2 = CopyEnhMetaFile(hEMF, null);// 这句: 句柄无效..cad的wmf文件不识别 + //EmfTool.DeleteEnhMetaFile(hEMF);//托管类应该是封装好的 + return hEMF2; + } + + + /* + * // 这是c#写入wmf流程 + * // c#画的wmf格式是可以的...用这样方式生成的就是可以写剪贴板 + * WindowsAPI.GetClientRect(doc.Window.Handle, out IntRect rcClient); + * int width = rcClient.Right - rcClient.Left; + * int height = rcClient.Bottom - rcClient.Top; + * EmfTool.Export(wmf, width, height);//cad的命令wmfin:不能导入c#自绘的 + * + * //c#方法,但是它读取不了cad的wmf + * wmfMeta = EmfTool.GetMetafile(wmf); + */ + + /// + /// 导出为 Emf 或 Wmf 文件 + /// 相关链接 + /// + /// 文件路径 + /// 窗口宽度 + /// 窗口高度 + /// 是否成功 + public static bool Export(string filePath, int width, int height) + { + try + { + using Bitmap bmp = new(width, height); + using Graphics gs = Graphics.FromImage(bmp); + using Metafile mf = new(filePath, gs.GetHdc()); + using Graphics g = Graphics.FromImage(mf); + Draw(g); + g.Save(); + return true; + } + catch { return false; } + } + + + /// + /// 绘制图形 + /// + /// 用于绘图的Graphics对象 + static void Draw(Graphics g) + { + HatchBrush hb = new(HatchStyle.LightUpwardDiagonal, Color.Black, Color.White); + + g.FillEllipse(Brushes.Gray, 10f, 10f, 200, 200); + g.DrawEllipse(new Pen(Color.Black, 1f), 10f, 10f, 200, 200); + + g.FillEllipse(hb, 30f, 95f, 30, 30); + g.DrawEllipse(new Pen(Color.Black, 1f), 30f, 95f, 30, 30); + + g.FillEllipse(hb, 160f, 95f, 30, 30); + g.DrawEllipse(new Pen(Color.Black, 1f), 160f, 95f, 30, 30); + + g.FillEllipse(hb, 95f, 30f, 30, 30); + g.DrawEllipse(new Pen(Color.Black, 1f), 95f, 30f, 30, 30); + + g.FillEllipse(hb, 95f, 160f, 30, 30); + g.DrawEllipse(new Pen(Color.Black, 1f), 95f, 160f, 30, 30); + + g.FillEllipse(Brushes.Blue, 60f, 60f, 100, 100); + g.DrawEllipse(new Pen(Color.Black, 1f), 60f, 60f, 100, 100); + + g.FillEllipse(Brushes.BlanchedAlmond, 95f, 95f, 30, 30); + g.DrawEllipse(new Pen(Color.Black, 1f), 95f, 95f, 30, 30); + + g.DrawRectangle(new Pen(Brushes.Blue, 0.1f), 6, 6, 208, 208); + + g.DrawLine(new Pen(Color.Black, 0.1f), 110f, 110f, 220f, 25f); + g.DrawString("剖面图", new Font("宋体", 9f), Brushes.Green, 220f, 20f); + } +#endif +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/Copyclip/TagClipboardInfo.cs b/src/CAD/IFox.CAD.Shared/Copyclip/TagClipboardInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..ed9195be796f8f13f80d39908a0994002a00d658 --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/Copyclip/TagClipboardInfo.cs @@ -0,0 +1,659 @@ +namespace IFoxCAD.Cad; + +using System; +using System.Diagnostics; +using System.Text; +using static IFoxCAD.Basal.WindowsAPI; + +public class ClipboardEnv +{ + // 0x01 将r17写死,代表每个cad版本都去找它,实现不隔离cad版本 + public static string CadVer = "AutoCAD.r17"; + // 0x02 当前版本在r17找不到的时候找,避免按需加载插件的时候无法获取剪贴板 + public static string CadCurrentVer = $"AutoCAD.r{Acap.Version.Major}"; +} + +/// +/// ARX剪贴板结构 +/// +[Serializable] +[StructLayout(LayoutKind.Sequential, Pack = 8, CharSet = CharSet.Unicode/*此参数将导致260*2*/)] +public struct TagClipboardInfo : IEquatable +{ + #region 字段,对应arx结构的,不要改动,本结构也不允许再加字段 + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] + public string szTempFile; // 临时文件夹的dwg文件 + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] + public string szSourceFile; // 文件名从中做出选择..是不是指定块表记录? + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)] + public string szSignature; + public int nFlags; // kbDragGeometry: 从AutoCAD拖动几何图形 + public Point3D dptInsert; // 插入点的原始世界坐标' + public IntRect rectGDI; // GDI coord 选择集的边界矩形 + public IntPtr mpView; // 用于验证这个对象是在这个视图中创建的 (HWND*) + public int dwThreadId; // AutoCAD thread 创建数据对象 + public int nLen; // 下一段的长度的数据,如果有的话,从chData + public int nType; // 类型的数据,如果有(eExpandedClipDataTypes) + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1)] + public string chData; // 数据的开始,如果有 + #endregion + + #region 属性,可以改动 + public string File => szTempFile; + public Point3d Point => new Point3d(dptInsert.X, dptInsert.Y, dptInsert.Z); + +#pragma warning disable CA2211 // 非常量字段应当不可见 + public static IntPtr AcadDwgview + = IntPtr.Zero; + //= AcedGetAcadDwgview(); // c#需要收集这个函数,我先不写,免得中间版本挂了 + + public static int MainWindowThreadId = + (int)WindowsAPI.GetWindowThreadProcessId(Acap.MainWindow.Handle, out uint processId); +#pragma warning restore CA2211 // 非常量字段应当不可见 + #endregion + + #region 构造 + /// + /// cad剪贴板 + /// + /// 临时dwg的保存路径 + /// 粘贴点 + public TagClipboardInfo(string tmpFile, Point3d insert) + { + szTempFile = tmpFile; + szSourceFile = string.Empty; + szSignature = "R15"; //恒定是这个 + nFlags = 0; + dptInsert = new Point3D(insert.X, insert.Y, insert.Z); + rectGDI = IntRect.Zero; + nLen = 0; + nType = 0; + chData = string.Empty; + + // mpView threadid 可能是用来删除的,用于剪贴板回调清理资源时候判断信息 + mpView = AcadDwgview; + dwThreadId = MainWindowThreadId; + } + #endregion + + #region 方法 + public override string ToString() + { + var sb = new StringBuilder(); + sb.AppendLine($"szTempFile:{szTempFile}"); + sb.AppendLine($"szSourceFile:{szSourceFile}"); + sb.AppendLine($"szSignature:{szSignature}"); + sb.AppendLine($"nFlags:{nFlags}"); + sb.AppendLine($"dptInsert:{dptInsert}"); + sb.AppendLine($"rectGDI:{rectGDI}"); + sb.AppendLine($"mpView:{mpView}"); + sb.AppendLine($"dwThreadId:{dwThreadId}"); + sb.AppendLine($"nLen:{nLen}"); + sb.AppendLine($"nType:{nType}"); + sb.AppendLine($"chData:{chData}"); + return sb.ToString(); + } + #endregion + + #region 测试大小 + void GetSize() + { + var v_1 = Marshal.OffsetOf(typeof(TagClipboardInfo), nameof(TagClipboardInfo.szTempFile)).ToInt32(); + var v_2 = Marshal.OffsetOf(typeof(TagClipboardInfo), nameof(TagClipboardInfo.szSourceFile)).ToInt32(); + var v_3 = Marshal.OffsetOf(typeof(TagClipboardInfo), nameof(TagClipboardInfo.szSignature)).ToInt32(); + var v_4 = Marshal.OffsetOf(typeof(TagClipboardInfo), nameof(TagClipboardInfo.nFlags)).ToInt32(); + var v_5 = Marshal.OffsetOf(typeof(TagClipboardInfo), nameof(TagClipboardInfo.dptInsert)).ToInt32(); + var v_6 = Marshal.OffsetOf(typeof(TagClipboardInfo), nameof(TagClipboardInfo.rectGDI)).ToInt32(); + var v_7 = Marshal.OffsetOf(typeof(TagClipboardInfo), nameof(TagClipboardInfo.mpView)).ToInt32(); + var v_8 = Marshal.OffsetOf(typeof(TagClipboardInfo), nameof(TagClipboardInfo.dwThreadId)).ToInt32(); + var v_9 = Marshal.OffsetOf(typeof(TagClipboardInfo), nameof(TagClipboardInfo.nLen)).ToInt32(); + var v_10 = Marshal.OffsetOf(typeof(TagClipboardInfo), nameof(TagClipboardInfo.nType)).ToInt32(); + var v_11 = Marshal.OffsetOf(typeof(TagClipboardInfo), nameof(TagClipboardInfo.chData)).ToInt32(); + var v_12 = Marshal.SizeOf(typeof(TagClipboardInfo)); //1120 + + var v_a = Marshal.SizeOf(typeof(Point3D));//24 + var v_b = Marshal.SizeOf(typeof(IntRect));//16 + } + #endregion + + #region 视口指针 + /* + [CommandMethod(nameof(Test_AcedGetAcadDwgview))] + public void testAcedGetAcadDwgview() + { + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + var ed = doc.Editor; + + var a = AcedGetAcadDwgview().ToString(); //自动执行的时候就存在了 + var b = ed.CurrentViewportObjectId.ToString(); + Debugx.Printl("a == b:" + a == b);//不对 + + var tab = ed.GetCurrentView(); + var c = tab.ObjectId.ToString(); + Debugx.Printl("a == c:" + a == c);//不对 + } + */ + + /// + /// 获取视口指针 + /// +#if NET35 + [DllImport("acad.exe", EntryPoint = "?acedGetAcadDwgView@@YAPAVCView@@XZ")] //acad08 +#else + [DllImport("acad.exe", EntryPoint = "?acedGetAcadDwgView@@YAPEAVCView@@XZ")]//acad21 +#endif + static extern IntPtr AcedGetAcadDwgview(); + #endregion + + #region 重载运算符_比较 + public bool Equals(TagClipboardInfo other) + { + return + szTempFile == other.szTempFile && + szSourceFile == other.szSourceFile && + szSignature == other.szSignature && + nFlags == other.nFlags && + dptInsert == other.dptInsert && + rectGDI == other.rectGDI && + mpView == other.mpView && + dwThreadId == other.dwThreadId && + nLen == other.nLen && + nType == other.nType && + chData == other.chData; + } + public static bool operator !=(TagClipboardInfo a, TagClipboardInfo b) + { + return !(a == b); + } + public static bool operator ==(TagClipboardInfo a, TagClipboardInfo b) + { + return a.Equals(b); + } + public override bool Equals(object obj) + { + return obj is TagClipboardInfo info && Equals(info); + } + public override int GetHashCode() + { + return + szTempFile.GetHashCode() ^ + szSourceFile.GetHashCode() ^ + szSignature.GetHashCode() ^ + nFlags ^ + dptInsert.GetHashCode() ^ + rectGDI.GetHashCode() ^ + mpView.GetHashCode() ^ + dwThreadId ^ + nLen ^ + nType ^ + chData.GetHashCode(); + } + + public IntPtr CloneToPtr() + { + var lParam = Marshal.AllocHGlobal(Marshal.SizeOf(this)); + if (lParam != IntPtr.Zero) + Marshal.StructureToPtr(this, lParam, true); + return lParam; + } + #endregion +} + + +/* + * OLE 剪贴板说明 https://blog.csdn.net/chinabinlang/article/details/9815495 + * 写入时候注意: + * 0x01 c#自带的是com剪贴板 + * 最好不要使用,它不能在已经打开的剪贴板中使用, + * 也无法写入多个cf对象,也就是复制bitmap的时候会覆盖cad图元 + * Clipboard.SetImage(bitmap); + * 0x02 + * 剪贴板写入各种类型 https://blog.csdn.net/glt3953/article/details/8808262 + * + */ + +public partial class ClipTool +{ + /// + /// 侦听剪贴板 + /// + /// + /// + [DllImport("user32.dll", SetLastError = true)] + public static extern bool AddClipboardFormatListener(IntPtr hWnd); + /// + /// 移除侦听剪贴板 + /// + /// + /// + [DllImport("user32.dll", SetLastError = true)] + public static extern bool RemoveClipboardFormatListener(IntPtr hWnd); + /// + /// 将CWnd加入一个窗口链 + /// 每当剪贴板的内容发生变化时,就会通知这些窗口 + /// + /// 句柄 + /// 返回剪贴板观察器链中下一个窗口的句柄 + [DllImport("User32.dll")] + public static extern int SetClipboardViewer(IntPtr hWndNewViewer); + /// + /// 从剪贴板链中移出的窗口句柄 + /// + /// 从剪贴板链中移出的窗口句柄 + /// hWndRemove的下一个在剪贴板链中的窗口句柄 + /// 如果成功,非零;否则为0。 + [DllImport("User32.dll", CharSet = CharSet.Auto)] + public static extern bool ChangeClipboardChain(IntPtr hWndRemove, IntPtr hWndNewNext); + + + /// + /// 开启剪贴板
+ /// 如果另一个窗口已经打开剪贴板,函数会失败.每次成功调用后都应有调用. + ///
+ /// + /// + [DllImport("user32.dll", SetLastError = true)] + static extern bool OpenClipboard(IntPtr hWndNewOwner); + /// + /// 关闭剪贴板 + /// + /// + [DllImport("user32.dll", SetLastError = true)] + public static extern bool CloseClipboard(); + /// + /// 根据数据格式获取剪贴板 + /// + /// 数据格式名称 + /// + [DllImport("user32.dll", SetLastError = true)] + public static extern uint RegisterClipboardFormat(string lpszFormat); + /// + /// 获取剪贴板 + /// + /// 通常为但是cad有自己的位码 + /// + [DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr GetClipboardData(uint uFormat); + /// + /// 设置剪贴板 + /// + /// 通常为但是cad有自己的位码 + /// 指定具有指定格式的数据的句柄,
+ /// 该参数为空则为延迟提交:
+ /// 有其他程序对剪切板中的数据进行请求时,该程序才会将指定格式的数据写入到剪切板中. + /// + /// + [DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr SetClipboardData(uint uFormat, IntPtr hMem); + /// + /// 清空剪切板并释放剪切板内数据的句柄 + /// + /// + [DllImport("user32.dll", SetLastError = true)] + public static extern bool EmptyClipboard(); + /// + /// 枚举剪贴板内数据类型 + /// + /// + /// + [DllImport("user32.dll", SetLastError = true)] + public static extern uint EnumClipboardFormats(uint format); + + + /// + /// 打开剪贴板
+ /// 写入之前必须清空,
+ /// 否则将导致发送 WM_DESTROYCLIPBOARD 消息到上一次剪贴板拥有者释放资源
+ /// 所以写入的时候必须一次性写入多个cf
+ ///
+ /// 接收返回的栈空间指针用于释放 + /// true写入,false读取 + /// + /// + [System.Diagnostics.DebuggerStepThrough] + public static bool OpenClipboardTask(bool isWrite, Action action) + { + //if (action == null) + // throw new ArgumentNullException(nameof(action)); + action.NotNull(nameof(action)); + bool openFlag = false; + try + { + openFlag = OpenClipboard(IntPtr.Zero); + if (!openFlag) + return false; + if (isWrite) + EmptyClipboard(); + action.Invoke(); + } + catch (Exception e) + { + Debugger.Break(); + Debugx.Printl(e.Message); + } + finally + { + if (openFlag) + CloseClipboard(); + } + return openFlag; + } + + /// + /// 获取剪贴板 + /// + /// 剪贴板的索引名 + /// 返回的结构 + public static bool GetClipboard(string clipKey, out T? tag) + { + bool locked = false; + T? result = default; + + ClipTool.OpenClipboardTask(false, () => { + // 读取剪贴板的指定数据 + var clipKeyFormat = RegisterClipboardFormat(clipKey);//ClipboardEnv.CadVer + var clipTypeData = GetClipboardData(clipKeyFormat); + + // 剪贴板的数据拷贝进去结构体中,会依照数据长度进行拷贝 + locked = WindowsAPI.GlobalLockTask(clipTypeData, ptr => { + // 非托管内存块->托管对象 + result = (T)Marshal.PtrToStructure(ptr, typeof(T)); + }); + }); + + tag = result; + return locked; + } +} + +#if true2 +// 无法备份emf内容 +// https://blog.csdn.net/vencon_s/article/details/46345083 +public static class ClipEx +{ + /// + /// 剪贴板数据保存目标数据列表 + /// + static readonly List _bytes = new(); + /// + /// 剪贴板数据类型列表 + /// + static readonly List _formats = new(); + + /// + /// 遍历剪贴板保存内容 + /// + /// true成功,false失败 + public static bool SaveClip() + { + bool result = ClipTool.OpenClipboardTask(false, free => { + _bytes.Clear(); + _formats.Clear(); + + uint cf = 0; + while (true) + { + cf = ClipTool.EnumClipboardFormats(cf);// 枚举剪贴板所有数据类型 + if (cf == 0) + break; + + _formats.Add(cf); + IntPtr clipTypeData = ClipTool.GetClipboardData(cf); + var locked = WindowsAPI.GlobalLockTask(clipTypeData, prt => { + uint size = WindowsAPI.GlobalSize(clipTypeData); + if (size > 0) + { + var buffer = new byte[size]; + Marshal.Copy(prt, buffer, 0, buffer.Length);// 将剪贴板数据保存到自定义字节数组 + _bytes.Add(buffer); + } + }); + } + }); + if (result) + result = _formats.Count > 0; + return result; + } + + /// + /// 恢复保存的数据 + /// + /// true成功,false失败 + public static bool RestoreClip() + { + if (_formats.Count <= 0) + return false; + + bool result = ClipTool.OpenClipboardTask(true, free => { + for (int i = 0; i < _formats.Count; i++) + { + int size = _bytes[i].Length; + IntPtr structPtr = Marshal.AllocHGlobal(size); + if (size > 0) + { + Marshal.Copy(_bytes[i], 0, structPtr, size); + ClipTool.SetClipboardData(_formats[i], structPtr); + } + } + }); + + if (result) + result = _formats.Count > 0; + return result; + } +} +#endif + + + +/// +/// 剪贴板的CF,也就是它的key +/// +public enum ClipboardFormat : uint +{ + /// + /// Text format. Each line ends with a carriage return/linefeed (CR-LF) combination. A null character signals + /// the end of the data. Use this format for ANSI text. + /// + CF_TEXT = 1, + + /// + /// A handle to a bitmap (HBITMAP). + /// + CF_BITMAP = 2, + + /// + /// Handle to a metafile picture format as defined by the METAFILEPICT structure. When passing a + /// CF_METAFILEPICT handle by means of DDE, the application responsible for deleting hMem should + /// also free the metafile referred to by the CF_METAFILEPICT handle. + /// + CF_METAFILEPICT = 3, + + /// + /// Microsoft Symbolic Link (SYLK) format. + /// + CF_SYLK = 4, + + /// + /// Software Arts' Data Interchange Format. + /// + CF_DIF = 5, + + /// + /// Tagged-image file format. + /// + CF_TIFF = 6, + + /// + /// Text format containing characters in the OEM character set. Each line ends with a carriage return/linefeed + /// (CR-LF) combination. A null character signals the end of the data. + /// + CF_OEMTEXT = 7, + + /// + /// A memory object containing a BITMAPINFO structure followed by the bitmap bits. + /// + CF_DIB = 8, + + /// + /// Handle to a color palette. Whenever an application places data in the clipboard that depends on or assumes + /// a color palette, it should place the palette on the clipboard as well. If the clipboard contains data in + /// the (logical color palette) format, the application should use the + /// SelectPalette and RealizePalette functions to realize (compare) any other data in the + /// clipboard against that logical palette. When displaying clipboard data, the clipboard always uses as its + /// current palette any object on the clipboard that is in the CF_PALETTE format. + /// + CF_PALETTE = 9, + + /// + /// Data for the pen extensions to the Microsoft Windows for Pen Computing. + /// + CF_PENDATA = 10, + + /// + /// Represents audio data more complex than can be represented in a CF_WAVE standard wave format. + /// + CF_RIFF = 11, + + /// + /// Represents audio data in one of the standard wave formats, such as 11 kHz or 22 kHz PCM. + /// + CF_WAVE = 12, + + /// + /// Unicode text format. Each line ends with a carriage return/linefeed (CR-LF) combination. A null character + /// signals the end of the data. + /// + CF_UNICODETEXT = 13, + + /// + /// A handle to an enhanced metafile (HENHMETAFILE). + /// + CF_ENHMETAFILE = 14, + + /// + /// A handle to type HDROP that identifies a list of files. An application can retrieve information + /// about the files by passing the handle to the DragQueryFile function. + /// + CF_HDROP = 15, + + /// + /// The data is a handle to the locale identifier associated with text in the clipboard. When you close the + /// clipboard, if it contains CF_TEXT data but no CF_LOCALE data, the system automatically sets + /// the CF_LOCALE format to the current input language. You can use the CF_LOCALE format to + /// associate a different locale with the clipboard text. + /// An application that pastes text from the clipboard can retrieve this format to determine which character + /// set was used to generate the text. + /// Note that the clipboard does not support plain text in multiple character sets. To achieve this, use a + /// formatted text data type such as RTF instead. + /// The system uses the code page associated with CF_LOCALE to implicitly convert from + /// to . Therefore, the correct code page table is used for + /// the conversion. + /// + CF_LOCALE = 16, + + /// + /// A memory object containing a BITMAPV5HEADER structure followed by the bitmap color space + /// information and the bitmap bits. + /// + CF_DIBV5 = 17, + + /// + /// Owner-display format. The clipboard owner must display and update the clipboard viewer window, and receive + /// the , , + /// , , and + /// messages. The hMem parameter must be null. + /// + CF_OWNERDISPLAY = 0x0080, + + /// + /// Text display format associated with a private format. The hMem parameter must be a handle to data + /// that can be displayed in text format in lieu of the privately formatted data. + /// + CF_DSPTEXT = 0x0081, + + /// + /// Bitmap display format associated with a private format. The hMem parameter must be a handle to + /// data that can be displayed in bitmap format in lieu of the privately formatted data. + /// + CF_DSPBITMAP = 0x0082, + + /// + /// Metafile-picture display format associated with a private format. The hMem parameter must be a + /// handle to data that can be displayed in metafile-picture format in lieu of the privately formatted data. + /// + CF_DSPMETAFILEPICT = 0x0083, + + /// + /// Enhanced metafile display format associated with a private format. The hMem parameter must be a + /// handle to data that can be displayed in enhanced metafile format in lieu of the privately formatted data. + /// + CF_DSPENHMETAFILE = 0x008E, + + /// + /// Start of a range of integer values for application-defined GDI object clipboard formats. The end of the + /// range is . Handles associated with clipboard formats in this range are not + /// automatically deleted using the GlobalFree function when the clipboard is emptied. Also, when using + /// values in this range, the hMem parameter is not a handle to a GDI object, but is a handle allocated + /// by the GlobalAlloc function with the GMEM_MOVEABLE flag. + /// + CF_GDIOBJFIRST = 0x0300, + + /// + /// See . + /// + CF_GDIOBJLAST = 0x03FF, + + /// + /// Start of a range of integer values for private clipboard formats. The range ends with + /// . Handles associated with private clipboard formats are not freed + /// automatically, the clipboard owner must free such handles, typically in response to the + /// message. + /// + CF_PRIVATEFIRST = 0x0200, + + /// + /// See . + /// + CF_PRIVATELAST = 0x02FF, +} + +#if true2 +// arx剪贴板头文件的枚举 +enum eClipInfoFlags +{ + kbDragGeometry = 0x01, +}; + +enum eXrefType +{ + kXrefTypeAttach = 1, + kXrefTypeOverlay = 2 +}; + +enum eExpandedClipDataTypes +{ + kDcPlotStyles = 1, + kDcXrefs = 2, + kDcLayouts = 3, + kDcBlocks = 4, + kDcLayers = 5, + kDcDrawings = 6, + kDcLinetypes = 7, + kDcTextStyles = 8, + kDcDimStyles = 9, + kDcBlocksWithAttdef = 10, + //#ifdef ADCHATCH + kDcHatches = 11, + //#endif + kTpXrefs = 12, + kTpImages = 13, + kTpTable = 14, + kDcTableStyles = 15, + kDcMultileaderStyles = 16, + kDcVisualStyles = 17, + kDcSectionViewStyles = 18, + kDcDetailViewStyles = 19, +}; +#endif \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/ExtensionMethod/BulgeVertexWidth.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/BulgeVertexWidth.cs new file mode 100644 index 0000000000000000000000000000000000000000..1f9e08d9d4758a1128add30d674d7314d7b156e9 --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/BulgeVertexWidth.cs @@ -0,0 +1,85 @@ +namespace IFoxCAD.Cad; + +/// +/// 多段线的顶点,凸度,头宽,尾宽 +/// +[Serializable] +public class BulgeVertexWidth +{ + /// + /// 顶点X + /// + public double X; + /// + /// 顶点Y + /// + public double Y; + /// + /// 凸度 + /// + public double Bulge; + /// + /// 头宽 + /// + public double StartWidth; + /// + /// 尾宽 + /// + public double EndWidth; + + public Point2d Vertex => new(X, Y); + + public BulgeVertexWidth() { } + + /// + /// 多段线的顶点,凸度,头宽,尾宽 + /// + public BulgeVertexWidth(double vertex_X, double vertex_Y, + double bulge = 0, + double startWidth = 0, + double endWidth = 0) + { + X = vertex_X; + Y = vertex_Y; + Bulge = bulge; + StartWidth = startWidth; + EndWidth = endWidth; + } + + /// + /// 多段线的顶点,凸度,头宽,尾宽 + /// + public BulgeVertexWidth(Point2d vertex, + double bulge = 0, + double startWidth = 0, + double endWidth = 0) + : this(vertex.X, vertex.Y, bulge, startWidth, endWidth) + { } + + /// + /// 多段线的顶点,凸度,头宽,尾宽 + /// + public BulgeVertexWidth(BulgeVertex bv) + : this(bv.Vertex.X, bv.Vertex.Y, bv.Bulge) + { } + + /// + /// 多段线的顶点,凸度,头宽,尾宽 + /// + /// 多段线 + /// 子段编号 + public BulgeVertexWidth(Polyline pl, int index) + { + var pt = pl.GetPoint2dAt(index);// 这里可以3d + X = pt.X; + Y = pt.Y; + Bulge = pl.GetBulgeAt(index); + StartWidth = pl.GetStartWidthAt(index); + EndWidth = pl.GetEndWidthAt(index); + } + + public BulgeVertex ToBulgeVertex() + { + return new BulgeVertex(Vertex, Bulge); + } +} diff --git a/src/CAD/IFox.CAD.Shared/ExtensionMethod/CollectionEx.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/CollectionEx.cs new file mode 100644 index 0000000000000000000000000000000000000000..acd120a24ecd01bf4636097749cc76227f41a602 --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/CollectionEx.cs @@ -0,0 +1,289 @@ +using System.ComponentModel; +using System.Xml.Linq; +using static System.Windows.Forms.AxHost; + +namespace IFoxCAD.Cad; + +/// +/// 集合扩展类 +/// +public static class CollectionEx +{ + /// + /// 对象id迭代器转换为集合 + /// + /// 对象id的迭代器 + /// 对象id集合,记得释放 + [System.Diagnostics.DebuggerStepThrough] + public static ObjectIdCollection ToCollection(this IEnumerable ids) + { + return new ObjectIdCollection(ids.ToArray()); + } + + /// + /// 实体迭代器转换为集合 + /// + /// 对象类型 + /// 实体对象的迭代器 + /// 实体集合,记得释放 + [System.Diagnostics.DebuggerStepThrough] + public static DBObjectCollection ToCollection(this IEnumerable objs) where T : DBObject + { + DBObjectCollection objCol = new(); + foreach (T obj in objs) + objCol.Add(obj); + return objCol; + } + + /// + /// double 数值迭代器转换为 double 数值集合 + /// + /// double 数值迭代器 + /// 数值集合,它没有Dispose + [System.Diagnostics.DebuggerStepThrough] + public static DoubleCollection ToCollection(this IEnumerable doubles) + { + return new DoubleCollection(doubles.ToArray()); + } + + /// + /// 二维点迭代器转换为二维点集合 + /// + /// 二维点迭代器 + /// 二维点集合,!acad记得释放 + [System.Diagnostics.DebuggerStepThrough] + public static Point2dCollection ToCollection(this IEnumerable pts) + { + return new Point2dCollection(pts.ToArray()); + } + + /// + /// 三维点迭代器转换为三维点集合 + /// + /// 三维点迭代器 + /// 三维点集合,记得释放 + [System.Diagnostics.DebuggerStepThrough] + public static Point3dCollection ToCollection(this IEnumerable pts) + { + return new Point3dCollection(pts.ToArray()); + } + + /// + /// 对象id集合转换为对象id列表 + /// + /// 对象id集合 + /// 对象id列表 + [System.Diagnostics.DebuggerStepThrough] + public static List ToList(this ObjectIdCollection ids) + { + return ids.Cast().ToList(); + } + + + /// + /// 遍历集合,执行委托 + /// + /// 集合值的类型 + /// 集合 + /// 委托 + [System.Diagnostics.DebuggerStepThrough] //[DebuggerHidden] 两个特性差不多 + public static void ForEach(this IEnumerable source, Action action) + { + // 这里不要嵌套调用ForEach委托, + // 因为这样可以在调用函数上断点直接跑Action内,不会进入此处(除了cad之外); + // 而cad很奇怪,只能用预处理方式避免 + // 嵌套调用ForEach委托: + // source.ForEach((a, _, _) => { + // action.Invoke(a); + // }); + + foreach (var element in source) + action.Invoke(element); + } + + /// + /// 遍历集合,执行委托(允许循环中断) + /// + /// 集合值的类型 + /// 集合 + /// 委托 + [System.Diagnostics.DebuggerStepThrough] + public static void ForEach(this IEnumerable source, Action action) + { + // 这里不要嵌套调用ForEach委托, + // 因为这样可以在调用函数上断点直接跑Action内,不会进入此处(除了cad之外); + // 而cad很奇怪,只能用预处理方式避免 + // 嵌套调用ForEach委托: + // source.ForEach((a, b, _) => { + // action.Invoke(a, b); + // }); + + LoopState state = new();/*这种方式比Action改Func更友好*/ + foreach (var element in source) + { + action.Invoke(element, state); + if (!state.IsRun) + break; + } + } + + /// + /// 遍历集合,执行委托(允许循环中断,输出索引值) + /// + /// 集合值的类型 + /// 集合 + /// 委托 + [System.Diagnostics.DebuggerStepThrough] + public static void ForEach(this IEnumerable source, Action action) + { + int i = 0; + LoopState state = new();/*这种方式比Action改Func更友好*/ + foreach (var element in source) + { + action.Invoke(element, state, i); + if (!state.IsRun) + break; + i++; + } + } + + + #region 关键字集合 + public enum KeywordName + { + GlobalName, + LocalName, + DisplayName, + } + + /// + /// 含有关键字 + /// + /// 关键字集合 + /// 关键字 + /// 关键字容器字段名 + /// true含有 + [System.Diagnostics.DebuggerStepThrough] + public static bool Contains(this KeywordCollection collection, string name, + KeywordName keywordName = KeywordName.GlobalName) + { + bool contains = false; + switch (keywordName) + { + case KeywordName.GlobalName: + for (int i = 0; i < collection.Count; i++) + { +#if gcad + var item = collection.get_Item(i); +#else + var item = collection[i]; +#endif + if (item.GlobalName == name) + { + contains = true; + break; + } + } + break; + case KeywordName.LocalName: + for (int i = 0; i < collection.Count; i++) + { +#if gcad + var item = collection.get_Item(i); +#else + var item = collection[i]; +#endif + if (item.LocalName == name) + { + contains = true; + break; + } + } + break; + case KeywordName.DisplayName: + for (int i = 0; i < collection.Count; i++) + { +#if gcad + var item = collection.get_Item(i); +#else + var item = collection[i]; +#endif + if (item.DisplayName == name) + { + contains = true; + break; + } + } + break; + } + return contains; + } + + /// + /// 获取词典, + /// KeywordCollection是允许重复关键字的,没有哈希索引,在多次判断时候会遍历多次O(n),所以生成一个词典进行O(1) + /// + /// + /// + [System.Diagnostics.DebuggerStepThrough] + public static Dictionary GetDict(this KeywordCollection collection) + { + Dictionary map = new(); + for (int i = 0; i < collection.Count; i++) + { +#if gcad + var item = collection.get_Item(i); +#else + var item = collection[i]; +#endif + map.Add(item.GlobalName, item.DisplayName); + } + return map; + } + #endregion + + + #region IdMapping + /// + /// 旧块名 + /// + /// + /// + [System.Diagnostics.DebuggerStepThrough] + public static List GetKeys(this IdMapping idmap) + { + List ids = new(); + foreach (IdPair item in idmap) + ids.Add(item.Key); + return ids; + } + + /// + /// 新块名 + /// + /// + /// + [System.Diagnostics.DebuggerStepThrough] + public static List GetValues(this IdMapping idmap) + { + List ids = new(); + foreach (IdPair item in idmap) + ids.Add(item.Value); + return ids; + } + + /// + /// 转换为词典 + /// + /// + /// + [System.Diagnostics.DebuggerStepThrough] + public static Dictionary ToDictionary(this IdMapping mapping) + { + var keyValuePairs = new Dictionary(); + foreach (IdPair item in mapping) + keyValuePairs.Add(item.Key, item.Value); + return keyValuePairs; + } + #endregion +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/ExtensionMethod/Curve2dEx.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/Curve2dEx.cs similarity index 86% rename from src/IFoxCAD.Cad/ExtensionMethod/Curve2dEx.cs rename to src/CAD/IFox.CAD.Shared/ExtensionMethod/Curve2dEx.cs index 5f7a8f472f605717d2de92c1f94d6b1d11a4f4a9..07db654fee2c9e43e61fae75d40249070a5e0f3b 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/Curve2dEx.cs +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/Curve2dEx.cs @@ -3,9 +3,10 @@ /// /// 二维解析类曲线转换为二维实体曲线扩展类 /// - public static class Curve2dEx { + internal static readonly Plane _planeCache = new(); + #region Curve2d /// @@ -14,7 +15,7 @@ public static class Curve2dEx /// Ge2d曲线 /// 曲线转换矩阵 /// Db曲线 - public static Curve ToCurve(this Curve2d curve, Matrix3d mat) + public static Curve? ToCurve(this Curve2d curve, Matrix3d mat) { return curve switch { @@ -65,13 +66,9 @@ public static Curve ToCurve(this CircularArc2d ca2d, Matrix3d mat) public static Curve ToCurve(this CircularArc2d ca2d) { if (ca2d.IsClosed()) - { return ToCircle(ca2d); - } else - { return ToArc(ca2d); - } } /// @@ -83,8 +80,8 @@ public static Circle ToCircle(this CircularArc2d c2d) { return new Circle( - new Point3d(new Plane(), c2d.Center), - new Vector3d(0, 0, 1), + new Point3d(_planeCache, c2d.Center), + Vector3d.ZAxis, c2d.Radius); } @@ -111,7 +108,7 @@ public static Arc ToArc(this CircularArc2d a2d) return new Arc( - new Point3d(new Plane(), a2d.Center), + new Point3d(_planeCache, a2d.Center), Vector3d.ZAxis, a2d.Radius, startangle, @@ -122,7 +119,7 @@ public static Arc ToArc(this CircularArc2d a2d) #region EllipticalArc2d - //椭圆弧 + // 椭圆弧 /// /// 将二维解析类椭圆弧转换为实体椭圆弧,然后进行矩阵变换 /// @@ -143,12 +140,10 @@ public static Ellipse ToCurve(this EllipticalArc2d ea2d, Matrix3d mat) /// 实体椭圆弧 public static Ellipse ToCurve(this EllipticalArc2d ea2d) { - Plane plane = new(); - Ellipse ell = - new( - new Point3d(plane, ea2d.Center), - new Vector3d(0, 0, 1), - new Vector3d(plane, ea2d.MajorAxis) * ea2d.MajorRadius, + Ellipse ell = new( + new Point3d(_planeCache, ea2d.Center), + Vector3d.ZAxis, + new Vector3d(_planeCache, ea2d.MajorAxis) * ea2d.MajorRadius, ea2d.MinorRadius / ea2d.MajorRadius, 0, Math.PI * 2); @@ -179,13 +174,11 @@ public static Ellipse ToCurve(this EllipticalArc2d ea2d) /// 实体类构造线 public static Xline ToCurve(this Line2d line2d) { - Plane plane = new(); - return - new Xline - { - BasePoint = new Point3d(plane, line2d.PointOnLine), - SecondPoint = new Point3d(plane, line2d.PointOnLine + line2d.Direction) - }; + return new Xline + { + BasePoint = new Point3d(_planeCache, line2d.PointOnLine), + SecondPoint = new Point3d(_planeCache, line2d.PointOnLine + line2d.Direction) + }; } /// @@ -242,12 +235,10 @@ public static Line ToCurve(this LineSegment2d ls2d, Matrix3d mat) /// 实体类直线 public static Line ToCurve(this LineSegment2d ls2d) { - Plane plane = new(); return new Line( - new Point3d(plane, ls2d.StartPoint), - new Point3d(plane, ls2d.EndPoint)); - + new Point3d(_planeCache, ls2d.StartPoint), + new Point3d(_planeCache, ls2d.EndPoint)); } #endregion LineSegment2d @@ -274,25 +265,17 @@ public static Spline ToCurve(this NurbCurve2d nc2d, Matrix3d mat) /// 实体类样条曲线 public static Spline ToCurve(this NurbCurve2d nc2d) { - int i; - Plane plane = new(); - Point3dCollection ctlpnts = new(); - for (i = 0; i < nc2d.NumControlPoints; i++) - { - ctlpnts.Add(new Point3d(plane, nc2d.GetControlPointAt(i))); - } + using Point3dCollection ctlpnts = new(); + for (int i = 0; i < nc2d.NumControlPoints; i++) + ctlpnts.Add(new Point3d(_planeCache, nc2d.GetControlPointAt(i))); DoubleCollection knots = new(); - foreach (double knot in nc2d.Knots) - { - knots.Add(knot); - } + for (int i = 0; i < nc2d.Knots.Count; i++) + knots.Add(nc2d.Knots[i]); DoubleCollection weights = new(); - for (i = 0; i < nc2d.NumWeights; i++) - { + for (int i = 0; i < nc2d.NumWeights; i++) weights.Add(nc2d.GetWeightAt(i)); - } NurbCurve2dData ncdata = nc2d.DefinitionData; @@ -310,4 +293,4 @@ public static Spline ToCurve(this NurbCurve2d nc2d) } #endregion NurbCurve2d -} +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/ExtensionMethod/Curve3dEx.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/Curve3dEx.cs similarity index 81% rename from src/IFoxCAD.Cad/ExtensionMethod/Curve3dEx.cs rename to src/CAD/IFox.CAD.Shared/ExtensionMethod/Curve3dEx.cs index 7e6f4a9e1718e602ae202392654968cc453b77fc..c12275b2a5c98ae72a13b00c4b2590be063f33f1 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/Curve3dEx.cs +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/Curve3dEx.cs @@ -1,4 +1,6 @@ -namespace IFoxCAD.Cad; +using System.Runtime.CompilerServices; + +namespace IFoxCAD.Cad; /// /// 三维解析类曲线转换为三维实体曲线扩展类 @@ -12,6 +14,7 @@ public static class Curve3dEx /// 第一个数 /// 第二个数 /// 两个数的差值的绝对值小于容差返回 ,反之返回 + [MethodImpl] public static bool IsEqualPoint(this Tolerance tol, double d1, double d2) { return Math.Abs(d1 - d2) < tol.EqualPoint; @@ -24,15 +27,14 @@ public static bool IsEqualPoint(this Tolerance tol, double d1, double d2) /// /// 三维解析类曲线 /// 曲线参数的列表 - public static List GetParamsAtIntersectionPoints(this Curve3d c3d) + public static List GetParamsAtIntersectionPoints(this Curve3d c3d, bool sort = true) { CurveCurveIntersector3d cci = new(c3d, c3d, Vector3d.ZAxis); List pars = new(); for (int i = 0; i < cci.NumberOfIntersectionPoints; i++) - { pars.AddRange(cci.GetIntersectionParameters(i)); - } - pars.Sort(); + if (sort) + pars.Sort(); return pars; } @@ -90,7 +92,7 @@ public static Curve3d GetSubCurve(this Curve3d curve, double from, double to) /// /// 三维解析类曲线 /// 三维实体类曲线 - public static Curve ToCurve(this Curve3d curve) + public static Curve? ToCurve(this Curve3d curve) { return curve switch { @@ -110,7 +112,7 @@ public static Curve ToCurve(this Curve3d curve) /// /// 三维解析类曲线 /// 三维解析类Nurb曲线 - public static NurbCurve3d ToNurbCurve3d(this Curve3d curve) + public static NurbCurve3d? ToNurbCurve3d(this Curve3d curve) { return curve switch { @@ -147,68 +149,69 @@ public static bool IsCircular(this Curve3d curve) /// 三维复合曲线 /// 曲线参数列表 /// 三维复合曲线列表 - public static List GetSplitCurves(this CompositeCurve3d c3d, List pars) + public static List? GetSplitCurves(this CompositeCurve3d c3d, List pars) { - Interval inter = c3d.GetInterval(); - Curve3d[] c3ds = c3d.GetCurves(); - pars.Sort(); - for (int i = pars.Count - 1; i > 0; i--) + // 曲线参数剔除重复的 + if (pars.Count > 0) { - if (Tolerance.Global.IsEqualPoint(pars[i], pars[i - 1])) - pars.RemoveAt(i); + pars.Sort(); + for (int i = pars.Count - 1; i > 0; i--) + if (Tolerance.Global.IsEqualPoint(pars[i], pars[i - 1])) + pars.RemoveAt(i); } - if (pars.Count == 0) - return new List(); + return null; + + // 这个是曲线参数类 + var inter = c3d.GetInterval(); + // 曲线们 + var c3ds = c3d.GetCurves(); if (c3ds.Length == 1 && c3ds[0].IsClosed()) { - //闭合曲线不允许打断于一点 - if (pars.Count > 1) + // 闭合曲线不允许打断于一点 + if (pars.Count < 2) + return null; + + // 如果包含起点 + if (Tolerance.Global.IsEqualPoint(pars[0], inter.LowerBound)) { - //如果包含起点 - if (Tolerance.Global.IsEqualPoint(pars[0], inter.LowerBound)) - { - pars[0] = inter.LowerBound; - //又包含终点,去除终点 - if (Tolerance.Global.IsEqualPoint(pars[pars.Count - 1], inter.UpperBound)) - { - pars.RemoveAt(pars.Count - 1); - if (pars.Count == 1) - return new List(); - } - } - else if (Tolerance.Global.IsEqualPoint(pars[pars.Count - 1], inter.UpperBound)) + pars[0] = inter.LowerBound; + // 又包含终点,去除终点 + if (Tolerance.Global.IsEqualPoint(pars[^1], inter.UpperBound)) { - pars[pars.Count - 1] = inter.UpperBound; + pars.RemoveAt(pars.Count - 1); + if (pars.Count == 1) + return null; } - //加入第一点以支持反向打断 - pars.Add(pars[0]); } - else + else if (Tolerance.Global.IsEqualPoint(pars[^1], inter.UpperBound)) { - return new List(); + pars[^1] = inter.UpperBound; } + // 加入第一点以支持反向打断 + pars.Add(pars[0]); } else { - //非闭合曲线加入起点和终点 + // 非闭合曲线加入起点和终点 if (Tolerance.Global.IsEqualPoint(pars[0], inter.LowerBound)) pars[0] = inter.LowerBound; else pars.Insert(0, inter.LowerBound); - if (Tolerance.Global.IsEqualPoint(pars[pars.Count - 1], inter.UpperBound)) - pars[pars.Count - 1] = inter.UpperBound; + if (Tolerance.Global.IsEqualPoint(pars[^1], inter.UpperBound)) + pars[^1] = inter.UpperBound; else pars.Add(inter.UpperBound); } List curves = new(); + List cc3ds = new(); for (int i = 0; i < pars.Count - 1; i++) { - List cc3ds = new(); - //复合曲线参数转换到包含曲线参数 - CompositeParameter cp1 = c3d.GlobalToLocalParameter(pars[i]); - CompositeParameter cp2 = c3d.GlobalToLocalParameter(pars[i + 1]); + cc3ds.Clear(); + // 复合曲线参数转换到包含曲线参数 + var cp1 = c3d.GlobalToLocalParameter(pars[i]); + var cp2 = c3d.GlobalToLocalParameter(pars[i + 1]); if (cp1.SegmentIndex == cp2.SegmentIndex) { cc3ds.Add( @@ -223,27 +226,28 @@ public static List GetSplitCurves(this CompositeCurve3d c3d, L c3ds[cp1.SegmentIndex].GetSubCurve( cp1.LocalParameter, inter.UpperBound)); + for (int j = cp1.SegmentIndex + 1; j < cp2.SegmentIndex; j++) - { cc3ds.Add((Curve3d)c3ds[j].Clone()); - } + inter = c3ds[cp2.SegmentIndex].GetInterval(); cc3ds.Add( c3ds[cp2.SegmentIndex].GetSubCurve( inter.LowerBound, cp2.LocalParameter)); } - curves.Add(new CompositeCurve3d(cc3ds.ToArray())); - } - - if (c3d.IsClosed() && c3ds.Length > 1) - { - var cs = curves[curves.Count - 1].GetCurves().ToList(); - cs.AddRange(curves[0].GetCurves()); - curves[curves.Count - 1] = new CompositeCurve3d(cs.ToArray()); - curves.RemoveAt(0); + curves.Add(new(cc3ds.ToArray())); } + // 封闭多段线 口口 并排形状,第二个口切割不成功,注释下面就成功了 + //if (c3d.IsClosed() && c3ds.Length > 1) + //{ + // var cus1 = curves[^1].GetCurves(); + // var cus2 = curves[0].GetCurves(); + // var cs = cus1.Combine2(cus2); + // curves[^1] = new(cs); + // curves.RemoveAt(0); + //} return curves; } @@ -252,41 +256,34 @@ public static List GetSplitCurves(this CompositeCurve3d c3d, L /// /// 三维复合曲线 /// 实体曲线 - public static Curve ToCurve(this CompositeCurve3d curve) + public static Curve? ToCurve(this CompositeCurve3d curve) { Curve3d[] cs = curve.GetCurves(); if (cs.Length == 0) - { return null; - } - else if (cs.Length == 1) - { + if (cs.Length == 1) return ToCurve(cs[0]); - } - else - { - bool hasNurb = false; - foreach (var c in cs) - { - if (c is NurbCurve3d || c is EllipticalArc3d) - { - hasNurb = true; - break; - } - } - if (hasNurb) - { - NurbCurve3d nc3d = cs[0].ToNurbCurve3d(); - for (int i = 1; i < cs.Length; i++) - nc3d.JoinWith(cs[i].ToNurbCurve3d()); - return nc3d.ToCurve(); - } - else + bool hasNurb = false; + + for (int i = 0; i < cs.Length; i++) + { + var c = cs[i]; + if (c is NurbCurve3d || c is EllipticalArc3d) { - return ToPolyline(curve); + hasNurb = true; + break; } } + if (hasNurb) + { + var nc3d = cs[0].ToNurbCurve3d(); + for (int i = 1; i < cs.Length; i++) + nc3d?.JoinWith(cs[i].ToNurbCurve3d()); + return nc3d?.ToCurve(); + } + + return ToPolyline(curve); } /// @@ -296,10 +293,10 @@ public static Curve ToCurve(this CompositeCurve3d curve) /// 实体类多段线 public static Polyline ToPolyline(this CompositeCurve3d cc3d) { - Polyline pl = new() - { - Elevation = cc3d.StartPoint[2] - }; + Polyline pl = new(); + pl.SetDatabaseDefaults(); + pl.Elevation = cc3d.StartPoint[2]; + Plane plane = pl.GetPlane(); Point2d endver = Point2d.Origin; int i = 0; @@ -408,7 +405,7 @@ public static Circle ToCircle(this CircularArc3d ca3d) => /// 实体圆弧 public static Arc ToArc(this CircularArc3d ca3d) { - //必须新建,而不能直接使用GetPlane()获取 + // 必须新建,而不能直接使用GetPlane()获取 double angle = ca3d.ReferenceVector.AngleOnPlane(new Plane(ca3d.Center, ca3d.Normal)); return new Arc(ca3d.Center, ca3d.Normal, ca3d.Radius, ca3d.StartAngle + angle, ca3d.EndAngle + angle); } @@ -466,7 +463,7 @@ public static Ellipse ToCurve(this EllipticalArc3d ea3d) ea3d.MinorRadius / ea3d.MajorRadius, 0, Math.PI * 2); - //Ge椭圆角度就是Db椭圆的参数 + // Ge椭圆角度就是Db椭圆的参数 if (!ea3d.IsClosed()) { ell.StartAngle = ell.GetAngleAtParameter(ea3d.StartAngle); @@ -540,12 +537,10 @@ public static Spline ToCurve(this NurbCurve3d nc3d) /// 实体类三维多段线 public static Polyline3d ToCurve(this PolylineCurve3d pl3d) { - Point3dCollection pnts = new(); + using Point3dCollection pnts = new(); for (int i = 0; i < pl3d.NumberOfControlPoints; i++) - { pnts.Add(pl3d.ControlPointAt(i)); - } bool closed = false; int n = pnts.Count - 1; @@ -558,4 +553,4 @@ public static Polyline3d ToCurve(this PolylineCurve3d pl3d) } #endregion PolylineCurve3d -} +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/ExtensionMethod/CurveEx.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/CurveEx.cs similarity index 48% rename from src/IFoxCAD.Cad/ExtensionMethod/CurveEx.cs rename to src/CAD/IFox.CAD.Shared/ExtensionMethod/CurveEx.cs index a8fa4351d0011354541d0aad5f49bbd13b0513aa..bfca634af56a45f77ea2c10de9f2b389c60bcb06 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/CurveEx.cs +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/CurveEx.cs @@ -1,6 +1,7 @@ -namespace IFoxCAD.Cad; -using IFoxCAD.Basal; + +namespace IFoxCAD.Cad; + /// /// 实体类曲线扩展类 /// @@ -13,8 +14,7 @@ public static class CurveEx /// 长度 public static double GetLength(this Curve curve) { - return - curve.GetDistanceAtParameter(curve.EndParam); + return curve.GetDistanceAtParameter(curve.EndParam); } /// @@ -25,6 +25,9 @@ public static double GetLength(this Curve curve) /// 打断后曲线的集合 public static IEnumerable GetSplitCurves(this Curve curve, IEnumerable pars) { + //if (pars is null) + // throw new ArgumentNullException(nameof(pars)); + pars.NotNull(nameof(pars)); return curve .GetSplitCurves(new DoubleCollection(pars.ToArray())) @@ -35,431 +38,168 @@ public static IEnumerable GetSplitCurves(this Curve curve, IEnumerable /// 曲线 - /// 打断点表 + /// 打断参数表 + /// 对参数表是否进行排序 + /// + /// 按参数值升序排序
+ /// 不排序,默认值 + ///
+ /// /// 打断后曲线的集合 - public static IEnumerable GetSplitCurves(this Curve curve, IEnumerable points) + public static IEnumerable GetSplitCurves(this Curve curve, IEnumerable pars, bool isOrder = false) { + //if (pars is null) + // throw new ArgumentNullException(nameof(pars)); + pars.NotNull(nameof(pars)); + if (isOrder) + pars = pars.OrderBy(x => x); + return curve - .GetSplitCurves(new Point3dCollection(points.ToArray())) + .GetSplitCurves(new DoubleCollection(pars.ToArray())) .Cast(); } - private struct EdgeItem : IEquatable + /// + /// 获取分割曲线集合 + /// + /// 曲线 + /// 打断点表 + /// 打断后曲线的集合 + public static IEnumerable GetSplitCurves(this Curve curve, IEnumerable points) { - public Edge Edge; - public bool Forward; - - public EdgeItem(Edge edge, bool forward) - { - Edge = edge; - Forward = forward; - } - - public CompositeCurve3d GetCurve() - { - CompositeCurve3d cc3d = Edge.Curve; - if (Forward) - { - return cc3d; - } - else - { - cc3d = cc3d.Clone() as CompositeCurve3d; - return cc3d.GetReverseParameterCurve() as CompositeCurve3d; - } - } - - public bool Equals(EdgeItem other) - { - return - Edge == other.Edge && - Forward == other.Forward; - } - - public void FindRegion(List edges, List> regions) - { - var region = new LoopList(); - var edgeItem = this; - region.Add(edgeItem); - var edgeItem2 = this.GetNext(edges); - if (edgeItem2.Edge != null) - { - bool hasList = false; - foreach (var edgeList2 in regions) - { - var node = edgeList2.GetNode(e => e.Equals(edgeItem)); - if (node != null) - { - if (node.Next.Value.Equals(edgeItem2)) - { - hasList = true; - break; - } - } - } - if (!hasList) - { - while (edgeItem2.Edge != null) - { - if (edgeItem2.Edge == edgeItem.Edge) - break; - region.Add(edgeItem2); - edgeItem2 = edgeItem2.GetNext(edges); - } - if (edgeItem2.Edge == edgeItem.Edge) - regions.Add(region); - } - } - } - - public EdgeItem GetNext(List edges) - { - Vector3d vec; - int next; - if (Forward) - { - vec = Edge.GetEndVector(); - next = Edge.EndIndex; - } - else - { - vec = Edge.GetStartVector(); - next = Edge.StartIndex; - } - - EdgeItem item = new(); - Vector3d vec2, vec3 = new(); - double angle = 0; - bool hasNext = false; - bool forward = false; - foreach (var edge in edges) - { - if (edge.IsNext(Edge, next, ref vec3, ref forward)) - { - if (hasNext) - { - var angle2 = vec.GetAngleTo(vec3, Vector3d.ZAxis); - if (angle2 < angle) - { - vec2 = vec3; - angle = angle2; - item.Edge = edge; - item.Forward = forward; - } - } - else - { - vec2 = vec3; - angle = vec.GetAngleTo(vec2, Vector3d.ZAxis); - item.Edge = edge; - item.Forward = forward; - hasNext = true; - } - } - } - return item; - } - - public override string ToString() - { - return - Forward ? - string.Format("{0}-{1}", Edge.StartIndex, Edge.EndIndex) : - string.Format("{0}-{1}", Edge.EndIndex, Edge.StartIndex); - } + //if (points is null) + // throw new ArgumentNullException(nameof(points)); + points.NotNull(nameof(points)); + using var pts = new Point3dCollection(points.ToArray()); + return curve.GetSplitCurves(pts).Cast(); } - private class Edge + /// + /// 获取分割曲线集合 + /// + /// 曲线 + /// 打断点表 + /// 对点表是否进行排序 + /// + /// 按参数值升序排序
+ /// 不排序,默认值 + ///
+ /// + /// 打断后曲线的集合 + public static IEnumerable GetSplitCurves(this Curve curve, IEnumerable points, bool isOrder = false) { - public CompositeCurve3d Curve; - public int StartIndex; - public int EndIndex; - - public Vector3d GetStartVector() - { - var inter = Curve.GetInterval(); - PointOnCurve3d poc = new(Curve, inter.LowerBound); - return poc.GetDerivative(1); - } - - public Vector3d GetEndVector() - { - var inter = Curve.GetInterval(); - PointOnCurve3d poc = new(Curve, inter.UpperBound); - return -poc.GetDerivative(1); - } - - public bool IsNext(Edge edge, int index, ref Vector3d vec, ref bool forward) - { - if (edge != this) - { - if (StartIndex == index) - { - vec = GetStartVector(); - forward = true; - return true; - } - else if (EndIndex == index) - { - vec = GetEndVector(); - forward = false; - return true; - } - } - return false; - } + //if (points is null) + // throw new ArgumentNullException(nameof(points)); + points.NotNull(nameof(points)); + if (isOrder) + points = points.OrderBy(point => { + var pt = curve.GetClosestPointTo(point, false); + return curve.GetParameterAtPoint(pt); + }); + + using Point3dCollection pts = new(points.ToArray()); + return curve.GetSplitCurves(pts).Cast(); } - public static List Topo(List curves) + /// + /// 获取曲线集所围成的封闭区域的曲线集,注意此函数不能处理平行边(两个点及两条线组成的闭合环) + /// + /// 曲线集合 + /// 所有的闭合环的曲线集合 + public static IEnumerable GetAllCycle(this IEnumerable curves) { - //首先按交点分解为Ge曲线集 - List geCurves = new(); - List> paramss = new(); + curves.NotNull(nameof(curves)); + // 新建图 + var graph = new Graph(); foreach (var curve in curves) { - var cc3d = curve.ToCompositeCurve3d(); - if (cc3d != null) - { - geCurves.Add(cc3d); - paramss.Add(new List()); - } +#if !gcad +#if NET35 + graph.AddEdge(curve.ToCurve3d()!); +#else + graph.AddEdge(curve.GetGeCurve()); +#endif +#else + graph.AddEdge(curve.ToCurve3d()!); +#endif } - - List edges = new(); - CurveCurveIntersector3d cci3d = new(); - List newCurves = new(); - - for (int i = 0; i < curves.Count; i++) - { - CompositeCurve3d gc1 = geCurves[i]; - List pars1 = paramss[i]; - for (int j = i; j < curves.Count; j++) - { - CompositeCurve3d gc2 = geCurves[j]; - List pars2 = paramss[j]; - - cci3d.Set(gc1, gc2, Vector3d.ZAxis); - - for (int k = 0; k < cci3d.NumberOfIntersectionPoints; k++) - { - double[] pars = cci3d.GetIntersectionParameters(k); - pars1.Add(pars[0]); - pars2.Add(pars[1]); - } - } - - if (pars1.Count > 0) - { - List c3ds = gc1.GetSplitCurves(pars1); - if (c3ds.Count > 0) - { - edges.AddRange( - c3ds.Select(c => new Edge { Curve = c })); - } - else if (gc1.IsClosed()) - { - newCurves.Add(gc1.ToCurve()); - } - else - { - edges.Add(new Edge { Curve = gc1 }); - } - } - else if (gc1.IsClosed()) - { - newCurves.Add(gc1.ToCurve()); - } - } - - //构建边的邻接表 - var knots = new List(); - var nums = new List(); - var closedEdges = new List(); - - foreach (var edge in edges) - { - if (edge.Curve.IsClosed()) - { - closedEdges.Add(edge); - } - else - { - if (knots.Contains(edge.Curve.StartPoint)) - { - edge.StartIndex = - knots.IndexOf(edge.Curve.StartPoint); - nums[edge.StartIndex]++; - } - else - { - knots.Add(edge.Curve.StartPoint); - nums.Add(1); - edge.StartIndex = knots.Count - 1; - } - - if (knots.Contains(edge.Curve.EndPoint)) - { - edge.EndIndex = - knots.IndexOf(edge.Curve.EndPoint); - nums[edge.EndIndex]++; - } - else - { - knots.Add(edge.Curve.EndPoint); - nums.Add(1); - edge.EndIndex = knots.Count - 1; - } - } - } - - newCurves.AddRange(closedEdges.Select(e => e.Curve.ToCurve())); - - edges = - edges - .Except(closedEdges) - .Where(e => nums[e.StartIndex] > 1 && nums[e.EndIndex] > 1) - .ToList(); - - foreach (var edge in edges.Except(closedEdges)) - { - if (nums[edge.StartIndex] == 1 || nums[edge.EndIndex] == 1) - { - if (nums[edge.StartIndex] == 1 && nums[edge.EndIndex] == 1) - { - nums[edge.StartIndex] = 0; - nums[edge.EndIndex] = 0; - } - else - { - int next = -1; - if (nums[edge.StartIndex] == 1) - { - nums[edge.StartIndex] = 0; - nums[next = edge.EndIndex]--; - } - else - { - nums[edge.EndIndex] = 0; - nums[next = edge.StartIndex]--; - } - } - } - } - - List> regions = new(); - foreach (var edge in edges) - { - var edgeItem = new EdgeItem(edge, true); - edgeItem.FindRegion(edges, regions); - edgeItem = new EdgeItem(edge, false); - edgeItem.FindRegion(edges, regions); - } - - for (int i = 0; i < regions.Count; i++) - { - for (int j = i + 1; j < regions.Count;) - { - bool eq = false; - if (regions[i].Count == regions[j].Count) - { - var node = regions[i].First; - var curve = node.Value.Edge.Curve; - var node2 = regions[j].GetNode(e => e.Edge.Curve == curve); - if (eq = node2 != null) - { - var b = node.Value.Forward; - var b2 = node2.Value.Forward; - for (int k = 1; k < regions[i].Count; k++) - { - node = node.GetNext(b); - node2 = node2.GetNext(b2); - if (node.Value.Edge.Curve != node2.Value.Edge.Curve) - { - eq = false; - break; - } - } - } - } - if (eq) - regions.RemoveAt(j); - else - j++; - } - } - - foreach (var region in regions) - { - var cs3ds = - region - .Select(e => e.GetCurve()) - .ToArray(); - newCurves.Add(new CompositeCurve3d(cs3ds.ToArray()).ToCurve()); + // 新建 dfs + var dfs = new DepthFirst(); + // 查询全部的 闭合环 + dfs.FindAll(graph); + // 遍历闭合环的列表,将每个闭合环转换为实体曲线 + var res = new List(); + foreach (var item in dfs.Curve3ds) + { + var curve = graph.GetCurves(item.ToList()).ToArray(); + var comcur = new CompositeCurve3d(curve).ToCurve(); + if (comcur is not null) + res.Add(comcur); } - - return newCurves; + return res; } - /// /// 曲线打断 /// /// 曲线列表 /// 打断后的曲线列表 - public static List BreakCurve(List curves) + public static List BreakCurve(this List curves) { - List geCurves = new(); - List> paramss = new(); + curves.NotNull(nameof(curves)); - foreach (var curve in curves) + var geCurves = new List(); // 存储曲线转换后的复合曲线 + var paramss = new List>(); // 存储每个曲线的交点参数值 + + for (int i = 0; i < curves.Count; i++) { - var cc3d = curve.ToCompositeCurve3d(); - if (cc3d != null) + var cc3d = curves[i].ToCompositeCurve3d(); + if (cc3d is not null) { geCurves.Add(cc3d); paramss.Add(new List()); } } - List oldCurves = new(); - List newCurves = new(); - CurveCurveIntersector3d cci3d = new(); + // var oldCurves = new List(); + var newCurves = new List(); + var cci3d = new CurveCurveIntersector3d(); for (int i = 0; i < curves.Count; i++) { - CompositeCurve3d gc1 = geCurves[i]; - List pars1 = paramss[i]; + var gc1 = geCurves[i]; + var pars1 = paramss[i]; // 引用 for (int j = i; j < curves.Count; j++) { - CompositeCurve3d gc2 = geCurves[j]; - List pars2 = paramss[j]; + var gc2 = geCurves[j]; + var pars2 = paramss[j]; // 引用 cci3d.Set(gc1, gc2, Vector3d.ZAxis); for (int k = 0; k < cci3d.NumberOfIntersectionPoints; k++) { - double[] pars = cci3d.GetIntersectionParameters(k); - pars1.Add(pars[0]); - pars2.Add(pars[1]); + var pars = cci3d.GetIntersectionParameters(k); + pars1.Add(pars[0]); // 引用修改会同步到源对象 + pars2.Add(pars[1]); // 引用修改会同步到源对象 } } if (pars1.Count > 0) { - List c3ds = gc1.GetSplitCurves(pars1); - if (c3ds.Count > 1) + var c3ds = gc1.GetSplitCurves(pars1); + if (c3ds is not null && c3ds.Count > 1) { - foreach (CompositeCurve3d c3d in c3ds) + foreach (var c3d in c3ds) { - Curve c = c3d.ToCurve(); - if (c != null) + var c3dCur = c3d.ToCurve(); + if (c3dCur is not null) { - c.SetPropertiesFrom(curves[i]); - newCurves.Add(c); + c3dCur.SetPropertiesFrom(curves[i]); + newCurves.Add(c3dCur); } } - oldCurves.Add(curves[i]); + // oldCurves.Add(curves[i]); } } } @@ -467,7 +207,7 @@ public static List BreakCurve(List curves) return newCurves; } - //转换DBCurve为GeCurved + // 转换DBCurve为GeCurved #region Curve @@ -476,8 +216,7 @@ public static List BreakCurve(List curves) ///
/// 曲线 /// ge曲线 - [Obsolete("请使用Cad自带的 GetGeCurve 函数!")] - public static Curve3d ToCurve3d(this Curve curve) + public static Curve3d? ToCurve3d(this Curve curve) { return curve switch { @@ -498,7 +237,7 @@ public static Curve3d ToCurve3d(this Curve curve) ///
/// 曲线 /// 复合曲线 - public static CompositeCurve3d ToCompositeCurve3d(this Curve curve) + public static CompositeCurve3d? ToCompositeCurve3d(this Curve curve) { return curve switch { @@ -508,7 +247,7 @@ public static CompositeCurve3d ToCompositeCurve3d(this Curve curve) Ellipse el => new CompositeCurve3d(new Curve3d[] { ToCurve3d(el) }), Polyline pl => new CompositeCurve3d(new Curve3d[] { ToCurve3d(pl) }), - Polyline2d pl2 => new CompositeCurve3d(new Curve3d[] { ToCurve3d(pl2) }), + Polyline2d pl2 => new CompositeCurve3d(new Curve3d[] { ToCurve3d(pl2)! }), Polyline3d pl3 => new CompositeCurve3d(new Curve3d[] { ToCurve3d(pl3) }), Spline sp => new CompositeCurve3d(new Curve3d[] { ToCurve3d(sp) }), _ => null @@ -520,7 +259,7 @@ public static CompositeCurve3d ToCompositeCurve3d(this Curve curve) ///
/// 曲线 /// Nurb曲线 - public static NurbCurve3d ToNurbCurve3d(this Curve curve) + public static NurbCurve3d? ToNurbCurve3d(this Curve curve) { return curve switch { @@ -728,23 +467,23 @@ public static NurbCurve3d ToCurve3d(this Spline spl) /// /// 二维多段线 /// 三维ge曲线 - public static Curve3d ToCurve3d(this Polyline2d pl2d) + public static Curve3d? ToCurve3d(this Polyline2d pl2d) { switch (pl2d.PolyType) { case Poly2dType.SimplePoly: case Poly2dType.FitCurvePoly: - Polyline pl = new(); - pl.ConvertFrom(pl2d, false); - return ToCurve3d(pl); - + Polyline pl = new(); + pl.SetDatabaseDefaults(); + pl.ConvertFrom(pl2d, false); + return ToCurve3d(pl); default: - return ToNurbCurve3d(pl2d); + return ToNurbCurve3d(pl2d); } - //Polyline pl = new Polyline(); - //pl.ConvertFrom(pl2d, false); - //return ToCurve3d(pl); + // Polyline pl = new Polyline(); + // pl.ConvertFrom(pl2d, false); + // return ToCurve3d(pl); } /// @@ -752,18 +491,19 @@ public static Curve3d ToCurve3d(this Polyline2d pl2d) /// /// 二维多段线 /// 三维Nurb曲线 - public static NurbCurve3d ToNurbCurve3d(this Polyline2d pl2d) + public static NurbCurve3d? ToNurbCurve3d(this Polyline2d pl2d) { switch (pl2d.PolyType) { case Poly2dType.SimplePoly: case Poly2dType.FitCurvePoly: - Polyline pl = new(); - pl.ConvertFrom(pl2d, false); - return ToNurbCurve3d(pl); + Polyline pl = new(); + pl.SetDatabaseDefaults(); + pl.ConvertFrom(pl2d, false); + return ToNurbCurve3d(pl); default: - return ToCurve3d(pl2d.Spline); + return ToCurve3d(pl2d.Spline); } } @@ -774,11 +514,9 @@ public static NurbCurve3d ToNurbCurve3d(this Polyline2d pl2d) /// 三维ge多段线 public static PolylineCurve3d ToPolylineCurve3d(this Polyline2d pl) { - Point3dCollection pnts = new(); + using Point3dCollection pnts = new(); foreach (Vertex2d ver in pl) - { pnts.Add(ver.Position); - } return new PolylineCurve3d(pnts); } @@ -817,11 +555,12 @@ public static NurbCurve3d ToNurbCurve3d(this Polyline3d pl3d) /// 三维ge多段线 public static PolylineCurve3d ToPolylineCurve3d(this Polyline3d pl) { - Point3dCollection pnts = new(); + using Point3dCollection pnts = new(); foreach (ObjectId id in pl) { - PolylineVertex3d ver = (PolylineVertex3d)id.GetObject(OpenMode.ForRead); - pnts.Add(ver.Position); + var ver = id.GetObject(OpenMode.ForRead); + if (ver != null) + pnts.Add(ver.Position); } return new PolylineCurve3d(pnts); } @@ -844,15 +583,15 @@ public static CompositeCurve3d ToCurve3d(this Polyline pl) switch (pl.GetSegmentType(i)) { case SegmentType.Line: - c3ds.Add(pl.GetLineSegmentAt(i)); - break; + c3ds.Add(pl.GetLineSegmentAt(i)); + break; case SegmentType.Arc: - c3ds.Add(pl.GetArcSegmentAt(i)); - break; + c3ds.Add(pl.GetArcSegmentAt(i)); + break; default: - break; + break; } } return new CompositeCurve3d(c3ds.ToArray()); @@ -863,33 +602,29 @@ public static CompositeCurve3d ToCurve3d(this Polyline pl) /// /// 多段线 /// Nurb曲线 - public static NurbCurve3d ToNurbCurve3d(this Polyline pl) + public static NurbCurve3d? ToNurbCurve3d(this Polyline pl) { - NurbCurve3d nc3d = null; + NurbCurve3d? nc3d = null; for (int i = 0; i < pl.NumberOfVertices; i++) { - NurbCurve3d nc3dtemp = null; + NurbCurve3d? nc3dtemp = null; switch (pl.GetSegmentType(i)) { case SegmentType.Line: - nc3dtemp = new NurbCurve3d(pl.GetLineSegmentAt(i)); - break; + nc3dtemp = new NurbCurve3d(pl.GetLineSegmentAt(i)); + break; case SegmentType.Arc: - nc3dtemp = pl.GetArcSegmentAt(i).ToNurbCurve3d(); - break; + nc3dtemp = pl.GetArcSegmentAt(i).ToNurbCurve3d(); + break; default: - break; + break; } - if (nc3d == null) - { + if (nc3d is null) nc3d = nc3dtemp; - } - else if (nc3dtemp != null) - { + else if (nc3dtemp is not null) nc3d.JoinWith(nc3dtemp); - } } return nc3d; } @@ -906,23 +641,24 @@ public static void ChamferAt(this Polyline polyline, int index, double radius, b if (index < 1 || index > polyline.NumberOfVertices - 2) throw new System.Exception("错误的索引号"); - if (polyline.GetSegmentType(index - 1) != SegmentType.Line || polyline.GetSegmentType(index) != SegmentType.Line) + if (SegmentType.Line != polyline.GetSegmentType(index - 1) || + SegmentType.Line != polyline.GetSegmentType(index)) throw new System.Exception("非直线段不能倒角"); - //获取当前索引号的前后两段直线,并组合为Ge复合曲线 + // 获取当前索引号的前后两段直线,并组合为Ge复合曲线 Curve3d[] c3ds = new Curve3d[] { polyline.GetLineSegmentAt(index - 1), polyline.GetLineSegmentAt(index) }; - var cc3d = new CompositeCurve3d(c3ds); + CompositeCurve3d cc3d = new(c3ds); - //试倒直角 - //子曲线的个数有三种情况: - //1、=3时倒角方向正确 - //2、=2时倒角方向相反 - //3、=0或为直线时失败 + // 试倒直角 + // 子曲线的个数有三种情况: + // 1、=3时倒角方向正确 + // 2、=2时倒角方向相反 + // 3、=0或为直线时失败 c3ds = cc3d.GetTrimmedOffset ( @@ -934,20 +670,17 @@ public static void ChamferAt(this Polyline polyline, int index, double radius, b if (c3ds.Length > 0 && c3ds[0] is CompositeCurve3d) { var newcc3d = c3ds[0] as CompositeCurve3d; - c3ds = newcc3d.GetCurves(); + c3ds = newcc3d!.GetCurves(); if (c3ds.Length == 3) { - c3ds = - cc3d.GetTrimmedOffset - ( - -radius, - Vector3d.ZAxis, - OffsetCurveExtensionType.Chamfer - ); + c3ds = cc3d.GetTrimmedOffset + ( + -radius, + Vector3d.ZAxis, + OffsetCurveExtensionType.Chamfer + ); if (c3ds.Length == 0 || c3ds[0] is LineSegment3d) - { throw new System.Exception("倒角半径过大"); - } } else if (c3ds.Length == 2) { @@ -959,27 +692,27 @@ public static void ChamferAt(this Polyline polyline, int index, double radius, b throw new System.Exception("倒角半径过大"); } - //GetTrimmedOffset会生成倒角+偏移,故先反方向倒角,再倒回 - c3ds = - cc3d.GetTrimmedOffset - ( - -radius, - Vector3d.ZAxis, - OffsetCurveExtensionType.Extend - ); + // GetTrimmedOffset会生成倒角+偏移,故先反方向倒角,再倒回 + c3ds = cc3d.GetTrimmedOffset + ( + -radius, + Vector3d.ZAxis, + OffsetCurveExtensionType.Extend + ); OffsetCurveExtensionType type = isFillet ? OffsetCurveExtensionType.Fillet : OffsetCurveExtensionType.Chamfer; - c3ds = - c3ds[0].GetTrimmedOffset - ( - radius, - Vector3d.ZAxis, - type - ); + c3ds = c3ds[0].GetTrimmedOffset + ( + radius, + Vector3d.ZAxis, + type + ); - //将结果Ge曲线转为Db曲线,并将相关的数值反映到原曲线 - Polyline plTemp = c3ds[0].ToCurve() as Polyline; + // 将结果Ge曲线转为Db曲线,并将相关的数值反映到原曲线 + var plTemp = c3ds[0].ToCurve() as Polyline; + if (plTemp is null) + return; polyline.RemoveVertexAt(index); polyline.AddVertexAt(index, plTemp.GetPoint2dAt(1), plTemp.GetBulgeAt(1), 0, 0); polyline.AddVertexAt(index + 1, plTemp.GetPoint2dAt(2), 0, 0, 0); @@ -987,5 +720,5 @@ public static void ChamferAt(this Polyline polyline, int index, double radius, b #endregion Polyline - #endregion Curve -} + #endregion +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/ExtensionMethod/DBDictionaryEx.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/DBDictionaryEx.cs similarity index 70% rename from src/IFoxCAD.Cad/ExtensionMethod/DBDictionaryEx.cs rename to src/CAD/IFox.CAD.Shared/ExtensionMethod/DBDictionaryEx.cs index 5dd09937aaccf0648889aff7e947daa7d24e1560..e099a936bce6cb4f6b257cee201460ae987483d7 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/DBDictionaryEx.cs +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/DBDictionaryEx.cs @@ -1,6 +1,4 @@ -using Group = Autodesk.AutoCAD.DatabaseServices.Group; - -namespace IFoxCAD.Cad; +namespace IFoxCAD.Cad; /// /// 字典扩展类 @@ -14,12 +12,15 @@ public static class DBDictionaryEx /// 字典 /// 事务 /// 对象迭代器 - public static IEnumerable GetAllObjects(this DBDictionary dict, Transaction trans = null) where T : DBObject + [System.Diagnostics.DebuggerStepThrough] + public static IEnumerable GetAllObjects(this DBDictionary dict, DBTrans? trans = null) where T : DBObject { - trans ??= DBTrans.Top.Transaction; + trans ??= DBTrans.Top; foreach (DBDictionaryEntry e in dict) { - yield return trans.GetObject(e.Value, OpenMode.ForRead) as T; + var ent = trans.GetObject(e.Value); + if (ent is not null) + yield return ent; } } @@ -31,16 +32,14 @@ public static IEnumerable GetAllObjects(this DBDictionary dict, Transactio /// 事务 /// 指定的键值 /// T 类型的对象 - public static T GetAt(this DBDictionary dict, string key, Transaction trans = null) where T : DBObject + public static T? GetAt(this DBDictionary dict, string key, DBTrans? trans = null) where T : DBObject { - trans ??= DBTrans.Top.Transaction; + trans ??= DBTrans.Top; if (dict.Contains(key)) { ObjectId id = dict.GetAt(key); if (!id.IsNull) - { - return trans.GetObject(id, OpenMode.ForRead) as T; - } + return trans.GetObject(id); } return null; } @@ -52,14 +51,14 @@ public static T GetAt(this DBDictionary dict, string key, Transaction trans = /// 字典 /// 事务 /// 键 - /// 值 - public static void SetAt(this DBDictionary dict, string key, T obj, Transaction trans = null) where T : DBObject + /// 值 + public static void SetAt(this DBDictionary dict, string key, T newValue, Transaction? trans = null) where T : DBObject { trans ??= DBTrans.Top.Transaction; using (dict.ForWrite()) { - dict.SetAt(key, obj); - trans.AddNewlyCreatedDBObject(obj, true); + dict.SetAt(key, newValue); + trans.AddNewlyCreatedDBObject(newValue, true); } } @@ -71,12 +70,12 @@ public static void SetAt(this DBDictionary dict, string key, T obj, Transacti /// 字典 /// 键值 /// 扩展数据 - public static XRecordDataList GetXRecord(this DBDictionary dict, string key) + public static XRecordDataList? GetXRecord(this DBDictionary dict, string key) { - Xrecord rec = dict.GetAt(key); - if (rec != null) - return rec.Data; - return null; + var rec = dict.GetAt(key); + if (rec is null) + return null; + return rec.Data; } /// @@ -87,8 +86,10 @@ public static XRecordDataList GetXRecord(this DBDictionary dict, string key) /// 键值 public static void SetXRecord(this DBDictionary dict, string key, XRecordDataList rb) { - using var data = new Xrecord { Data = rb }; - dict.SetAt(key, data); + // DxfCode.300 字符串可以写 Data + // DxfCode.1004 内存流不给写 Data,只能去写 XData + using Xrecord newValue = new() { Data = rb }; + dict.SetAt(key, newValue); } #endregion @@ -98,20 +99,18 @@ public static void SetXRecord(this DBDictionary dict, string key, XRecordDataLis /// 对象 /// 事务 /// 扩展字典对象 - public static DBDictionary GetXDictionary(this DBObject obj, Transaction trans = null) + public static DBDictionary? GetXDictionary(this DBObject obj, DBTrans? trans = null) { - trans ??= DBTrans.Top.Transaction; + trans ??= DBTrans.Top; ObjectId id = obj.ExtensionDictionary; if (id.IsNull) { using (obj.ForWrite()) - { obj.CreateExtensionDictionary(); - } id = obj.ExtensionDictionary; } - return id.GetObject(tr: trans); + return id.GetObject(trans: trans); } #region 数据表 @@ -127,10 +126,12 @@ public static DataTable CreateDataTable(Dictionary colTypes, o DataTable table = new(); foreach (var t in colTypes) table.AppendColumn(t.Value, t.Key); + var ncol = colTypes.Count; - var nrow = content.GetLength(0); var types = new CellType[ncol]; colTypes.Values.CopyTo(types, 0); + + var nrow = content.GetLength(0); for (int i = 0; i < nrow; i++) { DataCellCollection row = new(); @@ -153,53 +154,51 @@ public static DataTable CreateDataTable(Dictionary colTypes, o /// 数据 public static void SetValue(this DataCell cell, CellType type, object value) { - - switch (type) { case CellType.Bool: - cell.SetBool((bool)value); - break; + cell.SetBool((bool)value); + break; case CellType.CharPtr: - cell.SetString((string)value); - break; + cell.SetString((string)value); + break; case CellType.Integer: - cell.SetInteger((int)value); - break; + cell.SetInteger((int)value); + break; case CellType.Double: - cell.SetDouble((double)value); - break; + cell.SetDouble((double)value); + break; case CellType.ObjectId: - cell.SetObjectId((ObjectId)value); - break; + cell.SetObjectId((ObjectId)value); + break; case CellType.Point: - cell.SetPoint((Point3d)value); - break; + cell.SetPoint((Point3d)value); + break; case CellType.Vector: - cell.SetVector((Vector3d)value); - break; + cell.SetVector((Vector3d)value); + break; case CellType.HardOwnerId: - cell.SetHardOwnershipId((ObjectId)value); - break; + cell.SetHardOwnershipId((ObjectId)value); + break; case CellType.HardPtrId: - cell.SetHardPointerId((ObjectId)value); - break; + cell.SetHardPointerId((ObjectId)value); + break; case CellType.SoftOwnerId: - cell.SetSoftOwnershipId((ObjectId)value); - break; + cell.SetSoftOwnershipId((ObjectId)value); + break; case CellType.SoftPtrId: - cell.SetSoftPointerId((ObjectId)value); - break; + cell.SetSoftPointerId((ObjectId)value); + break; } } #endregion @@ -213,9 +212,13 @@ public static void SetValue(this DataCell cell, CellType type, object value) /// 是否创建子字典 /// 键值列表 /// 字典 - public static DBDictionary GetSubDictionary(this DBDictionary dict, bool createSubDictionary, IEnumerable dictNames, Transaction trans = null) + public static DBDictionary? GetSubDictionary(this DBDictionary dict, + bool createSubDictionary, + IEnumerable dictNames, + DBTrans? trans = null) { - trans ??= DBTrans.Top.Transaction; + DBDictionary? newdict = null; + trans ??= DBTrans.Top; if (createSubDictionary) { using (dict.ForWrite()) @@ -225,14 +228,14 @@ public static DBDictionary GetSubDictionary(this DBDictionary dict, bool createS { if (dict.Contains(name)) { - dict = dict.GetAt(name, trans); + newdict = dict.GetAt(name, trans); } else { DBDictionary subDict = new(); - dict.SetAt(name, subDict, trans); - dict = subDict; - dict.TreatElementsAsHard = true; + dict.SetAt(name, subDict, trans); + newdict = subDict; + newdict.TreatElementsAsHard = true; } } } @@ -241,13 +244,12 @@ public static DBDictionary GetSubDictionary(this DBDictionary dict, bool createS foreach (string name in dictNames) { if (dict.Contains(name)) - dict = dict.GetAt(name, trans); + newdict = dict.GetAt(name, trans); else return null; } } - - return dict; + return newdict; } @@ -259,10 +261,10 @@ public static DBDictionary GetSubDictionary(this DBDictionary dict, bool createS ///// 是否创建子字典 ///// 键值列表 ///// 字典 - //public static DBDictionary GetSubDictionary(this DBObject obj, bool createSubDictionary, params string[] dictNames) - //{ + // public static DBDictionary GetSubDictionary(this DBObject obj, bool createSubDictionary, params string[] dictNames) + // { // return obj.GetXDictionary().GetSubDictionary(createSubDictionary, dictNames); - //} + // } #endregion @@ -276,19 +278,15 @@ public static DBDictionary GetSubDictionary(this DBDictionary dict, bool createS public static ObjectId AddGroup(this DBDictionary dict, string name, ObjectIdCollection ids) { if (dict.Contains(name)) - { return ObjectId.Null; - } - else + + using (dict.ForWrite()) { - using (dict.ForWrite()) - { - Group g = new(); - g.Append(ids); - dict.SetAt(name, g); - DBTrans.Top.Transaction.AddNewlyCreatedDBObject(g, true); - return g.ObjectId; - } + Group g = new(); + g.Append(ids); + dict.SetAt(name, g); + DBTrans.Top.Transaction.AddNewlyCreatedDBObject(g, true); + return g.ObjectId; } } @@ -301,10 +299,10 @@ public static ObjectId AddGroup(this DBDictionary dict, string name, ObjectIdCol public static ObjectId AddGroup(this DBDictionary dict, string name, IEnumerable ids) { if (dict.Contains(name)) - { return ObjectId.Null; - } - return dict.AddGroup(name, new ObjectIdCollection(ids.ToArray())); + + using ObjectIdCollection idc = new(ids.ToArray());//需要using吗? 暂无测试 + return dict.AddGroup(name, idc); } @@ -316,10 +314,8 @@ public static ObjectId AddGroup(this DBDictionary dict, string name, IEnumerable /// 编组集合 public static IEnumerable GetGroups(this DBDictionary dict, Func func) { - return - dict - .GetAllObjects() - .Where(func); + return dict.GetAllObjects() + .Where(func); } /// @@ -329,11 +325,10 @@ public static IEnumerable GetGroups(this DBDictionary dict, Func编组集合 public static IEnumerable GetGroups(this Entity ent) { - return - ent.GetPersistentReactorIds() - .Cast() - .Select(id => id.GetObject()) - .OfType(); + return ent.GetPersistentReactorIds() + .Cast() + .Select(id => id.GetObject()) + .OfType(); } /// @@ -348,9 +343,7 @@ public static List RemoveNullGroup(this DBDictionary dict) { names.Add(g.Name); using (g.ForWrite()) - { g.Erase(); - } } return names; } @@ -371,32 +364,10 @@ public static List RemoveNullGroup(this DBDictionary dict, Func /// 实体对象扩展类 @@ -14,35 +16,41 @@ public static class DBObjectEx /// 要删除数据的组码 public static void RemoveXData(this DBObject obj, string appName, DxfCode dxfCode) { + if (obj.XData == null) + return; XDataList data = obj.XData; - var indexlst = new List(); - bool flag = false; - for (int i = 0; i < data.Count; i++) - { - if (data[i].TypeCode == (int)DxfCode.ExtendedDataRegAppName && data[i].Value.ToString() == appName) - { - flag = true; - } - if (flag) - { - if (data[i].TypeCode == (int)DxfCode.ExtendedDataRegAppName && data[i].Value.ToString() != appName) - break; - if (data[i].TypeCode == (int)dxfCode) - { - indexlst.Add(i); - } - } - } - foreach (var item in indexlst) - { - data.RemoveAt(item); - } + + // 测试命令 addxdata removexdata + // 移除指定App的扩展 + var indexs = data.GetXdataAppIndex(appName, new DxfCode[] { dxfCode }); + if (indexs.Count == 0) + return; + + for (int i = indexs.Count - 1; i >= 0; i--) + data.RemoveAt(indexs[i]); using (obj.ForWrite()) - { obj.XData = data; + } + /// + /// 删除扩展数据 + /// + /// 对象实例 + /// 应用程序名称 + public static void RemoveXData(this DBObject obj, string appName) + { + if (obj.XData == null) + return; + foreach (var data in obj.XData) + { + // 直接赋值进去等于清空名称 + using var rb = new ResultBuffer(); + rb.Add(new((int)DxfCode.ExtendedDataRegAppName, appName)); + using (obj.ForWrite()) + obj.XData = rb; } } + /// /// 修改扩展数据 /// @@ -52,34 +60,25 @@ public static void RemoveXData(this DBObject obj, string appName, DxfCode dxfCod /// 新的数据 public static void ChangeXData(this DBObject obj, string appName, DxfCode dxfCode, object newvalue) { + if (obj.XData == null) + return; XDataList data = obj.XData; - bool flag = false; - for (int i = 0; i < data.Count; i++) - { - if (data[i].TypeCode == (int)DxfCode.ExtendedDataRegAppName && data[i].Value.ToString() == appName) - { - flag = true; - } - if (flag) - { - if (data[i].TypeCode == (int)DxfCode.ExtendedDataRegAppName && data[i].Value.ToString() != appName) - break; - if (data[i].TypeCode == (int)dxfCode) - { - data[i] = new TypedValue((int)dxfCode, newvalue); - } - } - } + + var indexs = data.GetXdataAppIndex(appName, new DxfCode[] { dxfCode }); + if (indexs.Count == 0) + return; + + for (int i = indexs.Count - 1; i >= 0; i--) + data[indexs[i]] = new TypedValue((short)dxfCode, newvalue); using (obj.ForWrite()) - { obj.XData = data; - } } #endregion #region 读写模式切换 +#line hidden // 调试的时候跳过它 /// /// 实体自动管理读写函数 /// @@ -91,22 +90,16 @@ public static void ForWrite(this T obj, Action action) where T : DBObject var _isNotifyEnabled = obj.IsNotifyEnabled; var _isWriteEnabled = obj.IsWriteEnabled; if (_isNotifyEnabled) - { obj.UpgradeFromNotify(); - } else if (!_isWriteEnabled) - { obj.UpgradeOpen(); - } + action?.Invoke(obj); + if (_isNotifyEnabled) - { obj.DowngradeToNotify(_isWriteEnabled); - } else if (!_isWriteEnabled) - { obj.DowngradeOpen(); - } } /// @@ -155,5 +148,6 @@ public void Dispose() #endregion IDisposable 成员 } +#line default #endregion -} +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/ExtensionMethod/DBTransEx.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/DBTransEx.cs new file mode 100644 index 0000000000000000000000000000000000000000..fdd4e91ced58800d71fb95a71df1055170ec92bd --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/DBTransEx.cs @@ -0,0 +1,91 @@ +#define lack_test + +namespace IFoxCAD.Cad; + +#if lack_test +public static class DBTransEx +{ + /* + * 0x01 + * db.Purge(ids)是获取未硬引用(无引用?)的对象,也就可以删除的. + * 0x02 + * 如果一个图元引用一个图层, + * 假设这个图元是可以删除的(实际上它可能来自于词典记录的id) => 那么它被 db.Purge(ids) 识别, + * 但是这个图层因为有硬引用,所以不被 db.Purge(ids) 识别, + * 只能删除图元之后,循环第二次再通过 db.Purge(ids) 获取图层id. + * 0x03 + * 因为删除之后,符号表内的引用可能被修改,因此需要重复遍历符号表. + * 0x04 + * 试试循环事务 + * 0x05 + * 无法过滤外部参照图层,使得全部图层打开了 + */ + + /// + /// 清理符号表 + /// + /// + /// + /// 排除外部参照:默认true,为false时候会令图层全部显示再清理,包括冻结 + public static void Purge(this DBTrans tr, SymModes sym = SymModes.All, bool excludeXref = true) + { + using ObjectIdCollection ids = new(); + var db = tr.Database; + + if ((sym & SymModes.BlockTable) == SymModes.BlockTable) + { + if (!excludeXref) + GetAllIds(tr, tr.BlockTable, ids, excludeXref); + else + tr.BlockTable.ForEach(tabRec => { + if (!tabRec.IsFromExternalReference) + ids.Add(tabRec.Id); + }, checkIdOk: true); + } + if ((sym & SymModes.DimStyleTable) == SymModes.DimStyleTable) + GetAllIds(tr, tr.DimStyleTable, ids, excludeXref); + if ((sym & SymModes.LayerTable) == SymModes.LayerTable) + GetAllIds(tr, tr.LayerTable, ids, excludeXref); + if ((sym & SymModes.LinetypeTable) == SymModes.LinetypeTable) + GetAllIds(tr, tr.LinetypeTable, ids, excludeXref); + if ((sym & SymModes.TextStyleTable) == SymModes.TextStyleTable) + GetAllIds(tr, tr.TextStyleTable, ids, excludeXref); + if ((sym & SymModes.ViewportTable) == SymModes.ViewportTable) + GetAllIds(tr, tr.ViewportTable, ids, excludeXref); + if ((sym & SymModes.RegAppTable) == SymModes.RegAppTable) + GetAllIds(tr, tr.RegAppTable, ids, excludeXref); + + // SHUN007 说这两个表可能有错误 + if ((sym & SymModes.ViewTable) == SymModes.ViewTable) + GetAllIds(tr, tr.ViewTable, ids, excludeXref); + if ((sym & SymModes.UcsTable) == SymModes.UcsTable) + GetAllIds(tr, tr.UcsTable, ids, excludeXref); + + // Purge是查询能够清理的对象 + db.Purge(ids); + foreach (ObjectId id in ids) + id.Erase(); + } + + static void GetAllIds(DBTrans tr, + SymbolTable symbolTable, + ObjectIdCollection ids, + bool excludeXref = true) + where TTable : SymbolTable + where TRecord : SymbolTableRecord, new() + { + if (!excludeXref) + symbolTable.ForEach(id => ids.Add(id)); + else + { + symbolTable.ForEach(id => { + var tabRec = tr.GetObject(id); + if (tabRec == null) + return; + if (!tabRec.Name.Contains("|")) + ids.Add(tabRec.Id); + }); + } + } +} +#endif \ No newline at end of file diff --git a/src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/EditorEx.cs similarity index 47% rename from src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs rename to src/CAD/IFox.CAD.Shared/ExtensionMethod/EditorEx.cs index bca589166775cf1e60c5480b3927e8c67bf19d5d..d432bf33e86a6d4b932b5132aa27d7efcacb5f87 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/EditorEx.cs @@ -1,4 +1,4 @@ -using System.Runtime.InteropServices; +using IFoxCAD.Com; namespace IFoxCAD.Cad; @@ -15,7 +15,8 @@ public static class EditorEx /// 点 /// 过滤器 /// 选择集结果类 - public static PromptSelectionResult SelectAtPoint(this Editor editor, Point3d point, SelectionFilter filter = default) + public static PromptSelectionResult SelectAtPoint(this Editor editor, Point3d point, + SelectionFilter? filter = default) { return editor.SelectCrossingWindow(point, point, filter); } @@ -51,9 +52,289 @@ public static SelectionSet SelectByLineWeight(this Editor editor, LineWeight lin }; } - PromptSelectionResult res = editor.SelectAll(filter); + var res = editor.SelectAll(filter); return res.Value; } + + /// + /// 选择集 + /// + /// 命令行对象 + /// 模式 + /// 过滤器 + /// 消息 + /// 关键字和回调函数 + /// + public static PromptSelectionResult SSGet(this Editor editor, + string? mode = null, + SelectionFilter? filter = null, + string[]? messages = null, + Dictionary? keywords = null) + { + PromptSelectionOptions pso = new(); + PromptSelectionResult ss; + if (mode is not null) + { + mode = mode.ToUpper(); + pso.SinglePickInSpace = mode.Contains(":A"); + pso.RejectObjectsFromNonCurrentSpace = mode.Contains(":C"); + pso.AllowDuplicates = mode.Contains(":D"); + pso.SelectEverythingInAperture = mode.Contains(":E"); + pso.RejectObjectsOnLockedLayers = mode.Contains(":L"); + pso.PrepareOptionalDetails = mode.Contains(":N"); + pso.SingleOnly = mode.Contains(":S"); + pso.RejectPaperspaceViewport = mode.Contains(":V"); + pso.AllowSubSelections = mode.Contains("-A"); + pso.ForceSubSelections = mode.Contains("-F"); + } + if (messages is not null) + { + pso.MessageForAdding = messages[0]; + pso.MessageForRemoval = messages[1]; + } + + if (keywords is not null) + { + foreach (var keyword in keywords.Keys) + pso.Keywords.Add(keyword); + if (pso.MessageForRemoval is null) + pso.MessageForAdding = "选择对象"; + pso.MessageForAdding += $"[{string.Join(" / ", keywords.Keys.ToArray())}]"; + pso.KeywordInput += (s, e) => { + if (keywords.ContainsKey(e.Input)) + keywords[e.Input].Invoke(); + }; + } + try + { + if (filter is not null) + ss = editor.GetSelection(pso, filter); + else + ss = editor.GetSelection(pso); + } + catch (Exception) + { + //editor.WriteMessage($"\nKey is {e.Message}"); + throw; + } + return ss; + } + + + /* + * // 定义选择集选项 + * var pso = new PromptSelectionOptions + * { + * AllowDuplicates = false, // 重复选择 + * }; + * + * // getai遍历全图选择块有用到 + * var dic = new Dictionary() { + * { "Z,全部同名", ()=> { + * getai = BlockHelper.EnumAttIdentical.AllBlockName; + * SendEsc.Esc(); + * }}, + * { "X,动态块显示", ()=> { + * getai = BlockHelper.EnumAttIdentical.Display; + * }}, + * { "V,属性值-默认", ()=> { + * getai = BlockHelper.EnumAttIdentical.DisplayAndTagText; + * }}, + * // 允许以下操作,相同的会加入前面的 + * // { "V,属性值-默认|X,啊啊啊啊", ()=> { + * + * // }}, + * }; + * pso.SsgetAddKeys(dic); + * + * // 创建选择集过滤器,只选择块对象 + * var filList = new TypedValue[] { new TypedValue((int)DxfCode.Start, "INSERT") }; + * var filter = new SelectionFilter(filList); + * ssPsr = ed.GetSelection(pso, filter); + */ + + /// + /// 添加选择集关键字和回调 + /// + /// 选择集配置 + /// 关键字,回调委托 + /// + public static void SsgetAddKeys(this PromptSelectionOptions pso, + Dictionary dicActions) + { + Dictionary tmp = new(); + // 后缀名的|号切割,移除掉,组合成新的加入tmp + for (int i = dicActions.Count - 1; i >= 0; i--) + { + var pair = dicActions.ElementAt(i); + var key = pair.Key; + var keySp = key.Split('|'); + if (keySp.Length < 2) + continue; + + for (int j = 0; j < keySp.Length; j++) + { + var item = keySp[j]; + // 防止多个后缀通过|符越过词典约束同名 + // 后缀(key)含有,而且Action(value)不同,就把Action(value)累加到后面. + if (dicActions.ContainsKey(item)) + { + if (dicActions[item] != dicActions[key]) + dicActions[item] += dicActions[key]; + } + else + tmp.Add(item, dicActions[key]); + } + dicActions.Remove(key); + } + + foreach (var item in tmp) + dicActions.Add(item.Key, item.Value); + + // 去除关键字重复的,把重复的执行动作移动到前面 + for (int i = 0; i < dicActions.Count; i++) + { + var pair1 = dicActions.ElementAt(i); + var key1 = pair1.Key; + + for (int j = dicActions.Count - 1; j > i; j--) + { + var pair2 = dicActions.ElementAt(j); + var key2 = pair2.Key; + + if (key1.Split(',')[0] == key2.Split(',')[0]) + { + if (dicActions[key1] != dicActions[key2]) + dicActions[key1] += dicActions[key2]; + dicActions.Remove(key2); + } + } + } + + foreach (var item in dicActions) + { + var keySplitS = item.Key.Split(new string[] { ",", "|" }, StringSplitOptions.RemoveEmptyEntries); + for (int i = 0; i < keySplitS.Length; i += 2) + pso.Keywords.Add(keySplitS[i], keySplitS[i], + keySplitS[i + 1] + "(" + keySplitS[i] + ")"); + } + + // 回调的时候我想用Dict的O(1)索引, + // 但是此函数内进行new Dictionary() 在函数栈释放的时候,它被释放掉了. + // 因此 dicActions 参数的生命周期 + tmp = new(dicActions); + dicActions.Clear(); + foreach (var item in tmp) + dicActions.Add(item.Key.Split(',')[0], item.Value); + + var keyWords = pso.Keywords; + // 从选择集命令中显示关键字 + pso.MessageForAdding = keyWords.GetDisplayString(true); + // 关键字回调事件 ssget关键字 + pso.KeywordInput += (sender, e) => { + dicActions[e.Input].Invoke(); + }; + } + + + + + + + // #region 即时选择样板 + // /// + // /// 即时选择,框选更新关键字 + // /// + // public static void SelectTest() + // { + // Env.Editor.WriteMessage("\n[白嫖工具]--测试"); + // // 激活选中事件 + // Env.Editor.SelectionAdded += SelectTest_SelectionAdded; + // // 初始化坐标系 + // Env.Editor.CurrentUserCoordinateSystem = Matrix3d.Identity; + + // // 创建过滤器 + // var sf = new OpEqual(0, "arc"); + // var pso = new PromptSelectionOptions + // { + // MessageForAdding = "\n请选择对象:" + // }; + + // pso.Keywords.Add("Z"); + // pso.Keywords.Add("X"); + // pso.Keywords.Add("Q"); + // // 注册关键字 + // pso.KeywordInput += SelectTest_KeywordInput; + // try + // { + // // 用户选择 + // var psr = Env.Editor.GetSelection(pso, sf); + // // 处理代码 + + + // } + // catch (Exception ex)// 捕获关键字 + // { + // if (ex.Message == "XuError") + // { + // // 关闭关键字事件 + // pso.KeywordInput -= SelectTest_KeywordInput; + // // 关闭选中事件 + // Env.Editor.SelectionAdded -= SelectTest_SelectionAdded; + // // 重新调用自身 + // ZengLiangYuanJiao(); + // } + // } + // // 关闭关键字事件 + // pso.KeywordInput -= SelectTest_KeywordInput; + // // 关闭选中事件 + // Env.Editor.SelectionAdded -= SelectTest_SelectionAdded; + // } + + // /// + // /// 即时选择 + // /// + // /// + // /// + // private static void SelectTest_SelectionAdded(object sender, SelectionAddedEventArgs e) + // { + // // 关闭选中事件 + // Env.Editor.SelectionAdded -= SelectTest_SelectionAdded; + // using (var tr = new DBTrans()) + // { + // // 处理代码 + // for (int i = 0; i < e.AddedObjects.Count; i++) + // { + // // 处理完移除已处理的对象 + // e.Remove(i); + // } + // } + // // 激活选中事件 + // Env.Editor.SelectionAdded += SelectTest_SelectionAdded; + // } + + // /// + // /// 关键字响应 + // /// + // /// + // /// + // private static void SelectTest_KeywordInput(object sender, SelectionTextInputEventArgs e) + // { + // // 获取关键字 + // switch (e.Input) + // { + // case "Z": + // break; + // case "X": + // break; + + // case "Q": + // break; + // } + // // 抛出异常,用于更新提示信息 + // throw new ArgumentException("XuError"); + // } + // #endregion #endregion #region Info @@ -66,7 +347,7 @@ public static SelectionSet SelectByLineWeight(this Editor editor, LineWeight lin public static void StreamMessage(string format, params object[] args) { StreamMessage(string.Format(format, args)); - }// + } /// /// 带错误提示对话框的打印信息函数 @@ -85,7 +366,7 @@ public static void StreamMessage(string message) { Message(ex); } - }// + } /// /// 异常信息对话框 @@ -104,7 +385,7 @@ public static void Message(System.Exception ex) catch { } - }// + } /// /// 提示信息对话框 @@ -125,7 +406,7 @@ public static void InfoMessageBox(string caption, string message) { Message(ex); } - }// + } /// /// 提示信息对话框 @@ -139,13 +420,13 @@ public static void InfoMessageBox(string caption, string format, params object[] } /// - /// 提示信息对话框,默认标题为NFox.Cad + /// 提示信息对话框,默认标题为NFox.Cad /// /// 对话框文本 public static void InfoMessageBox(string message) { InfoMessageBox("NFox.Cad", message); - }// + } /// /// 提示信息对话框 @@ -155,7 +436,7 @@ public static void InfoMessageBox(string message) public static void InfoMessageBox(string format, params object[] args) { InfoMessageBox(string.Format(format, args)); - }// + } /// /// 命令行打印字符串 @@ -166,7 +447,7 @@ public static void WriteMessage(string message) try { if (Acceptable()) - Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\n" + message); + Acap.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\n" + message); else return; } @@ -174,7 +455,7 @@ public static void WriteMessage(string message) { Message(ex); } - }// + } /// /// 命令行打印字符串 @@ -189,23 +470,23 @@ public static void WriteMessage(string format, params object[] args) /// /// 判断是否有活动的编辑器对象 /// - /// 有,没有 + /// 有,没有 public static bool HasEditor() { - return Application.DocumentManager.MdiActiveDocument != null - && Application.DocumentManager.Count != 0 - && Application.DocumentManager.MdiActiveDocument.Editor != null; - }// + return Acap.DocumentManager.MdiActiveDocument is not null + && Acap.DocumentManager.Count != 0 + && Acap.DocumentManager.MdiActiveDocument.Editor is not null; + } /// /// 判断是否可以打印字符串 /// - /// 可以打印,不可以打印 + /// 可以打印,不可以打印 public static bool Acceptable() { return HasEditor() - && !Application.DocumentManager.MdiActiveDocument.Editor.IsDragging; - }// + && !Acap.DocumentManager.MdiActiveDocument.Editor.IsDragging; + } #endregion Info @@ -215,11 +496,10 @@ public static bool Acceptable() /// 根据点表返回矢量线的列表 /// /// 点表 - /// 是否闭合, 为闭合, 为不闭合 + /// 是否闭合, 为闭合, 为不闭合 /// public static List GetLines(IEnumerable pnts, bool isClosed) { - var itor = pnts.GetEnumerator(); if (!itor.MoveNext()) return new List(); @@ -253,7 +533,7 @@ public static List GetLines(IEnumerable pnts, bool isClosed /// 编辑器对象 /// 点表 /// 颜色码 - /// 是否闭合, 为闭合, 为不闭合 + /// 是否闭合, 为闭合, 为不闭合 public static void DrawVectors(this Editor editor, IEnumerable pnts, short colorIndex, bool isClosed) { var rlst = @@ -323,9 +603,7 @@ public static void DrawCircle(this Editor editor, Point2d pnt, short colorIndex, pnt + vec }; for (int i = 1; i < numEdges; i++) - { pnts.Add(pnt + vec.RotateBy(angle * i)); - } editor.DrawVectors(pnts, colorIndex, true); } @@ -386,32 +664,34 @@ public static Matrix3d GetMatrixFromWcsToMDcs(this Editor editor) public static Matrix3d GetMatrixFromMDcsToPDcs(this Editor editor) { if ((short)Env.GetVar("TILEMODE") == 1) - throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.InvalidInput, "Espace papier uniquement"); + throw new ArgumentException("TILEMODE == 1..Espace papier uniquement"); - Database db = editor.Document.Database; - Matrix3d mat; - using (Transaction tr = db.TransactionManager.StartTransaction()) + Matrix3d mat = Matrix3d.Identity; + using DBTrans tr = new(); + var vp = tr.GetObject(editor.CurrentViewportObjectId); + if (vp == null) + return mat; + + if (vp.Number == 1) { - Viewport vp = tr.GetObject(editor.CurrentViewportObjectId, OpenMode.ForRead) as Viewport; - if (vp.Number == 1) + try { - try - { - editor.SwitchToModelSpace(); - vp = tr.GetObject(editor.CurrentViewportObjectId, OpenMode.ForRead) as Viewport; - editor.SwitchToPaperSpace(); - } - catch - { - throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.InvalidInput, "Aucun fenêtre active"); - } + editor.SwitchToModelSpace(); + vp = tr.GetObject(editor.CurrentViewportObjectId); + editor.SwitchToPaperSpace(); + } + catch + { + throw new Exception("Aucun fenêtre active...ErrorStatus.InvalidInput"); } - Point3d vCtr = new(vp.ViewCenter.X, vp.ViewCenter.Y, 0.0); - mat = Matrix3d.Displacement(vCtr.GetAsVector().Negate()); - mat = Matrix3d.Displacement(vp.CenterPoint.GetAsVector()) * mat; - mat = Matrix3d.Scaling(vp.CustomScale, vp.CenterPoint) * mat; - tr.Commit(); } + if (vp == null) + return mat; + + Point3d vCtr = new(vp.ViewCenter.X, vp.ViewCenter.Y, 0.0); + mat = Matrix3d.Displacement(vCtr.GetAsVector().Negate()); + mat = Matrix3d.Displacement(vp.CenterPoint.GetAsVector()) * mat; + mat = Matrix3d.Scaling(vp.CustomScale, vp.CenterPoint) * mat; return mat; } @@ -434,10 +714,66 @@ public static Matrix3d GetMatrixFromPDcsToMDcs(this Editor editor) /// 变换矩阵 public static Matrix3d GetMatrix(this Editor editor, CoordinateSystemCode from, CoordinateSystemCode to) { +#if ac2009 + switch (from) + { + case CoordinateSystemCode.Wcs: + switch (to) + { + case CoordinateSystemCode.Ucs: + return editor.GetMatrixFromWcsToUcs(); + + case CoordinateSystemCode.MDcs: + return editor.GetMatrixFromMDcsToWcs(); + + case CoordinateSystemCode.PDcs: + throw new Exception("To be used only with DCS...ErrorStatus.InvalidInput"); + } + break; + case CoordinateSystemCode.Ucs: + switch (to) + { + case CoordinateSystemCode.Wcs: + return editor.GetMatrixFromUcsToWcs(); + + case CoordinateSystemCode.MDcs: + return editor.GetMatrixFromUcsToWcs() * editor.GetMatrixFromWcsToMDcs(); + + case CoordinateSystemCode.PDcs: + throw new Exception("To be used only with DCS... ErrorStatus.InvalidInput"); + } + break; + case CoordinateSystemCode.MDcs: + switch (to) + { + case CoordinateSystemCode.Wcs: + return editor.GetMatrixFromMDcsToWcs(); + + case CoordinateSystemCode.Ucs: + return editor.GetMatrixFromMDcsToWcs() * editor.GetMatrixFromWcsToUcs(); + + case CoordinateSystemCode.PDcs: + return editor.GetMatrixFromMDcsToPDcs(); + } + break; + case CoordinateSystemCode.PDcs: + switch (to) + { + case CoordinateSystemCode.Wcs: + throw new Exception("To be used only with DCS... ErrorStatus.InvalidInput"); + case CoordinateSystemCode.Ucs: + throw new Exception("To be used only with DCS... ErrorStatus.InvalidInput"); + case CoordinateSystemCode.MDcs: + return editor.GetMatrixFromPDcsToMDcs(); + } + break; + } + return Matrix3d.Identity; +#else return (from, to) switch { (CoordinateSystemCode.Wcs, CoordinateSystemCode.Ucs) => editor.GetMatrixFromWcsToUcs(), - (CoordinateSystemCode.Wcs, CoordinateSystemCode.MDcs) => editor.GetMatrixFromMDcsToWcs(), + (CoordinateSystemCode.Wcs, CoordinateSystemCode.MDcs) => editor.GetMatrixFromWcsToMDcs(), (CoordinateSystemCode.Ucs, CoordinateSystemCode.Wcs) => editor.GetMatrixFromUcsToWcs(), (CoordinateSystemCode.Ucs, CoordinateSystemCode.MDcs) => editor.GetMatrixFromUcsToWcs() * editor.GetMatrixFromWcsToMDcs(), (CoordinateSystemCode.MDcs, CoordinateSystemCode.Wcs) => editor.GetMatrixFromMDcsToWcs(), @@ -445,9 +781,10 @@ public static Matrix3d GetMatrix(this Editor editor, CoordinateSystemCode from, (CoordinateSystemCode.MDcs, CoordinateSystemCode.PDcs) => editor.GetMatrixFromMDcsToPDcs(), (CoordinateSystemCode.PDcs, CoordinateSystemCode.MDcs) => editor.GetMatrixFromPDcsToMDcs(), (CoordinateSystemCode.PDcs, CoordinateSystemCode.Wcs or CoordinateSystemCode.Ucs) - or (CoordinateSystemCode.Wcs or CoordinateSystemCode.Ucs, CoordinateSystemCode.PDcs) => throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.InvalidInput, "To be used only with DCS"), + or (CoordinateSystemCode.Wcs or CoordinateSystemCode.Ucs, CoordinateSystemCode.PDcs) => throw new Exception("To be used only with DCS...ErrorStatus.InvalidInput"), (_, _) => Matrix3d.Identity }; +#endif } #endregion @@ -462,25 +799,23 @@ public static Matrix3d GetMatrix(this Editor editor, CoordinateSystemCode from, /// 窗口右上点 public static void ZoomWindow(this Editor ed, Point3d minPoint, Point3d maxPoint) { - ViewTableRecord cvtr = ed.GetCurrentView(); ViewTableRecord vtr = new(); - vtr.CopyFrom(cvtr); + vtr.CopyFrom(ed.GetCurrentView()); + + var oldpnts = new Point3d[] { minPoint, maxPoint }; + var pnts = new Point3d[8]; + var dpnts = new Point3d[8]; - Point3d[] oldpnts = new Point3d[] { minPoint, maxPoint }; - Point3d[] pnts = new Point3d[8]; - Point3d[] dpnts = new Point3d[8]; + var mat = ed.GetMatrixFromWcsToMDcs(); for (int i = 0; i < 2; i++) - { for (int j = 0; j < 2; j++) - { for (int k = 0; k < 2; k++) { int n = i * 4 + j * 2 + k; pnts[n] = new Point3d(oldpnts[i][0], oldpnts[j][1], oldpnts[k][2]); - dpnts[n] = pnts[n].TransformBy(ed.GetMatrixFromWcsToMDcs()); + dpnts[n] = pnts[n].TransformBy(mat); } - } - } + double xmin, xmax, ymin, ymax; xmin = xmax = dpnts[0][0]; ymin = ymax = dpnts[0][1]; @@ -494,7 +829,8 @@ public static void ZoomWindow(this Editor ed, Point3d minPoint, Point3d maxPoint vtr.Width = xmax - xmin; vtr.Height = ymax - ymin; - vtr.CenterPoint = (dpnts[0] + (dpnts[7] - dpnts[0]) / 2).Convert2d(new Plane()); + vtr.CenterPoint = (dpnts[0] + (dpnts[7] - dpnts[0]) / 2) + .Convert2d(Curve2dEx._planeCache); ed.SetCurrentView(vtr); ed.Regen(); @@ -511,7 +847,7 @@ public static void ZoomWindow(this Editor ed, Extents3d ext) } /// - /// 缩放比例 + /// 按范围缩放 /// /// 命令行对象 /// 中心点 @@ -523,11 +859,11 @@ public static void Zoom(this Editor ed, Point3d CenPt, double width, double heig view.Width = width; view.Height = height; view.CenterPoint = new Point2d(CenPt.X, CenPt.Y); - ed.SetCurrentView(view);//更新当前视图 + ed.SetCurrentView(view);// 更新当前视图 } /// - ///缩放窗口范围 + /// 缩放窗口范围 /// /// 命令行对象 /// 第一点 @@ -549,22 +885,22 @@ public static void ZoomWindow(this Editor ed, Point3d lpt, Point3d rpt, double o /// 获取有效的数据库范围 /// /// 数据库 - /// 范围 - /// 数据库没有图元返回 true, 反之返回 false - public static bool GetValidExtents3d(this Database db, out Extents3d dbExtent) + /// 容差值:图元包围盒会超过数据库边界,用此参数扩大边界 + /// + public static Extents3d? GetValidExtents3d(this Database db, double extention = 1e-6) { - dbExtent = new Extents3d(Point3d.Origin, new Point3d(1, 1, 0)); - //数据库没有图元的时候,min是大,max是小,导致新建出错 + db.UpdateExt(true);// 更新当前模型空间的范围 + var ve = new Vector3d(extention, extention, extention); + // 数据库没有图元的时候,min是大,max是小,导致新建出错 + // 数据如下: + // min.X == 1E20 && min.Y == 1E20 && min.Z == 1E20 && + // max.X == -1E20 && max.Y == -1E20 && max.Z == -1E20) var a = db.Extmin; var b = db.Extmax; - var ve = new Vector3d(1, 1, 0); - if (!(a.X == 1E20 && a.Y == 1E20 && a.Z == 1E20 && - b.X == -1E20 && b.Y == -1E20 && b.Z == -1E20)) - { - dbExtent = new Extents3d(db.Extmin - ve, db.Extmax + ve); - return true; - } - return false; + if (a.X < b.X && a.Y < b.Y) + return new Extents3d(db.Extmin - ve, db.Extmax + ve); + + return null; } /// @@ -575,14 +911,12 @@ public static bool GetValidExtents3d(this Database db, out Extents3d dbExtent) public static void ZoomExtents(this Editor ed, double offsetDist = 0.00) { Database db = ed.Document.Database; - db.UpdateExt(true); - - if (db.GetValidExtents3d(out Extents3d extents3D) == true) - { - ed.ZoomWindow(extents3D.MinPoint, extents3D.MinPoint, offsetDist); - return; - } - ed.ZoomWindow(db.Extmax, db.Extmin, offsetDist); + // db.UpdateExt(true); // GetValidExtents3d内提供了 + var dbExtent = db.GetValidExtents3d(); + if (dbExtent == null) + ed.ZoomWindow(Point3d.Origin, new Point3d(1, 1, 0), offsetDist); + else + ed.ZoomWindow(db.Extmin, db.Extmax, offsetDist); } /// @@ -666,50 +1000,171 @@ public static PromptResult GetString(this Editor ed, string Message, string Defa return ed.GetString(strOp); } - #endregion Get交互类 + #endregion #region 执行lisp +#if NET35 + [DllImport("acad.exe", +#else + [DllImport("accore.dll", +#endif + CallingConvention = CallingConvention.Cdecl, EntryPoint = "acedInvoke")] + static extern int AcedInvoke(IntPtr args, out IntPtr result); -#if ac2009 - [System.Security.SuppressUnmanagedCodeSecurity] - [DllImport("acad.exe", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl, EntryPoint = "?acedEvaluateLisp@@YAHPB_WAAPAUresbuf@@@Z")] - private static extern int AcedEvaluateLisp(string lispLine, out IntPtr result); - - [DllImport("acad.exe", CallingConvention = CallingConvention.Cdecl, EntryPoint = "acedInvoke")] - private static extern int AcedInvoke(IntPtr args, out IntPtr result); +#if NET35 + [DllImport("acad.exe", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "?acedEvaluateLisp@@YAHPB_WAAPAUresbuf@@@Z")] #else - [System.Security.SuppressUnmanagedCodeSecurity] - [DllImport("accore.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl, EntryPoint = "?acedEvaluateLisp@@YAHPEB_WAEAPEAUresbuf@@@Z")] - private static extern int AcedEvaluateLisp(string lispLine, out IntPtr result); + // 高版本此接口不能使用lisp(command "xx"),但是可以直接在自动运行接口上 + [DllImport("accore.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "?acedEvaluateLisp@@YAHPEB_WAEAPEAUresbuf@@@Z")] +#endif + [System.Security.SuppressUnmanagedCodeSecurity]// 初始化默认值 + static extern int AcedEvaluateLisp(string lispLine, out IntPtr result); - [DllImport("accore.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "acedInvoke")] - private static extern int AcedInvoke(IntPtr args, out IntPtr result); +#if NET35 + [DllImport("acad.exe", +#else + [DllImport("accore.dll", #endif + CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ads_queueexpr")] + static extern int Ads_queueexpr(string strExpr); + + public enum RunLispFlag : byte + { + AdsQueueexpr = 1, + AcedEvaluateLisp = 2, + SendStringToExecute = 4, + } + + /* + * 测试命令: + * [CommandMethod(nameof(CmdTest_RunLisp))] + * public static void CmdTest_RunLisp() + * { + * var res = RunLisp("(setq abc 10)"); + * } + * 调用方式: + * (command "CmdTest_RunLisp1") + * bug说明: + * AcedEvaluateLisp 接口 + * 在高版本调用时候没有运行成功,使得 !abc 没有值 + * 在cad08成功,此bug与CommandFlags无关 + * 解决方案: + * 0x01 用异步接口,但是这样是显式调用了: + * (setq thisdrawing (vla-get-activedocument (vlax-get-acad-object)))(vla-SendCommand thisdrawing "CmdTest_RunLisp1 ") + * 0x02 使用 Ads_queueexpr 接口 + */ /// /// 发送lisp语句字符串到cad执行 /// /// 编辑器对象 - /// lisp语句 + /// lisp语句 + /// 运行方式 /// 缓冲结果,返回值 -#pragma warning disable IDE0060 // 删除未使用的参数 - public static ResultBuffer RunLisp(this Editor ed, string arg) -#pragma warning restore IDE0060 // 删除未使用的参数 + public static ResultBuffer? RunLisp(this Editor ed, string lispCode, RunLispFlag flag = RunLispFlag.AdsQueueexpr) { - _ = AcedEvaluateLisp(arg, out IntPtr rb); - if (rb != IntPtr.Zero) + if ((flag & RunLispFlag.AdsQueueexpr) == RunLispFlag.AdsQueueexpr) { - try - { - var rbb = DisposableWrapper.Create(typeof(ResultBuffer), rb, true) as ResultBuffer; - return rbb; - } - catch - { - return null; - } + // 这个在08/12发送lisp不会出错,但是发送bo命令出错了. + // 0x01 设置 CommandFlags.Session 可以同步, + // 0x02 自执行发送lisp都是异步,用来发送 含有(command)的lisp的 + _ = Ads_queueexpr(lispCode + "\n"); + } + if ((flag & RunLispFlag.AcedEvaluateLisp) == RunLispFlag.AcedEvaluateLisp) + { + _ = AcedEvaluateLisp(lispCode, out IntPtr rb); + if (rb != IntPtr.Zero) + return (ResultBuffer)DisposableWrapper.Create(typeof(ResultBuffer), rb, true); + } + if ((flag & RunLispFlag.SendStringToExecute) == RunLispFlag.SendStringToExecute) + { + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + doc?.SendStringToExecute(lispCode + "\n", false, false, false); } return null; } + #endregion + + #region Export + /// + /// 输出WMF
+ /// 此函数不适用于后台 + ///
+ /// 命令行对象 + /// 保存文件 + /// 选择集的对象,为null时候手选 + /// 是否清空选择集 + /// + public static void ComExportWMF(this Editor editor, string saveFile, + ObjectId[]? ids = null, bool wmfSetDel = false) + { + if (string.IsNullOrEmpty(saveFile)) + throw new ArgumentNullException(nameof(saveFile)); + if (File.Exists(saveFile)) + throw new FileFormatException("文件重复:" + saveFile); - #endregion 执行lisp -} + var dm = Acap.DocumentManager; + if (dm.Count == 0) + return; + + // 剔除后缀 + int dot = saveFile.LastIndexOf('.'); + if (dot != -1) + { + // 因为文件名可以有.所以后缀点必须是最后\的后面 + int s = saveFile.LastIndexOf('\\'); + if (s < dot) + saveFile = saveFile.Substring(0, dot); + } + + // ActiveSelectionSet: + // 第一次执行会触发选择,再次重复命令执行的时候,它会无法再选择(即使清空选择集). + // 因此此处netAPI进行选择,它就能读取当前选择集缓冲区的对象 + if (ids == null || ids.Length == 0) + { + var psr = editor.SelectImplied();// 预选 + if (psr.Status != PromptStatus.OK) + psr = editor.GetSelection();// 手选 + if (psr.Status != PromptStatus.OK) + return; + ids = psr.Value.GetObjectIds(); + } + editor.SetImpliedSelection(ids); + +#if zcad + var com = Acap.ZcadApplication; +#else + var com = Acap.AcadApplication; +#endif + var doc = com.GetProperty("ActiveDocument"); + var wmfSet = doc.GetProperty("ActiveSelectionSet"); + + // TODO 20221007 导出wmf的bug + // cad21 先net选择,再进行,此处再选择一次? + // cad21 调试期间无法选择性粘贴? + var exp = doc.Invoke("Export", saveFile, "wmf", wmfSet); // JPGOUT,PNGOUT + if (wmfSetDel) + wmfSet.Invoke("Delete"); + } + #endregion + + /// + /// 可以发送透明命令的状态
+ /// 福萝卜:这个应该是修正ribbon里输入丢焦点的问题,低版本可以不要 + ///
+ /// + /// + public static bool IsQuiescentForTransparentCommand(this Editor ed) + { +#if NET35 + //if (ed.IsQuiescent) + //{ + //} + return true; +#else + return ed.IsQuiescentForTransparentCommand; +#endif + } +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/ExtensionMethod/Entity/ArcEx.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/Entity/ArcEx.cs new file mode 100644 index 0000000000000000000000000000000000000000..f3ef9508c736ad81171e8e0847202b20fb3d9d6e --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/Entity/ArcEx.cs @@ -0,0 +1,73 @@ +namespace IFoxCAD.Cad; + +/// +/// 圆弧扩展类 +/// +public static class ArcEx +{ + #region 圆弧 + + /// + /// 根据圆心、起点、终点来创建圆弧(二维) + /// + /// 起点 + /// 圆心 + /// 终点 + /// 圆弧 + public static Arc CreateArcSCE(Point3d startPoint, Point3d centerPoint, Point3d endPoint) + { + Arc arc = new(); + arc.SetDatabaseDefaults(); + arc.Center = centerPoint; + arc.Radius = centerPoint.DistanceTo(startPoint); + Vector2d startVector = new(startPoint.X - centerPoint.X, startPoint.Y - centerPoint.Y); + Vector2d endVector = new(endPoint.X - centerPoint.X, endPoint.Y - centerPoint.Y); + // 计算起始和终止角度 + arc.StartAngle = startVector.Angle; + arc.EndAngle = endVector.Angle; + return arc; + } + /// + /// 三点法创建圆弧(二维) + /// + /// 起点 + /// 圆弧上的点 + /// 终点 + /// 圆弧 + public static Arc CreateArc(Point3d startPoint, Point3d pointOnArc, Point3d endPoint) + { + // 创建一个几何类的圆弧对象 + CircularArc3d geArc = new(startPoint, pointOnArc, endPoint); + // 将几何类圆弧对象的圆心和半径赋值给圆弧 +#if !gcad +#if NET35 + return (Arc)geArc.ToCurve(); +#else + return (Arc)Curve.CreateFromGeCurve(geArc); +#endif +#else + return (Arc)geArc.ToCurve(); +#endif + } + + /// + /// 根据起点、圆心和圆弧角度创建圆弧(二维) + /// + /// 起点 + /// 圆心 + /// 圆弧角度 + /// 圆弧 + public static Arc CreateArc(Point3d startPoint, Point3d centerPoint, double angle) + { + Arc arc = new(); + arc.SetDatabaseDefaults(); + arc.Center = centerPoint; + arc.Radius = centerPoint.DistanceTo(startPoint); + Vector2d startVector = new(startPoint.X - centerPoint.X, startPoint.Y - centerPoint.Y); + arc.StartAngle = startVector.Angle; + arc.EndAngle = startVector.Angle + angle; + return arc; + } + + #endregion +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/ExtensionMethod/Entity/BlockReferenceEx.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/Entity/BlockReferenceEx.cs new file mode 100644 index 0000000000000000000000000000000000000000..203bc5451479e22d9118f6dcedd1941ffd86cb22 --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/Entity/BlockReferenceEx.cs @@ -0,0 +1,143 @@ + + +namespace IFoxCAD.Cad; + +/// +/// 块参照扩展类 +/// +public static class BlockReferenceEx +{ + #region 裁剪块参照 + + private const string filterDictName = "ACAD_FILTER"; + private const string spatialName = "SPATIAL"; + + /// + /// 裁剪块参照 + /// + /// 块参照 + /// 裁剪多边形点表 + public static void ClipBlockRef(this BlockReference bref, IEnumerable pt3ds) + { + Matrix3d mat = bref.BlockTransform.Inverse(); + var pts = + pt3ds + .Select(p => p.TransformBy(mat).Point2d()) + .ToCollection(); + + SpatialFilterDefinition sfd = new(pts, Vector3d.ZAxis, 0.0, 0.0, 0.0, true); + using SpatialFilter sf = new() { Definition = sfd }; + var dict = bref.GetXDictionary()!.GetSubDictionary(true, new string[] { filterDictName })!; + dict.SetAt(spatialName, sf); + // SetToDictionary(dict, spatialName, sf); + } + + /// + /// 裁剪块参照 + /// + /// 块参照 + /// 第一角点 + /// 第二角点 + public static void ClipBlockRef(this BlockReference bref, Point3d pt1, Point3d pt2) + { + Matrix3d mat = bref.BlockTransform.Inverse(); + pt1 = pt1.TransformBy(mat); + pt2 = pt2.TransformBy(mat); + + Point2dCollection pts = new() + { + new Point2d(Math.Min(pt1.X, pt2.X), Math.Min(pt1.Y, pt2.Y)), + new Point2d(Math.Max(pt1.X, pt2.X), Math.Max(pt1.Y, pt2.Y)) + }; + + using SpatialFilter sf = new() + { + Definition = new(pts, Vector3d.ZAxis, 0.0, 0.0, 0.0, true) + }; + var dict = bref.GetXDictionary()! + .GetSubDictionary(true, new string[] { filterDictName })!; + dict.SetAt(spatialName, sf); + // SetToDictionary(dict, spatialName, sf); +#if !acad + pts.Dispose(); +#endif + } + #endregion + + #region 属性 + /// + /// 更新动态块属性值 + /// + /// 动态块 + /// 属性值字典 + public static void ChangeBlockProperty(this BlockReference blockReference, + Dictionary propertyNameValues) + { + if (!blockReference.IsDynamicBlock) + return; + + using (blockReference.ForWrite()) + { + foreach (DynamicBlockReferenceProperty item in blockReference.DynamicBlockReferencePropertyCollection) + if (propertyNameValues.ContainsKey(item.PropertyName)) + item.Value = propertyNameValues[item.PropertyName]; + } + } + #endregion + + /// + /// 遍历块内 + /// + /// + /// + /// + /// + [System.Diagnostics.DebuggerStepThrough] + public static void ForEach(this BlockReference brf, Action action, DBTrans? tr = null) + { + //if (action == null) + // throw new ArgumentNullException(nameof(action)); + action.NotNull(nameof(action)); + tr ??= DBTrans.Top; + var btr = tr.GetObject(brf.BlockTableRecord); + if (btr == null) + return; + btr.ForEach(action); + } + /// + /// 遍历块内 + /// + /// + /// + /// + /// + [System.Diagnostics.DebuggerStepThrough] + public static void ForEach(this BlockReference brf, Action action, DBTrans? tr = null) + { + //if (action == null) + // throw new ArgumentNullException(nameof(action)); + action.NotNull(nameof(action)); + tr ??= DBTrans.Top; + var btr = tr.GetObject(brf.BlockTableRecord); + if (btr == null) + return; + btr.ForEach(action); + } + /// + /// 遍历块内 + /// + /// + /// + /// + /// + [System.Diagnostics.DebuggerStepThrough] + public static void ForEach(this BlockReference brf, Action action, DBTrans? tr = null) + { + action.NotNull(nameof(action)); + tr ??= DBTrans.Top; + var btr = tr.GetObject(brf.BlockTableRecord); + if (btr == null) + return; + btr.ForEach(action); + } +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/ExtensionMethod/Entity/CircleEx.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/Entity/CircleEx.cs new file mode 100644 index 0000000000000000000000000000000000000000..4488f3a5d62545be2f8b25739d968c3edb9dec93 --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/Entity/CircleEx.cs @@ -0,0 +1,59 @@ +namespace IFoxCAD.Cad; + +/// +/// 圆扩展类 +/// +public static class CircleEx +{ + + /// + /// 两点创建圆(两点中点为圆心) + /// + /// 起点 + /// 终点 + /// + public static Circle CreateCircle(Point3d startPoint, Point3d endPoint) + { + Circle circle = new(); + circle.SetDatabaseDefaults(); + circle.Center = startPoint.GetMidPointTo(endPoint); + circle.Radius = startPoint.DistanceTo(endPoint) * 0.5; + return circle; + } + + /// + /// 三点法创建圆(失败则返回Null) + /// + /// 第一点 + /// 第二点 + /// 第三点 + /// + public static Circle? CreateCircle(Point3d pt1, Point3d pt2, Point3d pt3) + { + // 先判断三点是否共线,得到pt1点指向pt2、pt2点的矢量 + Vector3d va = pt1.GetVectorTo(pt2); + Vector3d vb = pt1.GetVectorTo(pt3); + // 如两矢量夹角为0或180度(π弧度),则三点共线. + if (va.GetAngleTo(vb) == 0 | va.GetAngleTo(vb) == Math.PI) + return null; + + // 创建一个几何类的圆弧对象 + CircularArc3d geArc = new(pt1, pt2, pt3); + return geArc.ToCircle(); + } + + /// + /// 通过圆心,半径绘制圆形 + /// + /// 圆心 + /// 半径 + /// 法向量的X + /// 法向量的Y + /// 法向量的Z + /// + public static Circle? CreateCircle(Point3d center, double radius, double vex = 0, double vey = 0, double vez = 1) + { + return new Circle(center, new Vector3d(vex, vey, vez), radius);// 平面法向量XY方向 + } + +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/ExtensionMethod/Entity/DBTextEx.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/Entity/DBTextEx.cs new file mode 100644 index 0000000000000000000000000000000000000000..5e01e58596bc75241b3598fe33f56812a3cdb3da --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/Entity/DBTextEx.cs @@ -0,0 +1,22 @@ +namespace IFoxCAD.Cad; + +/// +/// 单行文字扩展类 +/// +public static class DBTextEx +{ + + /// + /// 更正单行文字的镜像属性 + /// + /// 单行文字 + public static void ValidateMirror(this DBText txt) + { + if (!txt.Database.Mirrtext) + { + txt.IsMirroredInX = false; + txt.IsMirroredInY = false; + } + } + +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/ExtensionMethod/Entity/EntityBoundingInfo.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/Entity/EntityBoundingInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..baf2fc41a80ff0374da398c5843ae93f2880ca79 --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/Entity/EntityBoundingInfo.cs @@ -0,0 +1,281 @@ +#define Debug_Cause_Error +namespace IFoxCAD.Cad; + +/// +/// AABB和OBB信息 +/// +public struct BoundingInfo +{ + public double MinX; + public double MinY; + public double MinZ; + + public double MaxX; + public double MaxY; + public double MaxZ; + + /// + /// AABB这里永远是0 + /// + public double Angle; + public bool IsEffective; + + public Point3d Min => new(MinX, MinY, MinZ); + public Point3d Max => new(MaxX, MaxY, MaxZ); + public Extents3d Extents3d => new(Min, Max); + public Extents2d Extents2d => new(MinX, MinY, MaxX, MaxY); + + public BoundingInfo(double minX, double minY, double minZ, + double maxX, double maxY, double maxZ, + bool isEffective, double angle = 0) + { + MinX = minX; + MinY = minY; + MinZ = minZ; + MaxX = maxX; + MaxY = maxY; + MaxZ = maxZ; + IsEffective = isEffective; + Angle = angle; + } + + public BoundingInfo(Point3d min, Point3d max, bool isEffective, double angle = 0) + : this(min.X, min.Y, min.Z, + max.X, max.Y, max.Z, + isEffective, angle) + { } + + // public BoundingInfo(Rect rect, double angle = 0) + // { + // MinX = rect.X; + // MinY = rect.Y; + // MinZ = 0; + // MaxX = rect.Right; + // MaxY = rect.Top; + // MaxZ = 0; + // Angle = angle; + // } + + public override string ToString() + { + return Extents3d.ToString(); + } + + public void Move(Point3d pt1, Point3d pt2) + { + var ve = pt1 - pt2; + MinX -= ve.X; + MinY -= ve.Y; + MinZ -= ve.Z; + MaxX -= ve.X; + MaxY -= ve.Y; + MaxZ -= ve.Z; + } +} + +public class EntityBoundingInfo +{ + #region 保存异常类型的日志 + /// + /// 包围盒错误文件路径 + /// + public static string BoxLogAddress + { + get + { + _BoxLogAddress ??= LogHelper.GetDefaultOption(nameof(GetBoundingInfo) + ".config"); + return _BoxLogAddress; + } + set { _BoxLogAddress = value; } + } + static string? _BoxLogAddress; + static readonly HashSet _typeNames; + /// + /// 为了保证更好的性能, + /// 只是在第一次调用此功能的时候进行读取, + /// 免得高频调用时候高频触发磁盘 + /// + static EntityBoundingInfo() + { + _typeNames = new(); + if (!File.Exists(BoxLogAddress)) + return; + ExceptionToHashSet(); + } + + /// + /// 读取日志的异常到容器 + /// + static void ExceptionToHashSet() + { + var old_LogAddress = LogHelper.LogAddress; + try + { + LogHelper.OptionFile(BoxLogAddress); + var logTxts = new FileLogger().ReadLog(); + for (int i = 0; i < logTxts.Length; i++) + { + var line = logTxts[i]; + if (line.Contains(nameof(LogTxt.备注信息))) + { + int index = line.IndexOf(":"); + index = line.IndexOf("\"", index) + 1;// 1是"\"" + int index2 = line.IndexOf("\"", index); + var msg = line.Substring(index, index2 - index); + _typeNames.Add(msg); + } + } + } + finally + { + LogHelper.LogAddress = old_LogAddress; + } + } + + /// + /// 写入容器类型到异常日志 + /// + /// + /// + static void ExceptionToLog(Exception e, Entity ent) + { + // 无法处理的错误类型将被记录 + // 如果错误无法try,而是cad直接致命错误,那么此处也不会被写入, + // 这种情况无法避免程序安全性,总不能写了日志再去删除日志词条,这样会造成频繁IO的 + // 遇到一个不认识的类型再去写入?然后记录它是否可以写入? + var old_LogAddress = LogHelper.LogAddress; + var old_FlagOutFile = LogHelper.FlagOutFile; + try + { + LogHelper.FlagOutFile = true; + LogHelper.OptionFile(BoxLogAddress); + e.WriteLog(ent.GetType().Name, LogTarget.FileNotException); + } + finally + { + LogHelper.LogAddress = old_LogAddress; + LogHelper.FlagOutFile = old_FlagOutFile; + } + } + #endregion + + /// + /// 获取图元包围盒 + /// + /// + /// (左下角,右上角,是否有效) + /// 异常: + /// 会将包围盒类型记录到所属路径中,以此查询 + public static BoundingInfo GetBoundingInfo(Entity ent) + { +#if Debug_Cause_Error + // 错误类型处理 + if (ent is AttributeDefinition) // 属性定义 + return new(Point3d.Origin, Point3d.Origin, false); + if (ent is Xline xline)// 参照线 + return new(xline.BasePoint, xline.BasePoint, true); + if (ent is Ray ray)// 射线 + return new(ray.BasePoint, ray.BasePoint, true); +#endif + // 指定类型处理 + if (ent is BlockReference brf) + return GetBoxInfoInBlockReference(brf); + if (ent is MText mText) + return GetBoxInfoInMText(mText); + + if (!_typeNames.Contains(ent.GetType().Name)) // 屏蔽天正等等缺失包围盒的类型 + try + { + return new(ent.GeometricExtents.MinPoint, ent.GeometricExtents.MaxPoint, true); + } + catch (Exception e) { ExceptionToLog(e, ent); } + return new(Point3d.Origin, Point3d.Origin, false); + } + + /// + /// 处理块参照 + /// + static BoundingInfo GetBoxInfoInBlockReference(BlockReference brf) + { + try + { + // 这个获取是原点附近,需要平移到块基点 + var fit = brf.GeometryExtentsBestFit(); + var minX = fit.MinPoint.X + brf.Position.X; + var minY = fit.MinPoint.Y + brf.Position.Y; + var minZ = fit.MinPoint.Z + brf.Position.Z; + var maxX = fit.MaxPoint.X + brf.Position.X; + var maxY = fit.MaxPoint.Y + brf.Position.Y; + var maxZ = fit.MaxPoint.Z + brf.Position.Z; + return new(minX, minY, minZ, maxX, maxY, maxZ, true); + } + catch + { + // 如果是一条参照线的组块,将导致获取包围盒时报错 + // 0x01 是否需要进入块内,然后拿到每个图元的BasePoint再计算中点?感觉过于复杂. + // 0x02 这个时候拿基点走就算了 + return new(brf.Position, brf.Position, true); + } + } + + /// + /// 处理多行文字 + /// + static BoundingInfo GetBoxInfoInMText(MText mtxt) + { + /* + * MText Aussehen + * ------------------------------------ + * | | | + * | | |ht + * | | | + * |-----wl-------插入点------wr-------| + * | | | + * | | |hb + * | | | + * ------------------------------------ + */ + + double width = mtxt.ActualWidth;// 实际宽度 + double height = mtxt.ActualHeight;// 实际高度 + double wl = 0.0; + double hb = 0.0; + + // 对齐方式 + switch (mtxt.Attachment) + { + case AttachmentPoint.TopCenter: + case AttachmentPoint.MiddleCenter: + case AttachmentPoint.BottomCenter: + wl = width * -0.5; + break; + case AttachmentPoint.TopRight: + case AttachmentPoint.MiddleRight: + case AttachmentPoint.BottomRight: + wl = -width; + break; + } + switch (mtxt.Attachment) + { + case AttachmentPoint.TopLeft: + case AttachmentPoint.TopCenter: + case AttachmentPoint.TopRight: + hb = -height;// 下边线到插入点的距离 + break; + case AttachmentPoint.MiddleLeft: + case AttachmentPoint.MiddleCenter: + case AttachmentPoint.MiddleRight: + hb = height * -0.5; + break; + } + + double wr = width + wl; + double ht = height + hb; + + Point3d center = mtxt.Location; + return new(center.X + wl, center.Y + hb, 0, + center.X + wr, center.Y + ht, 0, + true, + mtxt.Rotation); + } +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/ExtensionMethod/Entity/EntityEx.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/Entity/EntityEx.cs new file mode 100644 index 0000000000000000000000000000000000000000..65f7ee4c674fadac8e3ebecc36a74a6fabde9183 --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/Entity/EntityEx.cs @@ -0,0 +1,123 @@ +namespace IFoxCAD.Cad; + + +/// +/// 实体图元扩展类 +/// +public static class EntityEx +{ + + + #region 实体线性变换 + + /// + /// 移动实体 + /// + /// 实体 + /// 基点 + /// 目标点 + public static void Move(this Entity ent, Point3d from, Point3d to) + { + ent.TransformBy(Matrix3d.Displacement(to - from)); + } + + /// + /// 缩放实体 + /// + /// 实体 + /// 缩放基点坐标 + /// 缩放比例 + public static void Scale(this Entity ent, Point3d center, double scaleValue) + { + ent.TransformBy(Matrix3d.Scaling(scaleValue, center)); + } + + /// + /// 旋转实体 + /// + /// 实体 + /// 旋转中心 + /// 转角,弧度制,正数为顺时针 + /// 旋转平面的法向矢量 + public static void Rotation(this Entity ent, Point3d center, double angle, Vector3d normal) + { + ent.TransformBy(Matrix3d.Rotation(angle, normal, center)); + } + + /// + /// 在XY平面内旋转实体 + /// + /// 实体 + /// 旋转中心 + /// 转角,弧度制,正数为顺时针 + public static void Rotation(this Entity ent, Point3d center, double angle) + { + ent.TransformBy(Matrix3d.Rotation(angle, Vector3d.ZAxis.TransformBy(ent.Ecs), center)); + } + + /// + /// 按对称轴镜像实体 + /// + /// 实体 + /// 对称轴起点 + /// 对称轴终点 + public static void Mirror(this Entity ent, Point3d startPoint, Point3d endPoint) + { + ent.TransformBy(Matrix3d.Mirroring(new Line3d(startPoint, endPoint))); + } + + /// + /// 按对称面镜像实体 + /// + /// 实体 + /// 对称平面 + public static void Mirror(this Entity ent, Plane plane) + { + ent.TransformBy(Matrix3d.Mirroring(plane)); + } + + /// + /// 按对称点镜像实体 + /// + /// 实体 + /// 对称点 + public static void Mirror(this Entity ent, Point3d basePoint) + { + ent.TransformBy(Matrix3d.Mirroring(basePoint)); + } + + #endregion + + #region 实体范围 + /// + /// 获取实体集合的范围 + /// + /// 实体迭代器 + /// 实体集合的范围 + public static Extents3d GetExtents(this IEnumerable ents) + { + var ext = new Extents3d(); + foreach (var item in ents) + { + ext.AddExtents(item.GeometricExtents); + } + return ext; + } + #endregion + + + + /// + /// 获取图元包围盒 + /// + /// + /// 包围盒信息 + /// 异常: + /// 会将包围盒类型记录到所属路径中,以此查询 + public static BoundingInfo GetBoundingBoxEx(this Entity ent) + { + return EntityBoundingInfo.GetBoundingInfo(ent); + } + + +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/ExtensionMethod/Entity/MTextEx.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/Entity/MTextEx.cs new file mode 100644 index 0000000000000000000000000000000000000000..fd39bac75dd60fb93c7d42403a53a3fb505bc6a0 --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/Entity/MTextEx.cs @@ -0,0 +1,45 @@ +namespace IFoxCAD.Cad; + +/// +/// 多行文字扩展类 +/// +public static class MTextEx +{ + + /// + /// 炸散多行文字 + /// + /// 存储多行文字炸散之后的对象的类型 + /// 多行文字 + /// 存储对象变量 + /// 回调函数,用于处理炸散之后的对象 + /// + /// 多行文字炸散后的对象
+ /// 回调函数处理的结果 + ///
+ /// + public static void ExplodeFragments(this MText mt, T obj, Func mTextFragmentCallback) + { + mt.ExplodeFragments( + (f, o) => + mTextFragmentCallback(f, (T)o), obj); + } + + /// + /// 获取多行文字的无格式文本 + /// + /// 多行文字 + /// 文本 + public static string GetUnFormatString(this MText mt) + { + List strs = new(); + mt.ExplodeFragments( + strs, + (f, o) => { + o.Add(f.Text); + return MTextFragmentCallbackStatus.Continue; + }); + return string.Join("", strs.ToArray()); + } + +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/ExtensionMethod/Entity/PolylineEx.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/Entity/PolylineEx.cs new file mode 100644 index 0000000000000000000000000000000000000000..02681b413e1dd62e0b70c6b142dfecc65434e85a --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/Entity/PolylineEx.cs @@ -0,0 +1,55 @@ +namespace IFoxCAD.Cad; + +/// +/// 多段线扩展类 +/// +public static class PolylineEx +{ + /// + /// 获取二维多段线的端点坐标 + /// + /// 二维多段线 + /// 事务 + /// 端点坐标集合 + public static IEnumerable GetPoints(this Polyline2d pl2d, DBTrans? trans = null) + { + trans ??= DBTrans.Top; + foreach (ObjectId id in pl2d) + { + var vertex = trans.GetObject(id); + if (vertex != null) + yield return vertex.Position; + } + + } + + /// + /// 获取三维多段线的端点坐标 + /// + /// 三维多段线 + /// 事务 + /// 端点坐标集合 + public static IEnumerable GetPoints(this Polyline3d pl3d, DBTrans? trans = null) + { + trans ??= DBTrans.Top; + foreach (ObjectId id in pl3d) + { + var vertex = trans.GetObject(id); + if (vertex != null) + yield return vertex.Position; + } + } + + /// + /// 获取多段线的端点坐标 + /// + /// 多段线 + /// 端点坐标集合 + public static IEnumerable GetPoints(this Polyline pl) + { + return + Enumerable + .Range(0, pl.NumberOfVertices) + .Select(pl.GetPoint3dAt); + } +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/ExtensionMethod/Enums.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/Enums.cs similarity index 38% rename from src/IFoxCAD.Cad/ExtensionMethod/Enums.cs rename to src/CAD/IFox.CAD.Shared/ExtensionMethod/Enums.cs index bae131f470e52fa65edf024abc6112fbf59b955d..d100ded9fb2ff4a5bc0a15ebee691f1cf15e1e7b 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/Enums.cs +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/Enums.cs @@ -1,5 +1,91 @@ namespace IFoxCAD.Cad; +/// +/// 参照路径转换 +/// +public enum PathConverterModes : byte +{ + /// + /// 相对路径 + /// + Relative, + /// + /// 绝对路径 + /// + Complete +} + +/// +/// 参照绑定 +/// +public enum XrefModes : byte +{ + /// + /// 卸载 + /// + Unload, + /// + /// 重载 + /// + Reload, + /// + /// 拆离 + /// + Detach, + /// + /// 绑定 + /// + Bind, +} + +public enum SymModes : ushort +{ + /// + /// 块表 + /// + BlockTable = 1, + + /// + /// 图层表 + /// + LayerTable = 2, + /// + /// 文字样式表 + /// + TextStyleTable = 4, + /// + /// 注册应用程序表 + /// + RegAppTable = 8, + /// + /// 标注样式表 + /// + DimStyleTable = 16, + /// + /// 线型表 + /// + LinetypeTable = 32, + Option1 = LayerTable | TextStyleTable | DimStyleTable | LinetypeTable | RegAppTable, + + /// + /// 用户坐标系表 + /// + UcsTable = 64, + /// + /// 视图表 + /// + ViewTable = 128, + /// + /// 视口表 + /// + ViewportTable = 256, + Option2 = UcsTable | ViewTable | ViewportTable, + + // 全部 + All = BlockTable | Option1 | Option2 +} + + /// /// 坐标系类型枚举 /// @@ -70,3 +156,19 @@ public enum PointOnRegionType ///
Error } + + + +public enum FontTTF +{ + [Description("宋体.ttf")] + 宋体, + [Description("simfang.ttf")] + 仿宋, + [Description("FSGB2312.ttf")] + 仿宋GB2312, + [Description("Arial.ttf")] + Arial, + [Description("Romans")] + Romans +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/ExtensionMethod/Filer/DwgFiler.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/Filer/DwgFiler.cs new file mode 100644 index 0000000000000000000000000000000000000000..6820f375a806e362159ba830d8c7abbfd9f38e4c --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/Filer/DwgFiler.cs @@ -0,0 +1,506 @@ +namespace IFoxCAD.Cad; + +/* + Arx自定义实体类,加 读函数(assertReadEnabled)和写函数(assertWriteEnabled) + + 所有属性位置都不要改动位置,因为涉及序列化 + [Serializable] 设置类 可以序列化 + [Newtonsoft.Json.JsonIgnore] 设置成员 不可序列化 +*/ + +#if NewtonsoftJson +[JsonConverter(typeof(ObjectIdConverter))] +#endif +[Serializable] +public class DwgFiler : Cad_DwgFiler +{ +#if NET35 + public int m_Position; +#else + public long m_Position; +#endif + public FilerType m_FilerType; + public Cad_ErrorStatus m_FilerStatus; + public List AddressList; + public int AddressListPt = 0; + public List BinaryChunkList; + public int BinaryChunkListPt = 0; + public List BooleanList; + public int BooleanListPt = 0; + public List ByteList; + public int ByteListPt = 0; + public List BytesList; + public int BytesListPt = 0; + public List DoubleList; + public int DoubleListPt = 0; + public List HandleList; + public int HandleListPt = 0; + //[NonSerialized] + //[ScriptIgnore] + public List HardOwnershipIdList; + public int HardOwnershipIdListPt = 0; + //[NonSerialized] + //[ScriptIgnore] + public List HardPointerIdList; + public int HardPointerIdListPt = 0; + public List Int16List; + public int Int16ListPt = 0; + public List Int32List; + public int Int32ListPt = 0; +#if !NET35 + public List Int64List; + public int Int64ListPt = 0; +#endif + public List Point2dList; + public int Point2dListPt = 0; + public List Point3dList; + public int Point3dListPt = 0; + public List Scale3dList; + public int Scale3dListPt = 0; + //[NonSerialized] + //[ScriptIgnore] + public List SoftOwnershipIdList; + public int SoftOwnershipIdListPt = 0; + //[NonSerialized] + //[ScriptIgnore] + public List SoftPointerIdList; + public int SoftPointerIdListPt = 0; + public List StringList; + public int StringListPt = 0; + public List Uint16List; + public int uint16ListPt = 0; + public List Uint32List; + public int uint32ListPt = 0; +#if !NET35 + public List Uint64List; + public int uint64ListPt = 0; +#endif + public List Vector2dList; + public int Vector2dListPt = 0; + public List Vector3dList; + public int Vector3dListPt = 0; + + public DwgFiler() + { + m_Position = 0; + m_FilerType = FilerType.CopyFiler; + m_FilerStatus = Cad_ErrorStatus.OK; + AddressList = new(); + BinaryChunkList = new(); + BooleanList = new(); + ByteList = new(); + BytesList = new(); + DoubleList = new(); + HandleList = new(); + HardOwnershipIdList = new(); + HardPointerIdList = new(); + Int16List = new(); + Int32List = new(); +#if !NET35 + Int64List = new(); +#endif + Point2dList = new(); + Point3dList = new(); + Scale3dList = new(); + SoftOwnershipIdList = new(); + SoftPointerIdList = new(); + StringList = new(); + Uint16List = new(); + Uint32List = new(); +#if !NET35 + Uint64List = new(); +#endif + Vector2dList = new(); + Vector3dList = new(); + } + +#if NET35 + public override int Position => m_Position; +#else + public override long Position => m_Position; +#endif + public override FilerType FilerType => m_FilerType; + + public override Cad_ErrorStatus FilerStatus + { + get { return m_FilerStatus; } + set { m_FilerStatus = value; } + } + + public override IntPtr ReadAddress() + { + if (AddressList.Count == 0) + return new(); + return AddressList[AddressListPt++]; + } + + public override byte[]? ReadBinaryChunk() + { + if (BinaryChunkList.Count == 0) + return null; + return BinaryChunkList[BinaryChunkListPt++]; + } + + public override bool ReadBoolean() + { + if (BooleanList.Count == 0) + return false; + return BooleanList[BooleanListPt++]; + } + + public override byte ReadByte() + { + if (ByteList.Count == 0) + return 0; + return ByteList[ByteListPt++]; + } + + public override void ReadBytes(byte[] value) + { + if (ByteList.Count == 0) + return; + value = new byte[BytesList[BytesListPt].Length]; + BytesList[BytesListPt++].CopyTo(value, 0); + } + + public override double ReadDouble() + { + if (DoubleList.Count == 0) + return 0; + return DoubleList[DoubleListPt++]; + } + + public override Handle ReadHandle() + { + if (HandleList.Count == 0) + return new(); + return HandleList[HandleListPt++]; + } + + public override ObjectId ReadHardOwnershipId() + { + if (HardOwnershipIdList.Count == 0) + return new(); + return HardOwnershipIdList[HardOwnershipIdListPt++]; + } + + public override ObjectId ReadHardPointerId() + { + if (HardPointerIdList.Count == 0) + return new(); + return HardPointerIdList[HardPointerIdListPt++]; + } + + public override short ReadInt16() + { + if (Int16List.Count == 0) + return 0; + return Int16List[Int16ListPt++]; + } + + public override int ReadInt32() + { + if (Int32List.Count == 0) + return 0; + return Int32List[Int32ListPt++]; + } + +#if !NET35 + public override long ReadInt64() + { + if (Int64List.Count == 0) + return 0; + return Int64List[Int64ListPt++]; + } +#endif + + public override Point2d ReadPoint2d() + { + if (Point2dList.Count == 0) + return new(); + return Point2dList[Point2dListPt++]; + } + + public override Point3d ReadPoint3d() + { + if (Point3dList.Count == 0) + return new(); + return Point3dList[Point3dListPt++]; + } + + public override Scale3d ReadScale3d() + { + if (Scale3dList.Count == 0) + return new(); + return Scale3dList[Scale3dListPt++]; + } + + public override ObjectId ReadSoftOwnershipId() + { + if (SoftOwnershipIdList.Count == 0) + return new(); + return SoftOwnershipIdList[SoftOwnershipIdListPt++]; + } + + public override ObjectId ReadSoftPointerId() + { + if (SoftPointerIdList.Count == 0) + return new(); + return SoftPointerIdList[SoftPointerIdListPt++]; + } + + public override string? ReadString() + { + if (StringList.Count == 0) + return null; + return StringList[StringListPt++]; + } + + public override ushort ReadUInt16() + { + if (Uint16List.Count == 0) + return 0; + return Uint16List[uint16ListPt++]; + } + + public override uint ReadUInt32() + { + if (Uint32List.Count == 0) + return 0; + return Uint32List[uint32ListPt++]; + } + +#if !NET35 + public override ulong ReadUInt64() + { + if (Uint64List.Count == 0) + return 0; + return Uint64List[uint64ListPt++]; + } +#endif + + public override Vector2d ReadVector2d() + { + if (Vector2dList.Count == 0) + return new(); + return Vector2dList[Vector2dListPt++]; + } + + public override Vector3d ReadVector3d() + { + if (Vector3dList.Count == 0) + return new(); + return Vector3dList[Vector3dListPt++]; + } + + public override void ResetFilerStatus() + { + AddressList.Clear(); + AddressListPt = 0; + BinaryChunkList.Clear(); + BinaryChunkListPt = 0; + BooleanList.Clear(); + BooleanListPt = 0; + ByteList.Clear(); + ByteListPt = 0; + BytesList.Clear(); + BytesListPt = 0; + DoubleList.Clear(); + DoubleListPt = 0; + HandleList.Clear(); + HandleListPt = 0; + HardOwnershipIdList.Clear(); + HardOwnershipIdListPt = 0; + HardPointerIdList.Clear(); + HardPointerIdListPt = 0; + Int16List.Clear(); + Int16ListPt = 0; + Int32List.Clear(); + Int32ListPt = 0; +#if !NET35 + Int64List.Clear(); + Int64ListPt = 0; +#endif + Point2dList.Clear(); + Point2dListPt = 0; + Point3dList.Clear(); + Point3dListPt = 0; + Scale3dList.Clear(); + Scale3dListPt = 0; + SoftOwnershipIdList.Clear(); + SoftOwnershipIdListPt = 0; + SoftPointerIdList.Clear(); + SoftPointerIdListPt = 0; + StringList.Clear(); + StringListPt = 0; + Uint16List.Clear(); + uint16ListPt = 0; + Uint32List.Clear(); + uint32ListPt = 0; +#if !NET35 + Uint64List.Clear(); + uint64ListPt = 0; +#endif + Vector2dList.Clear(); + Vector2dListPt = 0; + Vector3dList.Clear(); + Vector3dListPt = 0; + + m_FilerType = FilerType.CopyFiler; + } + +#if zcad // 中望官方的问题 + public override void Seek(int offset, int method) + { + var ed = Acap.DocumentManager.MdiActiveDocument.Editor; + ed.WriteMessage(MethodInfo.GetCurrentMethod().Name + " = " + " \n "); + } + public override void Seek(long offset, int method) + { + Seek((int)offset, method); + } +#endif + +#if acad || gcad + public override void Seek( +#if NET35 + int +#else + long +#endif + offset, int method) + { + var ed = Acap.DocumentManager.MdiActiveDocument.Editor; + ed.WriteMessage(MethodInfo.GetCurrentMethod().Name + " = " + " \n "); + } +#endif + + public override void WriteAddress(IntPtr value) + { + AddressList.Add(value); + } + + public override void WriteBinaryChunk(byte[] chunk) + { + BinaryChunkList.Add(chunk); + } + + public override void WriteBoolean(bool value) + { + BooleanList.Add(value); + } + + public override void WriteByte(byte value) + { + ByteList.Add(value); + } + + public override void WriteBytes(byte[] value) + { + BytesList.Add(value); + } + + public override void WriteDouble(double value) + { + DoubleList.Add(value); + } + + public override void WriteHandle(Handle handle) + { + HandleList.Add(handle); + } + + public override void WriteHardOwnershipId(ObjectId value) + { + HardOwnershipIdList.Add(value); + } + + public override void WriteHardPointerId(ObjectId value) + { + HardPointerIdList.Add(value); + } + + public override void WriteInt16(short value) + { + Int16List.Add(value); + } + + public override void WriteInt32(int value) + { + Int32List.Add(value); + } + +#if !NET35 + public override void WriteInt64(long value) + { + Int64List.Add(value); + } +#endif + public override void WritePoint2d(Point2d value) + { + Point2dList.Add(value); + } + + public override void WritePoint3d(Point3d value) + { + Point3dList.Add(value); + } + + public override void WriteScale3d(Scale3d value) + { + Scale3dList.Add(value); + } + + public override void WriteSoftOwnershipId(ObjectId value) + { + SoftOwnershipIdList.Add(value); + } + + public override void WriteSoftPointerId(ObjectId value) + { + SoftPointerIdList.Add(value); + } + + public override void WriteString(string value) + { + StringList.Add(value); + } + + public override void WriteUInt16(ushort value) + { + Uint16List.Add(value); + } + + public override void WriteUInt32(uint value) + { + Uint32List.Add(value); + } + +#if !NET35 + public override void WriteUInt64(ulong value) + { + Uint64List.Add(value); + } +#endif + + public override void WriteVector2d(Vector2d value) + { + Vector2dList.Add(value); + } + + public override void WriteVector3d(Vector3d value) + { + Vector3dList.Add(value); + } + + public override string ToString() + { +#if NewtonsoftJson + return JsonConvert.SerializeObject(this, Formatting.Indented); +#else + JavaScriptSerializer serializer = new(); + serializer.RegisterConverters(new[] { new ObjectIdConverter() }); + return serializer.Serialize(this); +#endif + } +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/ExtensionMethod/Filer/DwgFilerEx.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/Filer/DwgFilerEx.cs new file mode 100644 index 0000000000000000000000000000000000000000..f91fb4c84011ac6d4f5457cc8ef2370b4ed9a46e --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/Filer/DwgFilerEx.cs @@ -0,0 +1,87 @@ +namespace IFoxCAD.Cad; + +/// +/// Dwg序列化 +/// +public class DwgFilerEx +{ + #region 成员 + DBObject? _entity; + public DwgFiler DwgFiler { get; private set; } + #endregion + + #region 构造 + /// + /// Dwg序列化 + /// + public DwgFilerEx(DwgFiler? Cad_DwgFiler = null) + { + if (Cad_DwgFiler == null) + Cad_DwgFiler = new(); + DwgFiler = Cad_DwgFiler; + } + + /// + /// Dwg序列化 + /// + public DwgFilerEx(DBObject entity) : this() + { + DwgOut(entity); + } + + #endregion + + #region 方法 + public void DwgOut(DBObject entity) + { + _entity = entity; + _entity.DwgOut(DwgFiler); + } + + public void DwgIn() + { + _entity?.DwgIn(DwgFiler); + } + + /// + /// 反序列化 + /// + /// + /// + public static DwgFilerEx? DeserializeObject(string json) + { +#if NewtonsoftJson + return JsonConvert.DeserializeObject(json); +#else + JavaScriptSerializer serializer = new(); + serializer.RegisterConverters(new[] { new ObjectIdConverter() }); + return serializer.Deserialize(json); +#endif + } + + /// + /// 序列化 + /// + /// + public string SerializeObject() + { +#if NewtonsoftJson + return JsonConvert.SerializeObject(DwgFiler, Formatting.Indented); +#else + JavaScriptSerializer serializer = new(); + serializer.RegisterConverters(new[] { new ObjectIdConverter() }); + return serializer.Serialize(DwgFiler); +#endif + } + + public override string ToString() + { + return DwgFiler.ToString(); + } + + public static implicit operator Cad_DwgFiler(DwgFilerEx df) + { + return df.DwgFiler; + } + #endregion +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/ExtensionMethod/Filer/DxfFiler.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/Filer/DxfFiler.cs new file mode 100644 index 0000000000000000000000000000000000000000..975c5053c640dbf878328ca621208cebd6de22f8 --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/Filer/DxfFiler.cs @@ -0,0 +1,221 @@ +#if acad +namespace IFoxCAD.Cad; + +/* 此处暂未完成,无任何测试,尚且不知道怎么用 */ +using System.Runtime.Remoting; + +public class DxfFiler : Cad_DxfFiler +{ + public DxfFiler(IntPtr unmanagedPointer, [MarshalAs(UnmanagedType.U1)] bool autoDelete) : base(unmanagedPointer, autoDelete) + { + } + + public override bool IsModifyingExistingObject => base.IsModifyingExistingObject; + + public override double Thickness => base.Thickness; + + public override double Elevation => base.Elevation; + + public override bool AtEmbeddedObjectStart => base.AtEmbeddedObjectStart; + + public override bool AtEndOfObject => base.AtEndOfObject; + + public override bool AtExtendedData => base.AtExtendedData; + + public override bool AtEndOfFile => base.AtEndOfFile; + + public override int Precision { get => base.Precision; set => base.Precision = value; } + + public override string ErrorMessage => base.ErrorMessage; + + public override bool AtSubclassData(string value) + { + return base.AtSubclassData(value); + } + + public override object Clone() + { + return base.Clone(); + } + + public override void CopyFrom(RXObject source) + { + base.CopyFrom(source); + } + + public override ObjRef CreateObjRef(Type requestedType) + { + return base.CreateObjRef(requestedType); + } + + public override bool Equals(object obj) + { + return base.Equals(obj); + } + + public override void FilerStatus() + { + base.FilerStatus(); + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + + public override void HaltAtClassBoundaries(bool value) + { + base.HaltAtClassBoundaries(value); + } + + public override object InitializeLifetimeService() + { + return base.InitializeLifetimeService(); + } + + public override void PushBackItem() + { + base.PushBackItem(); + } + + public override ResultBuffer ReadResultBuffer() + { + return base.ReadResultBuffer(); + } + + public override void ResetFilerStatus() + { + base.ResetFilerStatus(); + } + + public override int RewindFiler() + { + return base.RewindFiler(); + } + + public override void SetError(string format, params string[] values) + { + base.SetError(format, values); + } + + public override void SetError(Cad_ErrorStatus value, string format, params string[] values) + { + base.SetError(value, format, values); + } + + public override string ToString() + { + return base.ToString(); + } + + public override void WriteBool(DxfCode opCode, bool value) + { + base.WriteBool(opCode, value); + } + + public override void WriteBoolean(DxfCode opCode, bool value) + { + base.WriteBoolean(opCode, value); + } + + public override void WriteByte(DxfCode opCode, byte value) + { + base.WriteByte(opCode, value); + } + + public override void WriteBytes(DxfCode opCode, byte[] chunk) + { + base.WriteBytes(opCode, chunk); + } + + public override void WriteDouble(DxfCode opCode, double value, int precision) + { + base.WriteDouble(opCode, value, precision); + } + + public override void WriteEmbeddedObjectStart() + { + base.WriteEmbeddedObjectStart(); + } + + public override void WriteHandle(DxfCode opCode, Handle value) + { + base.WriteHandle(opCode, value); + } + + public override void WriteInt16(DxfCode opCode, short value) + { + base.WriteInt16(opCode, value); + } + + public override void WriteInt32(DxfCode opCode, int value) + { + base.WriteInt32(opCode, value); + } + + public override void WriteObjectId(DxfCode opCode, ObjectId value) + { + base.WriteObjectId(opCode, value); + } + + public override void WritePoint2d(DxfCode opCode, Point2d value, int precision) + { + base.WritePoint2d(opCode, value, precision); + } + + public override void WritePoint3d(DxfCode opCode, Point3d value, int precision) + { + base.WritePoint3d(opCode, value, precision); + } + + public override void WriteResultBuffer(ResultBuffer buffer) + { + base.WriteResultBuffer(buffer); + } + + public override void WriteScale3d(DxfCode opCode, Scale3d value, int precision) + { + base.WriteScale3d(opCode, value, precision); + } + + public override void WriteString(DxfCode opCode, string value) + { + base.WriteString(opCode, value); + } + + public override void WriteUInt16(DxfCode opCode, ushort value) + { + base.WriteUInt16(opCode, value); + } + + public override void WriteUInt32(DxfCode opCode, uint value) + { + base.WriteUInt32(opCode, value); + } + + public override void WriteVector2d(DxfCode opCode, Vector2d value, int precision) + { + base.WriteVector2d(opCode, value, precision); + } + + public override void WriteVector3d(DxfCode opCode, Vector3d value, int precision) + { + base.WriteVector3d(opCode, value, precision); + } + + public override void WriteXDataStart() + { + base.WriteXDataStart(); + } + + protected override void DeleteUnmanagedObject() + { + base.DeleteUnmanagedObject(); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + } +} +#endif \ No newline at end of file diff --git a/src/IFoxCAD.Cad/ExtensionMethod/GeometryEx.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/GeometryEx.cs similarity index 77% rename from src/IFoxCAD.Cad/ExtensionMethod/GeometryEx.cs rename to src/CAD/IFox.CAD.Shared/ExtensionMethod/GeometryEx.cs index 7b13404b6c09dca500cf049b1dc0035004acc7bd..58e9da30362db999f44a37c97423491dd950ce82 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/GeometryEx.cs +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/GeometryEx.cs @@ -1,13 +1,12 @@ -namespace IFoxCAD.Cad; +namespace IFoxCAD.Cad; using System.Drawing; -using IFoxCAD.Basal; + /// /// 图形扩展类 /// public static class GeometryEx { - #region Point&Circle /// @@ -18,7 +17,7 @@ public static class GeometryEx /// 点与多边形的关系 public static PointOnRegionType PointOnRegion(this IEnumerable pts, Point2d pt) { - //遍历点集并生成首尾连接的多边形 + // 遍历点集并生成首尾连接的多边形 var ptlst = new LoopList(pts); if (ptlst.Count < 3) return PointOnRegionType.Error; @@ -26,15 +25,15 @@ public static PointOnRegionType PointOnRegion(this IEnumerable pts, Poi var ls2ds = new List(); foreach (var node in ptlst.GetNodes()) { - ls2ds.Add(new LineSegment2d(node.Value, node.Next.Value)); + ls2ds.Add(new LineSegment2d(node.Value, node.Next!.Value)); } var cc2d = new CompositeCurve2d(ls2ds.ToArray()); - //在多边形上? + // 在多边形上? if (cc2d.IsOn(pt)) return PointOnRegionType.On; - //在最小包围矩形外? + // 在最小包围矩形外? var bb2d = cc2d.BoundBlock; if (!bb2d.Contains(pt)) return PointOnRegionType.Outside; @@ -44,7 +43,7 @@ public static PointOnRegionType PointOnRegion(this IEnumerable pts, Poi foreach (var node in ptlst.GetNodes()) { var pt1 = node.Value; - var pt2 = node.Next.Value; + var pt2 = node.Next!.Value; if (pt.Y < pt1.Y && pt.Y < pt2.Y) continue; if (pt1.X < pt.X && pt2.X < pt.X) @@ -68,25 +67,23 @@ public static PointOnRegionType PointOnRegion(this IEnumerable pts, Poi /// 点与多边形的关系 public static PointOnRegionType PointOnRegion(this IEnumerable pts, Point3d pt) { - //遍历点集并生成首尾连接的多边形 + // 遍历点集并生成首尾连接的多边形 var ptlst = new LoopList(pts); - if (ptlst.First.Value == ptlst.Last.Value) + if (ptlst.First!.Value == ptlst.Last!.Value) ptlst.RemoveLast(); if (ptlst.Count < 3) return PointOnRegionType.Error; var ls3ds = new List(); foreach (var node in ptlst.GetNodes()) - { - ls3ds.Add(new LineSegment3d(node.Value, node.Next.Value)); - } + ls3ds.Add(new LineSegment3d(node.Value, node.Next!.Value)); var cc3d = new CompositeCurve3d(ls3ds.ToArray()); - //在多边形上? + // 在多边形上? if (cc3d.IsOn(pt)) return PointOnRegionType.On; - //在最小包围矩形外? + // 在最小包围矩形外? var bb2d = cc3d.BoundBlock; if (!bb2d.Contains(pt)) return PointOnRegionType.Outside; @@ -96,7 +93,7 @@ public static PointOnRegionType PointOnRegion(this IEnumerable pts, Poi foreach (var node in ptlst.GetNodes()) { var pt1 = node.Value; - var pt2 = node.Next.Value; + var pt2 = node.Next!.Value; if (pt.Y < pt1.Y && pt.Y < pt2.Y) continue; if (pt1.X < pt.X && pt2.X < pt.X) @@ -140,31 +137,30 @@ public static CircularArc2d GetMinCircle(Point2d pt1, Point2d pt2, out LoopList< /// 解析类圆对象 public static CircularArc2d GetMinCircle(Point2d pt1, Point2d pt2, Point2d pt3, out LoopList ptlst) { - ptlst = - new LoopList { pt1, pt2, pt3 }; + ptlst = new LoopList { pt1, pt2, pt3 }; - //遍历各点与下一点的向量长度,找到距离最大的两个点 + // 遍历各点与下一点的向量长度,找到距离最大的两个点 LoopListNode maxNode = ptlst.GetNodes().FindByMax ( out double maxLength, - node => node.Value.GetDistanceTo(node.Next.Value) + node => node.Value.GetDistanceTo(node.Next!.Value) ); - //以两点做最小包围圆 + // 以两点做最小包围圆 CircularArc2d ca2d = - GetMinCircle(maxNode.Value, maxNode.Next.Value, out LoopList tptlst); + GetMinCircle(maxNode.Value, maxNode.Next!.Value, out LoopList tptlst); - //如果另一点属于该圆 - if (ca2d.IsIn(maxNode.Previous.Value)) + // 如果另一点属于该圆 + if (ca2d.IsIn(maxNode.Previous!.Value)) { - //返回 + // 返回 ptlst = tptlst; return ca2d; } - - //否则按三点做圆 - ptlst.SetFirst(maxNode); + // 否则按三点做圆 + // ptlst.SetFirst(maxNode); + ptlst = new LoopList { maxNode.Value, maxNode.Next.Value, maxNode.Previous.Value }; ca2d = new CircularArc2d(pt1, pt2, pt3); ca2d.SetAngles(0, Math.PI * 2); return ca2d; @@ -179,32 +175,31 @@ public static CircularArc2d GetMinCircle(Point2d pt1, Point2d pt2, Point2d pt3, /// 基准点 /// 输出圆上的点 /// 解析类圆对象 - public static CircularArc2d GetMinCircle(Point2d pt1, Point2d pt2, Point2d pt3, Point2d pt4, out LoopList ptlst) + public static CircularArc2d? GetMinCircle(Point2d pt1, Point2d pt2, Point2d pt3, Point2d pt4, out LoopList? ptlst) { - LoopList iniptlst = new() { pt1, pt2, pt3, pt4 }; + var iniptlst = new LoopList() { pt1, pt2, pt3, pt4 }; ptlst = null; - CircularArc2d ca2d = null; + CircularArc2d? ca2d = null; - //遍历C43的组合,环链表的优势在这里 - foreach (LoopListNode firstNode in iniptlst.GetNodes()) + // 遍历C43的组合,环链表的优势在这里 + foreach (var firstNode in iniptlst.GetNodes()) { - //获取各组合下三点的最小包围圆 - LoopListNode secondNode = firstNode.Next; - LoopListNode thirdNode = secondNode.Next; - CircularArc2d tca2d = GetMinCircle(firstNode.Value, secondNode.Value, thirdNode.Value, out LoopList tptlst); + // 获取各组合下三点的最小包围圆 + var secondNode = firstNode.Next; + var thirdNode = secondNode!.Next; + var tca2d = GetMinCircle(firstNode.Value, secondNode.Value, thirdNode!.Value, out LoopList tptlst); - //如果另一点属于该圆,并且半径小于当前值就把它做为候选解 - if (tca2d.IsIn(firstNode.Previous.Value)) + // 如果另一点属于该圆,并且半径小于当前值就把它做为候选解 + if (!tca2d.IsIn(firstNode.Previous!.Value)) + continue; + if (ca2d is null || tca2d.Radius < ca2d.Radius) { - if (ca2d == null || tca2d.Radius < ca2d.Radius) - { - ca2d = tca2d; - ptlst = tptlst; - } + ca2d = tca2d; + ptlst = tptlst; } } - //返回直径最小的圆 + // 返回直径最小的圆 return ca2d; } @@ -217,7 +212,7 @@ public static CircularArc2d GetMinCircle(Point2d pt1, Point2d pt2, Point2d pt3, /// 三点围成的三角形的有向面积 private static double CalArea(Point2d ptBase, Point2d pt1, Point2d pt2) { - return (pt2 - ptBase).DotProduct((pt1 - ptBase).GetPerpendicularVector()) / 2; + return (pt2 - ptBase).DotProduct((pt1 - ptBase).GetPerpendicularVector()) * 0.5; } /// /// 计算三点围成的三角形的真实面积 @@ -240,10 +235,8 @@ public static double GetArea(this Point2d ptBase, Point2d pt1, Point2d pt2) /// OrientationType 类型值 public static OrientationType IsClockWise(this Point2d ptBase, Point2d pt1, Point2d pt2) { - return CalArea(ptBase, pt1, pt2) switch { - > 0 => OrientationType.CounterClockWise, < 0 => OrientationType.ClockWise, _ => OrientationType.Parallel @@ -297,10 +290,10 @@ public static OrientationType IsClockWise(Vector2d vecBase, Vector2d vec) /// 有向面积 private static double CalArea(IEnumerable pnts) { - IEnumerator itor = pnts.GetEnumerator(); + var itor = pnts.GetEnumerator(); if (!itor.MoveNext()) throw new ArgumentNullException(nameof(pnts)); - Point2d start = itor.Current; + var start = itor.Current; Point2d p1, p2 = start; double area = 0; @@ -345,63 +338,63 @@ public static OrientationType IsClockWise(this IEnumerable pnts) /// 点集 /// 输出圆上的点 /// 解析类圆对象 - public static CircularArc2d GetMinCircle(this List pnts, out LoopList ptlst) + public static CircularArc2d? GetMinCircle(this List pnts, out LoopList? ptlst) { - //点数较小时直接返回 + // 点数较小时直接返回 switch (pnts.Count) { case 0: - ptlst = null; - return null; + ptlst = null; + return null; case 1: - ptlst = new LoopList { pnts[0] }; - return new CircularArc2d(pnts[0], 0); + ptlst = new LoopList { pnts[0] }; + return new CircularArc2d(pnts[0], 0); case 2: - return GetMinCircle(pnts[0], pnts[1], out ptlst); + return GetMinCircle(pnts[0], pnts[1], out ptlst); case 3: - return GetMinCircle(pnts[0], pnts[1], pnts[2], out ptlst); + return GetMinCircle(pnts[0], pnts[1], pnts[2], out ptlst); case 4: - return GetMinCircle(pnts[0], pnts[1], pnts[2], pnts[3], out ptlst); + return GetMinCircle(pnts[0], pnts[1], pnts[2], pnts[3], out ptlst); } - //按前三点计算最小包围圆 - Point2d[] tpnts = new Point2d[4]; + // 按前三点计算最小包围圆 + var tpnts = new Point2d[4]; pnts.CopyTo(0, tpnts, 0, 3); - CircularArc2d ca2d = GetMinCircle(tpnts[0], tpnts[1], tpnts[2], out ptlst); + var ca2d = GetMinCircle(tpnts[0], tpnts[1], tpnts[2], out ptlst); - //找到点集中距离圆心的最远点为第四点 + // 找到点集中距离圆心的最远点为第四点 tpnts[3] = pnts.FindByMax(pnt => ca2d.Center.GetDistanceTo(pnt)); - //如果最远点属于圆结束 + // 如果最远点属于圆结束 while (!ca2d.IsIn(tpnts[3])) { - //如果最远点不属于圆,按此四点计算最小包围圆 + // 如果最远点不属于圆,按此四点计算最小包围圆 ca2d = GetMinCircle(tpnts[0], tpnts[1], tpnts[2], tpnts[3], out ptlst); - //将结果作为新的前三点 - if (ptlst.Count == 3) + // 将结果作为新的前三点 + if (ptlst!.Count == 3) { - tpnts[2] = ptlst.Last.Value; + tpnts[2] = ptlst.Last!.Value; } else { - //第三点取另两点中距离圆心较远的点 - //按算法中描述的任选其中一点的话,还是无法收敛...... + // 第三点取另两点中距离圆心较远的点 + // 按算法中描述的任选其中一点的话,还是无法收敛...... tpnts[2] = tpnts.Except(ptlst) - .FindByMax(pnt => ca2d.Center.GetDistanceTo(pnt)); + .FindByMax(pnt => ca2d!.Center.GetDistanceTo(pnt)); } - tpnts[0] = ptlst.First.Value; - tpnts[1] = ptlst.First.Next.Value; + tpnts[0] = ptlst.First!.Value; + tpnts[1] = ptlst.First.Next!.Value; - //按此三点计算最小包围圆 + // 按此三点计算最小包围圆 ca2d = GetMinCircle(tpnts[0], tpnts[1], tpnts[2], out ptlst); - //找到点集中圆心的最远点为第四点 + // 找到点集中圆心的最远点为第四点 tpnts[3] = pnts.FindByMax(pnt => ca2d.Center.GetDistanceTo(pnt)); } @@ -413,9 +406,9 @@ public static CircularArc2d GetMinCircle(this List pnts, out LoopList

/// 点集 /// 凸包 - public static List ConvexHull(this List points) + public static List? ConvexHull(this List points) { - if (points == null) + if (points is null) return null; if (points.Count <= 1) @@ -567,7 +560,7 @@ public static Matrix3d GetScaleMatrix(this Point3d point, double x, double y, do matdata[10] = z; matdata[11] = point.Z * (1 - z); matdata[15] = 1; - return new Matrix3d(matdata); + return new(matdata); } ///

@@ -579,7 +572,7 @@ public static Size GetSize(this Extents3d ext) { int width = (int)Math.Floor(ext.MaxPoint.X - ext.MinPoint.X); int height = (int)Math.Ceiling(ext.MaxPoint.Y - ext.MinPoint.Y); - return new Size(width, height); + return new(width, height); } /// @@ -589,7 +582,7 @@ public static Size GetSize(this Extents3d ext) /// 二维点 public static Point2d Point2d(this Point3d pt) { - return new Point2d(pt.X, pt.Y); + return new(pt.X, pt.Y); } /// /// 将三维点集转换为二维点集 @@ -604,10 +597,11 @@ public static IEnumerable Point2d(this IEnumerable pts) /// 将二维点转换为三维点 /// /// 二维点 + /// Z值 /// 三维点 - public static Point3d Point3d(this Point2d pt) + public static Point3d Point3d(this Point2d pt, double z = 0) { - return new Point3d(pt.X, pt.Y, 0); + return new(pt.X, pt.Y, z); } /// @@ -618,7 +612,22 @@ public static Point3d Point3d(this Point2d pt) /// 返回两个点之间的中点 public static Point3d GetMidPointTo(this Point3d pt1, Point3d pt2) { - return new Point3d((pt1.X + pt2.X) * 0.5, (pt1.Y + pt2.Y) * 0.5, (pt1.Z + pt2.Z) * 0.5); + return new(pt1.X * 0.5 + pt2.X * 0.5, + pt1.Y * 0.5 + pt2.Y * 0.5, + pt1.Z * 0.5 + pt2.Z * 0.5); + } + + /// + /// 获取两个点之间的中点 + /// + /// 第一点 + /// 第二点 + /// 返回两个点之间的中点 + public static Point2d GetMidPointTo(this Point2d pt1, Point2d pt2) + { + // (pt1 + pt2) / 2; // 溢出风险 + return new(pt1.X * 0.5 + pt2.X * 0.5, + pt1.Y * 0.5 + pt2.Y * 0.5); } /// @@ -647,4 +656,16 @@ public static Point3d Polar(this Point3d pt, double ang, double len) { return pt + Vector3d.XAxis.RotateBy(ang, Vector3d.ZAxis) * len; } -} + /// + /// 计算指定距离和角度的点 + /// + /// 本函数仅适用于x-y平面 + /// 基点 + /// 角度,x轴正向逆时针弧度 + /// 距离 + /// 目标点 + public static Point2d Polar(this Point2d pt, double ang, double len) + { + return pt + Vector2d.XAxis.RotateBy(ang) * len; + } +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/ExtensionMethod/JigEx.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/JigEx.cs new file mode 100644 index 0000000000000000000000000000000000000000..15024ab6552f1ca810b0d2e8d219589951f890a9 --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/JigEx.cs @@ -0,0 +1,410 @@ + + +namespace IFoxCAD.Cad; + +/* 封装jig + * 20220726 隐藏事件,利用函数进行数据库图元重绘 + * 20220710 修改SetOption()的空格结束,并添加例子到IFox + * 20220503 cad22需要防止刷新过程中更改队列,是因为允许函数重入导致,08不会有. + * 20220326 重绘图元的函数用错了,现在修正过来 + * 20211216 加入块表时候做一个差集,剔除临时图元 + * 20211209 补充正交变量设置和回收设置 + * 作者: 惊惊⎛⎝◕⏝⏝◕。⎠⎞ ⎛⎝≥⏝⏝0⎠⎞ ⎛⎝⓿⏝⏝⓿。⎠⎞ ⎛⎝≥⏝⏝≤⎠⎞ + * 博客: https://www.cnblogs.com/JJBox/p/15650770.html + */ + +public delegate void WorldDrawEvent(WorldDraw draw); +public class JigEx : DrawJig, IDisposable +{ + #region 成员 + /// + /// 事件:亮显/暗显会被刷新冲刷掉,所以这个事件用于补充非刷新的工作 + /// + event WorldDrawEvent? WorldDrawEvent; + /// + /// 最后的鼠标点,用来确认长度 + /// + public Point3d MousePointWcsLast; + /// + /// 最后的图元,用来生成 + /// + public Entity[] Entitys => _drawEntitys.ToArray(); + + + readonly Action>? _mouseAction; + readonly Tolerance _tolerance;// 容差 + + readonly Queue _drawEntitys;// 重复生成的图元,放在这里刷新 + JigPromptPointOptions? _options;// jig鼠标配置 + bool _worldDrawFlag = false; // 20220503 + + bool _systemVariables_Orthomode = false; + bool SystemVariables_Orthomode // 正交修改还原 + { + get => _systemVariables_Orthomode; + set + { + // 1正交,0非正交 // setvar: https://www.cnblogs.com/JJBox/p/10209541.html + if (Env.OrthoMode != value) + Env.OrthoMode = _systemVariables_Orthomode = value; + } + } + #endregion + + #region 构造 + /// + /// 在界面绘制图元 + /// + JigEx() + { + _drawEntitys = new(); + DimensionEntitys = new(); + } + + /// + /// 在界面绘制图元 + /// + /// + /// 用来频繁执行的回调:
+ /// 鼠标点;
+ /// 加入新建的图元,鼠标采样期间会Dispose图元的;
+ /// 所以已经在数据库图元利用事件加入,不要在此加入;
+ /// + /// 鼠标移动的容差 + public JigEx(Action>? action = null, double tolerance = 1e-6) : this() + { + _mouseAction = action; + _tolerance = new(tolerance, tolerance); + } + #endregion + + #region 重写 + /// + /// 鼠标采样器 + /// + /// + /// 返回状态:令频繁刷新结束 + protected override SamplerStatus Sampler(JigPrompts prompts) + { + if (_worldDrawFlag) + return SamplerStatus.NoChange;// OK的时候拖动鼠标与否都不出现图元 + + if (_options is null) + throw new NullReferenceException(nameof(_options)); + + var pro = prompts.AcquirePoint(_options); + if (pro.Status == PromptStatus.Keyword) + return SamplerStatus.OK; + else if (pro.Status != PromptStatus.OK) + return SamplerStatus.Cancel; + + // 上次鼠标点不同(一定要这句,不然图元刷新太快会看到奇怪的边线) + var mousePointWcs = pro.Value; + + // == 是比较类字段,但是最好转为哈希比较. + // IsEqualTo 是方形判断(仅加法),但是cad是距离. + // Distance 是圆形判断(会求平方根,使用了牛顿迭代), + // 大量数据(十万以上/频繁刷新)面前会显得非常慢. + if (mousePointWcs.IsEqualTo(MousePointWcsLast, _tolerance)) + return SamplerStatus.NoChange; + + // 上次循环的缓冲区图元清理,否则将会在vs输出遗忘 Dispose + while (_drawEntitys.Count > 0) + _drawEntitys.Dequeue().Dispose(); + + // 委托把容器扔出去接收新创建的图元,然后给重绘更新 + _mouseAction?.Invoke(mousePointWcs, _drawEntitys); + MousePointWcsLast = mousePointWcs; + + return SamplerStatus.OK; + } + + /// + /// 重绘已在数据库的图元 + /// + /// 0x01 此处不加入newEntity的,它们在构造函数的参数回调处加入,它们会进行频繁new和Dispose从而避免遗忘释放
+ /// 0x02 此处用于重绘已经在数据的图元
+ /// 0x03 此处用于图元亮显暗显,因为会被重绘冲刷掉所以独立出来不重绘,它们也往往已经存在数据库的 + ///
+ ///
+ /// + /// newEntity只会存在一个图元队列中,而数据库图元可以分多个集合 + /// 例如: 集合A亮显时 集合B暗显/集合B亮显时 集合A暗显,所以我没有设计多个"数据库图元集合"存放,而是由用户在构造函数外自行创建 + /// + /// + public void DatabaseEntityDraw(WorldDrawEvent action) + { + WorldDrawEvent = action; + } + + /* WorldDraw 封装外的操作说明: + * 0x01 + * 我有一个业务是一次性生成四个方向的箭头,因为cad08缺少瞬时图元, + * 那么可以先提交一次事务,再开一个事务,把Entity传给jig,最后选择删除部分. + * 虽然这个是可行的方案,但是Entity穿越事务本身来说是非必要不使用的. + * 0x02 + * 四个箭头最近鼠标的亮显,其余淡显, + * 在jig使用淡显ent.Unhighlight和亮显ent.Highlight() + * 需要绕过重绘,否则重绘将导致图元频闪,令这两个操作失效, + * 此时需要自定义一个集合 EntityList (不使用本函数的_drawEntitys) + * 再将 EntityList 传给 WorldDrawEvent 事件,事件内实现亮显和淡显(事件已经利用 DatabaseEntityDraw函数进行提供). + * 0x03 + * draw.Geometry.Draw(_drawEntitys[i]); + * 此函数有问题,acad08克隆一份数组也可以用来刷新, + * 而arx上面的jig只能一次改一个,所以可以用此函数. + * 起因是此函数属于异步刷新, + * 同步上下文的刷新是 RawGeometry + * 0x04 + * cad22测试出现,08不会, + * draw.RawGeometry.Draw(ent);会跳到 Sampler(),所以设置 _worldDrawFlag + * 但是禁止重绘重入的话(令图元不频繁重绘),那么鼠标停着的时候就看不见图元, + * 所以只能重绘结束的时候才允许鼠标采集,采集过程的时候不会触发重绘, + * 这样才可以保证容器在重绘中不被更改. + */ + /// + /// 重绘图形 + /// + protected override bool WorldDraw(WorldDraw draw) + { + _worldDrawFlag = true; + WorldDrawEvent?.Invoke(draw); + _drawEntitys.ForEach(ent => { + draw.RawGeometry.Draw(ent); + }); + _worldDrawFlag = false; + return true; + } + #endregion + + #region 方法 + /// + /// 鼠标配置:基点 + /// + /// 基点 + /// 光标绑定 + /// 提示信息 + /// 正交开关 + public JigPromptPointOptions SetOptions(Point3d basePoint, + CursorType cursorType = CursorType.RubberBand, + string msg = "点选第二点", + bool orthomode = false) + { + if (orthomode) + SystemVariables_Orthomode = true; + + _options = JigPointOptions(); + _options.Message = Environment.NewLine + msg; + _options.Cursor = cursorType; // 光标绑定 + _options.UseBasePoint = true; // 基点打开 + _options.BasePoint = basePoint; // 基点设定 + return _options; + } + + /// + /// 鼠标配置:提示信息,关键字 + /// + /// 信息 + /// 关键字 + /// 正交开关 + /// + public JigPromptPointOptions SetOptions(string msg, + Dictionary? keywords = null, + bool orthomode = false) + { + if (orthomode) + SystemVariables_Orthomode = true; + + _options = JigPointOptions(); + _options.Message = Environment.NewLine + msg; + + // 加入关键字,加入时候将空格内容放到最后 + string spaceValue = string.Empty; + const string spaceKey = " "; + + if (keywords != null) + foreach (var item in keywords) + if (item.Key == spaceKey) + spaceValue = item.Value; + else + _options.Keywords.Add(item.Key, item.Key, item.Value); + + /// 因为默认配置函数导致此处空格触发是无效的, + /// 但是用户如果想触发,就需要在外部减去默认UserInputControls配置 + /// 要放最后,才能优先触发其他关键字 + if (spaceValue != string.Empty) + _options.Keywords.Add(spaceKey, spaceKey, spaceValue); + else + _options.Keywords.Add(spaceKey, spaceKey, "<空格退出>"); + + // 外部设置减去配置 + // _options.UserInputControls = + // _options.UserInputControls + // ^ UserInputControls.NullResponseAccepted // 输入了鼠标右键,结束jig + // ^ UserInputControls.AnyBlankTerminatesInput; // 空格或回车,结束jig; + return _options; + } + + /// + /// 鼠标配置:自定义 + /// + /// + /// 正交开关 + public void SetOptions(Action action, bool orthomode = false) + { + if (orthomode) + SystemVariables_Orthomode = true; + + _options = new JigPromptPointOptions(); + action.Invoke(_options); + } + + /// + /// 执行 + /// + /// + public PromptResult Drag() + { + // jig功能必然是当前前台文档,所以封装内部更好调用 + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + var ed = doc.Editor; + var dr = ed.Drag(this); + + if (SystemVariables_Orthomode) + SystemVariables_Orthomode = !SystemVariables_Orthomode; + return dr; + } + + /// + /// 最后一次的图元加入数据库 + /// + /// 加入此空间 + /// 不生成的图元用于排除,例如刷新时候的提示文字 + /// 加入数据库的id集合 + public IEnumerable? AddEntityToMsPs(BlockTableRecord btrOfAddEntitySpace, + IEnumerable? removeEntity = null) + { + // 内部用 _drawEntitys 外部用 Entitys,减少一层转换 + if (_drawEntitys.Count == 0) + return null; + + IEnumerable es = _drawEntitys; + if (removeEntity != null) + es = es.Except(removeEntity);// 差集 + + return btrOfAddEntitySpace.AddEntity(es); + } + + + #region 配置 + /// + /// 用户输入控制默认配置 + /// 令jig.Drag().Status == + /// + /// + static JigPromptPointOptions JigPointOptions() + { + return new JigPromptPointOptions() + { + UserInputControls = + UserInputControls.GovernedByUCSDetect // 由UCS探测用 + | UserInputControls.Accept3dCoordinates // 接受三维坐标 + | UserInputControls.NullResponseAccepted // 输入了鼠标右键,结束jig + | UserInputControls.AnyBlankTerminatesInput // 空格或回车,结束jig; + }; + } + + /// + /// 空格默认是, + /// 将它设置为 + /// + public void SetSpaceIsKeyword() + { + var opt = _options; + //if (opt == null) + // throw new ArgumentNullException(nameof(_options)); + opt.NotNull(nameof(_options)); + + if ((opt.UserInputControls & UserInputControls.NullResponseAccepted) == UserInputControls.NullResponseAccepted) + opt.UserInputControls ^= UserInputControls.NullResponseAccepted; // 输入了鼠标右键,结束jig + if ((opt.UserInputControls & UserInputControls.AnyBlankTerminatesInput) == UserInputControls.AnyBlankTerminatesInput) + opt.UserInputControls ^= UserInputControls.AnyBlankTerminatesInput; // 空格或回车,结束jig + } + #endregion + + #region 注释数据 + /// + /// 注释数据,可以在缩放的时候不受影响 + /// + public DynamicDimensionDataCollection DimensionEntitys; + + /// + /// 重写注释数据 + /// + /// + /// + protected override DynamicDimensionDataCollection GetDynamicDimensionData(double dimScale) + { + base.GetDynamicDimensionData(dimScale); + return DimensionEntitys; + } + + #endregion + #endregion + + #region IDisposable接口相关函数 + public bool IsDisposed { get; private set; } = false; + + /// + /// 手动调用释放 + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// 析构函数调用释放 + /// + ~JigEx() + { + Dispose(false); + } + + protected virtual void Dispose(bool disposing) + { + // 不重复释放,并设置已经释放 + if (IsDisposed) return; + IsDisposed = true; + + // 最后一次的图元如果没有加入数据库,就在此销毁,所以JigEx调用的时候加using + _drawEntitys?.ForEach(ent => { + if (ent.Database == null && !ent.IsDisposed) + ent.Dispose(); + }); + _drawEntitys?.Clear(); + } + #endregion + + // 此处无测试 + // protected override void ViewportDraw(ViewportDraw draw) + // { + // base.ViewportDraw(draw); + // } +} + +#if false +| UserInputControls.DoNotEchoCancelForCtrlC // 不要取消CtrlC的回音 +| UserInputControls.DoNotUpdateLastPoint // 不要更新最后一点 +| UserInputControls.NoDwgLimitsChecking // 没有Dwg限制检查 +| UserInputControls.NoZeroResponseAccepted // 接受非零响应 +| UserInputControls.NoNegativeResponseAccepted // 不否定回复已被接受 +| UserInputControls.Accept3dCoordinates // 返回点的三维坐标,是转换坐标系了? +| UserInputControls.AcceptMouseUpAsPoint // 接受释放按键时的点而不是按下时 + +| UserInputControls.InitialBlankTerminatesInput // 初始 空格或回车,结束jig +| UserInputControls.AcceptOtherInputString // 接受其他输入字符串 +| UserInputControls.NoZDirectionOrtho // 无方向正射,直接输入数字时以基点到当前点作为方向 +| UserInputControls.UseBasePointElevation // 使用基点高程,基点的Z高度探测 +#endif \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/ExtensionMethod/JigExTransient.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/JigExTransient.cs new file mode 100644 index 0000000000000000000000000000000000000000..c7532b98ef14c1eecb49358b27cfbbea396f3442 --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/JigExTransient.cs @@ -0,0 +1,149 @@ +#if acad && !ac2008 +namespace IFoxCAD.Cad; + +/// +/// 瞬态容器 +/// +public class JigExTransient : IDisposable +{ + #region 私有字段 + // 整数集,暂时不知道有什么意义 + IntegerCollection _integerCollection; + // 维护集合 + HashSet _entities; + #endregion + + #region 公开属性 + /// + /// 对象集合 + /// + public Entity[] Entities => _entities.ToArray(); + /// + /// 数量 + /// + public int Count => _entities.Count; + #endregion + + #region 构造函数 + /// + /// 瞬态容器 + /// + public JigExTransient() + { + _integerCollection = new(); + _entities = new(); + } + #endregion + + #region 方法 + /// + /// 判断瞬态容器里是否含有对象 + /// + /// 对象 + /// 含有返回true + public bool Contains(Entity ent) + { + return _entities.Contains(ent); + } + + /// + /// 向瞬态容器中添加对象 + /// + /// 图元 + /// 绘图模式 + public void Add(Entity ent, TransientDrawingMode tdm = TransientDrawingMode.Main) + { + if (_entities.Add(ent)) + { + TransientManager + .CurrentTransientManager + .AddTransient(ent, tdm, 128, _integerCollection); + } + } + + + /// + /// 从瞬态容器中移除图元 + /// + /// 已经加入瞬态容器的图元 + public void Remove(Entity ent) + { + if (!Contains(ent)) + return; + TransientManager + .CurrentTransientManager + .EraseTransient(ent, _integerCollection); + _entities.Remove(ent); + } + + /// + /// 清空瞬态容器并移除图元显示 + /// + public void Clear() + { + foreach (var ent in _entities) + { + TransientManager + .CurrentTransientManager + .EraseTransient(ent, _integerCollection); + } + _entities.Clear(); + } + + + /// + /// 更新单个显示 + /// + /// 已经加入瞬态容器的图元 + public void Update(Entity ent) + { + if (!Contains(ent)) + return; + TransientManager + .CurrentTransientManager + .UpdateTransient(ent, _integerCollection); + } + + /// + /// 更新全部显示 + /// + public void UpdateAll() + { + foreach (var ent in _entities) + Update(ent); + } + #endregion + + #region IDisposable接口相关函数 + public bool IsDisposed { get; private set; } = false; + + /// + /// 手动释放 + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// 析构函数调用释放 + /// + ~JigExTransient() + { + Dispose(false); + } + + /// + /// 销毁瞬态容器 + /// + protected virtual void Dispose(bool disposing) + { + if (IsDisposed) return; + IsDisposed = true; + + Clear();// 清空瞬态容器并移除对象在图纸上的显示 + } + #endregion +} +#endif \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/ExtensionMethod/JsonConverter.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/JsonConverter.cs new file mode 100644 index 0000000000000000000000000000000000000000..975e511924c53940f2b43910a15fe8154d689b26 --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/JsonConverter.cs @@ -0,0 +1,118 @@ + + +namespace IFoxCAD.Cad; + +#if NewtonsoftJson +/* + * 参考 https://www.cnblogs.com/fps2tao/p/14798710.html + * json类型转换器,使用方法: + * 在类上面增加此特性: [JsonConverter(typeof(ObjectIdConverter))] + */ +/// +/// json转换器 +/// +public class ObjectIdConverter : JsonConverter +{ + /// + /// 约束类型 + /// + public override bool CanConvert(Type objectType) + { + return typeof(ObjectId) == objectType; + } + + /// + /// 反序列化_把字符串生成对象 + /// + public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) + { + if (reader.Value == null) + return ObjectId.Null; + try + { + using DBTrans tr = new(); + var id = tr.GetObjectId(reader.Value.ToString()); + return id; + } + catch { return ObjectId.Null; } + } + + /// + /// 序列化_写入json + /// + public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) + { + if (value is ObjectId id) + writer.WriteValue(id == ObjectId.Null ? 0 : id.Handle.Value); + } +} +#else +/* + * 参考 https://developer.aliyun.com/article/51053 + * json类型转换器,使用方法: + * public static string SerializeToJson(object obj) + * { + * JavaScriptSerializer serializer = new(); + * serializer.RegisterConverters(new[] { new ObjectIdConverter() }); + * return serializer.Serialize(obj); + * } + * + * public static T DeserializeJson(string jsonString) + * { + * JavaScriptSerializer serializer = new(); + * serializer.RegisterConverters(new[] { new ObjectIdConverter() }); + * return serializer.Deserialize(jsonString); + * } + */ +/// +/// json转换器 +/// +public class ObjectIdConverter : JavaScriptConverter +{ + const string _id = nameof(ObjectId); + + /// + /// 约束类型 + /// + public override IEnumerable SupportedTypes => new[] { typeof(ObjectId) }; + + /// + /// 序列化_写入json + /// + public override IDictionary Serialize(object obj, JavaScriptSerializer serializer) + { + if (obj is not ObjectId id) + return null!; + + Dictionary result = new() + { + { _id, id == ObjectId.Null ? 0 : id.Handle.Value } + }; + return result; + } + + /// + /// 反序列化_把字符串生成对象 + /// + public override object Deserialize(IDictionary dictionary, Type type, JavaScriptSerializer serializer) + { + //if (dictionary == null) + // throw new ArgumentNullException(nameof(dictionary)); + dictionary.NotNull(nameof(dictionary)); + if (type != typeof(ObjectId)) + return null!; + + ObjectId id = ObjectId.Null; + try + { + if (dictionary.TryGetValue(_id, out object va)) + { + using DBTrans tr = new(); + id = tr.GetObjectId(va.ToString()); + } + } + catch { } + return id; + } +} +#endif \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/ExtensionMethod/ObjEx.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/ObjEx.cs new file mode 100644 index 0000000000000000000000000000000000000000..1ad2a17f9ccb1ceb2bf421922367d9852b449972 --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/ObjEx.cs @@ -0,0 +1,24 @@ +namespace IFoxCAD.Cad; + +/// +/// 对象扩展类 +/// +public static class ObjEx +{ + /// + /// cad的打印 + /// + /// + public static void Print(this object obj) + { + Acap.DocumentManager.MdiActiveDocument.Editor.WriteMessage($"{obj}\n"); + } + /// + /// 系统的打印 + /// + /// + public static void PrintLine(this object obj) + { + Console.WriteLine(obj.ToString()); + } +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/ExtensionMethod/ObjectIdEx.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/ObjectIdEx.cs similarity index 47% rename from src/IFoxCAD.Cad/ExtensionMethod/ObjectIdEx.cs rename to src/CAD/IFox.CAD.Shared/ExtensionMethod/ObjectIdEx.cs index 84749e2143d170d6f6679266342c819a8e450aa3..a329da5ecb11b68d361aa451748d8f04e8d428cf 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/ObjectIdEx.cs +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/ObjectIdEx.cs @@ -6,21 +6,24 @@ public static class ObjectIdEx { #region GetObject - /// /// 获取指定类型对象 /// /// 指定的泛型 /// 对象id - /// 事务 - /// 打开模式 - /// 打开删除对象 + /// 打开模式 + /// 事务 + /// 是否打开已删除对象,默认为不打开 + /// 是否打开锁定图层对象,默认为不打开 /// 指定类型对象 - public static T GetObject(this ObjectId id, OpenMode mode = OpenMode.ForRead, bool openErased = false, Transaction tr = default) where T : DBObject + public static T? GetObject(this ObjectId id, + OpenMode openMode = OpenMode.ForRead, + Transaction? trans = null, + bool openErased = false, + bool openLockedLayer = false) where T : DBObject { - tr ??= DBTrans.Top.Transaction; - //tr = Env.GetTrans(tr); - return tr.GetObject(id, mode, openErased) as T; + trans ??= DBTrans.Top.Transaction; + return trans.GetObject(id, openMode, openErased, openLockedLayer) as T; } /// @@ -28,13 +31,20 @@ public static T GetObject(this ObjectId id, OpenMode mode = OpenMode.ForRead, /// /// 指定的泛型 /// 对象id集合 - /// 事务 - /// 打开模式 - /// 打开删除对象 + /// 打开模式 + /// 事务 + /// 是否打开已删除对象,默认为不打开 + /// 是否打开锁定图层对象,默认为不打开 /// 指定类型对象集合 - public static IEnumerable GetObject(this IEnumerable ids, OpenMode mode = OpenMode.ForRead, bool openErased = false, Transaction tr = default) where T : DBObject + [System.Diagnostics.DebuggerStepThrough] + public static IEnumerable GetObject(this IEnumerable ids, + OpenMode openMode = OpenMode.ForRead, + Transaction? trans = null, + bool openErased = false, + bool openLockedLayer = false) where T : DBObject { - return ids.Select(id => id.GetObject(mode, openErased, tr)); + trans ??= DBTrans.Top.Transaction; + return ids.Select(id => id.GetObject(openMode, trans, openErased, openLockedLayer)); } /// @@ -46,11 +56,19 @@ public static IEnumerable GetObject(this IEnumerable ids, OpenMo public static IEnumerable OfType(this IEnumerable ids) where T : DBObject { string dxfName = RXClass.GetClass(typeof(T)).DxfName; - return - ids - .Where(id => id.ObjectClass.DxfName == dxfName); + return ids.Where(id => id.ObjectClass().DxfName == dxfName); } #endregion GetObject + + public static RXClass ObjectClass(this ObjectId id) + { +#if NET35 + return RXClass.GetClass(id.GetType()); +#else + return id.ObjectClass; +#endif + } + /// /// id是否有效,未被删除 /// @@ -60,6 +78,7 @@ public static bool IsOk(this ObjectId id) { return !id.IsNull && id.IsValid && !id.IsErased && !id.IsEffectivelyErased && id.IsResident; } + /// /// 删除id代表的对象 /// @@ -68,12 +87,12 @@ public static void Erase(this ObjectId id) { if (id.IsOk()) { - var ent = id.GetObject(); + var ent = id.GetObject()!; using (ent.ForWrite()) { ent.Erase(); }// 第一种读写权限自动转换写法 - Env.Editor.Regen(); + // Env.Editor.Regen(); } } -} +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/ExtensionMethod/PointEx.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/PointEx.cs new file mode 100644 index 0000000000000000000000000000000000000000..2cfa8d2f43f04da02676f071f7da0ed58d4675aa --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/PointEx.cs @@ -0,0 +1,137 @@ + + +namespace IFoxCAD.Cad; + +public static class PointEx +{ + /// + /// 获取点的hash字符串,同时可以作为pt的字符串表示 + /// + /// 点 + /// 指示计算几维坐标的标志,1为计算x,2为计算x,y,其他为计算x,y,z + /// 保留的小数位数 + /// hash字符串 + public static string GetHashString(this Point3d pt, int xyz = 3, int decimalRetain = 6) + { + var de = $"f{decimalRetain}"; + return xyz switch + { + 1 => $"({pt.X.ToString(de)})", + 2 => $"({pt.X.ToString(de)},{pt.Y.ToString(de)})", + _ => $"({pt.X.ToString(de)},{pt.Y.ToString(de)},{pt.Z.ToString(de)})" + }; + } + + // 为了频繁触发所以弄个缓存 + static Plane? _PlaneCache; + /// + /// 两点计算弧度范围0到2Pi + /// + /// 起点 + /// 终点 + /// 方向 + /// 弧度值 + public static double GetAngle(this Point3d startPoint, Point3d endPoint, Vector3d? direction = null) + { + if (direction != null) + _PlaneCache = new Plane(Point3d.Origin, direction.Value); + if (_PlaneCache == null) + _PlaneCache = new Plane(Point3d.Origin, Vector3d.ZAxis); + return startPoint.GetVectorTo(endPoint).AngleOnPlane(_PlaneCache); + } + /// + /// 两点计算弧度范围0到2Pi + /// + /// 起点 + /// 终点 + /// 弧度值 + public static double GetAngle(this Point2d startPoint, Point2d endPoint) + { + return startPoint.GetVectorTo(endPoint).Angle; + } + + /// + /// 获取中点 + /// + /// + /// + /// + public static Point2d GetCenter(this Point2d a, Point2d b) + { + // (p1 + p2) / 2; // 溢出风险 + return new Point2d(a.X * 0.5 + b.X * 0.5, + a.Y * 0.5 + b.Y * 0.5); + } + + /// http://www.lee-mac.com/bulgeconversion.html + /// + /// 求凸度,判断三点是否一条直线上 + /// + /// 圆弧起点 + /// 圆弧腰点 + /// 圆弧尾点 + /// 逆时针为正,顺时针为负 + public static double GetArcBulge(this Point2d arc1, Point2d arc2, Point2d arc3, double tol = 1e-10) + { + double dStartAngle = arc2.GetAngle(arc1); + double dEndAngle = arc2.GetAngle(arc3); + // 求的P1P2与P1P3夹角 + var talAngle = (Math.PI - dStartAngle + dEndAngle) / 2; + // 凸度==拱高/半弦长==拱高比值/半弦长比值 + // 有了比值就不需要拿到拱高值和半弦长值了,因为接下来是相除得凸度 + double bulge = Math.Sin(talAngle) / Math.Cos(talAngle); + + // 处理精度 + if (bulge > 0.9999 && bulge < 1.0001) + bulge = 1; + else if (bulge < -0.9999 && bulge > -1.0001) + bulge = -1; + else if (Math.Abs(bulge) < tol) + bulge = 0; + return bulge; + } + + + #region 首尾相连 + /// + /// 首尾相连 + /// + [System.Diagnostics.DebuggerStepThrough] + public static void End2End(this Point2dCollection ptcol) + { + ptcol.NotNull(nameof(ptcol)); + + if (ptcol.Count == 0 || ptcol[0].Equals(ptcol[^1]))// 首尾相同直接返回 + return; + + // 首尾不同,去加一个到最后 + var lst = new Point2d[ptcol.Count + 1]; + for (int i = 0; i < lst.Length; i++) + lst[i] = ptcol[i]; + lst[^1] = lst[0]; + + ptcol.Clear(); + ptcol.AddRange(lst); + } + /// + /// 首尾相连 + /// + [System.Diagnostics.DebuggerStepThrough] + public static void End2End(this Point3dCollection ptcol) + { + ptcol.NotNull(nameof(ptcol)); + if (ptcol.Count == 0 || ptcol[0].Equals(ptcol[^1]))// 首尾相同直接返回 + return; + + // 首尾不同,去加一个到最后 + var lst = new Point3d[ptcol.Count + 1]; + for (int i = 0; i < lst.Length; i++) + lst[i] = ptcol[i]; + lst[^1] = lst[0]; + + ptcol.Clear(); + for (int i = 0; i < lst.Length; i++) + ptcol.Add(lst[i]); + } + #endregion +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/ExtensionMethod/SelectionSetEx.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/SelectionSetEx.cs similarity index 40% rename from src/IFoxCAD.Cad/ExtensionMethod/SelectionSetEx.cs rename to src/CAD/IFox.CAD.Shared/ExtensionMethod/SelectionSetEx.cs index 51f82ab77f1e96152da3a17ebe4293ad8d8a1162..2223554013eb6b5b60d50b90b68aa8e988f7fc27 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/SelectionSetEx.cs +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/SelectionSetEx.cs @@ -1,4 +1,6 @@ -namespace IFoxCAD.Cad; + + +namespace IFoxCAD.Cad; /// /// 选择集扩展类 @@ -11,6 +13,7 @@ public static class SelectionSetEx /// /// 选择集 /// 已选择的对象集合 + [System.Diagnostics.DebuggerStepThrough] public static IEnumerable GetSelectedObjects(this SelectionSet ss) { return ss.Cast(); @@ -22,6 +25,7 @@ public static IEnumerable GetSelectedObjects(this SelectionSet s /// 已选择的对象泛型 /// 选择集 /// 已选择的对象集合 + [System.Diagnostics.DebuggerStepThrough] public static IEnumerable GetSelectObjects(this SelectionSet ss) where T : SelectedObject { return ss.Cast().OfType(); @@ -33,13 +37,14 @@ public static IEnumerable GetSelectObjects(this SelectionSet ss) where T : /// 图元类型 /// 选择集 /// 已选择的对象id集合 + [System.Diagnostics.DebuggerStepThrough] public static IEnumerable GetObjectIds(this SelectionSet ss) where T : Entity { string dxfName = RXClass.GetClass(typeof(T)).DxfName; return ss .GetObjectIds() - .Where(id => id.ObjectClass.DxfName == dxfName); + .Where(id => id.ObjectClass().DxfName == dxfName); } /// @@ -47,12 +52,13 @@ public static IEnumerable GetObjectIds(this SelectionSet ss) where /// /// 选择集 /// 分组后的类型/对象id集合 + [System.Diagnostics.DebuggerStepThrough] public static IEnumerable> GetObjectIdGroup(this SelectionSet ss) { return ss .GetObjectIds() - .GroupBy(id => id.ObjectClass.DxfName); + .GroupBy(id => id.ObjectClass().DxfName); } #endregion @@ -63,35 +69,83 @@ public static IEnumerable> GetObjectIdGroup(this Sel /// /// 指定类型 /// 选择集 - /// 事务 /// 打开模式 + /// 事务 + /// 是否打开已删除对象,默认为不打开 + /// 是否打开锁定图层对象,默认为不打开 /// 图元集合 - public static IEnumerable GetEntities(this SelectionSet ss, OpenMode openMode = OpenMode.ForRead, Transaction tr = default) where T : Entity + [System.Diagnostics.DebuggerStepThrough] + public static IEnumerable GetEntities(this SelectionSet ss, + OpenMode openMode = OpenMode.ForRead, + DBTrans? trans = null, + bool openErased = false, + bool openLockedLayer = false) where T : Entity { - return - ss - .GetObjectIds() - .Select(id => tr.GetObject(id, openMode) as T); + //if (ss is null) + // throw new ArgumentNullException(nameof(ss)); + ss.NotNull(nameof(ss)); + trans ??= DBTrans.Top; + return ss.GetObjectIds() + .Select(id => trans.GetObject(id, openMode, openErased, openLockedLayer)) + .Where(ent => ent != null) + .OfType(); } - #endregion #region ForEach - /// /// 遍历选择集 /// /// 指定图元类型 /// 选择集 + /// 处理函数;(图元) + /// 打开模式 /// 事务 + /// 是否打开已删除对象,默认为不打开 + /// 是否打开锁定图层对象,默认为不打开 + [System.Diagnostics.DebuggerStepThrough] + public static void ForEach(this SelectionSet ss, + Action action, + OpenMode openMode = OpenMode.ForRead, + DBTrans? tr = default, + bool openErased = false, + bool openLockedLayer = false) where T : Entity + { + ForEach(ss, (ent, state) => { + action.Invoke(ent); + }, openMode, tr, openErased, openLockedLayer); + } + + /// + /// 遍历选择集 + /// + /// 指定图元类型 + /// 选择集 + /// 处理函数;(图元,终止方式) /// 打开模式 - /// 处理函数 - public static void ForEach(this SelectionSet ss, Action action, OpenMode openMode = OpenMode.ForRead, Transaction tr = default) where T : Entity + /// 事务 + /// 是否打开已删除对象,默认为不打开 + /// 是否打开锁定图层对象,默认为不打开 + /// + [System.Diagnostics.DebuggerStepThrough] + public static void ForEach(this SelectionSet ss, + Action action, + OpenMode openMode = OpenMode.ForRead, + DBTrans? trans = null, + bool openErased = false, + bool openLockedLayer = false) where T : Entity { - foreach (T ent in ss.GetEntities(openMode, tr)) + action.NotNull(nameof(action)); + trans ??= DBTrans.Top; + + LoopState state = new(); + var ents = ss.GetEntities(openMode, trans, openErased, openLockedLayer); + foreach (var ent in ents) { - action?.Invoke(ent); + action.Invoke(ent, state); + if (!state.IsRun) + break; } } #endregion -} +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableEx.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/SymbolTableEx.cs similarity index 51% rename from src/IFoxCAD.Cad/ExtensionMethod/SymbolTableEx.cs rename to src/CAD/IFox.CAD.Shared/ExtensionMethod/SymbolTableEx.cs index 4da3daf1f34fa4e5e4ded89bafbc558d6529528d..4c13159977670a4c59efbf86942ca269d0148d41 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableEx.cs +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/SymbolTableEx.cs @@ -26,8 +26,8 @@ public static ObjectId Add(this SymbolTable table, /// 图层id public static ObjectId Add(this SymbolTable table, string name, int colorIndex) { - colorIndex %= 256;//防止输入的颜色超出256 - colorIndex = Math.Abs(colorIndex);//防止负数 + colorIndex %= 257;// 防止输入的颜色超出256 + colorIndex = Math.Abs(colorIndex);// 防止负数 return table.Add(name, lt => lt.Color = Color.FromColorIndex(ColorMethod.ByColor, (short)colorIndex)); } /// @@ -38,19 +38,13 @@ public static ObjectId Add(this SymbolTable table, /// 新图层名 public static ObjectId Rename(this SymbolTable table, string Oldname, string NewName) { - if (table.Has(Oldname)) - { - table.Change(Oldname, ly => - { - ly.Name = NewName; - } - ); - return table[NewName]; - } - else - { + if (!table.Has(Oldname)) return ObjectId.Null; - } + + table.Change(Oldname, layer => { + layer.Name = NewName; + }); + return table[NewName]; } /// /// 删除图层 @@ -61,19 +55,17 @@ public static ObjectId Rename(this SymbolTable tab public static bool Delete(this SymbolTable table, string name) { if (name == "0" || name == "Defpoints" || !table.Has(name) || table[name] == DBTrans.Top.Database.Clayer) - { return false; - } + table.CurrentSymbolTable.GenerateUsageData(); var ltr = table.GetRecord(name); + if (ltr is null) + return false; + if (ltr.IsUsed) - { return false; - } using (ltr.ForWrite()) - { ltr.Erase(); - } return true; } #endregion @@ -89,30 +81,23 @@ public static bool Delete(this SymbolTable table, /// 添加属性定义的委托 /// 块定义id /// TODO: 需要测试匿名块等特殊的块是否能定义 - public static ObjectId Add(this SymbolTable table, string name, Action action = null, Func> ents = null, Func> attdef = null) + public static ObjectId Add(this SymbolTable table, + string name, + Action? action = null, + Func>? ents = null, + Func>? attdef = null) { - return table.Add(name, btr => - { + return table.Add(name, btr => { action?.Invoke(btr); + var entsres = ents?.Invoke(); - if (entsres != null) - { + if (entsres is not null) btr.AddEntity(entsres); - } + var adddefres = attdef?.Invoke(); - if (adddefres != null) - { + if (adddefres is not null) btr.AddEntity(adddefres); - } - //if (ents is not null) - //{ - // btr.AddEntity(ents?.Invoke()); - //} - //if (attdef is not null) - //{ - // btr.AddEntity(attdef?.Invoke()); - //} - }); + }); } /// /// 添加块定义 @@ -122,9 +107,17 @@ public static ObjectId Add(this SymbolTable table, /// 图元 /// 属性定义 /// - public static ObjectId Add(this SymbolTable table, string name, IEnumerable ents = null, IEnumerable attdef = null) + public static ObjectId Add(this SymbolTable table, + string name, + IEnumerable? ents = null, + IEnumerable? attdef = null) { - return table.Add(name, null, () => { return ents; }, () => { return attdef; }); + return table.Add(name, btr => { + if (ents is not null) + btr.AddEntity(ents); + if (attdef is not null) + btr.AddEntity(attdef); + }); } /// @@ -139,6 +132,49 @@ public static ObjectId Add(this SymbolTable table, return table.Add(name, null, () => { return ents; }); } + /// + /// 添加属性到块定义 + /// + /// 块表 + /// 块定义id + /// 属性列表 + public static void AddAttsToBlocks(this SymbolTable table, + ObjectId id, + List atts) + { + List attTags = new(); + table.Change(id, btr => { + + btr.GetEntities() + .ForEach(def => attTags.Add(def.Tag.ToUpper())); + + for (int i = 0; i < atts.Count; i++) + if (!attTags.Contains(atts[i].Tag.ToUpper())) + btr.AddEntity(atts[i]); + }); + } + /// + /// 添加属性到块定义 + /// + /// 块表 + /// 块定义名字 + /// 属性列表 + public static void AddAttsToBlocks(this SymbolTable table, + string name, + List atts) + { + List attTags = new(); + table.Change(name, btr => { + + btr.GetEntities() + .ForEach(def => attTags.Add(def.Tag.ToUpper())); + + for (int i = 0; i < atts.Count; i++) + if (!attTags.Contains(atts[i].Tag.ToUpper())) + btr.AddEntity(atts[i]); + }); + } + /// /// 从文件中获取块定义 /// @@ -148,21 +184,17 @@ public static ObjectId Add(this SymbolTable table, /// 块定义Id public static ObjectId GetBlockFrom(this SymbolTable table, string fileName, bool over) { - //FileInfo fi = new(fileName); - //string blkdefname = fi.Name; - //if (blkdefname.Contains(".")) - //{ - // blkdefname = blkdefname.Substring(0, blkdefname.LastIndexOf('.')); - //} - - string blkdefname = SymbolUtilityServices.RepairSymbolName(SymbolUtilityServices.GetSymbolNameFromPathName(fileName, "dwg"), false); + + string blkdefname = SymbolUtilityServices.RepairSymbolName( + SymbolUtilityServices.GetSymbolNameFromPathName(fileName, "dwg"), false); ObjectId id = table[blkdefname]; bool has = id != ObjectId.Null; if ((has && over) || !has) { - Database db = new(); + Database db = new(false, true); db.ReadDwgFile(fileName, FileShare.Read, true, null); + db.CloseInput(true); id = table.Database.Insert(BlockTableRecord.ModelSpace, blkdefname, db, false); } @@ -179,14 +211,12 @@ public static ObjectId GetBlockFrom(this SymbolTable块定义名 /// 是否覆盖 /// 块定义Id - public static ObjectId GetBlockFrom(this SymbolTable table, string fileName, string blockName, bool over) + public static ObjectId GetBlockFrom(this SymbolTable table, + string fileName, + string blockName, + bool over) { - return - table.GetRecordFrom( - t => t.BlockTable, - fileName, - blockName, - over); + return table.GetRecordFrom(t => t.BlockTable, fileName, blockName, over); } #endregion @@ -205,20 +235,19 @@ public static ObjectId Add(this SymbolTable { return table.Add( name, - ltt => - { + ltt => { ltt.AsciiDescription = description; - ltt.PatternLength = length; //线型的总长度 - ltt.NumDashes = dash.Length; //组成线型的笔画数目 - for (int i = 0; i < dash.Length; i++) + ltt.PatternLength = length; // 线型的总长度 + ltt.NumDashes = dash.Length; // 组成线型的笔画数目 + for (int i = 0; i < dash.Length; i++) { ltt.SetDashLengthAt(i, dash[i]); } - //ltt.SetDashLengthAt(0, 0.5); //0.5个单位的划线 - //ltt.SetDashLengthAt(1, -0.25); //0.25个单位的空格 - //ltt.SetDashLengthAt(2, 0); // 一个点 - //ltt.SetDashLengthAt(3, -0.25); //0.25个单位的空格 - } + // ltt.SetDashLengthAt(0, 0.5); // 0.5个单位的划线 + // ltt.SetDashLengthAt(1, -0.25); // 0.25个单位的空格 + // ltt.SetDashLengthAt(2, 0); // 一个点 + // ltt.SetDashLengthAt(3, -0.25); // 0.25个单位的空格 + } ); } #endregion @@ -232,18 +261,75 @@ public static ObjectId Add(this SymbolTable /// 字体名 /// 宽度比例 /// 文字样式Id - public static ObjectId Add(this SymbolTable table, string textStyleName, string font, double xscale) + public static ObjectId Add(this SymbolTable table, + string textStyleName, + string font, + double xscale = 1.0) { return table.Add( textStyleName, - tstr => - { + tstr => { tstr.Name = textStyleName; tstr.FileName = font; tstr.XScale = xscale; }); } + /// + /// 添加文字样式记录 + /// + /// 文字样式表 + /// 文字样式名 + /// 字体名枚举 + /// 宽度比例 + /// 文字样式Id + public static ObjectId Add(this SymbolTable table, + string textStyleName, + FontTTF fontTTF, + double xscale = 1.0) + { + return table.Add(textStyleName, fontTTF.GetDesc(), xscale); + } + + /// + ///

添加文字样式记录,如果存在就默认强制替换

+ /// 此函数为了 而设 + ///
+ /// 文字样式表 + /// 文字样式名 + /// 字体名 + /// 大字体名 + /// 宽度比例 + /// 高度 + /// 是否强制替换 + /// 文字样式Id + public static ObjectId AddWithChange(this SymbolTable table, + string textStyleName, + string smallFont, + string bigFont = "", + double xScale = 1, + double height = 0, + bool forceChange = true) + { + if (forceChange && table.Has(textStyleName)) + { + table.Change(textStyleName, ttr => { + ttr.FileName = smallFont; + ttr.XScale = xScale; + ttr.TextSize = height; + if (bigFont != string.Empty) + ttr.BigFontFileName = bigFont; + }); + return table[textStyleName]; + } + return table.Add(textStyleName, ttr => { + ttr.FileName = smallFont; + ttr.XScale = xScale; + ttr.TextSize = height; + }); + } + + #endregion #region 注册应用程序表 diff --git a/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableRecordEx.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/SymbolTableRecordEx.cs similarity index 44% rename from src/IFoxCAD.Cad/ExtensionMethod/SymbolTableRecordEx.cs rename to src/CAD/IFox.CAD.Shared/ExtensionMethod/SymbolTableRecordEx.cs index e5d55230bbf68ae0172deb9d7c28d30bec322861..1bd99a871524aca0fd2864270ba4a7f195c9d5fc 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableRecordEx.cs +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/SymbolTableRecordEx.cs @@ -5,9 +5,63 @@ ///
public static class SymbolTableRecordEx { + #region 块表记录 + #region 克隆实体id + /// + /// 深度克隆id到块表记录 + /// + /// 0x01 此方法不允许是未添加数据库的图元,因此它是id
+ /// 0x02 若为未添加数据库图元,则利用entity.Clone();同时不需要考虑动态块属性,可以使用entity.GetTransformedCopy + ///
+ ///
+ /// + /// + /// 克隆到当前块表记录,相当于原地克隆
+ /// 克隆到目标块表记录内,相当于制作新块 + ///
+ /// + /// 图元id集合,注意所有成员都要在同一个空间中 + /// 返回克隆后的id词典 + public static void DeepCloneEx(this BlockTableRecord btr, ObjectIdCollection objIds, IdMapping maoOut) + { + if (objIds is null || objIds.Count == 0) + throw new ArgumentNullException(nameof(objIds)); - #region 块表记录 + var db = objIds[0].Database; + using (btr.ForWrite()) + { + try + { + db.DeepCloneObjects(objIds, btr.ObjectId, maoOut, false); + + // 不在此提取,为了此函数被高频调用 + // 获取克隆键值对(旧块名,新块名) + // foreach (ObjectId item in blockIds) + // result.Add(mapping[item].Value); + } + catch (System.Exception e) + { + LogHelper.FlagOutVsOutput = true; + e.WriteLog("深度克隆出错了"); + } + } + } + + /// + /// 克隆图元实体(这个函数有问题,会出现偶尔成功,偶尔失败,拖动过变成匿名块) + /// 若为块则进行设置属性,因此控制动态块属性丢失; + /// + /// 图元 + /// 矩阵 + // public static void EntityTransformedCopy(this Entity ent, Matrix3d matrix) + // { + // var entNew = ent.GetTransformedCopy(matrix); + // if (ent is BlockReference blockReference) + // entNew.SetPropertiesFrom(blockReference); + // } + + #endregion #region 添加实体 /// @@ -17,10 +71,11 @@ public static class SymbolTableRecordEx /// 实体 /// 事务管理器 /// 对象 id - public static ObjectId AddEntity(this BlockTableRecord btr, Entity entity, Transaction trans = null) + public static ObjectId AddEntity(this BlockTableRecord btr, Entity entity, + Transaction? trans = null) { - if (entity is null) - throw new ArgumentNullException(nameof(entity), "对象为 null"); + // if (entity is null) + // throw new ArgumentNullException(nameof(entity), "对象为 null"); ObjectId id; trans ??= DBTrans.Top.Transaction; @@ -37,28 +92,26 @@ public static ObjectId AddEntity(this BlockTableRecord btr, Entity entity, Trans /// /// 实体类型 /// 块表记录 - /// 事务 /// 实体集合 + /// 事务 /// 对象 id 列表 - public static IEnumerable AddEntity(this BlockTableRecord btr, IEnumerable ents, Transaction trans = null) where T : Entity + public static IEnumerable AddEntity(this BlockTableRecord btr, IEnumerable ents, + Transaction? trans = null) where T : Entity { - if (ents.Any(ent => ent is null)) - throw new ArgumentNullException(nameof(ents), "实体集合内存在 null 对象"); + // if (ents.Any(ent => ent is null)) + // throw new ArgumentNullException(nameof(ents), "实体集合内存在 null 对象"); trans ??= DBTrans.Top.Transaction; using (btr.ForWrite()) { - return ents - .Select( - ent => - { - ObjectId id = btr.AppendEntity(ent); - trans.AddNewlyCreatedDBObject(ent, true); - return id; - }) - .ToList(); + return ents.Select(ent => { + ObjectId id = btr.AppendEntity(ent); + trans.AddNewlyCreatedDBObject(ent, true); + return id; + }).ToList(); } } + /// /// 添加多个实体 /// @@ -81,12 +134,27 @@ public static IEnumerable AddEntity(this BlockTableRecord btr, params /// 图元属性设置委托 /// 事务管理器 /// 图元id - private static ObjectId AddEnt(this BlockTableRecord btr, T ent, Action action, Transaction trans) where T : Entity + private static ObjectId AddEnt(this BlockTableRecord btr, T ent, + Action? action, Transaction? trans = null) where T : Entity { - trans ??= DBTrans.Top.Transaction; action?.Invoke(ent); return btr.AddEntity(ent, trans); } + /// + /// 委托式的添加图元 + /// + /// 块表 + /// 返回图元的委托 + /// 事务 + /// 图元id,如果委托返回 null,则为 ObjectId.Null + public static ObjectId AddEnt(this BlockTableRecord btr, Func action, Transaction? trans = null) + { + var ent = action.Invoke(); + if (ent is null) + return ObjectId.Null; + + return btr.AddEntity(ent, trans); + } /// /// 在指定绘图空间添加直线 @@ -97,7 +165,8 @@ private static ObjectId AddEnt(this BlockTableRecord btr, T ent, Action ac /// 绘图空间 /// 直线属性设置委托 /// 直线的id - public static ObjectId AddLine(this BlockTableRecord btr, Point3d start, Point3d end, Action action = default, Transaction trans = default) + public static ObjectId AddLine(this BlockTableRecord btr, Point3d start, Point3d end, + Action? action = null, Transaction? trans = null) { var line = new Line(start, end); return btr.AddEnt(line, action, trans); @@ -111,7 +180,8 @@ public static ObjectId AddLine(this BlockTableRecord btr, Point3d start, Point3d /// 圆属性设置委托 /// 事务管理器 /// 圆的id - public static ObjectId AddCircle(this BlockTableRecord btr, Point3d center, double radius, Action action = default, Transaction trans = default) + public static ObjectId AddCircle(this BlockTableRecord btr, Point3d center, double radius, + Action? action = null, Transaction? trans = null) { var circle = new Circle(center, Vector3d.ZAxis, radius); return btr.AddEnt(circle, action, trans); @@ -128,10 +198,45 @@ public static ObjectId AddCircle(this BlockTableRecord btr, Point3d center, doub /// 事务管理器 /// 三点有外接圆则返回圆的id,否则返回ObjectId.Null public static ObjectId AddCircle(this BlockTableRecord btr, Point3d p0, Point3d p1, Point3d p2, - Action action = default, Transaction trans = default) + Action? action = null, Transaction? trans = null) { - Circle circle = EntityEx.CreateCircle(p0, p1, p2); - return circle is not null ? btr.AddEnt(circle, action, trans) : throw new ArgumentNullException(nameof(circle), "对象为 null"); + var circle = CircleEx.CreateCircle(p0, p1, p2); + // return circle is not null ? btr.AddEnt(circle, action, trans) : throw new ArgumentNullException(nameof(circle), "对象为 null"); + //if (circle is null) + // throw new ArgumentNullException(nameof(circle), "对象为 null"); + circle.NotNull(nameof(circle)); + return btr.AddEnt(circle, action, trans); + } + /// + /// 在指定的绘图空间添加轻多段线 + /// + /// 绘图空间 + /// 多段线信息 + /// 线宽 + /// 是否闭合 + /// 轻多段线属性设置委托 + /// 事务管理器 + /// 轻多段线id + public static ObjectId AddPline(this BlockTableRecord btr, + List bvws, + double? constantWidth = null, + bool isClosed = true, + Action? action = null, Transaction? trans = null) + { + Polyline pl = new(); + pl.SetDatabaseDefaults(); + if (constantWidth is not null) + { + for (int i = 0; i < bvws.Count; i++) + pl.AddVertexAt(i, bvws[i].Vertex, bvws[i].Bulge, constantWidth.Value, constantWidth.Value); + } + else + { + for (int i = 0; i < bvws.Count; i++) + pl.AddVertexAt(i, bvws[i].Vertex, bvws[i].Bulge, bvws[i].StartWidth, bvws[i].EndWidth); + } + pl.Closed = isClosed;// 闭合 + return btr.AddEnt(pl, action, trans); } /// /// 在指定的绘图空间添加轻多段线 @@ -144,20 +249,26 @@ public static ObjectId AddCircle(this BlockTableRecord btr, Point3d p0, Point3d /// 轻多段线属性设置委托 /// 事务管理器 /// 轻多段线id - public static ObjectId AddPline(this BlockTableRecord btr, List pts, List bulges = default, List startWidths = default, List endWidths = default, Action action = default, Transaction trans = default) + public static ObjectId AddPline(this BlockTableRecord btr, + List pts, + List? bulges = null, + List? startWidths = null, + List? endWidths = null, + Action? action = null, + Transaction? trans = null) { - bulges ??= new List(new double[pts.Count]); - startWidths ??= new List(new double[pts.Count]); - endWidths ??= new List(new double[pts.Count]); + bulges ??= new(new double[pts.Count]); + startWidths ??= new(new double[pts.Count]); + endWidths ??= new(new double[pts.Count]); + Polyline pl = new(); + pl.SetDatabaseDefaults(); + for (int i = 0; i < pts.Count; i++) - { pl.AddVertexAt(i, pts[i].Point2d(), bulges[i], startWidths[i], endWidths[i]); - } return btr.AddEnt(pl, action, trans); } - /// /// 在指定的绘图空间添加轻多段线 /// @@ -166,19 +277,20 @@ public static ObjectId AddPline(this BlockTableRecord btr, List pts, Li /// 轻多段线属性设置委托 /// 事务管理器 /// 轻多段线id - public static ObjectId AddPline(this BlockTableRecord btr, List<(Point3d pt, double bulge, double startWidth, double endWidth)> pts, Action action = default, Transaction trans = default) - { - - Polyline pl = new(); - pts.ForEach((i, vertex) => - { - pl.AddVertexAt(i, vertex.pt.Point2d(), vertex.bulge, vertex.startWidth, vertex.endWidth); - }); - - return btr.AddEnt(pl, action, trans); - } + public static ObjectId AddPline(this BlockTableRecord btr, + List<(Point3d pt, double bulge, double startWidth, double endWidth)> pts, + Action? action = null, + Transaction? trans = null) + { + Polyline pl = new(); + pl.SetDatabaseDefaults(); + pts.ForEach((vertex, state, index) => { + pl.AddVertexAt(index, vertex.pt.Point2d(), vertex.bulge, vertex.startWidth, vertex.endWidth); + }); + return btr.AddEnt(pl, action, trans); + } /// /// 在指定绘图空间X-Y平面3点画圆弧 @@ -190,9 +302,11 @@ public static ObjectId AddPline(this BlockTableRecord btr, List<(Point3d pt, dou /// 圆弧属性设置委托 /// 事务管理器 /// 圆弧id - public static ObjectId AddArc(this BlockTableRecord btr, Point3d startPoint, Point3d pointOnArc, Point3d endPoint, Action action = default, Transaction trans = default) + public static ObjectId AddArc(this BlockTableRecord btr, + Point3d startPoint, Point3d pointOnArc, Point3d endPoint, + Action? action = null, Transaction? trans = null) { - var arc = EntityEx.CreateArc(startPoint, pointOnArc, endPoint); + var arc = ArcEx.CreateArc(startPoint, pointOnArc, endPoint); return btr.AddEnt(arc, action, trans); } #endregion @@ -200,24 +314,31 @@ public static ObjectId AddArc(this BlockTableRecord btr, Point3d startPoint, Poi #region 获取实体/实体id /// /// 获取块表记录内的指定类型的实体 + /// (此处不会检查id.IsOk()) /// /// 实体类型 /// 块表记录 - /// 事务 - /// 打开模式 + /// 打开模式 + /// 事务 + /// 是否打开已删除对象,默认为不打开 + /// 是否打开锁定图层对象,默认为不打开 /// 实体集合 - public static IEnumerable GetEntities(this BlockTableRecord btr, OpenMode mode = OpenMode.ForRead, Transaction trans = null) where T : Entity + public static IEnumerable GetEntities(this BlockTableRecord btr, + OpenMode openMode = OpenMode.ForRead, + Transaction? trans = null, + bool openErased = false, + bool openLockedLayer = false) where T : Entity { trans ??= DBTrans.Top.Transaction; return btr .Cast() - .Select(id => trans.GetObject(id, mode)) + .Select(id => trans.GetObject(id, openMode, openErased, openLockedLayer)) .OfType(); } /// - /// 按类型获取实体Id,AutoCad2010以上版本支持 + /// 按类型获取实体Id /// /// 实体类型 /// 块表记录 @@ -226,7 +347,7 @@ public static IEnumerable GetObjectIds(this BlockTableRecord btr) w { string dxfName = RXClass.GetClass(typeof(T)).DxfName; return btr.Cast() - .Where(id => id.ObjectClass.DxfName == dxfName); + .Where(id => id.ObjectClass()?.DxfName == dxfName); } /// @@ -236,47 +357,53 @@ public static IEnumerable GetObjectIds(this BlockTableRecord btr) w /// 实体Id分组 public static IEnumerable> GetObjectIds(this BlockTableRecord btr) { - return - btr - .Cast() - .GroupBy(id => id.ObjectClass.DxfName); + return btr.Cast() + .GroupBy(id => id.ObjectClass().DxfName); } + /// /// 获取绘制顺序表 /// /// 块表 - /// 事务 + /// 事务 + /// 是否打开已删除对象,默认为不打开 + /// 是否打开锁定图层对象,默认为不打开 /// 绘制顺序表 - public static DrawOrderTable GetDrawOrderTable(this BlockTableRecord btr, Transaction tr = null) + public static DrawOrderTable? GetDrawOrderTable(this BlockTableRecord btr, + Transaction? trans = null, + bool openErased = false, + bool openLockedLayer = false) { - tr ??= DBTrans.Top.Transaction; - return tr.GetObject(btr.DrawOrderTableId, OpenMode.ForRead) as DrawOrderTable; + trans ??= DBTrans.Top.Transaction; + return trans.GetObject(btr.DrawOrderTableId, OpenMode.ForRead, + openErased, openLockedLayer) as DrawOrderTable; } - #endregion #region 插入块参照 - /// /// 插入块参照 /// + /// 块表记录 /// 插入点 /// 块名 /// 块插入比例,默认为1 /// 块插入旋转角(弧度),默认为0 /// 属性字典{Tag,Value},默认为null + /// 事务 /// 块参照对象id public static ObjectId InsertBlock(this BlockTableRecord blockTableRecord, Point3d position, - string blockName, - Scale3d scale = default, - double rotation = default, - Dictionary atts = default, Transaction trans = null) + string blockName, + Scale3d scale = default, + double rotation = default, + Dictionary? atts = null, + Transaction? trans = null) { trans ??= DBTrans.Top.Transaction; if (!DBTrans.Top.BlockTable.Has(blockName)) { - DBTrans.Top.Editor.WriteMessage($"\n不存在名字为{blockName}的块定义。"); + DBTrans.Top.Editor?.WriteMessage($"\n不存在名字为{blockName}的块定义。"); return ObjectId.Null; } return blockTableRecord.InsertBlock(position, DBTrans.Top.BlockTable[blockName], scale, rotation, atts, trans); @@ -290,16 +417,18 @@ public static ObjectId InsertBlock(this BlockTableRecord blockTableRecord, Point /// 块插入旋转角(弧度),默认为0 /// 属性字典{Tag,Value},默认为null /// 块参照对象id - public static ObjectId InsertBlock(this BlockTableRecord blockTableRecord, Point3d position, - ObjectId blockId, - Scale3d scale = default, - double rotation = default, - Dictionary atts = default, Transaction trans = null) + public static ObjectId InsertBlock(this BlockTableRecord blockTableRecord, + Point3d position, + ObjectId blockId, + Scale3d scale = default, + double rotation = default, + Dictionary? atts = null, + Transaction? trans = null) { trans ??= DBTrans.Top.Transaction; if (!DBTrans.Top.BlockTable.Has(blockId)) { - DBTrans.Top.Editor.WriteMessage($"\n不存在名字为{DBTrans.Top.GetObject(blockId).Name}的块定义。"); + DBTrans.Top.Editor?.WriteMessage($"\n不存在块定义。"); return ObjectId.Null; } using var blockref = new BlockReference(position, blockId) @@ -308,24 +437,23 @@ public static ObjectId InsertBlock(this BlockTableRecord blockTableRecord, Point Rotation = rotation }; var objid = blockTableRecord.AddEntity(blockref); - if (atts != default) + + if (atts != null) { - var btr = DBTrans.Top.GetObject(blockref.BlockTableRecord); + var btr = DBTrans.Top.GetObject(blockref.BlockTableRecord)!; if (btr.HasAttributeDefinitions) { - var attdefs = btr - .GetEntities() - .Where(attdef => !(attdef.Constant || attdef.Invisible)); + var attdefs = btr.GetEntities(); foreach (var attdef in attdefs) { using AttributeReference attref = new(); + attref.SetDatabaseDefaults(); attref.SetAttributeFromBlock(attdef, blockref.BlockTransform); attref.Position = attdef.Position.TransformBy(blockref.BlockTransform); attref.AdjustAlignment(DBTrans.Top.Database); + if (atts.ContainsKey(attdef.Tag)) - { attref.TextString = atts[attdef.Tag]; - } blockref.AttributeCollection.AppendAttribute(attref); trans.AddNewlyCreatedDBObject(attref, true); @@ -334,10 +462,60 @@ public static ObjectId InsertBlock(this BlockTableRecord blockTableRecord, Point } return objid; } - #endregion + #endregion + #region 遍历 +#line hidden // 调试的时候跳过它 + /// + /// 遍历符号表记录,执行委托 + /// + /// 要运行的委托 + public static void ForEach(this TRecord record, Action task) + where TRecord : SymbolTableRecord, IEnumerable + { + foreach (ObjectId id in record) + task.Invoke(id); + } + /// + /// 遍历符号表记录,执行委托(允许循环中断) + /// + /// 要执行的委托 + public static void ForEach(this TRecord record, Action task) + where TRecord : SymbolTableRecord, IEnumerable + { + LoopState state = new();/*这种方式比Action改Func更友好*/ + foreach (ObjectId id in record) + { + task.Invoke(id, state); + if (!state.IsRun) + break; + } + } -} + /// + /// 遍历符号表记录,执行委托(允许循环中断,输出索引值) + /// + /// 要执行的委托 + [System.Diagnostics.DebuggerStepThrough] + public static void ForEach(this TRecord record, Action task) + where TRecord : SymbolTableRecord, IEnumerable + { + //if (task == null) + // throw new ArgumentNullException(nameof(task)); + task.NotNull(nameof(task)); + int i = 0; + LoopState state = new();/*这种方式比Action改Func更友好*/ + foreach (ObjectId id in record) + { + task.Invoke(id, state, i); + if (!state.IsRun) + break; + i++; + } + } +#line default + #endregion +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/ExtensionMethod/Tools.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/Tools.cs new file mode 100644 index 0000000000000000000000000000000000000000..11277224d98fad85debb811ac456441b0ed327eb --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/Tools.cs @@ -0,0 +1,60 @@ +using static IFoxCAD.Basal.Timer; + +namespace IFoxCAD.Cad; + +public static class Tools +{ + /// + /// 计时器 + /// + [System.Diagnostics.DebuggerStepThrough] + public static void TestTimes2(int count, string message, Action action) + { + System.Diagnostics.Stopwatch watch = new(); + watch.Start(); // 开始监视代码运行时间 + for (int i = 0; i < count; i++) + action.Invoke();// 需要测试的代码 + watch.Stop(); // 停止监视 + TimeSpan timespan = watch.Elapsed; // 获取当前实例测量得出的总时间 + double time = timespan.TotalMilliseconds; + string name = "毫秒"; + if (timespan.TotalMilliseconds > 1000) + { + time = timespan.TotalSeconds; + name = "秒"; + } + Env.Print($"{message} 代码执行 {count} 次的时间:{time} ({name})"); // 总毫秒数 + } + + /// + /// 纳秒计时器 + /// + [System.Diagnostics.DebuggerStepThrough] + public static void TestTimes(int count, string message, Action action, + TimeEnum timeEnum = TimeEnum.Millisecond) + { + var time = RunTime(() => { + for (int i = 0; i < count; i++) + action(); + }, timeEnum); + + string timeNameZn = ""; + switch (timeEnum) + { + case TimeEnum.Second: + timeNameZn = " 秒"; + break; + case TimeEnum.Millisecond: + timeNameZn = " 毫秒"; + break; + case TimeEnum.Microsecond: + timeNameZn = " 微秒"; + break; + case TimeEnum.Nanosecond: + timeNameZn = " 纳秒"; + break; + } + + Env.Print($"{message} 代码执行 {count} 次的时间:{time} ({timeNameZn})"); + } +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/ExtensionMethod/XrefEx.cs b/src/CAD/IFox.CAD.Shared/ExtensionMethod/XrefEx.cs new file mode 100644 index 0000000000000000000000000000000000000000..db54edc35470d7f6570c60223f85f86df4e03e1f --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/ExtensionMethod/XrefEx.cs @@ -0,0 +1,661 @@ +// #define error_demo + + + +namespace IFoxCAD.Cad; + +#region 参照工厂 +public interface IXrefBindModes +{ + /// + /// 卸载 + /// + public void Unload(); + /// + /// 重载 + /// + public void Reload(); + /// + /// 拆离 + /// + public void Detach(); + /// + /// 绑定 + /// + public void Bind(); +} + +public class XrefFactory : IXrefBindModes +{ + #region 私有字段 + readonly DBTrans _tr; + /// + /// 要处理的参照名称,就处理所有 + /// + readonly HashSet? _xrefNames; + #endregion + + #region 公开字段 + /// + /// 解析外部参照:线性引擎
+ /// 默认
+ /// 时会在cad命令历史打印一些AEC信息,并导致绑定慢一点...具体作用不详
+ ///
+ public bool UseThreadEngine = false; + /// + /// 解析外部参照:仅处理 Unresolved_未融入(未解析)的参照
+ /// 默认 + ///
+ public bool DoNewOnly = true; + /// + /// 解析外部参照:包含僵尸参照 + /// + public bool IncludeGhosts = true; + + + /// + /// 绑定模式和双美元符号相关(与cad保持相同的默认)
+ /// 为绑定模式,产生双美元; + /// 为插入模式,块重名会以本图覆盖; + ///
+ public bool BindOrInsert = false; + /// + /// bind时候是否拆离参照
+ /// 默认:学官方的绑定后自动拆离 + ///
+ public bool AutoDetach = true; + /// + /// bind时候是否删除被卸载的嵌套参照
+ /// 默认 + ///
+ public bool EraseNested = true; + /// + /// bind时候控制绑定的符号表:请保持默认
+ /// 目前仅推荐用于
+ /// 其他项有异常:
+ ///
+ public SymModes SymModesBind = SymModes.LayerTable; + #endregion + + #region 构造 + /// + /// 参照工厂 + /// + /// + /// 要处理的参照名称,就处理所有 + public XrefFactory(DBTrans tr, HashSet? xrefNames = null) + { + _tr = tr; + _xrefNames = xrefNames; + } + #endregion + + #region 重写 + public void Bind() + { + // 此功能有绑定出错的问题 + // db.BindXrefs(xrefIds, true); + + // 绑定后会自动拆离 + // 此功能修补了上面缺失 + DoubleBind(); + } + + public void Detach() + { + using ObjectIdCollection xrefIds = new(); + GetAllXrefNode(xrefIds); + foreach (ObjectId id in xrefIds) + _tr.Database.DetachXref(id); + } + + public void Reload() + { + using ObjectIdCollection xrefIds = new(); + GetAllXrefNode(xrefIds); + if (xrefIds.Count > 0) + _tr.Database.ReloadXrefs(xrefIds); + } + + public void Unload() + { + using ObjectIdCollection xrefIds = new(); + GetAllXrefNode(xrefIds); + if (xrefIds.Count > 0) + _tr.Database.UnloadXrefs(xrefIds); + } + #endregion + + #region 双重绑定 + /// + /// 获取参照 + /// + /// 返回全部参照id + void GetAllXrefNode(ObjectIdCollection xrefIds) + { + // 储存要处理的参照id + //var xrefIds = new ObjectIdCollection(); + XrefNodeForEach((xNodeName, xNodeId, xNodeStatus, xNodeIsNested) => { + if (XrefNamesContains(xNodeName)) + xrefIds.Add(xNodeId); + }); + } + + bool XrefNamesContains(string xNodeName) + { + // 为空的时候全部加入 || 有内容时候含有目标 + return _xrefNames is null || _xrefNames.Contains(xNodeName); + } + + /// + /// 遍历参照 + /// + /// (参照名,参照块表记录id,参照状态,是否嵌入) + void XrefNodeForEach(Action action) + { + // btRec.IsFromOverlayReference 是覆盖 + // btRec.GetXrefDatabase(true) 外部参照数据库 + + if (action == null) + return; + + // 解析外部参照:此功能不能锁定文档 + _tr.Database.ResolveXrefs(UseThreadEngine, DoNewOnly); + + var xg = _tr.Database.GetHostDwgXrefGraph(IncludeGhosts); + for (int i = 0; i < xg.NumNodes; i++) + { + var xNode = xg.GetXrefNode(i); + if (!xNode.BlockTableRecordId.IsOk()) + continue; + + action.Invoke(xNode.Name, + xNode.BlockTableRecordId, + xNode.XrefStatus, + xNode.IsNested); + } + } + + /// + /// 符号表记录加入容器 + /// + static void AddedxbindIds(ObjectIdCollection xbindXrefsIds, + SymbolTable symbolTable) + where TTable : SymbolTable + where TRecord : SymbolTableRecord, new() + { + symbolTable.ForEach(tabRec => { + if (tabRec.IsResolved) + xbindXrefsIds.Add(tabRec.ObjectId); + }, checkIdOk: true); + } + + + void GetXBindIds(ObjectIdCollection xbindIds) + { + // xbind + // 0x01 它是用来绑其他符号表,绑块表会有异常 + // 0x02 集合若有问题,就会出现eWrongObjectType + //var xbindIds = new ObjectIdCollection(); + + // 起初测试是将九大符号表记录均加入的,但经实测不行...(为什么?存疑) + #region Option1 + if ((SymModesBind & SymModes.LayerTable) == SymModes.LayerTable) + AddedxbindIds(xbindIds, _tr.LayerTable); + + if ((SymModesBind & SymModes.TextStyleTable) == SymModes.TextStyleTable) + AddedxbindIds(xbindIds, _tr.TextStyleTable); + + if ((SymModesBind & SymModes.RegAppTable) == SymModes.RegAppTable) + AddedxbindIds(xbindIds, _tr.RegAppTable); + + if ((SymModesBind & SymModes.DimStyleTable) == SymModes.DimStyleTable) + AddedxbindIds(xbindIds, _tr.DimStyleTable); + + if ((SymModesBind & SymModes.LinetypeTable) == SymModes.LinetypeTable) + AddedxbindIds(xbindIds, _tr.LinetypeTable); + #endregion + + #region Option2 + if ((SymModesBind & SymModes.UcsTable) == SymModes.UcsTable) + AddedxbindIds(xbindIds, _tr.UcsTable); + + if ((SymModesBind & SymModes.ViewTable) == SymModes.ViewTable) + AddedxbindIds(xbindIds, _tr.ViewTable); + + if ((SymModesBind & SymModes.ViewportTable) == SymModes.ViewportTable) + AddedxbindIds(xbindIds, _tr.ViewportTable); + #endregion + } + + void GetBindIds(ObjectIdCollection bindIds) + { + // bind 只绑块表 + //var bindIds = new ObjectIdCollection(); + + _tr.BlockTable.ForEach(btr => { + if (btr.IsLayout) + return; + + // 外部参照 && 已融入 + if (btr.IsFromExternalReference && btr.IsResolved) + bindIds.Add(btr.ObjectId); + }, checkIdOk: true); + } + + /// + /// 获取可以拆离的ids + /// + /// 返回已卸载中含有嵌套的参照,要重载之后才能绑定 + /// 返回未参照中嵌套的参照,直接拆离 + List GetDetachIds(Dictionary nested) + { + // 直接拆离的id + List detachIds = new(); + + // 收集要处理的id + XrefNodeForEach((xNodeName, xNodeId, xNodeStatus, xNodeIsNested) => { + switch (xNodeStatus) + { + case XrefStatus.Unresolved:// 未融入_ResolveXrefs参数2 + break; + case XrefStatus.FileNotFound:// 未融入(未解析)_未找到文件 + break; + case XrefStatus.Unreferenced:// 未参照 + { + // 为空的时候全部加入 || 有内容时候含有目标 + if (XrefNamesContains(xNodeName)) + detachIds.Add(xNodeId); + } + break; + case XrefStatus.Unloaded:// 已卸载 + { + // 为空的时候全部加入 || 有内容时候含有目标 + if (XrefNamesContains(xNodeName)) + { + var btr = _tr.GetObject(xNodeId); + if (btr != null && btr.IsFromExternalReference) + { + if (!xNodeIsNested) + detachIds.Add(xNodeId); + else if (!nested.ContainsKey(xNodeId)) + nested.Add(xNodeId, xNodeName);// 嵌套参照 + } + } + } + break; + case XrefStatus.Resolved:// 已融入_就是可以绑定的 + break; + case XrefStatus.NotAnXref:// 不是外部参照 + break; + default: + break; + } + }); + return detachIds; + } + + /// + /// 双重绑定参照 + /// 参考链接 + /// + void DoubleBind() + { + Dictionary nested = new(); + var detachIds = GetDetachIds(nested); + + // 拆离:未参照的文件 + if (AutoDetach) + { + for (int i = 0; i < detachIds.Count; i++) + _tr.Database.DetachXref(detachIds[i]); + } + // 重载:嵌套参照已卸载了,需要重载之后才能进行绑定 + var keys = nested.Keys; + if (keys.Count > 0) + { + using ObjectIdCollection idc = new(keys.ToArray()); + _tr.Database.ReloadXrefs(idc); + } + + // 绑定:切勿交换,否则会绑定无效 + using ObjectIdCollection bindIds = new(); + using ObjectIdCollection xbindIds = new(); + GetBindIds(bindIds); + GetXBindIds(xbindIds); + if (xbindIds.Count > 0) + _tr.Database.XBindXrefs(xbindIds, BindOrInsert); + if (bindIds.Count > 0) + _tr.Database.BindXrefs(bindIds, BindOrInsert); + + + // 内部删除嵌套参照的块操作 + if (EraseNested) + { +#if ac2008 + // 因为Acad08索引器存在会暴露isErase的(桌子底层的原因), + // 也就是可能获取两个名称一样的,只能用遍历的方式进行 + HashSet nestedHash = new(); + foreach (var item in nested) + nestedHash.Add(item.Value); + + // 遍历全图,找到参照名称一样的删除 + _tr.BlockTable.ForEach(btr => { + if (btr.IsLayout) + return; + if (nestedHash.Contains(btr.Name)) + { + btr.UpgradeOpen(); + btr.Erase(); + btr.DowngradeOpen(); + btr.Dispose(); + } + }, checkIdOk: true); +#else + foreach (var item in nested) + { + var name = item.Value; + if (_tr.BlockTable.Has(name)) + _tr.GetObject(_tr.BlockTable[name], OpenMode.ForWrite)? + .Erase(); + } +#endif + } + } + #endregion +} + + + +public static class XrefEx +{ + /// + /// 外部参照工厂 + /// + /// + /// 处理参照的枚举 + /// 要处理的参照名称,就处理所有 + public static void XrefFactory(this DBTrans tr, + XrefModes xrefModes, + HashSet? xrefNames = null) + { + var xf = new XrefFactory(tr, xrefNames); + tr.Task(() => { + switch (xrefModes) + { + case XrefModes.Unload: + xf.Unload(); + break; + case XrefModes.Reload: + xf.Reload(); + break; + case XrefModes.Detach: + xf.Detach(); + break; + case XrefModes.Bind: + xf.Bind(); + break; + default: + break; + } + }); + } +} + +#endregion + +#region 参照路径工具类 +/// +/// 获取外部参照的路径 +/// +public class XrefPath +{ + #region 属性 + /// + /// 基础路径 + /// + public string CurrentDatabasePath; + /// + /// 是否外部参照 + /// + public bool IsFromExternalReference { get; private set; } + /// + /// 外部参照保存的路径 + /// + /// 它们会是以下任一路径:
+ /// 0x01 相对路径
+ /// 0x02 绝对路径
+ /// 0x03 共目录优先找到的路径(文件夹整体移动会发生此类情况) + ///
+ ///
+ public string? PathSave { get; private set; } + /// + /// 找到的路径(参照面板的名称) + /// 路径不存在时,返回是外部参照dwg文件路径 + /// + public string? PathDescribe { get; private set; } + + string? _PathComplete; + /// + /// 绝对路径 + /// + public string? PathComplete => _PathComplete ??= + PathConverter(CurrentDatabasePath, PathDescribe, PathConverterModes.Complete); + + string? _PathRelative; + /// + /// 相对路径 + /// + public string? PathRelative => _PathRelative ??= + PathConverter(CurrentDatabasePath, PathComplete, PathConverterModes.Relative); + #endregion + + #region 构造 + /// + /// 获取外部参照的路径 + /// + /// 外部参照图元 + /// 事务 + /// 是否外部参照 + public XrefPath(BlockReference brf, DBTrans tr) + { + //if (brf == null) + // throw new ArgumentNullException(nameof(brf)); + brf.NotNull(nameof(brf)); + CurrentDatabasePath = Path.GetDirectoryName(tr.Database.Filename); + + var btRec = tr.GetObject(brf.BlockTableRecord);// 块表记录 + if (btRec == null) + return; + + IsFromExternalReference = btRec.IsFromExternalReference; + if (!IsFromExternalReference) + return; + + // 相对路径==".\\AA.dwg" + // 无路径=="AA.dwg" + PathSave = btRec.PathName; + + if ((!string.IsNullOrEmpty(PathSave) && PathSave[0] == '.') || File.Exists(PathSave)) + { + // 相对路径||绝对路径 + PathDescribe = PathSave; + } + else + { + // 无路径 + var db = btRec.GetXrefDatabase(true); + PathDescribe = db.Filename; + } + } + #endregion + + #region 静态函数 + /// + /// 获取相对路径或者绝对路径 + /// 参考链接 + /// + /// 基础目录(末尾无斜杠) + /// 相对路径或者绝对路径 + /// 依照枚举返回对应的字符串 + /// + public static string? PathConverter(string? directory, string? fileRelations, PathConverterModes converterModes) + { + //if (directory == null) + // throw new ArgumentNullException(nameof(directory)); + //if (fileRelations == null) + // throw new ArgumentNullException(nameof(fileRelations)); + + directory.NotNull(nameof(directory)); + fileRelations.NotNull(nameof(fileRelations)); + + string? result = null; + switch (converterModes) + { + case PathConverterModes.Relative: + result = GetRelativePath(directory, fileRelations); + break; + case PathConverterModes.Complete: + result = GetCompletePath(directory, fileRelations); + break; + default: + break; + } + return result; + } + +#if error_demo + /// + /// 绝对路径->相对路径 + /// + /// 绝对路径 + /// 相对关系 + /// + /// StringHelper.GetRelativePath("G:\\A1.项目\\20190920金山谷黄宅\\01.饰施图\\03.平面图", + /// "G:\\A1.项目\\20190920金山谷黄宅\\01.饰施图\\01.辅助文件\\图框\\A3图框.dwg"); + public static string GetRelativePath(string strDbPath, string strXrefPath) + { + Uri uri1 = new(strXrefPath); + Uri uri2 = new(strDbPath); + Uri relativeUri = uri2.MakeRelativeUri(uri1); + // 测试例子变成 01.%E8%BE%85%E5%8A%A9%E6%96%87%E4%BB%B6/%E5%9B%BE%E6%A1%86/A3%E5%9B%BE%E6%A1%86.dwg + string str = relativeUri.ToString(); + + // 因为这里不会实现".\A.dwg"而是"A.dwg",所以加入这个操作,满足同目录文件 + var strs = str.Split('\\'); + if (strs.Length == 1) + str = ".\\" + str; + return str; + } +#else + /// + /// 绝对路径->相对路径 + /// + /// 相对关系:文件夹路径 + /// 完整路径:文件路径 + /// 相对路径 + /// "..\\01.辅助文件\\图框\\A3图框.dwg" + /// ]]> + static string GetRelativePath(string directory, string file) + { + string[] directorys = directory.Split('\\'); + string[] files = file.Split('\\'); + // 获取两条路径中的最短路径 + int getMinLength = directorys.Length < files.Length ? directorys.Length : files.Length; + + // 用于确定我们退出的循环中的位置。 + int lastCommonRoot = -1; + int index; + // 找到共根 + for (index = 0; index < getMinLength; index++) + { + if (directorys[index] != files[index]) + break; + lastCommonRoot = index; + } + // 如果我们没有找到一个共同的前缀,那么抛出 + if (lastCommonRoot == -1) + throw new ArgumentException("路径没有公共相同路径部分"); + + // 建立相对路径 + var result = new StringBuilder(); + for (index = lastCommonRoot + 1; index < directorys.Length; index++) + if (directorys[index].Length > 0) + result.Append("..\\");// 上级目录加入 + + // 添加文件夹 + for (index = lastCommonRoot + 1; index < files.Length - 1; index++) + result.Append(files[index] + "\\"); + + // 本级目录 + if (result.Length == 0) + result.Append(".\\"); + // result.Append(strXrefPaths[^1]);// 下级目录加入 + result.Append(files[files.Length - 1]);// 下级目录加入 + return result.ToString(); + } +#endif + + /// + /// 相对路径->绝对路径 + /// + /// 文件夹路径 + /// 相对关系:有..的 + /// 完整路径 + /// "G:\\A1.项目\\20190920金山谷黄宅\\01.饰施图\\01.辅助文件\\图框\\A3图框.dwg" + /// ]]> + static string? GetCompletePath(string directory, string relativePath) + { + if (relativePath is null || relativePath.Trim() == string.Empty) + return null; + + var relativeName = Path.GetDirectoryName(relativePath); + if (relativeName is null) + return null; + + if (relativePath[0] != '.') + return relativePath; + + const char slash = '\\'; + + // 判断向上删除几个 + var path_xiangduis = relativeName.Split(slash); + int index = 0; + for (int i = 0; i < path_xiangduis.Length; i++) + { + if (path_xiangduis[i] != "..") + break; + index++; + } + + var result = new StringBuilder(); + // 前段 + var path_dwgs = directory.Split(slash); + path_dwgs = path_dwgs.Where(s => !string.IsNullOrEmpty(s)).ToArray();// 清理空数组 + for (int i = 0; i < path_dwgs.Length - index; i++) + { + result.Append(path_dwgs[i]); + result.Append(slash); + } + // 后段 + for (int i = 0; i < path_xiangduis.Length; i++) + { + var item = path_xiangduis[i]; + if (item != "." && item != "..") + { + result.Append(item); + result.Append(slash); + } + } + result.Append(Path.GetFileName(relativePath)); + return result.ToString(); + } + #endregion +} +#endregion \ No newline at end of file diff --git "a/src/CAD/IFox.CAD.Shared/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchConverter.cs" "b/src/CAD/IFox.CAD.Shared/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchConverter.cs" new file mode 100644 index 0000000000000000000000000000000000000000..9f549dfbdd830ce036611a01241732bb0874e00a --- /dev/null +++ "b/src/CAD/IFox.CAD.Shared/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchConverter.cs" @@ -0,0 +1,402 @@ +namespace IFoxCAD.Cad; + + +using System.Data; +using PointV = Point2d; + +/// +/// 填充边界转换器 +/// +public class HatchConverter +{ + #region 辅助类 + /// + /// 生成圆形数据 + /// + class CircleData + { + public PointV Center; + public double Radius; + + /// + /// 生成圆形数据 + /// + /// 对称点1 + /// 对称点2 + public CircleData(PointV symmetryAxisPoint1, PointV symmetryAxisPoint2) + { + Center = symmetryAxisPoint1.GetCenter(symmetryAxisPoint2); + Radius = symmetryAxisPoint1.GetDistanceTo(symmetryAxisPoint2) * 0.5; + } + } + + /// + /// 填充转换器的数据 + /// + class HatchConverterData + { + public List PolyLineData; + public List CircleData; + public List SplineData; + + /// + /// 填充转换器的数据 + /// + public HatchConverterData() + { + PolyLineData = new(); + CircleData = new(); + SplineData = new(); + } + } + #endregion + + #region 成员 + /// + /// 外部只能调用id,否则跨事务造成错误 + /// + public ObjectId OldHatchId + { + get + { + if (_oldHatch is null) + return ObjectId.Null; + return _oldHatch.ObjectId; + } + } + readonly Hatch? _oldHatch; + + readonly List _hcDatas; + /// + /// 填充边界id(生成的/已存在反应器的直接提取) + /// + public List BoundaryIds; + #endregion + + #region 构造 + /// + /// 填充边界转换器 + /// + HatchConverter() + { + _hcDatas = new(); + BoundaryIds = new(); + } + + /// + /// 填充边界转换器 + /// + /// 需要转化的Hatch对象 + public HatchConverter(Hatch hatch) : this() + { + _oldHatch = hatch; + + if (hatch.Associative) + { + // 填充边界反应器 + var assIds = hatch.GetAssociatedObjectIds(); + if (assIds != null) + { + foreach (ObjectId id in assIds) + if (id.IsOk()) + BoundaryIds.Add(id); + + if (BoundaryIds.Count == 0) + { + throw new ArgumentException("关联的填充边界被删除后没有清理反应器,请调用:" + + "\n hatch.RemoveAssociatedObjectIds()" + + "\n hatch.Associative = false"); + } + } + } + } + + /// + /// 提取边界信息 + /// + public void GetBoundarysData() + { + _oldHatch?.ForEach(loop => { + HatchConverterData hcData = new(); + + bool isCurve2d = true; + if (loop.IsPolyline) + { + // 边界是多段线 + HatchLoopIsPolyline(loop, hcData); + isCurve2d = false; + } + else + { + if (loop.Curves.Count == 2)// 1是不可能的,大于2的是曲线 + { + // 边界是曲线,过滤可能是圆形的情况 + var cir = TwoArcFormOneCircle(loop); + if (cir is not null) + { + hcData.CircleData.Add(cir); + isCurve2d = false; + } + } + } + + // 边界是曲线 + if (isCurve2d) + HatchLoopIsCurve2d(loop, hcData); + + _hcDatas.Add(hcData); + }); + } + #endregion + + #region 方法 + /// + /// 多段线处理 + /// + /// 填充边界 + /// 收集图元信息 + static void HatchLoopIsPolyline(HatchLoop loop, HatchConverterData hcData) + { + //if (loop is null) + // throw new ArgumentNullException(nameof(loop)); + + //if (hcData is null) + // throw new ArgumentNullException(nameof(hcData)); + + loop.NotNull(nameof(loop)); + hcData.NotNull(nameof(hcData)); + + + // 判断为圆形: + // 上下两个圆弧,然后填充,就会生成此种填充 + // 顶点数是3,凸度是半圆,两个半圆就是一个圆形 + if (loop.Polyline.Count == 3 && loop.Polyline[0].Bulge == 1 && loop.Polyline[1].Bulge == 1 || + loop.Polyline.Count == 3 && loop.Polyline[0].Bulge == -1 && loop.Polyline[1].Bulge == -1) + { + hcData.CircleData.Add(new CircleData(loop.Polyline[0].Vertex, loop.Polyline[1].Vertex)); + } + else + { + // 遍历多段线信息 + var bvc = loop.Polyline; + for (int i = 0; i < bvc.Count; i++) + hcData.PolyLineData.Add(new BulgeVertexWidth(bvc[i])); + } + } + + /// + /// 两个圆弧组成圆形 + /// + /// + /// + static CircleData? TwoArcFormOneCircle(HatchLoop loop) + { + //if (loop is null) + // throw new ArgumentNullException(nameof(loop)); + + loop.NotNull(nameof(loop)); + + if (loop.Curves.Count != 2) + throw new ArgumentException( + "边界非多段线,而且点数!=2,点数为:" + nameof(loop.Curves.Count) + ";两个矩形交集的时候会出现此情况."); + + CircleData? circular = null; + + // 判断为圆形: + // 用一条(不是两条)多段线画出两条圆弧为正圆,就会生成此种填充 + // 边界为曲线,数量为2,可能是两个半圆曲线,如果是,就加入圆形数据中 + + // 第一段 + var getCurves1Pts = loop.Curves[0].GetSamplePoints(3); // 曲线取样点分两份(3点) + var mid1Pt = getCurves1Pts[1]; // 腰点 + double bulge1 = loop.Curves[0].StartPoint.GetArcBulge(mid1Pt, loop.Curves[0].EndPoint); + + // 第二段 + var getCurves2Pts = loop.Curves[1].GetSamplePoints(3); + var mid2Pt = getCurves2Pts[1]; + double bulge2 = loop.Curves[1].StartPoint.GetArcBulge(mid2Pt, loop.Curves[1].EndPoint); + + // 第一段上弧&&第二段反弧 || 第一段反弧&&第二段上弧 + if (bulge1 == -1 && bulge2 == -1 || bulge1 == 1 && bulge2 == 1) + circular = new CircleData(loop.Curves[0].StartPoint, loop.Curves[1].StartPoint); // 两个起点就是对称点 + + return circular; + } + + /// + /// 处理边界曲线 + /// + /// 填充边界 + /// 收集图元信息 + static void HatchLoopIsCurve2d(HatchLoop loop, HatchConverterData hcData) + { + // 取每一段曲线,曲线可能是直线来的,但是圆弧会按照顶点来分段 + int curveIsClosed = 0; + + // 遍历边界的多个子段 + foreach (Curve2d curve in loop.Curves) + { + // 计数用于实现闭合 + curveIsClosed++; + if (curve is NurbCurve2d spl) + { + // 判断为样条曲线: + hcData.SplineData.Add(spl); + continue; + } + + var pts = curve.GetSamplePoints(3); + var midPt = pts[1]; + if (curve.StartPoint.IsEqualTo(curve.EndPoint, new Tolerance(1e-6, 1e-6)))// 首尾相同,就是圆形 + { + // 判断为圆形: + // 获取起点,然后采样三点,中间就是对称点(直径点) + hcData.CircleData.Add(new CircleData(curve.StartPoint, midPt)); + continue; + } + + // 判断为多段线,圆弧: + double bulge = curve.StartPoint.GetArcBulge(midPt, curve.EndPoint); + hcData.PolyLineData.Add(new BulgeVertexWidth(curve.StartPoint, bulge)); + + // 末尾点,不闭合的情况下就要获取这个 + if (curveIsClosed == loop.Curves.Count) + hcData.PolyLineData.Add(new BulgeVertexWidth(curve.EndPoint, 0)); + } + } + + /// + /// 创建边界图元 + /// + /// 返回图元 + public void CreateBoundary(List outEnts) + { + for (int i = 0; i < _hcDatas.Count; i++) + { + var data = _hcDatas[i]; + + // 生成边界:多段线 + if (data.PolyLineData.Count > 0) + { + Polyline pl = new(); + pl.SetDatabaseDefaults(); + for (int j = 0; j < data.PolyLineData.Count; j++) + { + pl.AddVertexAt(j, + data.PolyLineData[j].Vertex, + data.PolyLineData[j].Bulge, + data.PolyLineData[j].StartWidth, + data.PolyLineData[j].EndWidth); + } + outEnts.Add(pl); + } + + // 生成边界:圆 + data.CircleData.ForEach(item => { + outEnts.Add(new Circle(item.Center.Point3d(), Vector3d.ZAxis, item.Radius)); + }); + + // 生成边界:样条曲线 + data.SplineData.ForEach(item => { + outEnts.Add(item.ToCurve()); + }); + } + + if (_oldHatch is not null) + { + outEnts.ForEach(ent => { + ent.Color = _oldHatch.Color; + ent.Layer = _oldHatch.Layer; + }); + } + } + + + /// + /// 创建边界图元和新填充到当前空间 + /// + /// + /// 边界关联 + /// 是否创建填充,false则只创建边界 + /// 新填充id,边界在获取 + public ObjectId CreateBoundarysAndHatchToMsPs(BlockTableRecord btrOfAddEntitySpace, + bool boundaryAssociative = true, + bool createHatchFlag = true, + Transaction? trans = null) + { + List boEnts = new(); + CreateBoundary(boEnts); + boEnts.ForEach(ent => { + BoundaryIds.Add(btrOfAddEntitySpace.AddEntity(ent)); + }); + + if (!createHatchFlag) + return ObjectId.Null; + /* + * 此处为什么要克隆填充,而不是新建填充? + * 因为填充如果是新建的,那么将会丢失基点,概念如下: + * 两个一样的填充,平移其中一个,那么再提取他们的基点会是一样的! + * 所以生成时候就不等同于画面相同. + * 也因为我不知道什么新建方式可以新建一模一样的填充,因此使用了克隆 + * 那么它的平移后的基点在哪里呢? + */ + + using ObjectIdCollection idc = new(new ObjectId[] { OldHatchId }); + using IdMapping map = new(); + btrOfAddEntitySpace.DeepCloneEx(idc, map); + var newHatchId = map.GetValues()[0]; + trans ??= DBTrans.Top.Transaction; + + bool openErased = false; + bool openLockedLayer = false; + var hatchEnt = trans.GetObject(newHatchId, OpenMode.ForWrite, + openErased, openLockedLayer) as Hatch; + if (hatchEnt != null) + { + ResetBoundary(hatchEnt, boundaryAssociative); + hatchEnt.DowngradeOpen(); + } + return newHatchId; + } + + /// + /// 重设边界 + /// + /// + /// 边界关联 + void ResetBoundary(Hatch hatch, bool boundaryAssociative = true) + { + if (BoundaryIds.Count == 0) + return; + + // todo ------ acad08分离填充报错: Microsoft Visual Studio C 运行库在 acad.exe 中检测到一个错误 + // 0x01 测试命令 CmdTest_CreateHatch 创建是可以分离的, + // 那么可能是 克隆后 修改导致的, + // 我是克隆了之后移除原有边界,为了一些xdata之类的 + // 0x02 测试了 hatch.SetDatabaseDefaults(); 并不是因为这个 + // 0x03 测试了 v1110 不移除原有边界,而是加入了之后再移除旧的边界,也是一样 + // 要处理这个问题,我想:自己实现一个分离填充,不用cad自带的,然后单独填充每个. + // 填充边界的算法是扫描线算法.这样就可以绕过去了...发现过于麻烦,放弃... + + // v1110 删除原有边界 + while (hatch.NumberOfLoops != 0) + hatch.RemoveLoopAt(0); + + hatch.Associative = boundaryAssociative; + + using ObjectIdCollection obIds = new(); + for (int i = 0; i < BoundaryIds.Count; i++) + { + obIds.Clear(); + obIds.Add(BoundaryIds[i]); + // 要先添加最外面的边界 + if (i == 0) + hatch.AppendLoop(HatchLoopTypes.Outermost, obIds); + else + hatch.AppendLoop(HatchLoopTypes.Default, obIds); + } + // 计算填充并显示 + hatch.EvaluateHatch(true); + } + #endregion +} \ No newline at end of file diff --git "a/src/CAD/IFox.CAD.Shared/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchEx.cs" "b/src/CAD/IFox.CAD.Shared/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchEx.cs" new file mode 100644 index 0000000000000000000000000000000000000000..8bfd04310d10da6a1156dc604ad1451830236618 --- /dev/null +++ "b/src/CAD/IFox.CAD.Shared/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchEx.cs" @@ -0,0 +1,15 @@ +namespace IFoxCAD.Cad; + +public static class HatchEx +{ + /// + /// 遍历填充每条边 + /// + /// + /// + public static void ForEach(this Hatch hatch, Action action) + { + for (int i = 0; i < hatch.NumberOfLoops; i++) + action.Invoke(hatch.GetLoopAt(i)); + } +} \ No newline at end of file diff --git "a/src/CAD/IFox.CAD.Shared/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchInfo.cs" "b/src/CAD/IFox.CAD.Shared/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchInfo.cs" new file mode 100644 index 0000000000000000000000000000000000000000..1763aaf319f153dda11b863167e3a7ecb81b8553 --- /dev/null +++ "b/src/CAD/IFox.CAD.Shared/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchInfo.cs" @@ -0,0 +1,384 @@ + + +namespace IFoxCAD.Cad; + +/* + * 添加的第一个边界必须是外边界,即用于定义图案填充最外面的边界。 + * 要添加外部边界,请使用添加环的类型为 HatchLoopTypes.Outermost 常量的 AppendLoop 方法, + * 一旦外边界被定义,就可以继续添加另外的边界。 + * 添加内部边界请使用带 HatchLoopTypes.Default 常量的 AppendLoop 方法。 + * + * 多个外边界的时候,添加的是(外边界,外边界,外边界,普通边界....) + * 多个外边界的时候,添加的是(外边界,普通边界.....外边界,普通边界....) + */ + +/// +/// 图案填充 +/// +public class HatchInfo +{ + #region 成员 + /// + /// 边界id(最外面放第一) + /// + readonly List _boundaryIds; + /// + /// 填充图元 + /// + readonly Hatch _hatch; + /// + /// 边界关联(此处不能直接=>给填充成员,因为它会加入反应器) + /// + readonly bool _boundaryAssociative; + /// + /// 填充的名称:用户定义(固定名称)/渐变/填充依据定义文件 + /// + string? _hatchName; + /// + /// 填充模式类型(预定义/用户定义/自定义) + /// + HatchPatternType _patternTypeHatch; + /// + /// 渐变模式类型 + /// + GradientPatternType _patternTypeGradient; + /// + /// 比例/间距 + /// + double Scale => _hatch.PatternScale; + /// + /// 角度 + /// + double Angle => _hatch.PatternAngle; + + // 延后处理角度 + private double _angle; + #endregion + + #region 构造 + HatchInfo() + { + _hatch = new Hatch(); + _hatch.SetDatabaseDefaults(); + _boundaryIds = new(); + } + + /// + /// 图案填充 + /// + /// 关联边界 + /// 填充原点 + /// 比例 + /// 角度 + public HatchInfo(bool boundaryAssociative = true, + Point2d? hatchOrigin = null, + double hatchScale = 1, + double hatchAngle = 0) : this() + { + if (hatchScale <= 0) + throw new ArgumentException("填充比例不允许小于等于0"); + + _hatch.PatternScale = hatchScale;// 填充比例 + _angle = hatchAngle;// 填充角度 + _boundaryAssociative = boundaryAssociative; + + hatchOrigin ??= Point2d.Origin; + _hatch.Origin = hatchOrigin.Value; // 填充原点 + } + + /// + /// 图案填充 + /// + /// 边界 + /// 关联边界 + /// 填充原点 + /// 比例 + /// 角度 + public HatchInfo(IEnumerable boundaryIds, + bool boundaryAssociative = true, + Point2d? hatchOrigin = null, + double hatchScale = 1, + double hatchAngle = 0) + : this(boundaryAssociative, hatchOrigin, hatchScale, hatchAngle) + { + _boundaryIds.AddRange(boundaryIds); + } + + #endregion + + #region 方法 + /// + /// 模式1:预定义 + /// + public HatchInfo Mode1PreDefined(string name) + { + _hatchName = name; + _hatch.HatchObjectType = HatchObjectType.HatchObject; // 对象类型(填充/渐变) + _patternTypeHatch = HatchPatternType.PreDefined; + return this; + } + + /// + /// 模式2:用户定义 + /// + /// 是否双向 + public HatchInfo Mode2UserDefined(bool patternDouble = true) + { + _hatchName = "_USER"; + _hatch.HatchObjectType = HatchObjectType.HatchObject; // 对象类型(填充/渐变) + _patternTypeHatch = HatchPatternType.UserDefined; + + _hatch.PatternDouble = patternDouble; // 是否双向(必须写在 SetHatchPattern 之前) + _hatch.PatternSpace = Scale; // 间距(必须写在 SetHatchPattern 之前) + return this; + } + + /// + /// 模式3:自定义 + /// + /// + public HatchInfo Mode3UserDefined(string name) + { + _hatchName = name; + _hatch.HatchObjectType = HatchObjectType.HatchObject; // 对象类型(填充/渐变) + _patternTypeHatch = HatchPatternType.CustomDefined; + return this; + } + + /// + /// 模式4:渐变填充 + /// + /// 渐变填充名称 + /// 渐变色起始颜色 + /// 渐变色结束颜色 + /// 渐变移动 + /// 色调值 + /// 单色双色 + public HatchInfo Mode4Gradient(GradientName name, Color colorStart, Color colorEnd, + float gradientShift = 0, + float shadeTintValue = 0, + bool gradientOneColorMode = false) + { + // entget渐变的名字必然是"SOLID",但是这里作为"渐变"名,而不是"填充"名 + _hatchName = name.ToString(); + _hatch.HatchObjectType = HatchObjectType.GradientObject; // 对象类型(填充/渐变) + _patternTypeGradient = GradientPatternType.PreDefinedGradient;// 模式4:渐变 + // _patternTypeGradient = GradientPatternType.UserDefinedGradient;// 模式5:渐变..这种模式干啥用呢 + + // 设置渐变色填充的起始和结束颜色 + var gColor1 = new GradientColor(colorStart, 0); + var gColor2 = new GradientColor(colorEnd, 1); + _hatch.SetGradientColors(new GradientColor[] { gColor1, gColor2 }); + + _hatch.GradientShift = gradientShift; // 梯度位移 + _hatch.ShadeTintValue = shadeTintValue; // 阴影色值 + _hatch.GradientOneColorMode = gradientOneColorMode;// 渐变单色/双色 + _hatch.GradientAngle = Angle; // 渐变角度 + + return this; + } + + /// + /// 构建 + /// + /// 将填充加入此空间 + public ObjectId Build(BlockTableRecord btrOfAddEntitySpace) + { + // 加入数据库 + var hatchId = btrOfAddEntitySpace.AddEntity(_hatch); + + // 设置模式:渐变/填充 + if (_hatch.HatchObjectType == HatchObjectType.GradientObject) + _hatch.SetGradient(_patternTypeGradient, _hatchName); + else + _hatch.SetHatchPattern(_patternTypeHatch, _hatchName); + + // 处理充填角度 + _hatch.PatternAngle = _angle; + + + // 关联边界,如果不先添加数据库空间内就会出错 + // 为 true 会加入反应器,因此比较慢(二维码将会十几秒才生成好),视需求而定. + _hatch.Associative = _boundaryAssociative; + + // 利用 AppendLoop 重载加入,这里就不处理 + if (_boundaryIds.Count > 0) + AppendLoop(_boundaryIds, HatchLoopTypes.Default); + + // 计算填充并显示(若边界出错,这句会异常) + _hatch.EvaluateHatch(true); + + return hatchId; + } + + /// + /// 执行图元的属性修改 + /// + /// 扔出填充实体 + public HatchInfo Action(Action action) + { + action(_hatch); + return this; + } + + /// + /// 清空边界集合 + /// + public HatchInfo ClearBoundary() + { + _boundaryIds.Clear(); + return this; + } + + /// + /// 删除边界图元 + /// + public HatchInfo EraseBoundary() + { + for (int i = 0; i < _boundaryIds.Count; i++) + _boundaryIds[i].Erase(); + return this; + } + + /// + /// 加入边界 + /// + /// 边界id + /// 加入方式 + void AppendLoop(IEnumerable boundaryIds, + HatchLoopTypes hatchLoopTypes = HatchLoopTypes.Default) + { + ObjectIdCollection obIds = new(); + // 边界是闭合的,而且已经加入数据库 + // 填充闭合环类型.最外面 + + // 此段代码会出错的原因在于 如果是直线围成的闭合区域, + // 那么每次添加的其实一条直线,而直线不构成loop,所以不符合预期的数据,出错 + try + { + foreach (var border in boundaryIds) + { + obIds.Clear(); + obIds.Add(border); + _hatch.AppendLoop(hatchLoopTypes, obIds); + } // 此段代码会出错 + } + catch (Exception ex) + { + + Env.Print(ex.Message); + Env.Print("发生错误,传入的边界不符合要求,请核实传入的边界是否为闭合边界列表,而不是组成边界的图元列表"); + throw; + } + + // 下面这行代码出错的原因是: 添加了重复的线条,需要进行剔除 + // _hatch.AppendLoop(hatchLoopTypes, boundaryIds.ToCollection()); + + // 这个函数还需要进行改造: + // 1. 明确传入的 boundaryIds 到底是多个边界,还是一个边界的子图元 + // 2. 根据传入的参数的不同 需要不同的处理措施 + // 3. 临时性措施 try一下抛异常,让用户确保传入的参数是准确的 + + } + + /// + /// 加入边界(仿高版本的填充函数) + /// + /// 点集 + /// 凸度集 + /// 加入此空间 + /// 加入方式 + /// + public HatchInfo AppendLoop(Point2dCollection pts, + DoubleCollection bluges, + BlockTableRecord btrOfAddEntitySpace, + HatchLoopTypes hatchLoopTypes = HatchLoopTypes.Default) + { + //if (pts == null) + // throw new ArgumentNullException(nameof(pts)); + pts.NotNull(nameof(pts)); + pts.End2End(); +#if NET35 + _boundaryIds.Add(CreateAddBoundary(pts, bluges, btrOfAddEntitySpace)); +#else + // 2011新增API,可以不生成图元的情况下加入边界, + // 通过这里进入的话,边界 _boundaryIds 是空的,那么 Build() 时候就需要过滤空的 + _hatch.AppendLoop(hatchLoopTypes, pts, bluges); +#endif + return this; + } + +#if NET35 + /// + /// 通过点集和凸度生成边界的多段线 + /// + /// 点集 + /// 凸度集 + /// 加入此空间 + /// 多段线id + static ObjectId CreateAddBoundary(Point2dCollection? pts, + DoubleCollection? bluges, + BlockTableRecord btrOfAddEntitySpace) + { + if (pts is null) + throw new ArgumentException(null, nameof(pts)); + if (bluges is null) + throw new ArgumentException(null, nameof(bluges)); + + var bvws = new List(); + + var itor1 = pts.GetEnumerator(); + var itor2 = bluges.GetEnumerator(); + while (itor1.MoveNext() && itor2.MoveNext()) + bvws.Add(new BulgeVertexWidth(itor1.Current, itor2.Current)); + + return btrOfAddEntitySpace.AddPline(bvws); + } +#endif + #endregion + + #region 枚举 + /// + /// 渐变色填充的图案名称 + /// + public enum GradientName + { + /// + /// 线状渐变 + /// + Linear, + /// + /// 圆柱状渐变 + /// + Cylinder, + /// + /// 反圆柱状渐变 + /// + Invcylinder, + /// + /// 球状渐变 + /// + Spherical, + /// + /// 反球状渐变 + /// + Invspherical, + /// + /// 半球状渐变 + /// + Hemisperical, + /// + /// 反半球状渐变 + /// + InvHemisperical, + /// + /// 抛物面状渐变 + /// + Curved, + /// + /// 反抛物面状渐变 + /// + Incurved + } + #endregion +} \ No newline at end of file diff --git "a/src/CAD/IFox.CAD.Shared/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/AttachmentPointHelper.cs" "b/src/CAD/IFox.CAD.Shared/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/AttachmentPointHelper.cs" new file mode 100644 index 0000000000000000000000000000000000000000..f94fd08cbea92d6176635c11e1cf444695eb3586 --- /dev/null +++ "b/src/CAD/IFox.CAD.Shared/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/AttachmentPointHelper.cs" @@ -0,0 +1,98 @@ +namespace IFoxCAD.Cad; + +/// +/// 文字对齐点帮助类 +/// +public static class AttachmentPointHelper +{ + static readonly Dictionary _alignment = new() + { + { "左上", AttachmentPoint.TopLeft }, + { "中上", AttachmentPoint.TopCenter },// 单行的对齐 + { "右上", AttachmentPoint.TopRight }, + + { "左中", AttachmentPoint.MiddleLeft }, + { "正中", AttachmentPoint.MiddleCenter },// 多行的正中 + { "右中", AttachmentPoint.MiddleRight }, + + { "左对齐", AttachmentPoint.BaseLeft },// ※优先(放在前面优先获取) + { "左", AttachmentPoint.BaseLeft }, + + { "中间", AttachmentPoint.BaseMid }, + + { "右对齐", AttachmentPoint.BaseRight },// ※优先(放在前面优先获取) + { "右", AttachmentPoint.BaseRight }, + + { "左下", AttachmentPoint.BottomLeft }, + { "中下", AttachmentPoint.BottomCenter }, + { "右下", AttachmentPoint.BottomRight }, + + { "对齐", AttachmentPoint.BaseAlign },// ※优先(放在前面优先获取) + { "调整", AttachmentPoint.BaseAlign }, + + { "居中", AttachmentPoint.BaseCenter },// 单行的中 + { "铺满", AttachmentPoint.BaseFit }, + }; + + /// + /// 输入文字获得对齐方式 + /// + /// + /// + public static AttachmentPoint Get(string key) + { + return _alignment[key]; + } + + /// + /// 输入对齐方式获得文字说明 + /// + /// + /// + public static string Get(AttachmentPoint value) + { + return _alignment.FirstOrDefault(q => q.Value == value).Key; + } +} + +#if false +// 反射描述 +// 这些东西cad没有用到啊...所以不纳入了 +public enum AttachmentPoint2 +{ + [Description("下对齐")] + BottomAlign = 14, + [Description("中对齐")] + MiddleAlign = 15,// 0xF + [Description("上对齐")] + TopAlign = 16,// 0x10 + [Description("下铺满")] + BottomFit = 18, + [Description("中铺满")] + MiddleFit = 19, + [Description("上铺满")] + TopFit = 20, + [Description("下居中")] + BottomMid = 22, + [Description("中居中")] + MiddleMid = 23, + [Description("下居中")] + TopMid = 24, +} + +public static Dictionary GetEnumDic(Type enumType) +{ + Dictionary dic = new(); + var fieldinfos = enumType.GetFields(); + for (int i = 0; i < fieldinfos.Length; i++) + { + var field = fieldinfos[i]; + if (field.FieldType.IsEnum) + { + var objs = field.GetCustomAttributes(typeof(DescriptionAttribute), false); + dic.Add(field.Name, ((DescriptionAttribute)objs[0]).Description); + } + } + return dic; +} +#endif \ No newline at end of file diff --git "a/src/CAD/IFox.CAD.Shared/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextEntityAdd.cs" "b/src/CAD/IFox.CAD.Shared/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextEntityAdd.cs" new file mode 100644 index 0000000000000000000000000000000000000000..f9ba03dde0ecad9f96e282bcd3574e47467bf4e6 --- /dev/null +++ "b/src/CAD/IFox.CAD.Shared/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextEntityAdd.cs" @@ -0,0 +1,62 @@ +namespace IFoxCAD.Cad; +#if false +public static partial class EntityAdd +{ + /// + /// 创建单行文字 + /// + /// 数据库 + /// 内容 + /// 插入点 + /// 字体高度 + /// 文字样式 + /// 对齐方式 + /// 对齐点,因样式 可能无效 + /// + public static Entity AddDBTextToEntity(this Database db, + string textContents, + Point3d position, + double textHigh = 2.5, + ObjectId? textStyleId = null, + AttachmentPoint justify = AttachmentPoint.BaseLeft, + Point3d? justifyPoint = null) + { + var TextInfo = new TextInfo( + textContents, + position, + justify, + justifyPoint, + textStyleId, + textHigh); + return TextInfo.AddDBTextToEntity(); + } + + /// + /// 新建多行文字 + /// + /// 数据库 + /// 内容 + /// 插入点 + /// 字体高度 + /// 文字样式 + /// 对齐方式 + /// + public static Entity AddMTextToEntity(this Database db, + string textContents, + Point3d position, + double textHigh = 2.5, + ObjectId? textStyleId = null, + AttachmentPoint justify = AttachmentPoint.BaseLeft) + { + var TextInfo = new TextInfo( + textContents, + position, + justify, + null, + textStyleId, + textHigh); + return TextInfo.AddMTextToEntity(); + } +} + +#endif \ No newline at end of file diff --git "a/src/CAD/IFox.CAD.Shared/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextInfo.cs" "b/src/CAD/IFox.CAD.Shared/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextInfo.cs" new file mode 100644 index 0000000000000000000000000000000000000000..cf010d4f6ee903f2f0e221150a706dd6fed2b47d --- /dev/null +++ "b/src/CAD/IFox.CAD.Shared/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextInfo.cs" @@ -0,0 +1,179 @@ +namespace IFoxCAD.Cad; + +/// +/// 文字信息类 +/// +public class TextInfo +{ + readonly Database? Database; + readonly string? Contents; + readonly Point3d Position; + /// + /// 文字对齐方式的中文说明 + /// + public string TextJustifyCn => AttachmentPointHelper.Get(TextJustify); + readonly AttachmentPoint TextJustify; + readonly Point3d? AlignmentPoint; + + readonly double TextHeight; + readonly ObjectId? TextStyleId; + + /// + /// 文字信息类 + /// + /// 内容 + /// 基点 + /// 对齐方式 + /// 对齐点(对齐方式是左,此参数无效,为null不为左就报错) + /// 文字样式id + /// 文字高度 + /// 数据库 + public TextInfo(string? contents, + Point3d position, + AttachmentPoint justify, + Point3d? justifyPoint = null, + ObjectId? textStyleId = null, + double textHeight = 2.5, + Database? database = null) + { + Contents = contents; + Position = position; + TextJustify = justify; + + if (justifyPoint is null && TextJustify != AttachmentPoint.BaseLeft) + throw new ArgumentNullException(nameof(justifyPoint)); + + AlignmentPoint = justifyPoint; + TextHeight = textHeight; + TextStyleId = textStyleId; + Database = database; + } + + /// + /// 创建单行文字 + /// + public DBText AddDBTextToEntity() + { + if (string.IsNullOrEmpty(Contents)) + throw new ArgumentNullException(nameof(Contents) + "创建文字无内容"); + + var acText = new DBText(); + + acText.SetDatabaseDefaults(Database ?? DBTrans.Top.Database); + + if (TextStyleId is not null) + acText.SetTextStyleId(TextStyleId.Value); + + acText.Height = TextHeight; // 高度 + acText.TextString = Contents; // 内容 + acText.Position = Position; // 插入点(一定要先设置) + acText.Justify = TextJustify; // 使他们对齐 + // acText.HorizontalMode + + if (AlignmentPoint is not null) + acText.AlignmentPoint = AlignmentPoint.Value; + else if (acText.Justify != AttachmentPoint.BaseLeft) + acText.AlignmentPoint = Position; + + + acText.AdjustAlignment(Database ?? DBTrans.Top.Database); + return acText; + } + + /// + /// 创建多行文字 + /// + /// + public MText AddMTextToEntity() + { + if (string.IsNullOrEmpty(Contents)) + throw new ArgumentNullException(nameof(Contents) + "创建文字无内容"); + + var mText = new MText(); + + mText.SetDatabaseDefaults(Database ?? DBTrans.Top.Database); + + if (TextStyleId is not null) + mText.SetTextStyleId(TextStyleId.Value); + + mText.TextHeight = TextHeight; // 高度 + mText.Contents = Contents; // 内容 + mText.Location = Position; // 插入点(一定要先设置) + + // mText.SetAttachmentMovingLocation(TextJustify); + mText.Attachment = TextJustify;// 使他们对齐 + + return mText; + } +} + + +/// +/// 反射设定对象的文字样式id +/// +public static partial class TextInfoHelper +{ + /// + /// 设置文字样式id + /// + /// 单行文字 + /// 文字样式表记录id + public static void SetTextStyleId(this DBText acText, ObjectId ltrObjectId) + { + SetEntityTxtStyleId(acText, ltrObjectId); + } + + /// + /// 设置文字样式id + /// + /// 多行文字 + /// 文字样式表记录id + public static void SetTextStyleId(this MText acText, ObjectId ltrObjectId) + { + SetEntityTxtStyleId(acText, ltrObjectId); + } + + static void SetEntityTxtStyleId(Entity acText, ObjectId ltrObjectId) + { + GetTextStyleIdType(acText)?.SetValue(acText, ltrObjectId, null); + } + + /// + /// 获取文字样式id + /// + public static ObjectId GetTextStyleId(this DBText acText) + { + return GetEntityTxtStyleId(acText); + } + + /// + /// 获取文字样式id + /// + public static ObjectId GetTextStyleId(this MText acText) + { + return GetEntityTxtStyleId(acText); + } + + static ObjectId GetEntityTxtStyleId(Entity acText) + { + var result = ObjectId.Null; + var id = GetTextStyleIdType(acText)?.GetValue(acText, null); + if (id != null) + result = (ObjectId)id; + return result; + } + + static PropertyInfo? _textStyleId = null; + static PropertyInfo GetTextStyleIdType(Entity acText) + { + if (_textStyleId == null) + { + var entType = acText.GetType(); + var prs = entType.GetProperties(); + _textStyleId = prs.FirstOrDefault(a => a.Name == "TextStyle");// 反射获取属性 + if (_textStyleId == null) + _textStyleId = prs.FirstOrDefault(a => a.Name == "TextStyleId");// 反射获取属性 + } + return _textStyleId; + } +} diff --git a/src/CAD/IFox.CAD.Shared/IFox.CAD.Shared.projitems b/src/CAD/IFox.CAD.Shared/IFox.CAD.Shared.projitems new file mode 100644 index 0000000000000000000000000000000000000000..9983528568bd0c0ac645a23bd4eb1d32225a25d7 --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/IFox.CAD.Shared.projitems @@ -0,0 +1,95 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 20f254f7-aee5-42ae-a9b3-149bbdc7397f + + + IFox.CAD.Shared + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/IFox.CAD.Shared.shproj b/src/CAD/IFox.CAD.Shared/IFox.CAD.Shared.shproj new file mode 100644 index 0000000000000000000000000000000000000000..ac52df3c3406f9a87de89d676e05a487c47f471c --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/IFox.CAD.Shared.shproj @@ -0,0 +1,13 @@ + + + + 20f254f7-aee5-42ae-a9b3-149bbdc7397f + 14.0 + + + + + + + + diff --git a/src/IFoxCAD.Cad/ResultData/LispDottedPair.cs b/src/CAD/IFox.CAD.Shared/ResultData/LispDottedPair.cs similarity index 95% rename from src/IFoxCAD.Cad/ResultData/LispDottedPair.cs rename to src/CAD/IFox.CAD.Shared/ResultData/LispDottedPair.cs index 97953fca3e729ab0a3c2ca95d794070b56faac8c..009d13ed21e098a145309b76afd4e17c31910490 100644 --- a/src/IFoxCAD.Cad/ResultData/LispDottedPair.cs +++ b/src/CAD/IFox.CAD.Shared/ResultData/LispDottedPair.cs @@ -1,7 +1,7 @@ namespace IFoxCAD.Cad; /// -/// lisp 点对表的数据封装类 +/// lisp点对表的数据封装类 /// public class LispDottedPair : LispList { @@ -32,6 +32,7 @@ public LispDottedPair(TypedValue left, TypedValue right) } #endregion + #region 重写 /// /// 点对表的值 /// @@ -47,7 +48,8 @@ public override List Value value.InsertRange(1, this); return value; } - } + } + #endregion #region 转换器 diff --git a/src/IFoxCAD.Cad/ResultData/LispList.cs b/src/CAD/IFox.CAD.Shared/ResultData/LispList.cs similarity index 95% rename from src/IFoxCAD.Cad/ResultData/LispList.cs rename to src/CAD/IFox.CAD.Shared/ResultData/LispList.cs index 11934412ffc7e5c01ecad93e91a3d3436f8e9c35..c186e4399a2128a030bdab7e3bd30ba7e2aafea6 100644 --- a/src/IFoxCAD.Cad/ResultData/LispList.cs +++ b/src/CAD/IFox.CAD.Shared/ResultData/LispList.cs @@ -17,6 +17,8 @@ public LispList() { } /// TypedValue 迭代器 public LispList(IEnumerable values) : base(values) { } #endregion + + #region 重写 /// /// lisp 列表的值 /// @@ -33,6 +35,7 @@ public virtual List Value return value; } } + #endregion #region 添加数据 /// @@ -40,12 +43,11 @@ public virtual List Value /// /// 组码 /// 组码值 - public override void Add(int code, object obj) + public override void Add(int code, object? obj) { if (code < 5000) - { throw new System.Exception("传入的组码值不是 lisp数据 有效范围!"); - } + Add(new TypedValue(code, obj)); } @@ -54,7 +56,7 @@ public override void Add(int code, object obj) ///
/// dxfcode枚举值 /// 组码值 - public void Add(LispDataType code, object obj) + public void Add(LispDataType code, object? obj) { Add((int)code, obj); } @@ -181,7 +183,7 @@ public void Add(LispList value) /// /// LispList 隐式转换到 ResultBuffer /// - /// TypedValueList 实例 + /// TypedValueList 实例,要using public static implicit operator ResultBuffer(LispList values) => new(values.Value.ToArray()); /// /// TypedValue 数组隐式转换到 LispList @@ -189,4 +191,4 @@ public void Add(LispList value) /// TypedValue 数组 public static implicit operator LispList(TypedValue[] values) => new(values); #endregion -} +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/ResultData/TypedValueList.cs b/src/CAD/IFox.CAD.Shared/ResultData/TypedValueList.cs similarity index 96% rename from src/IFoxCAD.Cad/ResultData/TypedValueList.cs rename to src/CAD/IFox.CAD.Shared/ResultData/TypedValueList.cs index 2b424c255f4316a1cea9ccef30388b514557b6a3..16970d2b866ba2acf55c8659dcc4a95b4752aa77 100644 --- a/src/IFoxCAD.Cad/ResultData/TypedValueList.cs +++ b/src/CAD/IFox.CAD.Shared/ResultData/TypedValueList.cs @@ -15,7 +15,6 @@ public TypedValueList() { } /// /// public TypedValueList(IEnumerable values) : base(values) { } - #endregion #region 添加数据 @@ -58,7 +57,8 @@ public virtual void Add(int code, object obj) /// ResultBuffer 字符串 public override string ToString() { - return new ResultBuffer(this).ToString(); + using ResultBuffer a = new(this); + return a.ToString(); } #endregion -} +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/ResultData/XRecordDataList.cs b/src/CAD/IFox.CAD.Shared/ResultData/XRecordDataList.cs similarity index 89% rename from src/IFoxCAD.Cad/ResultData/XRecordDataList.cs rename to src/CAD/IFox.CAD.Shared/ResultData/XRecordDataList.cs index a1dc36f5457c1bb68bc10286bb5e684010997514..708e1554f1e74a78b63aa752d3fcf8bba5a80312 100644 --- a/src/IFoxCAD.Cad/ResultData/XRecordDataList.cs +++ b/src/CAD/IFox.CAD.Shared/ResultData/XRecordDataList.cs @@ -5,10 +5,18 @@ ///
public class XRecordDataList : TypedValueList { - public XRecordDataList() - { - } + #region 构造函数 + /// + /// 扩展字典数据封装类 + /// + public XRecordDataList() { } + + /// + /// 扩展字典数据封装类 + /// public XRecordDataList(IEnumerable values) : base(values) { } + #endregion + #region 添加数据 /// /// 添加数据 @@ -18,9 +26,8 @@ public XRecordDataList(IEnumerable values) : base(values) { } public override void Add(int code, object obj) { if (code >= 1000) - { throw new System.Exception("传入的组码值不是 XRecordData 有效范围!"); - } + Add(new TypedValue(code, obj)); } @@ -57,4 +64,4 @@ public void Add(DxfCode code, object obj) /// TypedValue 数组 public static implicit operator XRecordDataList(TypedValue[] values) => new(values); #endregion -} +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/ResultData/XdataList.cs b/src/CAD/IFox.CAD.Shared/ResultData/XdataList.cs similarity index 42% rename from src/IFoxCAD.Cad/ResultData/XdataList.cs rename to src/CAD/IFox.CAD.Shared/ResultData/XdataList.cs index 6d44ee75e288803a2f64234e9d514e1ddfba9bac..94f04d7f106807ce4f3e3d69628f203dbcc57ac7 100644 --- a/src/IFoxCAD.Cad/ResultData/XdataList.cs +++ b/src/CAD/IFox.CAD.Shared/ResultData/XdataList.cs @@ -5,11 +5,17 @@ /// public class XDataList : TypedValueList { - public XDataList() - { - } + #region 构造函数 + /// + /// 扩展数据封装类 + /// + public XDataList() { } + /// + /// 扩展数据封装类 + /// public XDataList(IEnumerable values) : base(values) { } + #endregion #region 添加数据 /// @@ -20,9 +26,8 @@ public XDataList(IEnumerable values) : base(values) { } public override void Add(int code, object obj) { if (code < 1000 || code > 1071) - { - throw new System.Exception("传入的组码值不是XData有效范围!"); - } + throw new System.Exception("传入的组码值不是 XData 有效范围!"); + Add(new TypedValue(code, obj)); } @@ -33,10 +38,87 @@ public override void Add(int code, object obj) /// 组码值 public void Add(DxfCode code, object obj) { - Add((int)code, obj); } + /// + /// 是否含有注册名 + /// + /// 注册名 + public bool Contains(string appName) + { + bool result = false; + RangeTask(appName, (tv, state, i) => { + result = true; + state.Break(); + }); + return result; + } + + /// + /// 注册名下含有指定成员 + /// + /// 注册名 + /// 内容 + public bool Contains(string appName, object value) + { + bool result = false; + RangeTask(appName, (tv, state, i) => { + if (tv.Value.Equals(value)) + { + result = true; + state.Break(); + } + }); + return result; + } + + /// + /// 获取appName的索引区间 + /// + /// 注册名称 + /// 任务组码对象 + /// 返回任务组码的索引 + public List GetXdataAppIndex(string appName, DxfCode[] dxfCodes) + { + List indexs = new(); + RangeTask(appName, (tv, state, i) => { + if (dxfCodes.Contains((DxfCode)tv.TypeCode)) + indexs.Add(i); + }); + return indexs; + } + + /// + /// 区间任务 + /// + /// + void RangeTask(string appName, Action action) + { + LoopState state = new(); + // 在名称和名称之间找 + int appNameIndex = -1; + for (int i = 0; i < this.Count; i++) + { + if (this[i].TypeCode == (short)DxfCode.ExtendedDataRegAppName) + { + if (this[i].Value.ToString() == appName) + { + appNameIndex = i; + continue; + } + if (appNameIndex != -1)//找到了下一个名称 + break; + } + if (appNameIndex != -1) // 找下一个的时候,获取任务(移除)的对象 + { + action(this[i], state, i); + if (!state.IsRun) + break; + } + } + } + #endregion #region 转换器 @@ -61,4 +143,4 @@ public void Add(DxfCode code, object obj) /// TypedValue 数组 public static implicit operator XDataList(TypedValue[] values) => new(values); #endregion -} +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/Runtime/AOP.cs b/src/CAD/IFox.CAD.Shared/Runtime/AOP.cs new file mode 100644 index 0000000000000000000000000000000000000000..4bb5cb30a937e1c5139f8405386cec05d86ee2e4 --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/Runtime/AOP.cs @@ -0,0 +1,99 @@ +// namespace IFoxCAD.Cad; +// using HarmonyLib; + +// public class IFoxRefuseInjectionTransaction : Attribute +// { +// /// +// /// 拒绝注入事务 +// /// +// public IFoxRefuseInjectionTransaction() +// { +// } +// } + +// public class AOP +// { +// /// +// /// 在此命名空间下的命令末尾注入清空事务栈函数 +// /// +// public static void Run(string nameSpace) +// { +// Dictionary cmdDic = new(); +// AutoClass.AppDomainGetTypes(type => { +// if (type.Namespace != nameSpace) +// return; +// // 类上面特性 +// if (type.IsClass) +// { +// var attr = type.GetCustomAttributes(true); +// if (RefuseInjectionTransaction(attr)) +// return; +// } + +// // 函数上面特性 +// var mets = type.GetMethods();// 获得它的成员函数 +// for (int ii = 0; ii < mets.Length; ii++) +// { +// var method = mets[ii]; +// // 找到特性,特性下面的方法要是Public,否则就被编译器优化掉了. +// var attr = method.GetCustomAttributes(true); +// for (int jj = 0; jj < attr.Length; jj++) +// if (attr[jj] is CommandMethodAttribute cmdAtt) +// { +// if (!RefuseInjectionTransaction(attr)) +// cmdDic.Add(cmdAtt.GlobalName, (cmdAtt, type, method)); +// } +// } +// }); + +// // 运行的命令写在了Test.dll,当然不是ifox.cad类库内了.... +// if (cmdDic.Count == 0) +// return; + +// var harmony = new Harmony(nameSpace); +// var mPrefix = SymbolExtensions.GetMethodInfo(() => IFoxCmdAddFirst());// 进入函数前 +// var mPostfix = SymbolExtensions.GetMethodInfo(() => IFoxCmdAddLast());// 进入函数后 +// var mp1 = new HarmonyMethod(mPrefix); +// var mp2 = new HarmonyMethod(mPostfix); + +// foreach (var item in cmdDic) +// { +// // 原函数执行(空间type,函数名) +// var mOriginal = AccessTools.Method(item.Value.MetType, item.Value.MetInfo.Name); +// // mOriginal.Invoke(); +// // 新函数执行:创造两个函数加入里面 +// var newMet = harmony.Patch(mOriginal, mp1, mp2); +// // newMet.Invoke(); +// } +// } + +// /// +// /// 拒绝注入事务 +// /// +// /// 属性 +// /// +// private static bool RefuseInjectionTransaction(object[] attr) +// { +// bool refuseInjectionTransaction = false; +// for (int kk = 0; kk < attr.Length; kk++) +// { +// if (attr[kk] is IFoxRefuseInjectionTransaction) +// { +// refuseInjectionTransaction = true; +// break; +// } +// } +// return refuseInjectionTransaction; +// } + +// public static void IFoxCmdAddFirst() +// { +// // 此生命周期会在静态事务栈上面,被无限延长 +// var _ = DBTrans.Top; +// } + +// public static void IFoxCmdAddLast() +// { +// DBTrans.FinishDatabase(); +// } +// } \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/Runtime/AcadEMR.cs b/src/CAD/IFox.CAD.Shared/Runtime/AcadEMR.cs new file mode 100644 index 0000000000000000000000000000000000000000..0d4fe6cc0beb3891aa25ccf786ebd23565669c14 --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/Runtime/AcadEMR.cs @@ -0,0 +1,152 @@ +#if true +namespace IFoxCAD.Cad; + +using System.Diagnostics; + +// 作者: [VB.net]福萝卜 莱昂纳多·胖子 +// Email:oneeshine@163.com +// QQ: 461884072 +// 测试 2006-2019+ + +/// +/// 去教育版 +/// +/// +internal class AcadEMR +{ + /// + /// 释放库 + /// + /// 句柄 + /// + [DllImport("kernel32.dll", CharSet = CharSet.Auto)] + static extern IntPtr FreeLibrary(IntPtr loadLibraryIntPtr); + + /// + /// 获取一个应用程序或dll的模块句柄,要求已经载入 + /// + /// + /// + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + static extern IntPtr GetModuleHandle(string name); + + /// + /// 获取要引入的函数,将符号名或标识号转换为DLL内部地址 + /// + /// exe/dll句柄 + /// 接口名 + /// + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true)] + static extern IntPtr GetProcAddress(IntPtr hModule, string procName); + + /// + /// 虚拟保护 + /// + /// + /// + /// + /// + /// + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + static extern bool VirtualProtect(IntPtr lpAddress, IntPtr dwSize, uint flNewProtect, ref uint lpflOldProtect); + + + /// + /// 移除教育版 + /// + /// 打印出错信息 + public static void Remove(bool echoes = false) + { + var dllName = Env.GetAcapVersionDll(); + IntPtr moduleHandle = GetModuleHandle(dllName); + if (moduleHandle == IntPtr.Zero) + { + if (echoes) + Env.Printl(typeof(AcadEMR).FullName + "." + nameof(Remove) + "找不到模块:" + dllName); + return; + } + + string funcname = System.Text.Encoding.Unicode.GetString(new byte[] { 63 }); + if (IntPtr.Size == 4) + funcname += "isEMR@AcDbDatabase@@QBE_NXZ"; + else + funcname += "isEMR@AcDbDatabase@@QEBA_NXZ"; + + IntPtr funcAdress = GetProcAddress(moduleHandle, funcname); + if (funcAdress == IntPtr.Zero) + { + if (echoes) + Env.Printl("无法找指定函数:" + funcname); + return; + } + + IntPtr ptr; + if (IntPtr.Size == 4) + ptr = new IntPtr(funcAdress.ToInt32() + 3); + else + ptr = new IntPtr(funcAdress.ToInt64() + 4); + + if (!CheckFunc(ref ptr, 51, 2))// 08 通过此处 + if (echoes) + Env.Printl("无法验证函数体:0x33"); + IntPtr destPtr = ptr; + + if (!CheckFunc(ref ptr, 57, 6))// 08 无法通过此处,所以只是打印提示 + if (echoes) + Env.Printl("无法验证函数体:0x39"); + if (!CheckFunc(ref ptr, 15, 2))// 08 无法通过此处,所以只是打印提示 + if (echoes) + Env.Printl("无法验证函数体:0x0F"); + + uint flag = default; + uint tccc = default; + + IntPtr ip100 = new(100); + if (!VirtualProtect(destPtr, ip100, 64, ref flag))// 修改内存权限 + { + if (echoes) + Env.Printl("内存模式修改失败!"); + return; + } + + Marshal.WriteByte(destPtr, 137); + VirtualProtect(destPtr, ip100, flag, ref tccc);// 恢复内存权限 + } + + /// + /// 验证函数体 + /// + /// + /// + /// + /// + static bool CheckFunc(ref IntPtr adress, byte val, int len) + { + if (adress.ToInt64() > 0) + { + if (Marshal.ReadByte(adress) == 233) + { + if (IntPtr.Size == 4) + { + var pass = Marshal.ReadInt32(new IntPtr(adress.ToInt32() + 1)); + adress = new IntPtr(adress.ToInt32() + pass + 5); + } + else + { + var pass = Marshal.ReadInt64(new IntPtr(adress.ToInt64() + 1)); + adress = new IntPtr(adress.ToInt64() + pass + 5); + } + } + if (adress.ToInt64() > 0 && Marshal.ReadByte(adress) == val) + { + if (IntPtr.Size == 4) + adress = new IntPtr(adress.ToInt32() + len); + else + adress = new IntPtr(adress.ToInt64() + len); + return true; + } + } + return false; + } +} +#endif \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/Runtime/AcadVersion.cs b/src/CAD/IFox.CAD.Shared/Runtime/AcadVersion.cs new file mode 100644 index 0000000000000000000000000000000000000000..6fe6afd2377530470a025ad944778f7d0c5df8bb --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/Runtime/AcadVersion.cs @@ -0,0 +1,71 @@ + + +namespace IFoxCAD.Cad; + +/// +/// cad版本号类 +/// +public static class AcadVersion +{ + private static readonly string _pattern = @"Autodesk\\AutoCAD\\R(\d+)\.(\d+)\\.*?"; + + /// + /// 所有安装的cad的版本号 + /// + public static List Versions + { + get + { + string[] copys = Registry.LocalMachine + .OpenSubKey(@"SOFTWARE\Autodesk\Hardcopy") + .GetValueNames(); + + var _versions = new List(); + for (int i = 0; i < copys.Length; i++) + { + if (!Regex.IsMatch(copys[i], _pattern)) + continue; + + var gs = Regex.Match(copys[i], _pattern).Groups; + var ver = new CadVersion + { + ProductRootKey = copys[i], + ProductName = Registry.LocalMachine + .OpenSubKey("SOFTWARE") + .OpenSubKey(copys[i]) + .GetValue("ProductName") + .ToString(), + + Major = int.Parse(gs[1].Value), + Minor = int.Parse(gs[2].Value), + }; + _versions.Add(ver); + } + return _versions; + } + } + + /// 已打开的cad的版本号 + /// 已打开cad的application对象 + /// cad版本号对象 + public static CadVersion? FromApp(object app) + { + app.NotNull(nameof(app)); + + string acver = app.GetType() + .InvokeMember( + "Version", + BindingFlags.GetProperty, + null, + app, + new object[0]).ToString(); + + var gs = Regex.Match(acver, @"(\d+)\.(\d+).*?").Groups; + int major = int.Parse(gs[1].Value); + int minor = int.Parse(gs[2].Value); + for (int i = 0; i < Versions.Count; i++) + if (Versions[i].Major == major && Versions[i].Minor == minor) + return Versions[i]; + return null; + } +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/Runtime/AssemInfo.cs b/src/CAD/IFox.CAD.Shared/Runtime/AssemInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..23e5c7d76d5e1fbf538122b078b38e422aa7ce24 --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/Runtime/AssemInfo.cs @@ -0,0 +1,85 @@ +namespace IFoxCAD.Cad; + +/// +/// 程序集信息 +/// +[Serializable] +public struct AssemInfo +{ + /// + /// 注册名 + /// + public string Name; + + /// + /// 程序集全名 + /// + public string Fullname; + + /// + /// 程序集路径 + /// + public string Loader; + + /// + /// 加载方式 + /// + public AssemLoadType LoadType; + + /// + /// 程序集说明 + /// + public string Description; +} + + +/// +/// 程序集加载类型 +/// +public enum AssemLoadType +{ + /// + /// 启动 + /// + Startting = 2, + + /// + /// 随命令 + /// + ByCommand = 12, + + /// + /// 无效 + /// + Disabled = 20 +} + + +/// +/// 注册中心配置信息 +/// +public enum AutoRegConfig +{ + /// + /// 不进行任何操作 + /// + Undefined = 0, + /// + /// 注册表 + /// + Regedit = 1, + /// + /// 反射特性 + /// + ReflectionAttribute = 2, + /// + /// 反射接口 + /// + ReflectionInterface = 4, + /// + /// 移除教育版 + /// + RemoveEMR = 8, + + All = Regedit | ReflectionAttribute | ReflectionInterface | RemoveEMR, +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/Runtime/AutoRegAssem.cs b/src/CAD/IFox.CAD.Shared/Runtime/AutoLoad.cs similarity index 60% rename from src/IFoxCAD.Cad/Runtime/AutoRegAssem.cs rename to src/CAD/IFox.CAD.Shared/Runtime/AutoLoad.cs index d3290842cfd7189af1f06ae48c88439dccab4902..004ae50f22d48f40c1a093204a598b8a94ab3eda 100644 --- a/src/IFoxCAD.Cad/Runtime/AutoRegAssem.cs +++ b/src/CAD/IFox.CAD.Shared/Runtime/AutoLoad.cs @@ -1,31 +1,9 @@ namespace IFoxCAD.Cad; -using Registry = Microsoft.Win32.Registry; -using RegistryKey = Microsoft.Win32.RegistryKey; -/// -/// 程序集加载类型 -/// -public enum AssemLoadType -{ - /// - /// 启动 - /// - Startting = 2, - - /// - /// 随命令 - /// - ByCommand = 12, - - /// - /// 无效 - /// - Disabled = 20 -} /// -/// 自动加载程序集的抽象类,继承自 IExtensionApplication 接口 +/// 自动加载和初始化抽象类 /// -public abstract class AutoRegAssem : IExtensionApplication +public abstract class AutoLoad : IExtensionApplication { private AssemInfo _info = new(); @@ -37,7 +15,7 @@ public abstract class AutoRegAssem : IExtensionApplication /// /// 程序集的目录 /// - public static DirectoryInfo CurrDirectory => Location.Directory; + public static DirectoryInfo CurrentDirectory => Location.Directory; /// /// 获取程序集的目录 @@ -56,7 +34,7 @@ public static DirectoryInfo GetDirectory(Assembly assem) /// /// 初始化程序集信息 /// - public AutoRegAssem() + public AutoLoad() { Assembly assem = Assembly.GetCallingAssembly(); _info.Loader = assem.Location; @@ -68,19 +46,48 @@ public AutoRegAssem() { RegApp(); } + } #region RegApp private static RegistryKey GetAcAppKey() { - - string key = HostApplicationServices.Current.MachineRegistryProductRootKey; - RegistryKey ackey = - Registry.CurrentUser.OpenSubKey(key, true); +#if NET35 + string key = HostApplicationServices.Current.RegistryProductRootKey; +#else + string key = HostApplicationServices.Current.UserRegistryProductRootKey; +#endif + RegistryKey ackey = Registry.CurrentUser.OpenSubKey(key, true); return ackey.CreateSubKey("Applications"); } + protected void AppendSupportPath(string path) + { +#if NET35 + string key = HostApplicationServices.Current.RegistryProductRootKey; +#else + string key = HostApplicationServices.Current.UserRegistryProductRootKey; +#endif + // 计算机\HKEY_CURRENT_USER\SOFTWARE\Autodesk\AutoCAD\R24.0\ACAD-4101:804 + RegistryKey ackey = Registry.CurrentUser.OpenSubKey($@"{key}\Profiles"); + + var listkey = ackey.GetSubKeyNames(); + foreach (var item in listkey) + { + var acadkey = ackey.OpenSubKey($@"{item}\General", true); + var name = "ACAD"; + var str = acadkey.GetValue(name)?.ToString(); + if (str is not null && !str.Contains(path)) + { + acadkey.SetValue(name, $@"{str}{path};"); + } + + } + + ackey.Close(); + } + private bool SearchForReg() { RegistryKey appkey = GetAcAppKey(); @@ -100,11 +107,12 @@ public void RegApp() rk.SetValue("LOADER", _info.Loader, RegistryValueKind.String); rk.SetValue("MANAGED", 1, RegistryValueKind.DWord); appkey.Close(); + } - #endregion RegApp +#endregion RegApp - #region IExtensionApplication 成员 +#region IExtensionApplication 成员 /// /// 初始化函数 @@ -116,5 +124,5 @@ public void RegApp() /// public abstract void Terminate(); - #endregion IExtensionApplication 成员 +#endregion IExtensionApplication 成员 } diff --git a/src/CAD/IFox.CAD.Shared/Runtime/AutoRegAssem.cs b/src/CAD/IFox.CAD.Shared/Runtime/AutoRegAssem.cs new file mode 100644 index 0000000000000000000000000000000000000000..e8bb7206a697783d411d77ac6ad1344fe3fa844b --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/Runtime/AutoRegAssem.cs @@ -0,0 +1,180 @@ +namespace IFoxCAD.Cad; + + +/// +/// 注册中心 +/// +/// 初始化程序集信息写入注册表并反射特性和接口
+/// 启动cad后的执行顺序为:
+/// 1:程序集配置中心构造函数
+/// 2:特性..(多个)
+/// 3:接口..(多个)
+///
+///
+public abstract class AutoRegAssem : IExtensionApplication +{ + #region 字段 + readonly AssemInfo _info; + readonly AutoReflection? _autoRef; + #endregion + + #region 静态方法 + /// + /// 程序集的路径 + /// + public static FileInfo Location => new(Assembly.GetCallingAssembly().Location); + /// + /// 程序集的目录 + /// + public static DirectoryInfo CurrDirectory => Location.Directory; + /// + /// 获取程序集的目录 + /// + /// 程序集 + /// 路径对象 + public static DirectoryInfo GetDirectory(Assembly? assem) + { + if (assem is null) + throw new(nameof(assem)); + return new FileInfo(assem.Location).Directory; + } + #endregion + + #region 构造函数 + /// + /// 注册中心 + /// + /// 配置项目 + public AutoRegAssem(AutoRegConfig autoRegConfig) + { + var assem = Assembly.GetCallingAssembly(); + _info = new() + { + Loader = assem.Location, + Fullname = assem.FullName, + Name = assem.GetName().Name, + LoadType = AssemLoadType.Startting + }; + + if ((autoRegConfig & AutoRegConfig.Regedit) == AutoRegConfig.Regedit) + { + if (!SearchForReg()) + RegApp(); + } + + if ((autoRegConfig & AutoRegConfig.RemoveEMR) == AutoRegConfig.RemoveEMR) + AcadEMR.Remove(); + + // 实例化了 AutoClass 之后会自动执行 IFoxAutoGo 接口下面的类, + // 以及自动执行特性 [IFoxInitialize] + // 类库用户不在此处进行其他代码,而是实现特性 + if ((autoRegConfig & AutoRegConfig.ReflectionInterface) == AutoRegConfig.ReflectionInterface || + (autoRegConfig & AutoRegConfig.ReflectionAttribute) == AutoRegConfig.ReflectionAttribute) + { + _autoRef = new AutoReflection(_info.Name, autoRegConfig); + _autoRef.Initialize(); + } + } + #endregion + + #region RegApp + + /// + /// 获取当前cad注册表位置 + /// + /// 打开权限 + /// + public static RegistryKey GetAcAppKey(bool writable = true) + { + RegistryKey? ackey = null; + var hc = HostApplicationServices.Current; +#if acad || zcad // 中望此处缺乏测试 +#if NET35 + string key = hc.RegistryProductRootKey; +#else + string key = hc.UserRegistryProductRootKey; +#endif + ackey = Registry.CurrentUser.OpenSubKey(key, writable); +#endif + +#if gcad + // gcad 此处是否仍然有bug? 等待其他人测试反馈 + var key = hc.RegistryProductRootKey; // 浩辰2020读出来是"" + string regedit = ""; + if (Env.GetAcadVersion() == 2019) + regedit = @"Software\Gstarsoft\GstarCAD\R" + 19 + @"\zh-CN"; + if (Env.GetAcadVersion() == 2020) + regedit = @"Software\Gstarsoft\GstarCAD\B" + 20 + @"\zh-CN"; + ackey = Registry.CurrentUser.OpenSubKey(regedit, writable); +#endif + + return ackey.CreateSubKey("Applications"); + } + + /// + /// 卸载注册表信息 + /// + public bool UnRegApp() + { + var appkey = GetAcAppKey(); + if (appkey.SubKeyCount == 0) + return false; + + var regApps = appkey.GetSubKeyNames(); + if (regApps.Contains(_info.Name)) + { + appkey.DeleteSubKey(_info.Name, false); + return true; + } + return false; + } + + /// + /// 是否已经存在注册表 + /// + /// + bool SearchForReg() + { + // 在使用netloadx的时候,此处注册表是失效的,具体原因要进行netloadx测试 + var appkey = GetAcAppKey(); + if (appkey.SubKeyCount == 0) + return false; + + var regApps = appkey.GetSubKeyNames(); + if (regApps.Contains(_info.Name)) + { + // 20220409 bug:文件名相同,路径不同,需要判断路径 + var info = appkey.OpenSubKey(_info.Name); + return info.GetValue("LOADER")?.ToString().ToLower() == _info.Loader.ToLower(); + } + return false; + } + + /// + /// 在注册表写入自动加载的程序集信息 + /// + public void RegApp() + { + var appkey = GetAcAppKey(); + var rk = appkey.CreateSubKey(_info.Name); + rk.SetValue("DESCRIPTION", _info.Fullname, RegistryValueKind.String); + rk.SetValue("LOADCTRLS", _info.LoadType, RegistryValueKind.DWord); + rk.SetValue("LOADER", _info.Loader, RegistryValueKind.String); + rk.SetValue("MANAGED", 1, RegistryValueKind.DWord); + appkey.Close(); + } + + // 这里的是不会自动执行的 + public void Initialize() + { + } + public void Terminate() + { + } + + ~AutoRegAssem() + { + _autoRef?.Terminate(); + } + #endregion RegApp +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/Runtime/CadVersion.cs b/src/CAD/IFox.CAD.Shared/Runtime/CadVersion.cs new file mode 100644 index 0000000000000000000000000000000000000000..d9614bf5c7c21426092421fc4a57244b8327ecf4 --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/Runtime/CadVersion.cs @@ -0,0 +1,92 @@ +namespace IFoxCAD.Cad; + +#if ac2009 + +public class CadVersion +{ + /// + /// 主版本 + /// + public int Major { get; set; } + + /// + /// 次版本 + /// + public int Minor { get; set; } + + /// + /// 版本号 + /// + public double ProgId => double.Parse($"{Major}.{Minor}"); + + /// + /// 注册表名称 + /// + public string? ProductName { get; set; } + + /// + /// 注册表位置 + /// + public string? ProductRootKey { get; set; } + + /// + /// 转换为字符串 + /// + /// 表示版本号的字符串 + public override string ToString() + { + return $"名称:{ProductName}\n版本号:{ProgId}\n注册表位置:{ProductRootKey}"; + } + // public override bool Equals(object obj) + // { + // return base.Equals(obj); + // } + + // public override int GetHashCode() + // { + // return base.GetHashCode(); + // } + + // // public override string ToString() + // // { + // // return base.ToString(); + // // } +} +#else +public record CadVersion +{ + /// + /// 主版本 + /// + public int Major; + + /// + /// 次版本 + /// + public int Minor; + + /// + /// 版本号 + /// + public double ProgId => double.Parse($"{Major}.{Minor}"); + + /// + /// 注册表名称 + /// + public string? ProductName; + + /// + /// 注册表位置 + /// + public string? ProductRootKey; + + /// + /// 转换为字符串 + /// + /// 表示版本号的字符串 + public override string ToString() + { + return $"名称:{ProductName}\n版本号:{ProgId}\n注册表位置:{ProductRootKey}"; + } +} +#endif \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/Runtime/DBTrans.cs b/src/CAD/IFox.CAD.Shared/Runtime/DBTrans.cs new file mode 100644 index 0000000000000000000000000000000000000000..1bd5c741390ad7d74215c3a4c5a82704df5fb841 --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/Runtime/DBTrans.cs @@ -0,0 +1,712 @@ +namespace IFoxCAD.Cad; + + +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Windows.Forms; + +/// +/// 事务栈 +/// 隐匿事务在数据库其中担任的角色 +/// +[DebuggerDisplay("{DebuggerDisplay,nq}")] +[DebuggerTypeProxy(typeof(DBTrans))] +public class DBTrans : IDisposable +{ + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + string DebuggerDisplay => ToString(" | "); + + #region 私有字段 + /// + /// 事务栈 + /// + static readonly Stack _dBTrans = new(); + /// + /// 文档锁 + /// + readonly DocumentLock? _documentLock; + /// + /// 是否提交事务 + /// + bool _commit; + /// + /// 文件名 + /// + readonly string? _fileName; + #endregion + + #region 公开属性 + /// + /// 返回当前事务 + /// + public static DBTrans Top + { + get + { + /* + * 0x01 + * 事务栈上面有事务,这个事务属于当前文档, + * 那么直接提交原本事务然后再开一个(一直把栈前面的同数据库提交清空) + * 那不就发生跨事务读取图元了吗?....否决 + * + * 0x02 + * 跨文档事务出错 Autodesk.AutoCAD.Runtime.Exception:“eNotFromThisDocument” + * Curves.GetEntities()会从Top获取事务(Top会new一个),此时会是当前文档; + * 然后命令文中发生了 using DBTrans tr = new(); + * 当退出命令此事务释放,但是从来不释放Top, + * 然后我新建了一个文档,再进行命令=>又进入Top,Top返回了前一个文档的事务 + * 因此所以无法清理栈,所以Dispose不触发,导致无法刷新图元和Ctrl+Z出错 + * 所以用AOP方式修复 + * + * 0x03 + * 经过艰苦卓绝的测试,aop模式由于不能断点调试,所以暂时放弃。 + */ + + // 由于大量的函数依赖本属性,强迫用户先开启事务 + if (_dBTrans.Count == 0) + throw new ArgumentNullException("事务栈没有任何事务,请在调用前创建:" + nameof(DBTrans)); + var trans = _dBTrans.Peek(); + return trans; + } + } + /// + /// 文档 + /// + public Document? Document { get; private set; } + /// + /// 命令行 + /// + public Editor? Editor { get; private set; } + /// + /// 事务管理器 + /// + public Transaction Transaction { get; private set; } + /// + /// 数据库 + /// + public Database Database { get; private set; } + #endregion + + #region 构造函数 + /// + /// 事务栈 + /// 默认构造函数,默认为打开当前文档,默认提交事务 + /// + /// 要打开的文档 + /// 事务是否提交 + /// 是否锁文档 + public DBTrans(Document? doc = null, bool commit = true, bool doclock = false) + { + Document = doc ?? Acap.DocumentManager.MdiActiveDocument; + Database = Document.Database; + Editor = Document.Editor; + Transaction = Database.TransactionManager.StartTransaction(); + _commit = commit; + if (doclock) + _documentLock = Document.LockDocument(); + + _dBTrans.Push(this); + } + + /// + /// 事务栈 + /// 打开数据库,默认提交事务 + /// + /// 要打开的数据库 + /// 事务是否提交 + public DBTrans(Database database, bool commit = true) + { + Database = database; + Transaction = Database.TransactionManager.StartTransaction(); + _commit = commit; + _dBTrans.Push(this); + } + + /// + /// 事务栈 + /// 打开文件,默认提交事务 + /// + /// 要打开的文件名 + /// 事务是否提交 + /// 开图模式 + /// 密码 + /// 后台打开false;前台打开true(必须设置CommandFlags.Session) + public DBTrans(string fileName, + bool commit = true, + FileOpenMode fileOpenMode = FileOpenMode.OpenForReadAndWriteNoShare, + string? password = null, + bool activeOpen = false) + { + if (fileName == null || string.IsNullOrEmpty(fileName.Trim())) + throw new ArgumentNullException(nameof(fileName)); + + _fileName = fileName.Replace("/", "\\");// doc.Name总是"D:\\JX.dwg" + + // 此处若为失败的文件名,那么保存的时候就会丢失名称, + // 因此用 _fileName 储存 + if (!File.Exists(_fileName)) + { + if (activeOpen) + { + throw new IOException("错误:事务栈明确为前台开图时,文件不存在"); + } + else + { + // cad08测试: + // 第2个参数使用false,将导致关闭cad的时候出现致命错误: + // Unhandled Access Violation Reading Ox113697a0 Exception at 4b4154h + Database = new Database(true, true); + } + } + else + { + var doc = Acap.DocumentManager + .Cast() + .FirstOrDefault(doc => !doc.IsDisposed && doc.Name == _fileName); + + if (activeOpen) + { + if (doc is null) + { + try + { + // 设置命令标记: CommandFlags.Session + // 若没有设置: Open()之后的会进入中断状态(不会执行,直到切换文档ctrl+tab或者关闭文档) + doc = Acap.DocumentManager.Open(fileName, fileOpenMode == FileOpenMode.OpenForReadAndReadShare, password); + } + catch (Exception e) + { + throw new IOException($"错误:此文件打开错误:{fileName}\n错误信息:{e.Message}"); + } + } + // 设置命令标记: CommandFlags.Session + // 若没有设置: doc.IsActive 会异常 + if (!doc.IsActive) + Acap.DocumentManager.MdiActiveDocument = doc; + + // Open()是跨文档,所以必须要锁文档 + // 否则 Editor?.Redraw() 的 tm.QueueForGraphicsFlush() 将报错提示文档锁 + _documentLock = doc.LockDocument(); + + Database = doc.Database; + Document = doc; + Editor = doc.Editor; + } + else + { + if (doc is null) + { + Database = new Database(false, true); + if (Path.GetExtension(_fileName).ToLower().Contains("dxf")) + { + Database.DxfIn(_fileName, null); + } + else + { +#if ac2008 + Database.ReadDwgFile(_fileName, FileOpenModeHelper.GetFileShare(fileOpenMode), true, password); +#else + Database.ReadDwgFile(_fileName, fileOpenMode, true, password); +#endif + } + Database.CloseInput(true); + } + else + { + Database = doc.Database; + Document = doc; + Editor = doc.Editor; + } + } + } + + Transaction = Database.TransactionManager.StartTransaction(); + _commit = commit; + _dBTrans.Push(this); + } + #endregion + + #region 类型转换 + /// + /// 隐式转换为Transaction + /// + /// 事务管理器 + /// 事务管理器 + public static implicit operator Transaction(DBTrans tr) + { + return tr.Transaction; + } + #endregion + + #region 符号表 + + /// + /// 块表 + /// + public SymbolTable BlockTable => _BlockTable ??= new(this, Database.BlockTableId); + SymbolTable? _BlockTable; + /// + /// 当前绘图空间 + /// + public BlockTableRecord CurrentSpace => BlockTable.GetRecord(Database.CurrentSpaceId)!; + /// + /// 模型空间 + /// + public BlockTableRecord ModelSpace => BlockTable.GetRecord(BlockTable.CurrentSymbolTable[BlockTableRecord.ModelSpace])!; + /// + /// 图纸空间 + /// + public BlockTableRecord PaperSpace => BlockTable.GetRecord(BlockTable.CurrentSymbolTable[BlockTableRecord.PaperSpace])!; + /// + /// 层表 + /// + public SymbolTable LayerTable => _LayerTable ??= new(this, Database.LayerTableId); + SymbolTable? _LayerTable; + /// + /// 文字样式表 + /// + public SymbolTable TextStyleTable => _TextStyleTable ??= new(this, Database.TextStyleTableId); + SymbolTable? _TextStyleTable; + /// + /// 注册应用程序表 + /// + public SymbolTable RegAppTable => _RegAppTable ??= new(this, Database.RegAppTableId); + SymbolTable? _RegAppTable; + /// + /// 标注样式表 + /// + public SymbolTable DimStyleTable => _DimStyleTable ??= new(this, Database.DimStyleTableId); + SymbolTable? _DimStyleTable; + /// + /// 线型表 + /// + public SymbolTable LinetypeTable => _LinetypeTable ??= new(this, Database.LinetypeTableId); + SymbolTable? _LinetypeTable; + /// + /// 用户坐标系表 + /// + public SymbolTable UcsTable => _UcsTable ??= new(this, Database.UcsTableId); + SymbolTable? _UcsTable; + /// + /// 视图表 + /// + public SymbolTable ViewTable => _ViewTable ??= new(this, Database.ViewTableId); + SymbolTable? _ViewTable; + /// + /// 视口表 + /// + public SymbolTable ViewportTable => _ViewportTable ??= new(this, Database.ViewportTableId); + SymbolTable? _ViewportTable; + #endregion + + #region 字典 + /// + /// 命名对象字典 + /// + public DBDictionary NamedObjectsDict => GetObject(Database.NamedObjectsDictionaryId)!; + /// + /// 组字典 + /// + public DBDictionary GroupDict => GetObject(Database.GroupDictionaryId)!; + /// + /// 多重引线样式字典 + /// + public DBDictionary MLeaderStyleDict => GetObject(Database.MLeaderStyleDictionaryId)!; + /// + /// 多线样式字典 + /// + public DBDictionary MLStyleDict => GetObject(Database.MLStyleDictionaryId)!; + /// + /// 材质字典 + /// + public DBDictionary MaterialDict => GetObject(Database.MaterialDictionaryId)!; + /// + /// 表格样式字典 + /// + public DBDictionary TableStyleDict => GetObject(Database.TableStyleDictionaryId)!; + /// + /// 视觉样式字典 + /// + public DBDictionary VisualStyleDict => GetObject(Database.VisualStyleDictionaryId)!; + /// + /// 颜色字典 + /// + public DBDictionary ColorDict => GetObject(Database.ColorDictionaryId)!; + /// + /// 打印设置字典 + /// + public DBDictionary PlotSettingsDict => GetObject(Database.PlotSettingsDictionaryId)!; + /// + /// 打印样式表名字典 + /// + public DBDictionary PlotStyleNameDict => GetObject(Database.PlotStyleNameDictionaryId)!; + /// + /// 布局字典 + /// + public DBDictionary LayoutDict => GetObject(Database.LayoutDictionaryId)!; + +#if !zcad // 中望官方的问题 + /// + /// 数据链接字典 + /// + public DBDictionary DataLinkDict => GetObject(Database.DataLinkDictionaryId)!; + +#if !ac2009 + /// + /// 详细视图样式字典 + /// + public DBDictionary DetailViewStyleDict => GetObject(Database.DetailViewStyleDictionaryId)!; + /// + /// 剖面视图样式字典 + /// + public DBDictionary SectionViewStyleDict => GetObject(Database.SectionViewStyleDictionaryId)!; +#endif +#endif + #endregion + + #region 获取对象 + /// + /// 根据对象id获取图元对象 + /// + /// 要获取的图元对象的类型 + /// 对象id + /// 打开模式,默认为只读 + /// 是否打开已删除对象,默认为不打开 + /// 是否打开锁定图层对象,默认为不打开 + /// 图元对象,类型不匹配时返回 + public T? GetObject(ObjectId id, + OpenMode openMode = OpenMode.ForRead, + bool openErased = false, + bool openLockedLayer = false) where T : DBObject + { + return Transaction.GetObject(id, openMode, openErased, openLockedLayer) as T; + } + + /// + /// 根据对象句柄字符串获取对象Id + /// + /// 句柄字符串 + /// 对象id + public ObjectId GetObjectId(string handleString) + { + var hanle = new Handle(Convert.ToInt64(handleString, 16)); + // return Database.GetObjectId(false, hanle, 0); + return DBTransHelper.TryGetObjectId(Database, hanle); + } + #endregion + + #region 保存文件 + /// + /// 保存文件 + /// + /// + public void SaveDwgFile(DwgVersion version = DwgVersion.AC1800) + { + SaveFile(version); + } + + /// + /// 保存文件
+ ///
+ /// 默认2004dwg;若保存dxf则需要在路径输入扩展名 + /// 为true时候无效,将变为自动识别环境变量 + /// 另存为文件,前台将调用时它将无效,将变为弹出面板 + /// 保存路径失败的提示 + public void SaveFile(DwgVersion version = DwgVersion.AC1800, + bool automatic = true, + string? saveAsFile = null, + bool echoes = true) + { + // 遍历当前所有文档,文档必然是前台的 + Document? doc = null; + foreach (Document docItem in Acap.DocumentManager) + { + if (docItem.Database.Filename == this.Database.Filename) + { + doc = docItem; + break; + } + } + // 前台开图,使用命令保存;不需要切换文档 + if (doc != null) + { + if (saveAsFile == null) + doc.SendStringToExecute("_qsave\n", false, true, true); + else + /// 无法把 给这个面板 + doc.SendStringToExecute($"_Saveas\n", false, true, true); + return; + } + + // 后台开图,用数据库保存 + string? fileMsg; + bool creatFlag = false; + saveAsFile = saveAsFile?.Trim(); + if (string.IsNullOrEmpty(saveAsFile)) + { + fileMsg = _fileName; + saveAsFile = fileMsg; + //creatFlag = true; + } + else + { + fileMsg = saveAsFile; + + // 路径失败也保存到桌面 + var path = Path.GetDirectoryName(saveAsFile); + if (string.IsNullOrEmpty(path)) + { + creatFlag = true; + } + else if (!Directory.Exists(path)) + { + try { Directory.CreateDirectory(path); } + catch { creatFlag = true; } + } + + // 文件名缺失时 + if (!creatFlag && + string.IsNullOrEmpty(Path.GetFileName(saveAsFile).Trim())) + creatFlag = true; + } + if (saveAsFile != null) + { + var fileNameWith = Path.GetFileNameWithoutExtension(saveAsFile).Trim(); + if (string.IsNullOrEmpty(fileNameWith)) + creatFlag = true; + } + else + { + creatFlag = true; + } + + if (creatFlag) + { + var (error, file) = GetOrCreateSaveAsFile(); + if (echoes && error) + MessageBox.Show($"错误参数:\n{fileMsg}\n\n它将保存:\n{file}", "错误的文件路径"); + saveAsFile = file; + } + + if (Path.GetExtension(saveAsFile).ToLower().Contains("dxf")) + { + // dxf用任何版本号都会报错 +#if acad || gcad + Database.DxfOut(saveAsFile, 7, true); +#endif + +#if zcad // 中望这里没有测试 + Database.DxfOut(saveAsFile, 7, version, true); +#endif + return; + } + + if (automatic) + version = Env.GetDefaultDwgVersion(); + + // dwg需要版本号,而dxf不用,dwg用dxf版本号会报错 + // 若扩展名和版本号冲突,按照扩展名为准 + if (version.IsDxfVersion()) + version = DwgVersion.Current; + + Database.SaveAs(saveAsFile, version); + } + + + /// + /// 获取文件名,无效的话就制造 + /// + /// + (bool error, string path) GetOrCreateSaveAsFile() + { + var file = Database.Filename; + if (!string.IsNullOrEmpty(file)) + return (false, file); + + // 为了防止用户输入了错误的路径造成无法保存, + // 所以此处将进行保存到桌面, + // 而不是弹出警告就结束 + // 防止前台关闭了所有文档导致没有Editor,所以使用 MessageBox 发送警告 + var fileName = Path.GetFileNameWithoutExtension(_fileName).Trim(); + var fileExt = Path.GetExtension(_fileName); + + if (string.IsNullOrEmpty(fileName)) + fileName = DateTime.Now.ToString("--yyMMdd--hhmmssffff"); + if (string.IsNullOrEmpty(fileExt)) + fileExt = ".dwg"; + + // 构造函数(fileName)用了不存在的路径进行后台打开,就会出现此问题 + // 测试命令 FileNotExist + var dir = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + + "\\后台保存出错的文件\\"; + + if (!Directory.Exists(dir)) + Directory.CreateDirectory(dir); + + file = dir + fileName + fileExt; + while (File.Exists(file)) + { + var time = DateTime.Now.ToString("--yyMMdd--hhmmssffff"); + file = dir + fileName + time + fileExt; + Thread.Sleep(100); + } + return (true, file); + } + + #endregion + + #region 前台后台任务 + /// + /// 前台后台任务分别处理 + /// + /// + /// 备注:
+ /// 0x01 文字偏移问题主要出现线性引擎函数上面,
+ /// 在 参照绑定/深度克隆 的底层共用此函数导致
+ /// 0x02 后台是利用前台当前数据库进行处理的
+ /// 0x03 跨进程通讯暂无测试(可能存在bug)
+ ///
+ /// 委托 + /// 开启单行文字偏移处理 + public void Task(Action action, bool handlingDBTextDeviation = true) + { + //if (action == null) + // throw new ArgumentNullException(nameof(action)); + action.NotNull(nameof(action)); + // 前台开图 || 后台直接处理 + if (Document != null || !handlingDBTextDeviation) + { + action.Invoke(); + return; + } + + // 后台 + // 这种情况发生在关闭了所有文档之后,进行跨进程通讯 + // 此处要先获取激活的文档,不能直接获取当前数据库否则异常 + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + if (doc == null) + { + action.Invoke(); + return; + } + // 处理单行文字偏移 + // 前台绑定参照的时候不能用它,否则抛出异常:eWasErased + // 所以本函数自动识别前后台做处理 + var dbBak = doc.Database; + HostApplicationServices.WorkingDatabase = Database; + action.Invoke(); + HostApplicationServices.WorkingDatabase = dbBak; + } + #endregion + + #region IDisposable接口相关函数 + /// + /// 取消事务 + /// + public void Abort() + { + _commit = false; + Dispose(); + } + + /// + /// 提交事务 + /// + public void Commit() + { + _commit = true; + Dispose(); + } + + public bool IsDisposed { get; private set; } = false; + + /// + /// 手动调用释放 + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// 析构函数调用释放 + /// + ~DBTrans() + { + Dispose(false); + } + + protected virtual void Dispose(bool disposing) + { + /* 事务dispose流程: + * 1. 根据传入的参数确定是否提交,true为提交,false为不提交 + * 2. 如果锁文档就将文档锁dispose + * 3. 不管是否提交,既然进入dispose,就要将事务栈的当前事务弹出 + * 注意这里的事务栈不是cad的事务管理器,而是dbtrans的事务 + * 4. 清理非托管的字段 + */ + + // 不重复释放,并设置已经释放 + if (IsDisposed) return; + IsDisposed = true; + + // 致命错误时候此处是空,直接跳过 + if (Transaction != null) + { + if (_commit) + { + // 刷新队列(后台不刷新) + Editor?.Redraw(); + // 调用cad的事务进行提交,释放托管状态(托管对象) + Transaction.Commit(); + } + else + { + // 否则取消所有的修改 + Transaction.Abort(); + } + + // 将cad事务进行销毁 + if (!Transaction.IsDisposed) + Transaction.Dispose(); + } + // 将文档锁销毁 + _documentLock?.Dispose(); + + // 将当前事务栈弹栈 + _dBTrans.Pop(); + } + #endregion + + #region ToString + public override string ToString() + { + return ToString(null, null); + } + public string ToString(IFormatProvider? provider) + { + return ToString(null, provider); + } + public string ToString(string? format = null, IFormatProvider? formatProvider = null) + { + List lines = new(); + lines.Add($"StackCount = {_dBTrans.Count}"); + lines.Add($"_fileName = \"{_fileName}\""); + lines.Add($"_commit = {_commit}"); + lines.Add($"_documentLock = {_documentLock != null}"); + + lines.Add($"Document = {Document != null}"); + lines.Add($"Editor = {Editor != null}"); + lines.Add($"Transaction = {Transaction != null}"); + lines.Add($"Database = {Database != null}"); + + if (!string.IsNullOrEmpty(format)) + return string.Join(format, lines.ToArray()); + + return string.Join("\n", lines.ToArray()); + } + #endregion +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/Runtime/Env.cs b/src/CAD/IFox.CAD.Shared/Runtime/Env.cs similarity index 33% rename from src/IFoxCAD.Cad/Runtime/Env.cs rename to src/CAD/IFox.CAD.Shared/Runtime/Env.cs index cf2676331515747cc84a276708fc52e44e6e6cb2..df64594bfe7f88869f8bd27cf688cbecb143d238 100644 --- a/src/IFoxCAD.Cad/Runtime/Env.cs +++ b/src/CAD/IFox.CAD.Shared/Runtime/Env.cs @@ -1,12 +1,14 @@ -using Autodesk.AutoCAD.GraphicsSystem; + namespace IFoxCAD.Cad; /// /// 系统管理类 -/// 封装了一些系统 osmode、cmdecho、dimblk 系统变量 -/// 封装了常用的 文档 编辑器 数据库等对象为静态变量 -/// 封装了配置页面的注册表信息获取函数 +/// +/// 封装了一些系统 osmode;cmdecho;dimblk 系统变量
+/// 封装了常用的 文档 编辑器 数据库等对象为静态变量
+/// 封装了配置页面的注册表信息获取函数 +///
///
public static class Env { @@ -20,7 +22,7 @@ public static class Env /// /// 当前文档 /// - public static Document Document => Application.DocumentManager.MdiActiveDocument; + public static Document Document => Acap.DocumentManager.MdiActiveDocument; /// /// 编辑器对象 @@ -36,6 +38,7 @@ public static class Env #region Preferences +#if !zcad // 中望官方的问题 /// /// 获取当前配置的数据 /// @@ -44,12 +47,13 @@ public static class Env /// 对象 public static object GetCurrentProfileProperty(string subSectionName, string propertyName) { - UserConfigurationManager ucm = Application.UserConfigurationManager; + UserConfigurationManager ucm = Acap.UserConfigurationManager; IConfigurationSection cpf = ucm.OpenCurrentProfile(); IConfigurationSection ss = cpf.OpenSubsection(subSectionName); return ss.ReadProperty(propertyName, ""); } + /// /// 获取对话框配置的数据 /// @@ -57,7 +61,7 @@ public static object GetCurrentProfileProperty(string subSectionName, string pro /// 配置项 public static IConfigurationSection GetDialogSection(object dialog) { - UserConfigurationManager ucm = Application.UserConfigurationManager; + UserConfigurationManager ucm = Acap.UserConfigurationManager; IConfigurationSection ds = ucm.OpenDialogSection(dialog); return ds; } @@ -69,23 +73,31 @@ public static IConfigurationSection GetDialogSection(object dialog) /// 配置项 public static IConfigurationSection GetGlobalSection(string propertyName) { - UserConfigurationManager ucm = Application.UserConfigurationManager; + UserConfigurationManager ucm = Acap.UserConfigurationManager; IConfigurationSection gs = ucm.OpenGlobalSection(); IConfigurationSection ss = gs.OpenSubsection(propertyName); return ss; } - +#endif #endregion Preferences #region Enum - /// /// 控制在AutoLISP的command函数运行时AutoCAD是否回显提示和输入, 为显示, 为不显示 /// public static bool CmdEcho { - get => Convert.ToInt16(Application.GetSystemVariable("cmdecho")) == 1; - set => Application.SetSystemVariable("cmdecho", Convert.ToInt16(value)); + get => Convert.ToInt16(Acap.GetSystemVariable("cmdecho")) == 1; + set => Acap.SetSystemVariable("cmdecho", Convert.ToInt16(value)); + } + + /// + /// 控制在光标是否为正交模式, 为打开正交, 为关闭正交 + /// + public static bool OrthoMode + { + get => Convert.ToInt16(Acap.GetSystemVariable("ORTHOMODE")) == 1; + set => Acap.SetSystemVariable("ORTHOMODE", Convert.ToInt16(value)); } #region Dimblk @@ -193,9 +205,56 @@ public enum DimblkType /// /// 建筑标记 /// - ArchTick, + ArchTick } + private static readonly Dictionary dimdescdict = new() + { + { "实心闭合", DimblkType.Defult }, + { "点", DimblkType.Dot }, + { "小点", DimblkType.DotSmall }, + { "空心点", DimblkType.DotBlank }, + { "原点标记", DimblkType.Origin }, + { "原点标记 2", DimblkType.Origin2 }, + { "打开", DimblkType.Open }, + { "直角", DimblkType.Open90 }, + { "30 度角", DimblkType.Open30 }, + { "闭合", DimblkType.Closed }, + { "空心小点", DimblkType.Small }, + { "无", DimblkType.None }, + { "倾斜", DimblkType.Oblique }, + { "实心框", DimblkType.BoxFilled }, + { "方框", DimblkType.BoxBlank }, + { "空心闭合", DimblkType.ClosedBlank }, + { "实心基准三角形", DimblkType.DatumFilled }, + { "基准三角形", DimblkType.DatumBlank }, + { "完整标记", DimblkType.Integral }, + { "建筑标记", DimblkType.ArchTick }, + + { "", DimblkType.Defult }, + { "_DOT", DimblkType.Dot }, + { "_DOTSMALL", DimblkType.DotSmall }, + { "_DOTBLANK", DimblkType.DotBlank }, + { "_ORIGIN", DimblkType.Origin }, + { "_ORIGIN2", DimblkType.Origin2 }, + { "_OPEN", DimblkType.Open }, + { "_OPEN90", DimblkType.Open90 }, + { "_OPEN30", DimblkType.Open30 }, + { "_CLOSED", DimblkType.Closed }, + { "_SMALL", DimblkType.Small }, + { "_NONE", DimblkType.None }, + { "_OBLIQUE", DimblkType.Oblique }, + { "_BOXFILLED", DimblkType.BoxFilled }, + { "_BOXBLANK", DimblkType.BoxBlank }, + { "_CLOSEDBLANK", DimblkType.ClosedBlank }, + { "_DATUMFILLED", DimblkType.DatumFilled }, + { "_DATUMBLANK", DimblkType.DatumBlank }, + { "_INTEGRAL", DimblkType.Integral }, + { "_ARCHTICK", DimblkType.ArchTick }, + }; + + + /// /// 标注箭头属性 /// @@ -203,20 +262,26 @@ public static DimblkType Dimblk { get { - string s = (string)Application.GetSystemVariable("dimblk"); - if (string.IsNullOrEmpty(s)) - { - return DimblkType.Defult; - } - else - { - return s.ToEnum(); - } + string s = ((string)Acap.GetSystemVariable("dimblk")).ToUpper(); + // if (string.IsNullOrEmpty(s)) + // { + // return DimblkType.Defult; + // } + // else + // { + // if (dimdescdict.TryGetValue(s, out DimblkType value)) + // { + // return value; + // } + // return s.ToEnum(); + // // return s.FromDescName(); + // } + return dimdescdict[s]; } set { string s = GetDimblkName(value); - Application.SetSystemVariable("dimblk", s); + Acap.SetSystemVariable("dimblk", s); } } @@ -341,11 +406,11 @@ public static OSModeType OSMode { get { - return (OSModeType)Convert.ToInt16(Application.GetSystemVariable("osmode")); + return (OSModeType)Convert.ToInt16(Acap.GetSystemVariable("osmode")); } set { - Application.SetSystemVariable("osmode", (int)value); + Acap.SetSystemVariable("osmode", (int)value); } } /// @@ -360,10 +425,6 @@ public static bool Include(this OSModeType osm1, OSModeType osm2) } #endregion OsMode - private static T ToEnum(this string value) - { - return (T)Enum.Parse(typeof(T), value, true); - } private static string GetName(this T value) { @@ -372,47 +433,140 @@ private static string GetName(this T value) #endregion Enum - #region 环境变量 + #region 系统变量 /// - /// 获取cad变量 + /// 获取cad系统变量 /// /// 变量名 /// 变量值 - public static object GetVar(string varName) + public static object GetVar(string? varName) { - return Application.GetSystemVariable(varName); + return Acap.GetSystemVariable(varName); } /// - /// 设置cad变量 + /// 设置cad系统变量
+ /// 0x01 建议先获取现有变量值和设置的是否相同,否则直接设置会发生异常
+ /// 0x02 建议锁文档,否则 Psltscale 设置发生异常
+ /// 发生异常的时候vs输出窗口会打印一下,但是如果不介意也没啥问题 ///
/// 变量名 /// 变量值 - public static void SetVar(string varName, object value) + /// 输出异常,默认true;此设置仅为打印到命令栏,无法控制vs输出 + public static void SetVar(string? varName, object? value, bool echo = true) { - Application.SetSystemVariable(varName, value); + try + { + Acap.SetSystemVariable(varName, value); + } + catch (System.Exception) + { + if (echo) + Env.Print($"{varName} 是不存在的变量!"); + } } -#nullable enable + #endregion + + #region 环境变量 +#if acad +#if NET35 + [System.Security.SuppressUnmanagedCodeSecurity] + [DllImport("acad.exe", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl, EntryPoint = "acedGetEnv")] + static extern int AcedGetEnv(string? envName, StringBuilder ReturnValue); + + [System.Security.SuppressUnmanagedCodeSecurity] + [DllImport("acad.exe", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl, EntryPoint = "acedSetEnv")] + static extern int AcedSetEnv(string? envName, StringBuilder NewValue); +#else + [System.Security.SuppressUnmanagedCodeSecurity] + [DllImport("accore.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl, EntryPoint = "acedGetEnv")] + static extern int AcedGetEnv(string? envName, StringBuilder ReturnValue); + + [System.Security.SuppressUnmanagedCodeSecurity] + [DllImport("accore.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl, EntryPoint = "acedSetEnv")] + static extern int AcedSetEnv(string? envName, StringBuilder NewValue); +#endif +#endif + +#if gcad + [System.Security.SuppressUnmanagedCodeSecurity] + [DllImport("gced.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl, EntryPoint = "gcedGetEnv")] + static extern int AcedGetEnv(string? envName, StringBuilder ReturnValue); + + [System.Security.SuppressUnmanagedCodeSecurity] + [DllImport("gced.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl, EntryPoint = "gcedSetEnv")] + static extern int AcedSetEnv(string? envName, StringBuilder NewValue); +#endif + + // TODO: 中望没有测试,此处仅为不报错;本工程所有含有"中望"均存在问题 +#if zcad + [System.Security.SuppressUnmanagedCodeSecurity] + [DllImport("zced.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl, EntryPoint = "zcedGetEnv")] + static extern int AcedGetEnv(string? envName, StringBuilder ReturnValue); + + [System.Security.SuppressUnmanagedCodeSecurity] + [DllImport("zced.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl, EntryPoint = "zcedSetEnv")] + static extern int AcedSetEnv(string? envName, StringBuilder NewValue); +#endif + /// - /// 获取系统环境变量 + /// 读取acad环境变量
+ /// 也能获取win环境变量 ///
- /// 变量名 - /// 指定的环境变量的值;或者如果找不到环境变量,则返回 null - public static string? GetEnv(string var) + /// 变量名 + /// 返回值从不为null,需判断 + public static string GetEnv(string? name) { - //从当前进程或者从当前用户或本地计算机的 Windows 操作系统注册表项检索环境变量的值 - return Environment.GetEnvironmentVariable(var); + // 它将混合查询以下路径: + // acad2008注册表路径: 计算机\HKEY_CURRENT_USER\SOFTWARE\Autodesk\AutoCAD\R17.1\ACAD-6001:804\FixedProfile\General + // 用户: 计算机\HKEY_CURRENT_USER\Environment + // 系统: 计算机\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment + + // GetEnv("Path")长度很长: + // 可用内存 (最新格式) 1 MB (标准格式) + // https://docs.microsoft.com/zh-cn/windows/win32/sysinfo/registry-element-size-limits + + var sbRes = new StringBuilder(1 << 23); + _ = AcedGetEnv(name, sbRes); + return sbRes.ToString(); } + /// - /// 设置系统环境变量 + /// 设置acad环境变量
+ /// 它是不会报错的,但是直接设置会写入注册表的,
+ /// 如果是设置高低版本cad不同的变量,建议先读取判断再设置
///
- /// 变量名 - /// 变量值 - public static void SetEnv(string var, string? value) + /// 变量名 + /// 变量值 + /// + public static int SetEnv(string? name, string? var) { - //创建、修改或删除当前进程中或者为当前用户或本地计算机保留的 Windows 操作系统注册表项中存储的环境变量 - Environment.SetEnvironmentVariable(var, value); + return AcedSetEnv(name, new StringBuilder(var)); } -#nullable disable + #endregion + + #region win环境变量/由于 Aced的 能够同时获取此变量与cad内的,所以废弃 + // /// + // /// 获取系统环境变量 + // /// + // /// 变量名 + // /// 指定的环境变量的值;或者如果找不到环境变量,则返回 null + // public static string? GetEnv(string? var) + // { + // // 从当前进程或者从当前用户或本地计算机的 Windows 操作系统注册表项检索环境变量的值 + // // 用户: 计算机\HKEY_CURRENT_USER\Environment + // // 系统: 计算机\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment + // return Environment.GetEnvironmentVariable(var); + // } + // /// + // /// 设置系统环境变量 + // /// + // /// 变量名 + // /// 变量值 + // public static void SetEnv(string? var, string? value) + // { + // // 创建、修改或删除当前进程中或者为当前用户或本地计算机保留的 Windows 操作系统注册表项中存储的环境变量 + // Environment.SetEnvironmentVariable(var, value); + // } #endregion @@ -421,4 +575,187 @@ public static void SetEnv(string var, string? value) ///
/// 要打印的对象 public static void Print(object message) => Editor.WriteMessage($"{message}\n"); -} + /// + /// 命令行打印,会自动调用对象的toString函数,在打印内容前添加换行 + /// + /// 要打印的对象 + public static void Printl(object message) => Editor.WriteMessage($"{Environment.NewLine}{message}\n"); + + /// + /// 判断当前是否在UCS坐标下 + /// + /// Bool + public static bool IsUcs() => (short)GetVar("WORLDUCS") == 0; + + + #region dwg版本号/cad版本号/年份 + /// + /// 获取当前配置文件的保存版本 + /// + /// + public static DwgVersion GetDefaultDwgVersion() + { + DwgVersion version; + var ffs = Env.GetEnv("DefaultFormatForSave"); + version = ffs switch + { + "1" => DwgVersion.AC1009,// R12/LT12 dxf + "8" => DwgVersion.AC1014,// R14/LT98/LT97 dwg + "12" => DwgVersion.AC1015,// 2000 dwg + "13" => DwgVersion.AC1800a,// 2000 dxf + "24" => DwgVersion.AC1800,// 2004 dwg + "25" => (DwgVersion)26,// 2004 dxf + "36" => (DwgVersion)27,// 2007 dwg DwgVersion.AC1021 + "37" => (DwgVersion)28,// 2007 dxf + + // "38" => (DwgVersion),// dwt 样板文件...啊惊没找到这个是什么 + "48" => (DwgVersion)29,// 2010 dwg DwgVersion.AC1024 + "49" => (DwgVersion)30,// 2010 dxf + "60" => (DwgVersion)31,// 2013 dwg DwgVersion.AC1027 + "61" => (DwgVersion)32,// 2013 dxf + "64" => (DwgVersion)33,// 2018 dwg DwgVersion.AC1032 + "65" => (DwgVersion)34,// 2018 dxf + _ => throw new NotImplementedException(),// 提醒维护 + }; + return version; + } + + /// + /// 是否为dxf版本号 + /// + /// + /// + public static bool IsDxfVersion(this DwgVersion dwgVersion) + { + var result = (int)dwgVersion switch + { + 16 => true,// R12/LT12 dxf + 24 => true,// 2000 dxf + 26 => true,// 2004 dxf + 28 => true,// 2007 dxf + 30 => true,// 2010 dxf + 32 => true,// 2013 dxf + 34 => true,// 2018 dxf + _ => false, + }; + return result; + } + + /// + /// 获取cad年份 + /// + /// 超出年份就报错 + public static int GetAcadVersion() + { + var ver = Acap.Version.Major + "." + Acap.Version.Minor; + int acarVarNum = ver switch + { + "16.2" => 2006, + "17.0" => 2007, + "17.1" => 2008, + "17.2" => 2009, + "18.0" => 2010, + "18.1" => 2011, + "18.2" => 2012, + "19.0" => 2013, + "19.1" => 2014, + "20.0" => 2015, + "20.1" => 2016, + "21.0" => 2017, + "22.0" => 2018, + "23.0" => 2019, + "23.1" => 2020, + "24.0" => 2021, + "24.1" => 2022, + "24.2" => 2023, + _ => throw new NotImplementedException(), + }; + return acarVarNum; + } + /// + /// 获取带cad版本号的dll + /// + /// dll名字 + /// dll的前面 + public static string GetAcapVersionDll(string str = "acdb") + { + return str + Acap.Version.Major + ".dll"; + } + #endregion + + + #region cad变量功能延伸 + /// + /// 设置cad系统变量
+ /// 提供一个反序列化后,无cad异常输出的功能
+ /// 注意,您需要再此执行时候设置文档锁
+ ///
+ /// 否则也将导致修改数据库异常
+ ///
+ /// + /// + /// 成功返回当前值,失败null + /// + public static object? SetVarEx(string? key, string? value) + { + //if (key == null) + // throw new ArgumentNullException(nameof(key)); + //if (value == null) + // throw new ArgumentNullException(nameof(value)); + key.NotNull(nameof(key)); + value.NotNull(nameof(value)); + + + var currentVar = Env.GetVar(key); + if (currentVar == null) + return null; + + object? valueType = currentVar.GetType().Name switch + { + "String" => value.Replace("\"", string.Empty), + "Double" => double.Parse(value), + "Int16" => short.Parse(value), + "Int32" => int.Parse(value), + _ => throw new NotImplementedException(), + }; + + // 相同的参数进行设置会发生一次异常 + if (currentVar.ToString().ToUpper() != valueType!.ToString().ToUpper()) + Env.SetVar(key, valueType); + + return currentVar; + } + + /// + /// 设置新系统变量,返回现有系统变量 + /// + /// 设置的变量词典 + /// 返回现有变量词典,然后下次就可以利用它进行设置回来了 + public static Dictionary SaveCadVar(Dictionary args) + { + //if (args is null) + // throw new ArgumentNullException(nameof(args)); + args.NotNull(nameof(args)); + var dict = new Dictionary(); + foreach (var item in args) + { + // 判断是否为系统变量 + var ok = SetVarEx(item.Key, item.Value); + if (ok != null) + { + dict.Add(item.Key, ok.ToString()); + continue; + } + + // 判断是否为系统变量 + var envstr = Env.GetEnv(item.Key); + if (!string.IsNullOrEmpty(envstr)) + { + Env.SetEnv(item.Key, item.Value); + dict.Add(item.Key, envstr); + } + } + return dict; + } + #endregion +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/Runtime/FileOpenMode.cs b/src/CAD/IFox.CAD.Shared/Runtime/FileOpenMode.cs new file mode 100644 index 0000000000000000000000000000000000000000..b75d403aaf2af061b671a8680197269ec10d7d13 --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/Runtime/FileOpenMode.cs @@ -0,0 +1,85 @@ +#if ac2008 // NET35 +namespace Autodesk.AutoCAD.DatabaseServices +{ + [Wrapper("AcDbDatabase::OpenMode")] + public enum FileOpenMode + { + /// + /// 只读模式打开 + /// + OpenForReadAndReadShare = 1, + OpenForReadAndWriteNoShare = 2, + OpenForReadAndAllShare = 3, + OpenTryForReadShare = 4, + } + + public static class FileOpenModeHelper + { + /* + * 这个开图方式会致命错误,不清楚怎么用的文件句柄开图 + * using FileStream fileStream = new(_fileName, FileMode.Open, fileAccess, GetFileShare(fileOpenMode)); + * Database.ReadDwgFile(fileStream.SafeFileHandle.DangerousGetHandle(), true, password); + */ + public static FileShare GetFileShare(FileOpenMode fileOpenMode) + { + // FileAccess fileAccess = FileAccess.Read; + FileShare fileShare = FileShare.Read; + switch (fileOpenMode) + { + // 不完美匹配 + case FileOpenMode.OpenTryForReadShare: + // fileAccess = FileAccess.ReadWrite; + fileShare = FileShare.ReadWrite; + break; + // 完美匹配 + case FileOpenMode.OpenForReadAndAllShare: + // fileAccess = FileAccess.ReadWrite; + fileShare = FileShare.ReadWrite; + break; + // 完美匹配 + case FileOpenMode.OpenForReadAndWriteNoShare: + // fileAccess = FileAccess.ReadWrite; + fileShare = FileShare.None; + break; + // 完美匹配 + case FileOpenMode.OpenForReadAndReadShare: + // fileAccess = FileAccess.Read; + fileShare = FileShare.Read; + break; + } + return fileShare; + } + } +} +#endif + +#if NET35 +namespace Autodesk.AutoCAD.Internal +{ + public class Utils + { + public static void SetFocusToDwgView() + { + IntPtr window; + if (Acap.DocumentManager.Count == 0) + { + window = Acap.MainWindow.Handle; + } + else + { + // 它们是层级关系 + // Main + // -->MDI(大小被 DwgView 局限) + // ---->docW(比MDI大) + // -------->msctls_statusbar32 + // -------->DwgView + var docW = Acap.DocumentManager.MdiActiveDocument.Window.Handle; + var msctls_statusbar32 = IFoxCAD.Basal.WindowsAPI.GetTopWindow(docW); + window = IFoxCAD.Basal.WindowsAPI.GetWindow(msctls_statusbar32, 2U); + } + if (window != IntPtr.Zero) + IFoxCAD.Basal.WindowsAPI.SetFocus(window); + } + } +} +#endif \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/Runtime/IAutoGo.cs b/src/CAD/IFox.CAD.Shared/Runtime/IAutoGo.cs new file mode 100644 index 0000000000000000000000000000000000000000..3b71d4cb856acc3ef17e3caf6fce7e1d5ec41ec6 --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/Runtime/IAutoGo.cs @@ -0,0 +1,405 @@ +namespace IFoxCAD.Cad; + +using System.Collections.ObjectModel; +using System.Diagnostics; + +/// +/// 加载时优先级 +/// +[Flags] +public enum Sequence : byte +{ + First,// 最先 + Last, // 最后 +} + +/// +/// 加载时自动执行接口 +/// +public interface IFoxAutoGo +{ + // 控制加载顺序 + Sequence SequenceId(); + // 关闭cad的时候会自动执行 + void Terminate(); + // 打开cad的时候会自动执行 + void Initialize(); +} + +/// +/// 加载时自动执行特性 +/// +[AttributeUsage(AttributeTargets.Method)] +public class IFoxInitialize : Attribute +{ + /// + /// 优先级 + /// + internal Sequence SequenceId; + /// + /// 用于初始化;用于结束回收 + /// + internal bool IsInitialize; + /// + /// 用于初始化和结束回收 + /// + /// 优先级 + /// 用于初始化;用于结束回收 + public IFoxInitialize(Sequence sequence = Sequence.Last, bool isInitialize = true) + { + SequenceId = sequence; + IsInitialize = isInitialize; + } +} + +// 为了解决IExtensionApplication在一个dll内无法多次实现接口的关系 +// 所以在这里反射加载所有的 IAutoGo ,以达到能分开写"启动运行"函数的目的 +class RunClass +{ + public Sequence Sequence { get; } + readonly MethodInfo _methodInfo; + object? _instance; + /// + /// 执行此方法 + /// + /// + /// + /// 已经创建的对象 + public RunClass(MethodInfo method, Sequence sequence, object? instance = null) + { + _methodInfo = method; + Sequence = sequence; + _instance = instance; + } + + /// + /// 运行方法 + /// + public void Run() + { + _methodInfo.Invoke(ref _instance); + } +} + +/// +/// 此类作为加载后cad自动运行接口的一部分,用于反射特性和接口 +/// +/// 启动cad后的执行顺序为:
+/// 1:特性..(多个)
+/// 2:接口..(多个) +///
+///
+public class AutoReflection +{ + static List _InitializeList = new(); // 储存方法用于初始化 + static List _TerminateList = new(); // 储存方法用于结束释放 + + readonly string _dllName; + readonly AutoRegConfig _autoRegConfig; + + /// + /// 反射执行 + /// + /// 1.特性:
+ /// 2.接口: + ///
+ ///
+ /// 约束在此dll进行加速 + public AutoReflection(string dllName, AutoRegConfig configInfo) + { + _dllName = dllName; + _autoRegConfig = configInfo; + } + + // 启动cad的时候会自动执行 + public void Initialize() + { + try + { + // 收集特性,包括启动时和关闭时 + if ((_autoRegConfig & AutoRegConfig.ReflectionAttribute) == AutoRegConfig.ReflectionAttribute) + GetAttributeFunctions(_InitializeList, _TerminateList); + + if ((_autoRegConfig & AutoRegConfig.ReflectionInterface) == AutoRegConfig.ReflectionInterface) + { + GetInterfaceFunctions(_InitializeList, nameof(Initialize), _TerminateList, nameof(Terminate)); + } + + if (_InitializeList.Count > 0) + { + // 按照 SequenceId 排序_升序 + _InitializeList = _InitializeList.OrderBy(runClass => runClass.Sequence).ToList(); + RunFunctions(_InitializeList); + } + } + catch + { + Debugger.Break(); + } + } + + /// + /// 关闭cad的时候会自动执行 + /// + public void Terminate() + { + try + { + //if ((_autoRegConfig & AutoRegConfig.ReflectionInterface) == AutoRegConfig.ReflectionInterface) + // GetInterfaceFunctions(_TerminateList, nameof(Terminate)); + + if (_TerminateList.Count > 0) + { + // 按照 SequenceId 排序_降序 + _TerminateList = _TerminateList.OrderByDescending(runClass => runClass.Sequence).ToList(); + RunFunctions(_TerminateList); + } + } + catch (System.Exception e) + { + Env.Printl(e.Message); + Debugger.Break(); + } + } + + /// + /// 遍历程序域下所有类型 + /// + /// 输出每个成员执行 + /// 过滤此dll,不含后缀 + public static void AppDomainGetTypes(Action action, string? dllNameWithoutExtension = null) + { +#if DEBUG + int error = 0; +#endif + try + { + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); +#if !NET35 + // cad2021出现如下报错 + // System.NotSupportedException:动态程序集中不支持已调用的成员 + // assemblies = assemblies.Where(p => !p.IsDynamic).ToArray();// 这个要容器类型转换 + assemblies = Array.FindAll(assemblies, p => !p.IsDynamic); +#endif + // 主程序域 + for (int ii = 0; ii < assemblies.Length; ii++) + { + var assembly = assemblies[ii]; + + // 获取类型集合,反射时候还依赖其他的dll就会这个错误 + // 此通讯库要跳过,否则会报错. + var location = Path.GetFileNameWithoutExtension(assembly.Location); + if (dllNameWithoutExtension != null && location != dllNameWithoutExtension) + continue; + if (location == "AcInfoCenterConn")// 通讯库 + continue; + + Type[]? types = null; + try + { + types = assembly.GetTypes(); + } + catch (ReflectionTypeLoadException) { continue; } + + if (types is null) + continue; + + for (int jj = 0; jj < types.Length; jj++) + { + var type = types[jj]; + if (type is not null) + { +#if DEBUG + ++error; +#endif + action.Invoke(type); + } + } + } + } + catch (System.Exception e) + { +#if DEBUG + Debugx.Printl($"出错:{nameof(AppDomainGetTypes)};计数{error};错误信息:{e.Message}"); + Debugger.Break(); +#endif + } + } + + /// + /// 收集接口下的函数 + /// + /// 储存要运行的方法 + /// 查找方法名 + /// + void GetInterfaceFunctions(List initializes, string initializeName, + List terminates, string terminateName) + { + AppDomainGetTypes(type => { + // 接口的静态类屏蔽,继承接口无法使用静态类,因此跳过 + if (type.IsAbstract) + return; + + var ints = type.GetInterfaces(); + for (int sss = 0; sss < ints.Length; sss++) + { + if (ints[sss].Name != nameof(IFoxAutoGo)) + continue; + + Sequence? sequence = null; + MethodInfo? initialize = null; + MethodInfo? terminate = null; + object? instance = null; + + var mets = type.GetMethods(); + for (int jj = 0; jj < mets.Length; jj++) + { + var method = mets[jj]; + + // 接口的静态方法屏蔽,继承的方法也不可能是静态的,因此跳过 + if (method.IsAbstract) + continue; + + if (method.Name == nameof(IFoxAutoGo.SequenceId)) + { + // 避免触发两次构造函数,所以这里需要ref构造的对象出来 + var obj = method.Invoke(ref instance); + if (obj is not null) + sequence = (Sequence)obj; + continue; + } + if (method.Name == initializeName) + { + initialize = method; + continue; + } + if (method.Name == terminateName) + { + terminate = method; + continue; + } + if (sequence is not null && initialize is not null && terminate is not null) + break; + } + + // 避免在terminate释放的时候去再次构造,所以需要一次性收集 + // 若是释放的时候去再次构造: + // 0x01 initialize构造的字段拥有的资源就处于系统释放了,这是不合理的 + // 0x02 同时也发生了,为了释放而去构造这个操作 + var seq = sequence is null ? Sequence.Last : sequence.Value; + if (initialize is not null) + initializes.Add(new(initialize, seq, instance)); + if (terminate is not null) + terminates.Add(new(terminate, seq, instance)); + break; + } + }, _dllName); + } + + /// + /// 收集特性下的函数 + /// + void GetAttributeFunctions(List initializes, + List terminates) + { + AppDomainGetTypes(type => { + if (!type.IsClass) + return; + + // 特性的静态类不屏蔽 + //if (type.IsAbstract) + // return; + + var mets = type.GetMethods(); + for (int ii = 0; ii < mets.Length; ii++) + { + var method = mets[ii]; + + // 特性的静态方法不屏蔽 + //if (method.IsAbstract) + // continue; + + var attr = method.GetCustomAttributes(true); + for (int jj = 0; jj < attr.Length; jj++) + { + if (attr[jj] is IFoxInitialize jjAtt) + { + var runc = new RunClass(method, jjAtt.SequenceId); + if (jjAtt.IsInitialize) + initializes.Add(runc); + else + terminates.Add(runc); + break; + } + } + } + }, _dllName); + } + + /// + /// 执行收集到的函数 + /// + static void RunFunctions(List runClassList) + { + for (int i = 0; i < runClassList.Count; i++) + runClassList[i].Run(); + runClassList.Clear(); + } + +#if Debug + /// + /// 检查当前程序域重复出现命令, + /// 当出现重复时候将引起断点 + /// + public static void DebugCheckCmdRecurrence() + { + HashSet keys = new(); + + // 本dll中存在冲突命令,此时cad自动接口可以运行,但是加载命令之后会报错,因此利用断点告诉程序员 + AutoReflection.AppDomainGetTypes(type => { + var mets = type.GetMethods(); + for (int ii = 0; ii < mets.Length; ii++) + { + var method = mets[ii]; + var attr = method.GetCustomAttributes(true); + for (int jj = 0; jj < attr.Length; jj++) + if (attr[jj] is CommandMethodAttribute att) + { + if (keys.Contains(att.GlobalName)) + Debugger.Break(); + keys.Add(att.GlobalName); + } + } + }, Assembly.GetCallingAssembly().GetName().Name); + + // 其他dll中存在冲突命令,此时会覆盖命令,友好的提示程序员 + keys.Clear(); + HashSet msgMod = new(); + AutoReflection.AppDomainGetTypes(type => { + var mets = type.GetMethods(); + for (int ii = 0; ii < mets.Length; ii++) + { + var method = mets[ii]; + var attr = method.GetCustomAttributes(true); + for (int jj = 0; jj < attr.Length; jj++) + if (attr[jj] is CommandMethodAttribute att) + { + if (keys.Contains(att.GlobalName)) + msgMod.Add(att.GlobalName); + keys.Add(att.GlobalName); + } + } + }); + var sb = new StringBuilder(); + foreach (string key in msgMod) + sb.AppendLine(key); + if (sb.Length != 0) + { + Env.Printl("当前cad环境加载的多个DLL中存在重复命令将被覆盖:"); + Env.Printl("{"); + Env.Printl(sb.ToString()); + Env.Printl("}"); + } + } +#endif +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/Runtime/IdleAction.cs b/src/CAD/IFox.CAD.Shared/Runtime/IdleAction.cs new file mode 100644 index 0000000000000000000000000000000000000000..6431391ea8bfad21ec9960389db4b1cc09054383 --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/Runtime/IdleAction.cs @@ -0,0 +1,58 @@ +namespace IFoxCAD.Cad; + +/// +/// 空闲执行 +/// by DYH +/// 20230114 +/// +public static class IdleAction +{ + /// + /// 是否已经加载 + /// + private static bool alreadyLoad = false; + /// + /// 委托列表 + /// + private static readonly List actions = new(); + /// + /// 未处理的委托数量 + /// + public static int Count { get { return actions.Count; } } + /// + /// 添加空闲执行委托 + /// + /// 委托 + public static void Add(Action action) + { + actions.Add(action); + if (!alreadyLoad) + { + Acap.Idle -= Acap_Idle; + Acap.Idle += Acap_Idle; + alreadyLoad = true; + } + } + /// + /// 空闲处理事件 + /// + /// Acap + /// 事件参数 + private static void Acap_Idle(object sender, EventArgs e) + { + if (Count == 0) + { + alreadyLoad = false; + Acap.Idle -= Acap_Idle; + return; + } + try + { + actions[0]?.Invoke(); + } + finally + { + actions.RemoveAt(0); + } + } +} diff --git a/src/CAD/IFox.CAD.Shared/Runtime/LateBinding.cs b/src/CAD/IFox.CAD.Shared/Runtime/LateBinding.cs new file mode 100644 index 0000000000000000000000000000000000000000..45cafa9d7ab915a4f1f19ac4dde26280ea456183 --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/Runtime/LateBinding.cs @@ -0,0 +1,83 @@ +namespace IFoxCAD.Com; + +/// +/// 后绑代码工具 +/// +public static class LateBinding +{ + /// + /// 从运行对象表 (ROT) 获取指定对象的运行实例 + /// + /// + /// + public static object GetInstance(string appName) + { + return Marshal.GetActiveObject(appName); + } + /// + /// 创建实例 + /// + /// + /// + public static object CreateInstance(string appName) + { + return Activator.CreateInstance(Type.GetTypeFromProgID(appName)); + } + /// + /// 获取或创建实例 + /// + /// + /// + public static object GetOrCreateInstance(string appName) + { + try { return GetInstance(appName); } + catch { return CreateInstance(appName); } + } + /// + /// 释放实例 + /// + /// + public static void ReleaseInstance(this object obj) + { + Marshal.ReleaseComObject(obj); + } + + /// + /// 获取属性 + /// + /// + /// + /// + /// + public static object GetProperty(this object obj, string propName, params object[] parameter) + { + return obj.GetType().InvokeMember(propName, + BindingFlags.GetProperty, + null, obj, parameter); + } + /// + /// 设置属性 + /// + /// + /// + /// + public static void SetProperty(this object obj, string propName, params object[] parameter) + { + obj.GetType().InvokeMember(propName, + BindingFlags.SetProperty, + null, obj, parameter); + } + /// + /// 执行函数 + /// + /// + /// + /// + /// + public static object Invoke(this object obj, string memberName, params object[] parameter) + { + return obj.GetType().InvokeMember(memberName, + BindingFlags.Public | BindingFlags.InvokeMethod, + null, obj, parameter); + } +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/Runtime/Log.cs b/src/CAD/IFox.CAD.Shared/Runtime/Log.cs new file mode 100644 index 0000000000000000000000000000000000000000..812d694be31c88eb0f14862714636d30aa389f27 --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/Runtime/Log.cs @@ -0,0 +1,453 @@ +namespace IFoxCAD.Cad; + +using System; +using System.Diagnostics; +using System.Threading; +using Exception = Exception; + +#region 写入日志到不同的环境中 +// https://zhuanlan.zhihu.com/p/338492989 +public abstract class LogBase +{ + public abstract void DeleteLog(); + public abstract string[] ReadLog(); + public abstract void WriteLog(string message); +} + +/// +/// 日志输出环境 +/// +public enum LogTarget +{ + /// + /// 文件(包含错误和备注) + /// + File = 1, + /// + /// 文件(不包含错误,也就是只写备注信息) + /// + FileNotException = 2, + /// + /// 数据库 + /// + Database = 4, + /// + /// windows日志 + /// + EventLog = 8, +} + +/// +/// 写入到文件中 +/// +public class FileLogger : LogBase +{ + public override void DeleteLog() + { + File.Delete(LogHelper.LogAddress); + } + public override string[] ReadLog() + { + List lines = new(); + using (var sr = new StreamReader(LogHelper.LogAddress, true/*自动识别文件头*/)) + { + string line; + while ((line = sr.ReadLine()) != null) + lines.Add(line); + } + return lines.ToArray(); + } + public override void WriteLog(string? message) + { + // 把异常信息输出到文件 + var sw = new StreamWriter(LogHelper.LogAddress, true/*当天日志文件存在就追加,否则就创建*/); + sw.Write(message); + sw.Flush(); + sw.Close(); + sw.Dispose(); + } +} + +/// +/// 写入到数据库(暂时不支持) +/// +public class DBLogger : LogBase +{ + public override void DeleteLog() + { + throw new NotImplementedException(); + } + public override string[] ReadLog() + { + throw new NotImplementedException(); + } + public override void WriteLog(string? message) + { + throw new NotImplementedException(); + } +} + +/// +/// 写入到win日志 +/// +public class EventLogger : LogBase +{ + // 需要win权限 + // https://blog.csdn.net/weixin_38208401/article/details/77870909 + // NET50要加 + // https://docs.microsoft.com/en-us/answers/questions/526018/windows-event-log-with-net-5.html + + public string LogName = "IFoxCadLog"; + public override void DeleteLog() + { +#if !NET5_0 && !NET6_0 + if (EventLog.Exists(LogName)) + EventLog.Delete(LogName); +#endif + } + public override string[] ReadLog() + { + List lines = new(); +#if !NET5_0 && !NET6_0 + try + { + EventLog eventLog = new() + { + Log = LogName + }; + foreach (EventLogEntry entry in eventLog.Entries) + lines.Add(entry.Message); + } + catch (System.Security.SecurityException e) + { + throw new Exception("您没有权限读取win日志::" + e.Message); + } +#endif + return lines.ToArray(); + } + public override void WriteLog(string? message) + { +#if !NET5_0 && !NET6_0 + try + { + EventLog eventLog = new() + { + Source = LogName + }; + eventLog.WriteEntry(message, EventLogEntryType.Information); + } + catch (System.Security.SecurityException e) + { + throw new Exception("您没有权限写入win日志::" + e.Message); + } +#endif + } +} + +#endregion + +#region 静态方法 +public static class LogHelper +{ +#pragma warning disable CA2211 // 非常量字段应当不可见 + /// + /// 日志文件完整路径 + /// + public static string? LogAddress; + /// + /// 输出错误信息到日志文件的开关 + /// + public static bool FlagOutFile = false; + /// + /// 输出错误信息到vs输出窗口的开关 + /// + public static bool FlagOutVsOutput = true; +#pragma warning restore CA2211 // 非常量字段应当不可见 + + /// + /// 读写锁 + /// 当资源处于写入模式时,其他线程写入需要等待本次写入结束之后才能继续写入 + /// + static readonly ReaderWriterLockSlim _logWriteLock = new(); + + /// + /// 提供给外部设置log文件保存路径 + /// + /// null就生成默认配置 + public static void OptionFile(string? newlogAddress = null) + { + _logWriteLock.EnterWriteLock();// 写模式锁定 读写锁 + try + { + LogAddress = newlogAddress; + if (string.IsNullOrEmpty(LogAddress)) + LogAddress = GetDefaultOption(DateTime.Now.ToString("yy-MM-dd") + ".log"); + } + finally + { + _logWriteLock.ExitWriteLock();// 解锁 读写锁 + } + } + + /// + /// 输入文件名,获取保存路径的完整路径 + /// + /// 文件名,null获取默认路径 + /// 创建路径 + /// 完整路径 + public static string GetDefaultOption(string fileName, bool createDirectory = true) + { + // 微软回复:静态构造函数只会被调用一次, + // 并且在它执行完成之前,任何其它线程都不能创建这个类的实例或使用这个类的静态成员 + // https://blog.csdn.net/weixin_34204722/article/details/90095812 + var sb = new StringBuilder(); + sb.Append(Environment.CurrentDirectory); + sb.Append("\\ErrorLog"); + + // 新建文件夹 + if (createDirectory) + { + var path = sb.ToString(); + if (!Directory.Exists(path)) + { + // 设置文件夹属性为普通 + Directory.CreateDirectory(path) + .Attributes = FileAttributes.Normal; + } + } + sb.Append('\\'); + sb.Append(fileName); + return sb.ToString(); + } + + public static string WriteLog(this string? message, + LogTarget target = LogTarget.File) + { + if (message == null) + return string.Empty; + return LogAction(null, message, target); + } + + public static string WriteLog(this Exception? exception, + LogTarget target = LogTarget.File) + { + if (exception == null) + return string.Empty; + return LogAction(exception, null, target); + } + + public static string WriteLog(this Exception? exception, string? message, + LogTarget target = LogTarget.File) + { + if (exception == null) + return string.Empty; + return LogAction(exception, message, target); + } + + + /// 错误 + /// 备注信息 + /// 记录方式 + static string LogAction(Exception? ex, + string? message, + LogTarget target) + { + if (ex == null && message == null) + return string.Empty; + + if (LogAddress == null) + { + if (target == LogTarget.File || + target == LogTarget.FileNotException) + OptionFile(); + } + + // 不写入错误 + if (target == LogTarget.FileNotException) + ex = null; + + try + { + _logWriteLock.EnterWriteLock();// 写模式锁定 读写锁 + + var logtxt = new LogTxt(ex, message); + // var logtxtJson = Newtonsoft.Json.JsonConvert.SerializeObject(logtxt, Formatting.Indented); + var logtxtJson = logtxt?.ToString(); + if (logtxtJson == null) + return string.Empty; + + if (FlagOutFile) + { + LogBase? logger; + switch (target) + { + case LogTarget.File: + logger = new FileLogger(); + logger.WriteLog(logtxtJson); + break; + case LogTarget.FileNotException: + logger = new FileLogger(); + logger.WriteLog(logtxtJson); + break; + case LogTarget.Database: + logger = new DBLogger(); + logger.WriteLog(logtxtJson); + break; + case LogTarget.EventLog: + logger = new EventLogger(); + logger.WriteLog(logtxtJson); + break; + } + } + + if (FlagOutVsOutput) + { + Debugx.Printl("错误日志: " + LogAddress); + Debug.Write(logtxtJson); + } + return logtxtJson; + } + finally + { + _logWriteLock.ExitWriteLock();// 解锁 读写锁 + } + } +} +#endregion + +#region 序列化 +[Serializable] +public class LogTxt +{ + public string? 当前时间; + public string? 备注信息; + public string? 异常信息; + public string? 异常对象; + public string? 触发方法; + public string? 调用堆栈; + + public LogTxt() { } + + public LogTxt(Exception? ex, string? message) : this() + { + if (ex == null && message == null) + throw new ArgumentNullException(nameof(ex)); + + // 以不同语言显示日期 + // DateTime.Now.ToString("f", new System.Globalization.CultureInfo("es-ES")) + // DateTime.Now.ToString("f", new System.Globalization.CultureInfo("zh-cn")) + // 为了最小信息熵,所以用这样的格式,并且我喜欢补0 + 当前时间 = DateTime.Now.ToString("yy-MM-dd hh:mm:ss"); + + if (ex != null) + { + 异常信息 = ex.Message; + 异常对象 = ex.Source; + 触发方法 = ex.TargetSite == null ? string.Empty : ex.TargetSite.ToString(); + 调用堆栈 = ex.StackTrace == null ? string.Empty : ex.StackTrace.Trim(); + } + if (message != null) + 备注信息 = message; + } + + /// 为了不引入json的dll,所以这里自己构造 + public override string? ToString() + { + var sb = new StringBuilder(); + sb.Append('{'); + sb.Append(Environment.NewLine); + sb.AppendLine($" \"{nameof(当前时间)}\": \"{当前时间}\""); + sb.AppendLine($" \"{nameof(备注信息)}\": \"{备注信息}\""); + sb.AppendLine($" \"{nameof(异常信息)}\": \"{异常信息}\""); + sb.AppendLine($" \"{nameof(异常对象)}\": \"{异常对象}\""); + sb.AppendLine($" \"{nameof(触发方法)}\": \"{触发方法}\""); + sb.AppendLine($" \"{nameof(调用堆栈)}\": \"{调用堆栈}\""); + sb.Append('}'); + return sb.ToString(); + } +} +#endregion + + +#if false // 最简单的实现 +public static class Log +{ + /// + /// 读写锁 + /// 当资源处于写入模式时,其他线程写入需要等待本次写入结束之后才能继续写入 + /// + static readonly ReaderWriterLockSlim _logWriteLock = new(); + + /// + /// 日志文件完整路径 + /// + static readonly string _logAddress; + + static Log() + { + // 微软回复:静态构造函数只会被调用一次, + // 并且在它执行完成之前,任何其它线程都不能创建这个类的实例或使用这个类的静态成员 + // https://blog.csdn.net/weixin_34204722/article/details/90095812 + var sb = new StringBuilder(); + sb.Append(Environment.CurrentDirectory); + sb.Append("\\ErrorLog"); + + // 新建文件夹 + var path = sb.ToString(); + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path) + .Attributes = FileAttributes.Normal; // 设置文件夹属性为普通 + } + + sb.Append('\\'); + sb.Append(DateTime.Now.ToString("yy-MM-dd")); + sb.Append(".log"); + _logAddress = sb.ToString(); + } + + + /// + /// 将异常打印到日志文件 + /// + /// 异常 + /// 备注 + /// DEBUG模式打印到vs输出窗口 + public static string? WriteLog(this Exception? ex, + string? remarks = null, + bool printDebugWindow = true) + { + try + { + _logWriteLock.EnterWriteLock();// 写模式锁定 读写锁 + + var logtxt = new LogTxt(ex, remarks); + // var logtxtJson = Newtonsoft.Json.JsonConvert.SerializeObject(logtxt, Formatting.Indented); + var logtxtJson = logtxt.ToString(); + + if (logtxtJson == null) + return string.Empty; + + // 把异常信息输出到文件 + var sw = new StreamWriter(_logAddress, true/*当天日志文件存在就追加,否则就创建*/); + sw.Write(logtxtJson); + sw.Flush(); + sw.Close(); + sw.Dispose(); + + if (printDebugWindow) + { + Debugx.Printl("错误日志: " + _logAddress); + Debug.Write(logtxtJson); + // Debugger.Break(); + // Debug.Assert(false, "终止进程"); + } + return logtxtJson; + } + finally + { + _logWriteLock.ExitWriteLock();// 解锁 读写锁 + } + } +} +#endif \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/Runtime/MethodInfoHelper.cs b/src/CAD/IFox.CAD.Shared/Runtime/MethodInfoHelper.cs new file mode 100644 index 0000000000000000000000000000000000000000..5eb5f935accaae6159eac9dfdf7877efad1ffa7a --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/Runtime/MethodInfoHelper.cs @@ -0,0 +1,64 @@ + + +namespace IFoxCAD.Cad; + +internal static class MethodInfoHelper +{ +#if cache + private static readonly Dictionary methodDic = new(); +#endif + + /// + /// 执行函数 + /// + /// 函数 + /// 已经外部创建的对象,为空则此处创建 + public static object? Invoke(this MethodInfo methodInfo, ref object? instance) + { + methodInfo.NotNull(nameof(methodInfo)); + object? result = null; + if (methodInfo.IsStatic) + { + // 新函数指针进入此处 + // 参数数量一定要匹配,为null则参数个数不同导致报错, + // 参数为stirng[],则可以传入object[]代替,其他参数是否还可以实现默认构造? + var args = new List { }; + var paramInfos = methodInfo.GetParameters(); + for (int i = 0; i < paramInfos.Length; i++) + args.Add(null!); + result = methodInfo.Invoke(null, args.ToArray());// 静态调用 + } + else + { +#if cache + // 原命令的函数指针进入此处 + // object instance; + if (methodDic.ContainsKey(methodInfo)) + instance = methodDic[methodInfo]; +#endif + if (instance == null) + { + var reftype = methodInfo.ReflectedType; + if (reftype == null) + return null; + + var fullName = reftype.FullName; // 命名空间+类 + if (fullName == null) + return null; + + var type = reftype.Assembly.GetType(fullName); + if (type == null) + return null; + + instance = Activator.CreateInstance(type);// 构造类 +#if cache + if (!type.IsAbstract)// 无法创建抽象类成员 + methodDic.Add(methodInfo, instance); +#endif + } + if (instance != null) + result = methodInfo.Invoke(instance, null); // 非静态,调用实例化方法 + } + return result; + } +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/Runtime/PE/AcadPeInfo.cs b/src/CAD/IFox.CAD.Shared/Runtime/PE/AcadPeInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..3957a68420307e503b1c769fc32627e0895aa92d --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/Runtime/PE/AcadPeInfo.cs @@ -0,0 +1,333 @@ +using System.Diagnostics; + +namespace IFoxCAD.Cad; + +// 选择模式 +[Flags] +public enum AcadPeEnum : byte +{ + AcadExe = 1, + AccoreDll = 2, + Acdb = 4, + ExeAndCore = AcadExe | AccoreDll, +} + +// 这里的枚举对应 GetMethodException 错误值 +[Flags] +public enum GetMethodErrorNum : byte +{ + Ok = 0, + NoModule = 1, + NoFuncName = 2, +} + +// 自动获取本工程上面的发送命令的接口 +public class AcadPeInfo +{ + #region 静态单例获取exe/dll信息 + static PeInfo? _PeForAcadExe; + public static PeInfo? PeForAcadExe + { + get + { + if (_PeForAcadExe is null) + { + // 获取此acad.exe获取所有的函数名 + var file = Process.GetCurrentProcess().MainModule.FileName; + _PeForAcadExe = new PeInfo(file); + } + return _PeForAcadExe; + } + } + + static PeInfo? _PeForAccoreDll; + public static PeInfo? PeForAccoreDll + { + get + { + if (_PeForAccoreDll is null) + { + // 获取此dll所有的函数名 + var file = Process.GetCurrentProcess().MainModule.FileName; + var dll = Path.GetDirectoryName(file) + "\\accore.dll"; + if (File.Exists(dll))// 08没有,高版本分离的 + _PeForAccoreDll = new PeInfo(dll); + } + return _PeForAccoreDll; + } + } + + static PeInfo? _PeForAcdbDll; + public static PeInfo? PeForAcdbDll + { + get + { + if (_PeForAcdbDll is null) + { + // 获取此dll所有的函数名 + var file = Process.GetCurrentProcess().MainModule.FileName; + var dll = Path.GetDirectoryName(file) + $"\\acdb{Acap.Version.Major}.dll"; + if (File.Exists(dll)) + _PeForAcdbDll = new PeInfo(dll); + } + return _PeForAcdbDll; + } + } + + List? _Methods; // 这个不是静态的 + /// + /// 同名函数指针们 + /// + public List? Methods + { + get + { + if (_Methods is null) + { + _Methods = new(); + + if ((_acadPeEnum & AcadPeEnum.AcadExe) == AcadPeEnum.AcadExe) + GetPeMethod(PeForAcadExe); + if ((_acadPeEnum & AcadPeEnum.AccoreDll) == AcadPeEnum.AccoreDll) + GetPeMethod(PeForAccoreDll); + if ((_acadPeEnum & AcadPeEnum.Acdb) == AcadPeEnum.Acdb) + GetPeMethod(PeForAcdbDll); + } + return _Methods; + } + } + #endregion + + #region 字段/构造 + /// + /// 用于查找PE不带修饰的函数名 + /// + string _findFuncName; + /// + /// 枚举查找对象 + /// + AcadPeEnum _acadPeEnum; + + /// + /// 通过函数名获取指针,指定类型 + /// + /// 不带修饰的函数名 + /// 读取哪个cad内部文件的枚举(目前只支持两个) + public AcadPeInfo(string methodName, AcadPeEnum acadPeEnum) + { + _findFuncName = methodName; + _acadPeEnum = acadPeEnum; + } + + /// + /// 获取CAD的函数指针 + /// + /// 委托 + /// 不带修饰的函数名 + /// 读取哪个cad内部文件的枚举(目前只支持两个) + /// 委托 + public static TDelegate? GetDelegate(string methodName, AcadPeEnum acadPeEnum) + where TDelegate : class + { + return new AcadPeInfo(methodName, acadPeEnum) + .GetDelegate(); + } + #endregion + + #region 方法 + /// + /// 储存旧值<去除修饰函数名(查找的),带修饰函数名们> + /// + static Dictionary> _Dict = new(); + + /// + /// 返回函数指针 + /// + /// Pe信息:可能来自exe/dll + /// 错误信息 + GetMethodErrorNum GetPeMethod(PeInfo? peInfo) + { + if (peInfo == null) + return GetMethodErrorNum.NoFuncName;// cad08需要检查 AccoreDll 的时候跳过 + + var identifyStr = _findFuncName + ";" + peInfo.FullName; + if (_Dict.ContainsKey(identifyStr))// 如果已经找过,直接返回 + { + _Methods = _Dict[identifyStr]; + } + else + { + _Methods ??= new(); + try + { + PeFunction.Finds(peInfo, _findFuncName, _Methods); + if (_Methods.Count != 0)// 此时从不含有 + _Dict.Add(identifyStr, _Methods); + } + catch (GetPeMethodException ex) + { return (GetMethodErrorNum)ex.ErrorNum; } + } + return GetMethodErrorNum.Ok; + } + + /// + /// 转为委托 + /// + /// 委托对象 + /// + public TDelegate? GetDelegate() where TDelegate : class + { + if (Methods is null || Methods.Count == 0) + return null; + + TDelegate? func = null; + + /* + * 0x01 + * 这里永远不报错,但是不代表不会出错. + * 调用C盘exe/dll时需要权限, + * 所以会出现:[DLLImport]可以,直接运行cad也可以,但是调试不行. + * 此时可以提权vs再调试,有时候会出现:调试不显示东西,但是运行是对的. + * + * 0x02 + * 出错时候用完整的修饰名 + * + * 0x03 + * 这里可能同时存在acad.exe和accore.dll相同指针? + * 所以我是用排序方法找最短的指针,所以它是第First个. + */ + + // 排序,最少长度原则本身就是让完全相同字符串在最前面 + // 这里替换为有序哈希,因为我总是需要不带修饰的返回函数,所以是排序长度的第一个 + _Methods = _Methods.OrderBy(str => str.CName?.Length) + .ThenBy(str => str.MethodName.Length) + .ToList(); + + func = Marshal.GetDelegateForFunctionPointer(Methods.First().GetProcAddress(), typeof(TDelegate)) as TDelegate; + return func; + } + #endregion +} + +/// +/// 通过名字查找exe/dll内所有名字 +/// +public class PeFunction +{ + #region 字段/构造 + string? _CName; + /// + /// 纯c语言名 + /// + public string? CName + { + get + { + if (_CName is null && MethodName is not null) + { + _CName = MethodName.Replace("?", string.Empty); // 剔除cpp前缀 + int num = _CName.IndexOf("@"); + if (num > -1) + _CName = _CName.Substring(0, num); // 剔除参数部分 + } + return _CName; + } + } + + /// + /// 模块文件路径 + /// + public string? ModuleFullName; + /// + /// 模块指针 + /// + public IntPtr ModuleIntPtr; + /// + /// 函数名 + /// + public string MethodName; + /// + /// 通过名字查找exe/dll内所有名字 + /// + /// 没修饰的方法名 + public PeFunction(string methodName) + { + MethodName = methodName; + } + #endregion + + /// + /// 获取函数指针 + /// + public IntPtr GetProcAddress() + { + return WindowsAPI.GetProcAddress(ModuleIntPtr, MethodName); + } + + /// + /// 通过名字查找exe/dll内所有名字 + /// + /// pe结构 + /// 用于查找的方法名 + /// 返回函数集合 + public static void Finds(PeInfo peInfo, + string findFuncName, + List funcAdress_Out) + { + if (findFuncName == null) + throw new GetPeMethodException(2, "没有找到对应的函数:" + findFuncName); + + var peModuleFullName = peInfo.FullName; + if (peModuleFullName == null) + throw new GetPeMethodException(1, "找不到模块:" + peModuleFullName + "当前程序没有加载这个东西?"); + var hModule = WindowsAPI.GetModuleHandle(peModuleFullName); // 执行前必须加载了先,acad.exe/accore.dll + if (hModule == IntPtr.Zero) + throw new GetPeMethodException(1, "找不到模块:" + peModuleFullName + "当前程序没有加载这个东西?"); + + // 遍历函数接口名单 + var names = peInfo.ExportDirectory?.FunctionNames(); + if (names == null) + throw new ArgumentException(nameof(names)); + + foreach (var name in names) + { + if (name.Contains(findFuncName))// 这里是名称含有,不是容器含有 + { + var fn = new PeFunction(name) + { + ModuleFullName = peModuleFullName, + ModuleIntPtr = hModule + }; + funcAdress_Out.Add(fn); + } + } + } +} + + + +/// +/// 错误信息 +/// +public class GetPeMethodException : ApplicationException +{ + public int ErrorNum; + public string? ErrorMsg; + public Exception? InnerException1; + + public GetPeMethodException(string msg) : base(msg) + { + ErrorMsg = msg; + } + + public GetPeMethodException(int errorNum, string msg) : base(msg) + { + ErrorNum = errorNum; + } + + public GetPeMethodException(string msg, Exception innerException) : base(msg, innerException) + { + InnerException1 = innerException; + ErrorMsg = msg; + } +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/Runtime/PE/DBmod.cs b/src/CAD/IFox.CAD.Shared/Runtime/PE/DBmod.cs new file mode 100644 index 0000000000000000000000000000000000000000..7502e2501f40faccfd004ff16cdab4caffad0772 --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/Runtime/PE/DBmod.cs @@ -0,0 +1,92 @@ +namespace IFoxCAD.Cad; + +/// +/// 获取数据库修改状态 +/// +/// 相关链接 +/// +[Flags] +public enum DBmod : short +{ + [Description("数据库冇修改")] + DatabaseNoModifies = 0, + [Description("数据库有修改")] + Database = 1, + [Description("变量有修改")] + Value = 4, + [Description("窗口有修改")] + Window = 8, + [Description("视图有修改")] + View = 16, + [Description("字段有修改")] + Field = 32 +} + +public class DBmodEx +{ + public static DBmod DBmod => (DBmod)Env.GetVar("dbmod"); + + delegate long DelegateAcdbSetDbmod(IntPtr db, DBmod newValue); + static DelegateAcdbSetDbmod? acdbSetDbmod;//别改名称 + + public static long AcdbSetDbmod(IntPtr db, DBmod newValue) + { + acdbSetDbmod ??= AcadPeInfo.GetDelegate( + nameof(acdbSetDbmod), AcadPeEnum.Acdb); + if (acdbSetDbmod is null) + return -1; + return acdbSetDbmod.Invoke(db, newValue);// 调用方法 + } + + /// + /// Dbmod 不被修改的任务 + /// + /// + public static void DBmodTask(Action action) + { + var dm = Acap.DocumentManager; + if (dm.Count == 0) + return; + var doc = dm.MdiActiveDocument; + if (doc is null) + return; + + var bak = DBmod; + action.Invoke(); + if (bak == DBmod.DatabaseNoModifies && DBmod != DBmod.DatabaseNoModifies) + AcdbSetDbmod(doc.Database.UnmanagedObject, DBmod.DatabaseNoModifies); + } + + static bool _flag = true; + /// + /// 请在无法处理的初始化才使用它 + /// (源泉在初始化的时候进行了修改数据库,所以必须要用一个新线程等待lisp执行完成才可以) + /// + public static void DatabaseNoModifies() + { + if (_flag)// 仅执行一次,在初始化时候 + { + var dm = Acap.DocumentManager; + if (dm.Count == 0) + return; + var doc = dm.MdiActiveDocument; + if (doc is null) + return; + + if (DBmod != DBmod.DatabaseNoModifies) + AcdbSetDbmod(doc.Database.UnmanagedObject, DBmod.DatabaseNoModifies); + _flag = false; + } + } + + + //[CommandMethod(nameof(TestCmd_AcdbSetDbmodChange))] + //public void TestCmd_AcdbSetDbmodChange() + //{ + // DBmodTask(() => { + // using DBTrans tr = new(); + // Line line = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + // tr.CurrentSpace.AddEntity(line); + // }); + //} +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/Runtime/PE/PostCmd.cs b/src/CAD/IFox.CAD.Shared/Runtime/PE/PostCmd.cs new file mode 100644 index 0000000000000000000000000000000000000000..cd0b39e38ec42230c431e100d9aa3984a02a3847 --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/Runtime/PE/PostCmd.cs @@ -0,0 +1,199 @@ +namespace IFoxCAD.Cad; + +public class PostCmd +{ + /* + * #if NET35 || NET40 + * [DllImport("acad.exe", EntryPoint = "acedCmd", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)] + * #else + * // cad2015的AcedCmd改为AcedCmdS.参数也改了,不过不影响pe读取,accore.dll是2013版本后才开始改 + * [DllImport("accore.dll", EntryPoint = "acedCmdS", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)] + * static extern int AcedCmd(IntPtr rbp, bool forFutureUse = false, IntPtr pForFutureUse = IntPtr.Zero); + * #endif + * static extern int AcedCmd(IntPtr rbp); + * public static int AcedCmd(ResultBuffer args) + * { + * if (Acap.DocumentManager.IsApplicationContext) + * return 0; + * else + * return AcedCmd(args.UnmanagedObject); + * } + */ + delegate int DelegateAcedCmd(IntPtr parameter); + static DelegateAcedCmd? acedCmd;//nameof 别改名称 + /// + /// 发送命令(同步)如果2015.+这里报错,那么表示vs需要提权测试 + /// + static PromptStatus AcedCmd(ResultBuffer args) + { + if (Acap.DocumentManager.IsApplicationContext) + return 0; + if (acedCmd is null) + { + string str = nameof(acedCmd); + if (Acap.Version.Major >= 20)// 2015.+ + str += "S"; + + acedCmd = AcadPeInfo.GetDelegate( + str, AcadPeEnum.ExeAndCore); + } + if (acedCmd is null) + return 0; + + var result = (PromptStatus)acedCmd.Invoke(args.UnmanagedObject); + if (result != PromptStatus.OK) + throw new ArgumentException("发送命令出错,是否vs权限不足?"); + return result; + } + + /* + * [DllImport("accore.dll", EntryPoint = "acedCommand")] + * static extern int AcedCommand(IntPtr vlist); + */ + delegate int DelegateAcedCommand(IntPtr parameter); + static DelegateAcedCommand? acedCommand;//nameof 别改名称 + /// + /// 发送命令(同步) + /// + static PromptStatus AcedCommand(IntPtr args) + { + acedCommand ??= AcadPeInfo.GetDelegate( + nameof(acedCommand), AcadPeEnum.ExeAndCore); + if (acedCommand is null) + return PromptStatus.Error; + return (PromptStatus)acedCommand.Invoke(args);// 调用方法 + } + + /* + * [DllImport("acad.exe", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl, + * EntryPoint = "?acedPostCommand@@YAHPB_W@Z")] + * public static extern int AcedPostCommand(string strExpr); + */ + delegate int DelegateAcedPostCommand(byte[] parameter); + static DelegateAcedPostCommand? acedPostCommand;//nameof 别改名称 + /// + /// 发送命令(同步) + /// 这个可以在多线程发送 + /// + static PromptStatus AcedPostCommand(string args) + { + acedPostCommand ??= AcadPeInfo.GetDelegate( + nameof(acedPostCommand), AcadPeEnum.ExeAndCore); + + // 不然到CAD之后会乱码 + byte[] bytes = Encoding.Unicode.GetBytes(args); + if (acedPostCommand is null) + return PromptStatus.Error; + return (PromptStatus)acedPostCommand.Invoke(bytes);// 调用方法 + } + + delegate int DelegateAcedInvoke(byte[] parameter); + static DelegateAcedInvoke? acedInvoke;//nameof 别改名称 + /// + /// 发送命令(同步) + /// + static PromptStatus AcedInvoke(string args) + { + acedInvoke ??= AcadPeInfo.GetDelegate( + nameof(acedInvoke), AcadPeEnum.ExeAndCore); + + // 不然到CAD之后会乱码 + byte[] bytes = Encoding.Unicode.GetBytes(args); + + if (acedInvoke is null) + return PromptStatus.Error; + return (PromptStatus)acedInvoke.Invoke(bytes);// 调用方法 + } + + /// + /// 发送命令(异步)+CommandFlags.Session可以同步发送 + /// + static void AsyncCommand(string args) + { + object[] commandArray = { args + "\n" }; +#if zcad + var com = Acap.ZcadApplication; +#else + var com = Acap.AcadApplication; +#endif + // activeDocument 加载lisp第二个文档有问题,似乎要切换了才能 + var doc = com.GetType() + .InvokeMember("ActiveDocument", BindingFlags.GetProperty, null, com, null); + doc?.GetType() + .InvokeMember("SendCommand", BindingFlags.InvokeMethod, null, doc, commandArray);// 返回值是null + } + + public enum RunCmdFlag : byte + { + AcedCmd = 1, + AcedCommand = 2, + AcedPostCommand = 4, + AcedInvoke = 8, + SendStringToExecute = 16, + AsyncCommand = 32, + } + + /* + * 发送命令会记录在命令历史 + * 发送lisp的(command "xx")就不会 + */ + public static PromptStatus SendCommand(ResultBuffer args) + { + return AcedCmd(args); + } + public static PromptStatus SendCommand(IntPtr args) + { + return AcedCommand(args); + } + public static PromptStatus SendCommand(string args, RunCmdFlag flag) + { + PromptStatus ret = PromptStatus.OK; + if (!Acap.DocumentManager.IsApplicationContext) + { + if ((flag & RunCmdFlag.AcedCmd) == RunCmdFlag.AcedCmd) + { + using ResultBuffer rb = new() + { + new((int)LispDataType.Text, args), + }; + ret = SendCommand(rb); + } + if ((flag & RunCmdFlag.AcedCommand) == RunCmdFlag.AcedCommand) + { + // 此处是这样转换吗? + using ResultBuffer rb = new() + { + new((int)LispDataType.Text, args), + }; + ret = SendCommand(rb.UnmanagedObject); + } + if ((flag & RunCmdFlag.AcedPostCommand) == RunCmdFlag.AcedPostCommand) + { + ret = AcedPostCommand(args); + } + if ((flag & RunCmdFlag.AcedInvoke) == RunCmdFlag.AcedInvoke) + { + ret = AcedInvoke(args); + } + } + else + { + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + if (doc == null) + return PromptStatus.Error; + + if ((flag & RunCmdFlag.SendStringToExecute) == RunCmdFlag.SendStringToExecute) + { + doc.SendStringToExecute(args, true, false, false); + } + if ((flag & RunCmdFlag.AsyncCommand) == RunCmdFlag.AsyncCommand) + { + // 此处+CommandFlags.Session可以同步发送,bo命令可以,其他是否可以? + // 仿人工输入,像lisp一样可以直接发送关键字 + AsyncCommand(args); + } + } + return ret; + } +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/Runtime/PE/ProgramPE.cs b/src/CAD/IFox.CAD.Shared/Runtime/PE/ProgramPE.cs new file mode 100644 index 0000000000000000000000000000000000000000..e1f834a6cecc58f8795912d4d9499ec3723973b8 --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/Runtime/PE/ProgramPE.cs @@ -0,0 +1,1567 @@ +namespace IFoxCAD.Cad; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Text; + + +/* 来源 https://blog.csdn.net/zgke/article/details/2955560 我在他基础上面增加了X64的处理 + * 调用例子 + static void Main(string[] args) + { + var path = @"C:\Program Files\Autodesk\AutoCAD 2021\acad.exe"; + // path = @"G:\AutoCAD 2008\acad.exe"; + + var pe = new JoinBox.BasalCurrency.PeInfo(path); + + // 输出所有的函数名 + var sb = new StringBuilder(); + foreach (var item in pe.ExportDirectory.NameList) + { + sb.Append(Environment.NewLine); + var str = System.Text.Encoding.Default.GetString(item as byte[]); + sb.Append(str); + } + Debugx.Printl(sb.ToString()); + + // 原作者的封装 + var ss = pe.GetPETable(); + foreach (var item in ss.Tables) + { + } + } +*/ + +/// +/// 微软软件结构PE信息 +/// +public class PeInfo +{ + #region 成员 + /// + /// 获取是否正常打开文件 + /// + public bool OpenFile { get; private set; } = false; + public DosHeader? DosHeader { get; private set; } + public DosStub? DosStub { get; private set; } + public PEHeader? PEHeader { get; private set; } + public OptionalHeader? OptionalHeader { get; private set; } + public OptionalDirAttrib? OptionalDirAttrib { get; private set; } + public SectionTable? SectionTable { get; private set; } + /// + /// 函数接口名单 + /// + public ExportDirectory? ExportDirectory { get; private set; } + public ImportDirectory? ImportDirectory { get; private set; } + public ResourceDirectory? ResourceDirectory { get; private set; } + /// + /// PE文件完整路径 + /// + public string? FullName; + + bool _IsX86 = true; + + /// + /// 全部文件数据 + /// + readonly byte[]? _PEFileByte; + /// + /// 文件读取的位置 + /// + long _PEFileIndex = 0; + #endregion + + #region 构造 + public PeInfo(string fullName) + { + if (fullName is null) + throw new ArgumentException(nameof(fullName)); ; + + FullName = fullName; + FileStream? file = null; + OpenFile = false; + try + { + // 文件流 + file = new FileStream(fullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);// FileShare才能进c盘 + _PEFileByte = new byte[file.Length]; + file.Read(_PEFileByte, 0, _PEFileByte.Length); + LoadFile(); + OpenFile = true; + } + catch (Exception) { throw; } + finally + { + file?.Close(); + } + } + #endregion + + #region 读表方法 + /// + /// 开始读取 + /// + private void LoadFile() + { + LoadDosHeader(); // 获取DOS头,为了兼容DOS所以首先处理这个,然后才到PE头 + LoadDosStub(); // 获取DOS的身体 + LoadPEHeader(); // PE头 + LoadOptionalHeader(); // PE头扩展 + LoadOptionalDirAttrib(); // 获取选项目录属性 + LoadSectionTable(); // 获取节表 + LoadExportDirectory(); // 获取输出表 + LoadImportDirectory(); // 获取输入表 + LoadResourceDirectory(); // 获取资源目录 + } + + /// + /// 获得DOS头 + /// + private void LoadDosHeader() + { + DosHeader = new DosHeader + { + FileStarIndex = _PEFileIndex + }; + Loadbyte(ref DosHeader.e_magic); + Loadbyte(ref DosHeader.e_cblp); + Loadbyte(ref DosHeader.e_cp); + Loadbyte(ref DosHeader.e_crlc); + Loadbyte(ref DosHeader.e_cparhdr); + Loadbyte(ref DosHeader.e_minalloc); + Loadbyte(ref DosHeader.e_maxalloc); + Loadbyte(ref DosHeader.e_ss); + Loadbyte(ref DosHeader.e_sp); + Loadbyte(ref DosHeader.e_csum); + Loadbyte(ref DosHeader.e_ip); + Loadbyte(ref DosHeader.e_cs); + Loadbyte(ref DosHeader.e_rva); + Loadbyte(ref DosHeader.e_fg); + Loadbyte(ref DosHeader.e_bl1); + Loadbyte(ref DosHeader.e_oemid); + Loadbyte(ref DosHeader.e_oeminfo); + Loadbyte(ref DosHeader.e_bl2); + Loadbyte(ref DosHeader.e_PESTAR); + + DosHeader.FileEndIndex = _PEFileIndex; + } + + /// + /// 获得DOS SUB字段 + /// + private void LoadDosStub() + { + if (DosHeader is null) + return; + + long Size = GetLong(DosHeader.e_PESTAR) - _PEFileIndex; // 获得SUB的大小 + DosStub = new DosStub(Size) + { + FileStarIndex = _PEFileIndex + }; + Loadbyte(ref DosStub.DosStubData); + DosStub.FileEndIndex = _PEFileIndex; + } + + /// + /// 获得PE的文件头 + /// + /// + private void LoadPEHeader() + { + PEHeader = new PEHeader + { + FileStarIndex = _PEFileIndex + }; + Loadbyte(ref PEHeader.Header); + Loadbyte(ref PEHeader.Machine);// [76 1]==[0x4C 1]是x32, + _IsX86 = PEHeader.Machine[0] == 0x4c && PEHeader.Machine[1] == 0x1; + // (PEHeader.Machine[0] == 0x64 && PEHeader.Machine[1] == 0x86)// x64 + Loadbyte(ref PEHeader.NumberOfSections); + Loadbyte(ref PEHeader.TimeDateStamp); + Loadbyte(ref PEHeader.PointerToSymbolTable); + Loadbyte(ref PEHeader.NumberOfSymbols); + Loadbyte(ref PEHeader.SizeOfOptionalHeader); + Loadbyte(ref PEHeader.Characteristics); + PEHeader.FileEndIndex = _PEFileIndex; + } + + /// + /// 获得OPTIONAL PE扩展属性 + /// + /// + private void LoadOptionalHeader() + { + // 这里必须通过PE文件来判断它是一个什么架构,从而使用x86-x64 + OptionalHeader = new OptionalHeader(_IsX86) + { + FileStarIndex = _PEFileIndex + }; + Loadbyte(ref OptionalHeader.Magic); + Loadbyte(ref OptionalHeader.MajorLinkerVersion); + Loadbyte(ref OptionalHeader.MinorLinkerVersion); + Loadbyte(ref OptionalHeader.SizeOfCode); + Loadbyte(ref OptionalHeader.SizeOfInitializedData); + Loadbyte(ref OptionalHeader.SizeOfUninitializedData); + Loadbyte(ref OptionalHeader.AddressOfEntryPoint); + Loadbyte(ref OptionalHeader.BaseOfCode); + Loadbyte(ref OptionalHeader.ImageBase); + Loadbyte(ref OptionalHeader.BaseOfData); + Loadbyte(ref OptionalHeader.SectionAlignment); + Loadbyte(ref OptionalHeader.FileAlignment); + + Loadbyte(ref OptionalHeader.MajorOperatingSystemVersion); + Loadbyte(ref OptionalHeader.MinorOperatingSystemVersion); + Loadbyte(ref OptionalHeader.MajorImageVersion); + Loadbyte(ref OptionalHeader.MinorImageVersion); + Loadbyte(ref OptionalHeader.MajorSubsystemVersion); + Loadbyte(ref OptionalHeader.MinorSubsystemVersion); + Loadbyte(ref OptionalHeader.Win32VersionValue); + Loadbyte(ref OptionalHeader.SizeOfImage); + Loadbyte(ref OptionalHeader.SizeOfHeards); + Loadbyte(ref OptionalHeader.CheckSum); + Loadbyte(ref OptionalHeader.Subsystem); + Loadbyte(ref OptionalHeader.DLLCharacteristics); + Loadbyte(ref OptionalHeader.SizeOfStackReserve); + Loadbyte(ref OptionalHeader.SizeOfStackCommit); + Loadbyte(ref OptionalHeader.SizeOfHeapReserve); + Loadbyte(ref OptionalHeader.SizeOfHeapCommit); + Loadbyte(ref OptionalHeader.LoaderFlags); + Loadbyte(ref OptionalHeader.NumberOfRvaAndSizes); + + OptionalHeader.FileEndIndex = _PEFileIndex; + } + + /// + /// 获取目录表 + /// + /// + private void LoadOptionalDirAttrib() + { + if (OptionalHeader is null) + return; + + OptionalDirAttrib = new OptionalDirAttrib + { + FileStarIndex = _PEFileIndex + }; + + long DirCount = GetLong(OptionalHeader.NumberOfRvaAndSizes);// 这里导致无法使用64位 + for (int i = 0; i != DirCount; i++) + { + OptionalDirAttrib.DirAttrib? directAttrib = new(); + Loadbyte(ref directAttrib.DirRva); + Loadbyte(ref directAttrib.DirSize); + OptionalDirAttrib.DirByte.Add(directAttrib); + } + OptionalDirAttrib.FileEndIndex = _PEFileIndex; + } + + /// + /// 获取节表 + /// + private void LoadSectionTable() + { + if (PEHeader is null) + return; + + SectionTable = new SectionTable(); + long Count = GetLong(PEHeader.NumberOfSections); + SectionTable.FileStarIndex = _PEFileIndex; + for (long i = 0; i != Count; i++) + { + var Section = new SectionTable.SectionData(); + + Loadbyte(ref Section.SectName); + Loadbyte(ref Section.VirtualAddress); + Loadbyte(ref Section.SizeOfRawDataRVA); + Loadbyte(ref Section.SizeOfRawDataSize); + Loadbyte(ref Section.PointerToRawData); + Loadbyte(ref Section.PointerToRelocations); + Loadbyte(ref Section.PointerToLinenumbers); + Loadbyte(ref Section.NumberOfRelocations); + Loadbyte(ref Section.NumberOfLinenumbers); + Loadbyte(ref Section.Characteristics); + SectionTable.Section.Add(Section); + } + SectionTable.FileEndIndex = _PEFileIndex; + } + + /// + /// 读取输出表 + /// + private void LoadExportDirectory() + { + if (OptionalDirAttrib is null) + return; + if (OptionalDirAttrib.DirByte.Count == 0) + return; + + if (OptionalDirAttrib.DirByte[0] is not OptionalDirAttrib.DirAttrib exporRVA || + GetLong(exporRVA.DirRva) == 0) + return; + + long exporAddress = GetLong(exporRVA.DirRva); // 获取的位置 + ExportDirectory = new ExportDirectory(); + + if (SectionTable is null) + return; + + for (int i = 0; i != SectionTable.Section.Count; i++) // 循环节表 + { + if (SectionTable.Section[i] is not SectionTable.SectionData sect) + continue; + + long starRva = GetLong(sect.SizeOfRawDataRVA); + long endRva = GetLong(sect.SizeOfRawDataSize); + + if (exporAddress >= starRva && exporAddress < starRva + endRva) + { + _PEFileIndex = exporAddress - GetLong(sect.SizeOfRawDataRVA) + GetLong(sect.PointerToRawData); + + ExportDirectory.FileStarIndex = _PEFileIndex; + ExportDirectory.FileEndIndex = _PEFileIndex + GetLong(exporRVA.DirSize); + + Loadbyte(ref ExportDirectory.Characteristics); + Loadbyte(ref ExportDirectory.TimeDateStamp); + Loadbyte(ref ExportDirectory.MajorVersion); + Loadbyte(ref ExportDirectory.MinorVersion); + Loadbyte(ref ExportDirectory.Name); + Loadbyte(ref ExportDirectory.Base); + Loadbyte(ref ExportDirectory.NumberOfFunctions); + Loadbyte(ref ExportDirectory.NumberOfNames); + Loadbyte(ref ExportDirectory.AddressOfFunctions); + Loadbyte(ref ExportDirectory.AddressOfNames); + Loadbyte(ref ExportDirectory.AddressOfNameOrdinals); + + _PEFileIndex = GetLong(ExportDirectory.AddressOfFunctions) - GetLong(sect.SizeOfRawDataRVA) + GetLong(sect.PointerToRawData); + long endIndex = GetLong(ExportDirectory.AddressOfNames) - GetLong(sect.SizeOfRawDataRVA) + GetLong(sect.PointerToRawData); + long numb = (endIndex - _PEFileIndex) / 4; + for (long z = 0; z != numb; z++) + { + byte[] Data = new byte[4]; + Loadbyte(ref Data); + ExportDirectory.AddressOfFunctionsList.Add(Data); + } + + _PEFileIndex = endIndex; + endIndex = GetLong(ExportDirectory.AddressOfNameOrdinals) - GetLong(sect.SizeOfRawDataRVA) + GetLong(sect.PointerToRawData); + numb = (endIndex - _PEFileIndex) / 4; + for (long z = 0; z != numb; z++) + { + byte[] Data = new byte[4]; + Loadbyte(ref Data); + ExportDirectory.AddressOfNamesList.Add(Data); + } + + _PEFileIndex = endIndex; + endIndex = GetLong(ExportDirectory.Name) - GetLong(sect.SizeOfRawDataRVA) + GetLong(sect.PointerToRawData); + numb = (endIndex - _PEFileIndex) / 2; + for (long z = 0; z != numb; z++) + { + byte[] Data = new byte[2]; + Loadbyte(ref Data); + ExportDirectory.AddressOfNameOrdinalsList.Add(Data); + } + + _PEFileIndex = endIndex; + + if (_PEFileByte is not null) + { + long ReadIndex = 0; + while (true) + { + if (_PEFileByte[_PEFileIndex + ReadIndex] == 0) + { + if (_PEFileByte[_PEFileIndex + ReadIndex + 1] == 0) + break; + + var Date = new byte[ReadIndex]; + Loadbyte(ref Date); + ExportDirectory.FunctionNamesByte.Add(Date); + + _PEFileIndex++; + ReadIndex = 0; + } + ReadIndex++; + } + } + break; + } + } + } + + /// + /// 读取输入表 + /// + private void LoadImportDirectory() + { + if (OptionalDirAttrib is null) + return; + if (OptionalDirAttrib.DirByte.Count < 1) + return; + if (OptionalDirAttrib.DirByte[1] is not OptionalDirAttrib.DirAttrib ImporRVA) + return; + + long ImporAddress = GetLong(ImporRVA.DirRva); // 获取的位置 + if (ImporAddress == 0) + return; + long ImporSize = GetLong(ImporRVA.DirSize); // 获取大小 + + ImportDirectory = new ImportDirectory(); + + long SizeRva = 0; + long PointerRva = 0; + + long StarRva = 0; + long EndRva = 0; + + #region 获取位置 + if (SectionTable is null) + return; + for (int i = 0; i != SectionTable.Section.Count; i++) // 循环节表 + { + if (SectionTable.Section[i] is not SectionTable.SectionData Sect) + continue; + + StarRva = GetLong(Sect.SizeOfRawDataRVA); + EndRva = GetLong(Sect.SizeOfRawDataSize); + + if (ImporAddress >= StarRva && ImporAddress < StarRva + EndRva) + { + SizeRva = GetLong(Sect.SizeOfRawDataRVA); + PointerRva = GetLong(Sect.PointerToRawData); + _PEFileIndex = ImporAddress - SizeRva + PointerRva; + + ImportDirectory.FileStarIndex = _PEFileIndex; + ImportDirectory.FileEndIndex = _PEFileIndex + ImporSize; + break; + } + } + + if (SizeRva == 0 && PointerRva == 0) + return; + #endregion + + #region 输入表结构 + while (true) + { + var import = new ImportDirectory.ImportDate(); + Loadbyte(ref import.OriginalFirstThunk); + Loadbyte(ref import.TimeDateStamp); + Loadbyte(ref import.ForwarderChain); + Loadbyte(ref import.Name); + Loadbyte(ref import.FirstThunk); + + if (GetLong(import.Name) == 0) + break; + ImportDirectory.ImportList.Add(import); // 添加 + } + #endregion + + + #region 获取输入DLL名称 + for (int z = 0; z != ImportDirectory.ImportList.Count; z++) // 获取引入DLL名字 + { + if (ImportDirectory.ImportList[z] is not ImportDirectory.ImportDate Import) + continue; + + long ImportDLLName = GetLong(Import.Name) - SizeRva + PointerRva; + _PEFileIndex = ImportDLLName; + long ReadCount = 0; + while (_PEFileByte is not null) // 获取引入名 + { + if (_PEFileByte[_PEFileIndex + ReadCount] == 0) + { + Import.DLLName = new byte[ReadCount]; + Loadbyte(ref Import.DLLName); + break; + } + ReadCount++; + } + } + #endregion + + #region 获取引入方法 先获取地址 然后获取名字和头 + for (int z = 0; z != ImportDirectory.ImportList.Count; z++) // 获取引入方法 + { + if (ImportDirectory.ImportList[z] is not ImportDirectory.ImportDate import) + continue; + + long importDLLName = GetLong(import.OriginalFirstThunk) - SizeRva + PointerRva; + _PEFileIndex = importDLLName; + while (true) + { + var function = new ImportDirectory.ImportDate.FunctionList(); + Loadbyte(ref function.OriginalFirst); + + long loadIndex = GetLong(function.OriginalFirst); + if (loadIndex == 0) + break; + long oldIndex = _PEFileIndex; + + _PEFileIndex = loadIndex - SizeRva + PointerRva; + + if (loadIndex >= StarRva && loadIndex < StarRva + EndRva) // 发现有些数字超级大 + { + int ReadCount = 0; + + while (_PEFileByte is not null) + { + if (ReadCount == 0) + Loadbyte(ref function.FunctionHead); + if (_PEFileByte[_PEFileIndex + ReadCount] == 0) + { + byte[] FunctionName = new byte[ReadCount]; + Loadbyte(ref FunctionName); + function.FunctionName = FunctionName; + + break; + } + ReadCount++; + } + } + else + { + function.FunctionName = new byte[1]; + } + + _PEFileIndex = oldIndex; + import.DLLFunctionList.Add(function); + } + } + #endregion + } + + /// + /// 读取资源表 + /// + private void LoadResourceDirectory() + { + #region 初始化 + if (OptionalDirAttrib is null || OptionalDirAttrib.DirByte.Count < 3) + return; + if (OptionalDirAttrib.DirByte[2] is not OptionalDirAttrib.DirAttrib ImporRVA) + return; + + long ImporAddress = GetLong(ImporRVA.DirRva); // 获取的位置 + if (ImporAddress == 0) + return; + long ImporSize = GetLong(ImporRVA.DirSize); // 获取大小 + + ResourceDirectory = new ResourceDirectory(); + + long SizeRva = 0; + long PointerRva = 0; + long StarRva = 0; + long PEIndex = 0; + #endregion + + #region 获取位置 + if (SectionTable is null) + return; + + for (int i = 0; i != SectionTable.Section.Count; i++) // 循环节表 + { + if (SectionTable.Section[i] is not SectionTable.SectionData sect) + continue; + + StarRva = GetLong(sect.SizeOfRawDataRVA); + var EndRva = GetLong(sect.SizeOfRawDataSize); + if (ImporAddress >= StarRva && ImporAddress < StarRva + EndRva) + { + SizeRva = GetLong(sect.SizeOfRawDataRVA); + PointerRva = GetLong(sect.PointerToRawData); + _PEFileIndex = ImporAddress - SizeRva + PointerRva; + PEIndex = _PEFileIndex; + ResourceDirectory.FileStarIndex = _PEFileIndex; + ResourceDirectory.FileEndIndex = _PEFileIndex + ImporSize; + break; + } + } + + if (SizeRva == 0 && PointerRva == 0) + return; + #endregion + + AddResourceNode(ResourceDirectory, PEIndex, 0, StarRva); + } + + /// + /// 添加资源节点 + /// + /// + /// + /// + /// + private void AddResourceNode(ResourceDirectory node, long PEIndex, long RVA, long resourSectRva) + { + _PEFileIndex = PEIndex + RVA; // 设置位置 + Loadbyte(ref node.Characteristics); + Loadbyte(ref node.TimeDateStamp); + Loadbyte(ref node.MajorVersion); + Loadbyte(ref node.MinorVersion); + Loadbyte(ref node.NumberOfNamedEntries); + Loadbyte(ref node.NumberOfIdEntries); + + long NameRVA = GetLong(node.NumberOfNamedEntries); + for (int i = 0; i != NameRVA; i++) + { + var Entry = new ResourceDirectory.DirectoryEntry(); + Loadbyte(ref Entry.Name); + Loadbyte(ref Entry.Id); + byte[] temp = new byte[2]; + temp[0] = Entry.Name[0]; + temp[1] = Entry.Name[1]; + + if (_PEFileByte is null) + return; + + long NameIndex = GetLong(temp) + PEIndex; + temp[0] = _PEFileByte[NameIndex + 0]; + temp[1] = _PEFileByte[NameIndex + 1]; + + long NameCount = GetLong(temp); + node.Name = new byte[NameCount * 2]; + + for (int z = 0; z != node.Name.Length; z++) + node.Name[z] = _PEFileByte[NameIndex + 2 + z]; + // System.Windows.Forms.MessageBox.Show(GetString(Entry.ID)); + + temp[0] = Entry.Id[2]; + temp[1] = Entry.Id[3]; + + long oldIndex = _PEFileIndex; + + if (GetLong(temp) == 0) + { + temp[0] = Entry.Id[0]; + temp[1] = Entry.Id[1]; + + _PEFileIndex = GetLong(temp) + PEIndex; + + var dataRVA = new ResourceDirectory.DirectoryEntry.DataEntry(); + + Loadbyte(ref dataRVA.ResourRVA); + Loadbyte(ref dataRVA.ResourSize); + Loadbyte(ref dataRVA.ResourTest); + Loadbyte(ref dataRVA.ResourWen); + + _PEFileIndex = oldIndex; + Entry.DataEntryList.Add(dataRVA); + // System.Windows.Forms.MessageBox.Show(GetString(DataRVA.ResourRVA)+"*"+GetString(DataRVA.ResourSize)); + } + else + { + temp[0] = Entry.Id[0]; + temp[1] = Entry.Id[1]; + + var Resource = new ResourceDirectory(); + Entry.NodeDirectoryList.Add(Resource); + AddResourceNode(Resource, PEIndex, GetLong(temp), resourSectRva); + } + _PEFileIndex = oldIndex; + node.EntryList.Add(Entry); + } + + long Count = GetLong(node.NumberOfIdEntries); + for (int i = 0; i != Count; i++) + { + var entry = new ResourceDirectory.DirectoryEntry(); + Loadbyte(ref entry.Name); + Loadbyte(ref entry.Id); + // System.Windows.Forms.MessageBox.Show(GetString(Entry.Name)+"_"+GetString(Entry.Id)); + + byte[] temp = new byte[2]; + temp[0] = entry.Id[2]; + temp[1] = entry.Id[3]; + + long OldIndex = _PEFileIndex; + + if (GetLong(temp) == 0) + { + temp[0] = entry.Id[0]; + temp[1] = entry.Id[1]; + + _PEFileIndex = GetLong(temp) + PEIndex; + + var dataRVA = new ResourceDirectory.DirectoryEntry.DataEntry(); + Loadbyte(ref dataRVA.ResourRVA); + Loadbyte(ref dataRVA.ResourSize); + Loadbyte(ref dataRVA.ResourTest); + Loadbyte(ref dataRVA.ResourWen); + + long FileRva = GetLong(dataRVA.ResourRVA) - resourSectRva + PEIndex; + + dataRVA.FileStarIndex = FileRva; + dataRVA.FileEndIndex = FileRva + GetLong(dataRVA.ResourSize); + + _PEFileIndex = OldIndex; + entry.DataEntryList.Add(dataRVA); + // System.Windows.Forms.MessageBox.Show(GetString(DataRVA.ResourRVA)+"*"+GetString(DataRVA.ResourSize)); + } + else + { + temp[0] = entry.Id[0]; + temp[1] = entry.Id[1]; + var Resource = new ResourceDirectory(); + entry.NodeDirectoryList.Add(Resource); + AddResourceNode(Resource, PEIndex, GetLong(temp), resourSectRva); + } + _PEFileIndex = OldIndex; + node.EntryList.Add(entry); + } + } + + #endregion + + #region 工具方法 + /// + /// 读数据 读byte[]的数量 会改边PEFileIndex的值 + /// + /// + private void Loadbyte(ref byte[] data) + { + if (_PEFileByte is null) + return; + + for (int i = 0; i != data.Length; i++) + { + data[i] = _PEFileByte[_PEFileIndex]; + _PEFileIndex++; + } + } + /// + /// 转换byte为字符串 + /// + /// byte[] + /// AA BB CC DD + private string GetString(byte[] data) + { + string Temp = ""; + for (int i = 0; i != data.Length - 1; i++) + Temp += data[i].ToString("X02") + " "; + + Temp += data[data.Length - 1].ToString("X02"); + // Temp += data[^1].ToString("X02"); + return Temp; + } + /// + /// 转换字符为显示数据 + /// + /// byte[] + /// ASCII DEFAULT UNICODE BYTE + /// + private string GetString(byte[] data, string type) + { + if (type.Trim().ToUpper() == "ASCII") + return System.Text.Encoding.ASCII.GetString(data); + if (type.Trim().ToUpper() == "DEFAULT") + return System.Text.Encoding.Default.GetString(data); + if (type.Trim().ToUpper() == "UNICODE") + return System.Text.Encoding.Unicode.GetString(data); + if (type.Trim().ToUpper() == "BYTE") + { + string Temp = ""; + for (int i = data.Length - 1; i != 0; i--) + Temp += data[i].ToString("X02") + " "; + Temp += data[0].ToString("X02"); + return Temp; + } + return GetInt(data); + } + /// + /// 转换BYTE为INT + /// + /// + /// + static string GetInt(byte[] data) + { + string Temp = ""; + for (int i = 0; i != data.Length - 1; i++) + { + int ByteInt = (int)data[i]; + Temp += ByteInt.ToString() + " "; + } + int EndByteInt = (int)data[data.Length - 1]; + // int EndByteInt = (int)data[^1]; + Temp += EndByteInt.ToString(); + return Temp; + } + /// + /// 转换数据为LONG + /// + /// + /// + private long GetLong(byte[] data) + { + if (data.Length > 4) + return 0; + + string MC = ""; + // if (data.Length <= 4) + for (int i = data.Length - 1; i != -1; i--) + MC += data[i].ToString("X02"); + return Convert.ToInt64(MC, 16); + } + /// + /// 添加一行信息 + /// + /// 表 + /// 数据 + /// 名称 + /// 说明 + private void AddTableRow(DataTable refTable, byte[]? data, string name, string describe) + { + if (data == null) + throw new ArgumentException(nameof(data)); + + refTable.Rows.Add( + new string[] + { + name, + data.Length.ToString(), + GetString(data), + GetLong(data).ToString(), + GetString(data,"ASCII"), + describe + }); + } + #endregion + + #region Table绘制 + /// + /// 获取PE信息 DataSet方式 + /// + /// 多个表 最后资源表 绘制成树结构TABLE + public DataSet? GetPETable() + { + if (OpenFile == false) + return null; + + var ds = new DataSet("PEFile"); + var a1 = TableDosHeader(); + if (a1 is not null) + ds.Tables.Add(a1); + var a2 = TablePEHeader(); + if (a2 is not null) + ds.Tables.Add(a2); + var a3 = TableOptionalHeader(); + if (a3 is not null) + ds.Tables.Add(a3); + var a4 = TableOptionalDirAttrib(); + if (a4 is not null) + ds.Tables.Add(a4); + var a5 = TableSectionData(); + if (a5 is not null) + ds.Tables.Add(a5); + + + var a6 = TableExportDirectory(); + var a7 = TableExportFunction(); + if (a6 is not null) + ds.Tables.Add(a6); + if (a7 is not null) + ds.Tables.Add(a7); + + var a8 = TableImportDirectory(); + var a9 = TableImportFunction(); + if (a8 is not null) + ds.Tables.Add(a8); + if (a9 is not null) + ds.Tables.Add(a9); + + var a10 = TableResourceDirectory(); + if (a10 is not null) + ds.Tables.Add(a10); + + return ds; + } + + private DataTable? TableDosHeader() + { + if (DosHeader is null) + return null; + + var returnTable = new DataTable("DosHeader FileStar{" + DosHeader.FileStarIndex.ToString() + "}FileEnd{" + DosHeader.FileEndIndex.ToString() + "}"); + returnTable.Columns.Add("Name"); + returnTable.Columns.Add("Size"); + returnTable.Columns.Add("Value16"); + returnTable.Columns.Add("Value10"); + returnTable.Columns.Add("ASCII"); + returnTable.Columns.Add("Describe"); + + AddTableRow(returnTable, DosHeader.e_magic, "e_magic", "魔术数字"); + AddTableRow(returnTable, DosHeader.e_cblp, "e_cblp", "文件最后页的字节数"); + AddTableRow(returnTable, DosHeader.e_cp, "e_cp", "文件页数"); + AddTableRow(returnTable, DosHeader.e_crlc, "e_crlc", "重定义元素个数"); + AddTableRow(returnTable, DosHeader.e_cparhdr, "e_cparhdr", "头部尺寸,以段落为单位"); + AddTableRow(returnTable, DosHeader.e_minalloc, "e_minalloc", "所需的最小附加段"); + AddTableRow(returnTable, DosHeader.e_maxalloc, "e_maxalloc", "所需的最大附加段"); + AddTableRow(returnTable, DosHeader.e_ss, "e_ss", "初始的SS值(相对偏移量)"); + AddTableRow(returnTable, DosHeader.e_sp, "e_sp", "初始的SP值"); + AddTableRow(returnTable, DosHeader.e_csum, "e_csum", "校验和"); + AddTableRow(returnTable, DosHeader.e_ip, "e_ip", "初始的IP值"); + AddTableRow(returnTable, DosHeader.e_cs, "e_cs", "初始的CS值(相对偏移量)"); + AddTableRow(returnTable, DosHeader.e_rva, "e_rva", ""); + AddTableRow(returnTable, DosHeader.e_fg, "e_fg", ""); + AddTableRow(returnTable, DosHeader.e_bl1, "e_bl1", ""); + AddTableRow(returnTable, DosHeader.e_oemid, "e_oemid", ""); + AddTableRow(returnTable, DosHeader.e_oeminfo, "e_oeminfo", ""); + AddTableRow(returnTable, DosHeader.e_bl2, "e_bl2", ""); + AddTableRow(returnTable, DosHeader.e_PESTAR, "e_PESTAR", "PE开始 +本结构的位置"); + + return returnTable; + } + + private DataTable? TablePEHeader() + { + if (PEHeader is null) + return null; + + var returnTable = new DataTable("PeHeader FileStar{" + PEHeader.FileStarIndex.ToString() + "}FileEnd{" + PEHeader.FileEndIndex.ToString() + "}"); + returnTable.Columns.Add("Name"); + returnTable.Columns.Add("Size"); + returnTable.Columns.Add("Value16"); + returnTable.Columns.Add("Value10"); + returnTable.Columns.Add("ASCII"); + returnTable.Columns.Add("Describe"); + + + AddTableRow(returnTable, PEHeader.Header, "Header", "PE文件标记"); + AddTableRow(returnTable, PEHeader.Machine, "Machine", "该文件运行所要求的CPU.对于Intel平台,该值是IMAGE_FILE_MACHINE_I386 (14Ch).我们尝试了LUEVELSMEYER的pe.txt声明的14Dh和14Eh,但Windows不能正确执行. "); + AddTableRow(returnTable, PEHeader.NumberOfSections, "NumberOfSections", "文件的节数目.如果我们要在文件中增加或删除一个节,就需要修改这个值."); + AddTableRow(returnTable, PEHeader.TimeDateStamp, "TimeDateStamp", "文件创建日期和时间. "); + AddTableRow(returnTable, PEHeader.PointerToSymbolTable, "PointerToSymbolTable", "用于调试. "); + AddTableRow(returnTable, PEHeader.NumberOfSymbols, "NumberOfSymbols", "用于调试. "); + AddTableRow(returnTable, PEHeader.SizeOfOptionalHeader, "SizeOfOptionalHeader", "指示紧随本结构之后的 OptionalHeader 结构大小,必须为有效值."); + AddTableRow(returnTable, PEHeader.Characteristics, "Characteristics", "关于文件信息的标记,比如文件是exe还是dll."); + + return returnTable; + } + + private DataTable? TableOptionalHeader() + { + if (OptionalHeader is null) + return null; + + var returnTable = new DataTable("OptionalHeader FileStar{" + OptionalHeader.FileStarIndex.ToString() + "}FileEnd{" + OptionalHeader.FileEndIndex.ToString() + "}"); + returnTable.Columns.Add("Name"); + returnTable.Columns.Add("Size"); + returnTable.Columns.Add("Value16"); + returnTable.Columns.Add("Value10"); + returnTable.Columns.Add("ASCII"); + returnTable.Columns.Add("Describe"); + + AddTableRow(returnTable, OptionalHeader.Magic, "Magic", "Magic 010B=普通可以执行,0107=ROM映像"); + AddTableRow(returnTable, OptionalHeader.MajorLinkerVersion, "MajorLinkerVersion", "主版本号"); + AddTableRow(returnTable, OptionalHeader.MinorLinkerVersion, "MinorLinkerVersion", "副版本号"); + AddTableRow(returnTable, OptionalHeader.SizeOfCode, "SizeOfCode", "代码段大小"); + AddTableRow(returnTable, OptionalHeader.SizeOfInitializedData, "SizeOfInitializedData", "已初始化数据大小"); + AddTableRow(returnTable, OptionalHeader.SizeOfUninitializedData, "SizeOfUninitializedData", "未初始化数据大小"); + AddTableRow(returnTable, OptionalHeader.AddressOfEntryPoint, "AddressOfEntryPoint", "执行将从这里开始(RVA)"); + AddTableRow(returnTable, OptionalHeader.BaseOfCode, "BaseOfCode", "代码基址(RVA)"); + AddTableRow(returnTable, OptionalHeader.ImageBase, "ImageBase", "数据基址(RVA)"); + if (_IsX86) + { + AddTableRow(returnTable, OptionalHeader.BaseOfData, "ImageFileCode", "映象文件基址"); + } + AddTableRow(returnTable, OptionalHeader.SectionAlignment, "SectionAlign", "区段列队"); + AddTableRow(returnTable, OptionalHeader.MajorOperatingSystemVersion, "MajorOSV", "文件列队"); + AddTableRow(returnTable, OptionalHeader.MinorOperatingSystemVersion, "MinorOSV", "操作系统主版本号"); + AddTableRow(returnTable, OptionalHeader.MajorImageVersion, "MajorImageVer", "映象文件主版本号"); + AddTableRow(returnTable, OptionalHeader.MinorImageVersion, "MinorImageVer", "映象文件副版本号"); + AddTableRow(returnTable, OptionalHeader.MajorSubsystemVersion, "MajorSV", "子操作系统主版本号"); + AddTableRow(returnTable, OptionalHeader.MinorSubsystemVersion, "MinorSV", "子操作系统副版本号"); + AddTableRow(returnTable, OptionalHeader.Win32VersionValue, "UNKNOW", "Win32版本值"); + AddTableRow(returnTable, OptionalHeader.SizeOfImage, "SizeOfImage", "映象文件大小"); + AddTableRow(returnTable, OptionalHeader.SizeOfHeards, "SizeOfHeards", "标志头大小"); + AddTableRow(returnTable, OptionalHeader.CheckSum, "CheckSum", "文件效验"); + AddTableRow(returnTable, OptionalHeader.Subsystem, "Subsystem", "子系统(映象文件)1本地 2WINDOWS-GUI 3WINDOWS-CUI 4 POSIX-CUI"); + AddTableRow(returnTable, OptionalHeader.DLLCharacteristics, "DLL_Characteristics", "DLL标记"); + AddTableRow(returnTable, OptionalHeader.SizeOfStackReserve, "Bsize", "保留栈的大小"); + AddTableRow(returnTable, OptionalHeader.SizeOfStackCommit, "TimeBsize", "初始时指定栈大小"); + AddTableRow(returnTable, OptionalHeader.SizeOfHeapReserve, "AucBsize", "保留堆的大小"); + AddTableRow(returnTable, OptionalHeader.SizeOfHeapCommit, "SizeOfBsize", "初始时指定堆大小"); + AddTableRow(returnTable, OptionalHeader.LoaderFlags, "FuckBsize", "加载器标志"); + AddTableRow(returnTable, OptionalHeader.NumberOfRvaAndSizes, "DirectCount", "数据目录数"); + + return returnTable; + } + + private DataTable? TableOptionalDirAttrib() + { + if (OptionalDirAttrib is null) + return null; + + var returnTable = new DataTable( + "OptionalDirAttrib FileStar{" + + OptionalDirAttrib.FileStarIndex.ToString() + + "}FileEnd{" + + OptionalDirAttrib.FileEndIndex.ToString() + + "}"); + returnTable.Columns.Add("Name"); + returnTable.Columns.Add("Size"); + returnTable.Columns.Add("Value16"); + returnTable.Columns.Add("Value10"); + returnTable.Columns.Add("ASCII"); + returnTable.Columns.Add("Describe"); + + var tableName = new Hashtable + { + { 0, "输出表" }, + { 1, "输入表" }, + { 2, "资源表" }, + { 3, "异常表" }, + { 4, "安全表" }, + { 5, "基部重定位表" }, + { 6, "调试数据" }, + { 7, "版权数据" }, + { 8, "全局PTR" }, + { 9, "TLS表" }, + { 10, "装入配置表" }, + { 11, "其他表1" }, + { 12, "其他表2" }, + { 13, "其他表3" }, + { 14, "其他表4" }, + { 15, "其他表5" } + }; + + for (int i = 0; i != OptionalDirAttrib.DirByte.Count; i++) + { + if (OptionalDirAttrib.DirByte[i] is not OptionalDirAttrib.DirAttrib MyDirByte) + continue; + + string? Name; + var tn = tableName[i]; + if (tn is not null) + Name = tn.ToString(); + else + Name = $"未知表{i}"; + AddTableRow(returnTable, MyDirByte.DirRva, Name!, "地址"); + AddTableRow(returnTable, MyDirByte.DirSize, "", "大小"); + } + return returnTable; + } + + private DataTable? TableSectionData() + { + if (SectionTable is null) + return null; + + var returnTable = new DataTable( + "SectionData FileStar{" + + SectionTable.FileStarIndex.ToString() + + "}FileEnd{" + + SectionTable.FileEndIndex.ToString() + + "}"); + returnTable.Columns.Add("Name"); + returnTable.Columns.Add("Size"); + returnTable.Columns.Add("Value16"); + returnTable.Columns.Add("Value10"); + returnTable.Columns.Add("ASCII"); + returnTable.Columns.Add("Describe"); + + for (int i = 0; i != SectionTable.Section.Count; i++) + { + if (SectionTable.Section[i] is not SectionTable.SectionData SectionDate) + continue; + + AddTableRow(returnTable, SectionDate.SectName, "SectName", "名字"); + AddTableRow(returnTable, SectionDate.VirtualAddress, "VirtualAddress", "虚拟内存地址"); + AddTableRow(returnTable, SectionDate.SizeOfRawDataRVA, "SizeOfRawDataRVA", "RVA偏移"); + AddTableRow(returnTable, SectionDate.SizeOfRawDataSize, "SizeOfRawDataSize", "RVA大小"); + AddTableRow(returnTable, SectionDate.PointerToRawData, "PointerToRawData", "指向RAW数据"); + AddTableRow(returnTable, SectionDate.PointerToRelocations, "PointerToRelocations", "指向定位号"); + AddTableRow(returnTable, SectionDate.PointerToLinenumbers, "PointerToLinenumbers", "指向行数"); + AddTableRow(returnTable, SectionDate.NumberOfRelocations, "NumberOfRelocations", "定位号"); + AddTableRow(returnTable, SectionDate.NumberOfLinenumbers, "NumberOfLinenumbers", "行数号"); + AddTableRow(returnTable, SectionDate.Characteristics, "Characteristics", "区段标记"); + } + return returnTable; + } + + private DataTable? TableExportDirectory() + { + if (ExportDirectory is null) + return null; + + var returnTable = new DataTable( + "ExportDirectory FileStar{" + + ExportDirectory.FileStarIndex.ToString() + + "}FileEnd{" + + ExportDirectory.FileEndIndex.ToString() + + "}"); + returnTable.Columns.Add("Name"); + returnTable.Columns.Add("Size"); + returnTable.Columns.Add("Value16"); + returnTable.Columns.Add("Value10"); + returnTable.Columns.Add("ASCII"); + returnTable.Columns.Add("Describe"); + + AddTableRow(returnTable, ExportDirectory.Characteristics, "Characteristics", "一个保留字段,目前为止值为0."); + AddTableRow(returnTable, ExportDirectory.TimeDateStamp, "TimeDateStamp", "产生的时间."); + AddTableRow(returnTable, ExportDirectory.MajorVersion, "MajorVersion", "主版本号"); + AddTableRow(returnTable, ExportDirectory.MinorVersion, "MinorVersion", "副版本号"); + AddTableRow(returnTable, ExportDirectory.Name, "Name", "一个RVA,指向一个dll的名称的ascii字符串."); + AddTableRow(returnTable, ExportDirectory.Base, "Base", "输出函数的起始序号.一般为1."); + AddTableRow(returnTable, ExportDirectory.NumberOfFunctions, "NumberOfFunctions", "输出函数入口地址的数组 中的元素个数."); + AddTableRow(returnTable, ExportDirectory.NumberOfNames, "NumberOfNames", "输出函数名的指针的数组 中的元素个数,也是输出函数名对应的序号的数组 中的元素个数."); + AddTableRow(returnTable, ExportDirectory.AddressOfFunctions, "AddressOfFunctions", "一个RVA,指向输出函数入口地址的数组."); + AddTableRow(returnTable, ExportDirectory.AddressOfNames, "AddressOfNames", "一个RVA,指向输出函数名的指针的数组."); + AddTableRow(returnTable, ExportDirectory.AddressOfNameOrdinals, "AddressOfNameOrdinals", "一个RVA,指向输出函数名对应的序号的数组."); + + return returnTable; + } + private DataTable? TableExportFunction() + { + if (ExportDirectory is null) + return null; + + var returnTable = new DataTable("ExportFunctionList"); + returnTable.Columns.Add("Name"); + returnTable.Columns.Add("Size"); + returnTable.Columns.Add("Value16"); + returnTable.Columns.Add("Value10"); + returnTable.Columns.Add("ASCII"); + returnTable.Columns.Add("Describe"); + + for (int i = 0; i != ExportDirectory.FunctionNamesByte.Count; i++) + { + AddTableRow(returnTable, + ExportDirectory.FunctionNamesByte[i], + "Name", + "_ExportDirectory.Name-Sect.SizeOfRawDataRVA+Sect.PointerToRawData"); + } + + for (int i = 0; i != ExportDirectory.AddressOfNamesList.Count; i++) + { + if (ExportDirectory.AddressOfNamesList[i] is not byte[] a) + continue; + AddTableRow(returnTable, a, "NamesList", ""); + } + + for (int i = 0; i != ExportDirectory.AddressOfFunctionsList.Count; i++) + { + if (ExportDirectory.AddressOfFunctionsList[i] is not byte[] a) + continue; + AddTableRow(returnTable, a, "Functions", ""); + } + + for (int i = 0; i != ExportDirectory.AddressOfNameOrdinalsList.Count; i++) + { + if (ExportDirectory.AddressOfNameOrdinalsList[i] is not byte[] a) + continue; + AddTableRow(returnTable, a, "NameOrdinals", ""); + } + return returnTable; + } + private DataTable? TableImportDirectory() + { + if (ImportDirectory is null) + return null; + + var returnTable = new DataTable("ImportDirectory FileStar{" + ImportDirectory.FileStarIndex.ToString() + "}FileEnd{" + ImportDirectory.FileEndIndex.ToString() + "}"); + returnTable.Columns.Add("Name"); + returnTable.Columns.Add("Size"); + returnTable.Columns.Add("Value16"); + returnTable.Columns.Add("Value10"); + returnTable.Columns.Add("ASCII"); + returnTable.Columns.Add("Describe"); + + for (int i = 0; i != ImportDirectory.ImportList.Count; i++) + { + if (ImportDirectory.ImportList[i] is not ImportDirectory.ImportDate ImportByte) + continue; + + AddTableRow(returnTable, ImportByte.DLLName, "输入DLL名称", "**********"); + AddTableRow(returnTable, ImportByte.OriginalFirstThunk, "OriginalFirstThunk", "这里实际上保存着一个RVA,这个RVA指向一个DWORD数组,这个数组可以叫做输入查询表.每个数组元素,或者叫一个表项,保存着一个指向函数名的RVA或者保存着一个函数的序号."); + AddTableRow(returnTable, ImportByte.TimeDateStamp, "TimeDateStamp", "当这个值为0的时候,表明还没有bind.不为0的话,表示已经bind过了.有关bind的内容后面介绍."); + AddTableRow(returnTable, ImportByte.ForwarderChain, "ForwarderChain", ""); + AddTableRow(returnTable, ImportByte.Name, "Name", "一个RVA,这个RVA指向一个ascii以空字符结束的字符串,这个字符串就是本结构对应的dll文件的名字."); + AddTableRow(returnTable, ImportByte.FirstThunk, "FirstThunk", "一个RVA,这个RVA指向一个DWORD数组,这个数组可以叫输入地址表.如果bind了的话,这个数组的每个元素,就是一个输入函数的入口地址."); + } + + return returnTable; + } + private DataTable? TableImportFunction() + { + if (ImportDirectory is null) + return null; + + var returnTable = new DataTable("ImportFunctionList"); + returnTable.Columns.Add("Name"); + returnTable.Columns.Add("Size"); + returnTable.Columns.Add("Value16"); + returnTable.Columns.Add("Value10"); + returnTable.Columns.Add("ASCII"); + returnTable.Columns.Add("Describe"); + + for (int i = 0; i != ImportDirectory.ImportList.Count; i++) + { + if (ImportDirectory.ImportList[i] is not ImportDirectory.ImportDate ImportByte) + continue; + + AddTableRow(returnTable, ImportByte.DLLName, "DLL-Name", "**********"); + + for (int z = 0; z != ImportByte.DLLFunctionList.Count; z++) + { + if (ImportByte.DLLFunctionList[z] is not ImportDirectory.ImportDate.FunctionList Function) + continue; + + AddTableRow(returnTable, Function.FunctionName, "FunctionName", ""); + AddTableRow(returnTable, Function.FunctionHead, "FunctionHead", ""); + AddTableRow(returnTable, Function.OriginalFirst, "OriginalFirstThunk", ""); + } + } + return returnTable; + } + private DataTable? TableResourceDirectory() + { + if (ResourceDirectory is null) + return null; + var returnTable = new DataTable("ResourceDirectory FileStar{" + ResourceDirectory.FileStarIndex.ToString() + "}FileEnd{" + ResourceDirectory.FileEndIndex.ToString() + "}"); + returnTable.Columns.Add("GUID"); + returnTable.Columns.Add("Text"); + returnTable.Columns.Add("ParentID"); + AddResourceDirectoryRow(returnTable, ResourceDirectory, ""); + return returnTable; + } + private void AddResourceDirectoryRow(DataTable myTable, ResourceDirectory Node, string parentID) + { + string Name = ""; + if (Node.Name is not null) + Name = GetString(Node.Name, "UNICODE"); + + for (int i = 0; i != Node.EntryList.Count; i++) + { + if (Node.EntryList[i] is not ResourceDirectory.DirectoryEntry Entry) + continue; + + long ID = GetLong(Entry.Name); + + string GUID = Guid.NewGuid().ToString(); + + string IDNAME = "ID{" + ID + "}"; + if (Name.Length != 0) + IDNAME += "Name{" + Name + "}"; + + if (parentID.Length == 0) + { + IDNAME += ID switch + { + 1 => "Type{Cursor}", + 2 => "Type{Bitmap}", + 3 => "Type{Icon}", + 4 => "Type{Cursor}", + 5 => "Type{Menu}", + 6 => "Type{Dialog}", + 7 => "Type{String Table}", + 8 => "Type{Font Directory}", + 9 => "Type{Font}", + 10 => "Type{Accelerators}", + 11 => "Type{Unformatted}", + 12 => "Type{Message Table}", + 13 => "Type{Group Cursor}", + 14 => "Type{Group Icon}", + 15 => "Type{Information}", + 16 => "Type{Version}", + _ => "Type{未定义}", + }; + } + + myTable.Rows.Add(new string[] { GUID, IDNAME, parentID }); + + for (int z = 0; z != Entry.DataEntryList.Count; z++) + { + if (Entry.DataEntryList[z] is not ResourceDirectory.DirectoryEntry.DataEntry Data) + continue; + + string Text = "Address{" + GetString(Data.ResourRVA) + "} Size{" + GetString(Data.ResourSize) + "} FileBegin{" + Data.FileStarIndex.ToString() + "-" + Data.FileEndIndex.ToString() + "}"; + + myTable.Rows.Add(new string[] { Guid.NewGuid().ToString(), Text, GUID }); + } + + for (int z = 0; z != Entry.NodeDirectoryList.Count; z++) + { + if (Entry.NodeDirectoryList[z] is not ResourceDirectory a) + continue; + AddResourceDirectoryRow(myTable, a, GUID); + } + } + } + #endregion +} + +#region 类 +/// +/// DOS文件都MS开始 +/// +public class DosHeader // IMAGE_DOS_HEADER +{ + public byte[] e_magic = new byte[2]; // 魔术数字 + public byte[] e_cblp = new byte[2]; // 文件最后页的字节数 + public byte[] e_cp = new byte[2]; // 文件页数 + public byte[] e_crlc = new byte[2]; // 重定义元素个数 + public byte[] e_cparhdr = new byte[2]; // 头部尺寸,以段落为单位 + public byte[] e_minalloc = new byte[2]; // 所需的最小附加段 + public byte[] e_maxalloc = new byte[2]; // 所需的最大附加段 + public byte[] e_ss = new byte[2]; // 初始的SS值(相对偏移量) + public byte[] e_sp = new byte[2]; // 初始的SP值 + public byte[] e_csum = new byte[2]; // 校验和 + public byte[] e_ip = new byte[2]; // 初始的IP值 + public byte[] e_cs = new byte[2]; // 初始的CS值(相对偏移量) + public byte[] e_rva = new byte[2]; + public byte[] e_fg = new byte[2]; + public byte[] e_bl1 = new byte[8]; + public byte[] e_oemid = new byte[2]; + public byte[] e_oeminfo = new byte[2]; + public byte[] e_bl2 = new byte[20]; + public byte[] e_PESTAR = new byte[2]; // PE开始 +自己的位置........重点 + + public long FileStarIndex = 0; + public long FileEndIndex = 0; +} + +/// +/// DOS程序 提示 +/// +public class DosStub +{ + public byte[] DosStubData; + public DosStub(long Size) + { + DosStubData = new byte[Size]; + } + public long FileStarIndex = 0; + public long FileEndIndex = 0; +} + +/// +/// PE文件头 +/// +public class PEHeader // IMAGE_FILE_HEADER +{ + public byte[] Header = new byte[4];// PE文件标记 + public byte[] Machine = new byte[2];// 该文件运行所要求的CPU.对于Intel平台,该值是IMAGE_FILE_MACHINE_I386 (14Ch).我们尝试了LUEVELSMEYER的pe.txt声明的14Dh和14Eh,但Windows不能正确执行.看起来,除了禁止程序执行之外,本域对我们来说用处不大. + public byte[] NumberOfSections = new byte[2];// 文件的节数目.如果我们要在文件中增加或删除一个节,就需要修改这个值. + public byte[] TimeDateStamp = new byte[4];// 文件创建日期和时间.我们不感兴趣. + public byte[] PointerToSymbolTable = new byte[4];// 用于调试. + public byte[] NumberOfSymbols = new byte[4];// 用于调试. + public byte[] SizeOfOptionalHeader = new byte[2];// 指示紧随本结构之后的 OptionalHeader 结构大小,必须为有效值. IMAGE_OPTIONAL_HEADER32 结构大小 + public byte[] Characteristics = new byte[2];// 关于文件信息的标记,比如文件是exe还是dll. + + public long FileStarIndex = 0; + public long FileEndIndex = 0; +} + +/// +/// PE头扩展 +/// +// https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_optional_header32 // IMAGE_OPTIONAL_HEADER32 +// https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_optional_header64 // IMAGE_OPTIONAL_HEADER64 +public class OptionalHeader +{ + public byte[] Magic = new byte[2]; // Magic 010B=普通可以执行,0107=ROM映像 + public byte[] MajorLinkerVersion = new byte[1]; // 主版本号 + public byte[] MinorLinkerVersion = new byte[1]; // 副版本号 + public byte[] SizeOfCode = new byte[4]; // 代码段大小 + public byte[] SizeOfInitializedData = new byte[4]; // 已初始化数据大小 + public byte[] SizeOfUninitializedData = new byte[4]; // 未初始化数据大小 + public byte[] AddressOfEntryPoint = new byte[4]; // 执行将从这里开始(RVA).........入口点指向,dll填充0 + public byte[] BaseOfCode = new byte[4]; // 代码基址(RVA) + + public byte[] BaseOfData = new byte[4]; // 映象文件基址 + + public byte[] ImageBase = new byte[4]; // 数据基址(RVA) + + public byte[] SectionAlignment = new byte[4]; // 区段列队..... + public byte[] FileAlignment = new byte[4]; // 文件列队 + + public byte[] MajorOperatingSystemVersion = new byte[2]; // 操作系统主版本号 + public byte[] MinorOperatingSystemVersion = new byte[2]; // 操作系统副版本号 + public byte[] MajorImageVersion = new byte[2]; // 映象文件主版本号 + public byte[] MinorImageVersion = new byte[2]; // 映象文件副版本号 + public byte[] MajorSubsystemVersion = new byte[2]; // 子操作系统主版本号 + public byte[] MinorSubsystemVersion = new byte[2]; // 子操作系统副版本号 + public byte[] Win32VersionValue = new byte[4]; // Win32版本值 + public byte[] SizeOfImage = new byte[4]; // 映象文件大小 + public byte[] SizeOfHeards = new byte[4]; // 标志头大小 + public byte[] CheckSum = new byte[4]; // 文件效验 + public byte[] Subsystem = new byte[2]; // 子系统(映象文件)1本地 2WINDOWS-GUI 3WINDOWS-CUI 4 POSIX-CUI + public byte[] DLLCharacteristics = new byte[2]; // DLL标记 + + public byte[] SizeOfStackReserve = new byte[4]; // 保留栈的大小 + public byte[] SizeOfStackCommit = new byte[4]; // 初始时指定栈大小 + public byte[] SizeOfHeapReserve = new byte[4]; // 保留堆的大小 + public byte[] SizeOfHeapCommit = new byte[4]; // 初始时指定堆大小 + public byte[] LoaderFlags = new byte[4]; // 加载器标志 + public byte[] NumberOfRvaAndSizes = new byte[4]; // 数据目录数 + + public long FileStarIndex = 0; + public long FileEndIndex = 0; + + public OptionalHeader(bool is32) + { + if (!is32) + { + // X64没有了,但是为了代码保留修改幅度不大,所以置0 + BaseOfData = new byte[0];// x64必须置于0 + // x64长度增加的 + int ulonglong = 8; + ImageBase = new byte[ulonglong]; // 数据基址(RVA) + SizeOfStackReserve = new byte[ulonglong]; // 保留栈的大小 + SizeOfStackCommit = new byte[ulonglong]; // 初始时指定栈大小 + SizeOfHeapReserve = new byte[ulonglong]; // 保留堆的大小 + SizeOfHeapCommit = new byte[ulonglong]; // 初始时指定堆大小 + } + } +} + +/// +/// 目录结构 +/// +public class OptionalDirAttrib +{ + public ArrayList DirByte = new(); + public class DirAttrib + { + public byte[] DirRva = new byte[4]; // 地址 + public byte[] DirSize = new byte[4]; // 大小 + } + public long FileStarIndex = 0; + public long FileEndIndex = 0; +} + +/// +/// 节表 +/// +public class SectionTable +{ + public ArrayList Section = new(); + public class SectionData + { + public byte[] SectName = new byte[8]; // 名字 + public byte[] VirtualAddress = new byte[4]; // 虚拟内存地址 + public byte[] SizeOfRawDataRVA = new byte[4]; // RVA偏移 + public byte[] SizeOfRawDataSize = new byte[4]; // RVA大小 + public byte[] PointerToRawData = new byte[4]; // 指向RAW数据 + public byte[] PointerToRelocations = new byte[4]; // 指向定位号 + public byte[] PointerToLinenumbers = new byte[4]; // 指向行数 + public byte[] NumberOfRelocations = new byte[2]; // 定位号 + public byte[] NumberOfLinenumbers = new byte[2]; // 行数号 + public byte[] Characteristics = new byte[4]; // 区段标记 + } + + public long FileStarIndex = 0; + public long FileEndIndex = 0; +} + +/// +/// 输出表 +/// +public class ExportDirectory +{ + public byte[] Characteristics = new byte[4]; // 一个保留字段,目前为止值为0. + public byte[] TimeDateStamp = new byte[4]; // 产生的时间 + public byte[] MajorVersion = new byte[2]; // 主版本号 + public byte[] MinorVersion = new byte[2]; // 副版本号 + public byte[] Name = new byte[4]; // 一个RVA,指向一个dll的名称的ascii字符串 + public byte[] Base = new byte[4]; // 输出函数的起始序号.一般为1 + public byte[] NumberOfFunctions = new byte[4]; // 输出函数入口地址的数组中的元素个数 + public byte[] NumberOfNames = new byte[4]; // 输出函数名的指针的数组中的元素个数,也是输出函数名对应的序号的数组中的元素个数 + public byte[] AddressOfFunctions = new byte[4]; // 一个RVA,指向输出函数入口地址的数组 + public byte[] AddressOfNames = new byte[4]; // 一个RVA,指向输出函数名的指针的数组 + public byte[] AddressOfNameOrdinals = new byte[4]; // 一个RVA,指向输出函数名对应的序号的数组 + + public ArrayList AddressOfFunctionsList = new(); + public ArrayList AddressOfNamesList = new(); + public ArrayList AddressOfNameOrdinalsList = new(); + /// + /// 函数指针名称集合 + /// + public List FunctionNamesByte = new(); + public long FileStarIndex = 0; + public long FileEndIndex = 0; + + /// + /// 获取函数名 + /// + public HashSet FunctionNames() + { + HashSet names = new(); + for (int i = 0; i < FunctionNamesByte.Count; i++) + names.Add(Encoding.Default.GetString(FunctionNamesByte[i])); + return names; + } +} + + +/// +/// 输入表 +/// +public class ImportDirectory +{ + public ArrayList ImportList = new(); + + public class ImportDate + { + public byte[] OriginalFirstThunk = new byte[4]; // 这里实际上保存着一个RVA,这个RVA指向一个DWORD数组,这个数组可以叫做输入查询表.每个数组元素,或者叫一个表项,保存着一个指向函数名的RVA或者保存着一个函数的序号. + public byte[] TimeDateStamp = new byte[4]; // 当这个值为0的时候,表明还没有bind.不为0的话,表示已经bind过了.有关bind的内容后面介绍. + public byte[] ForwarderChain = new byte[4]; + public byte[] Name = new byte[4]; // 一个RVA,这个RVA指向一个ascii以空字符结束的字符串,这个字符串就是本结构对应的dll文件的名字. + public byte[] FirstThunk = new byte[4]; // 一个RVA,这个RVA指向一个DWORD数组,这个数组可以叫输入地址表.如果bind了的话,这个数组的每个元素,就是一个输入函数的入口地址. + + public byte[]? DLLName; // DLL名称 + public ArrayList DLLFunctionList = new(); + public class FunctionList + { + public byte[] OriginalFirst = new byte[4]; + public byte[]? FunctionName; + public byte[] FunctionHead = new byte[2]; + } + } + public long FileStarIndex = 0; + public long FileEndIndex = 0; +} + +/// +/// 资源表 +/// +public class ResourceDirectory +{ + public byte[] Characteristics = new byte[4]; + public byte[] TimeDateStamp = new byte[4]; + public byte[] MajorVersion = new byte[2]; + public byte[] MinorVersion = new byte[2]; + public byte[] NumberOfNamedEntries = new byte[2]; + public byte[] NumberOfIdEntries = new byte[2]; + public byte[]? Name; + public ArrayList EntryList = new(); + + public class DirectoryEntry + { + public byte[] Name = new byte[4]; + public byte[] Id = new byte[4]; + public ArrayList DataEntryList = new(); + public ArrayList NodeDirectoryList = new(); + + public class DataEntry + { + public byte[] ResourRVA = new byte[4]; + public byte[] ResourSize = new byte[4]; + public byte[] ResourTest = new byte[4]; + public byte[] ResourWen = new byte[4]; + + public long FileStarIndex = 0; + public long FileEndIndex = 0; + } + } + + public long FileStarIndex = 0; + public long FileEndIndex = 0; +} +#endregion \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/Runtime/RedrawEx.cs b/src/CAD/IFox.CAD.Shared/Runtime/RedrawEx.cs new file mode 100644 index 0000000000000000000000000000000000000000..3799eed1ae392f7b92626ee4a03f019a9bbe00d8 --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/Runtime/RedrawEx.cs @@ -0,0 +1,191 @@ +namespace IFoxCAD.Cad; + +[Flags] +public enum BrightEntity : int +{ + /// + /// 块更新 + /// + RecordGraphicsModified = 1, + /// + /// 标注更新 + /// + RecomputeDimensionBlock = 2, + /// + /// 重画 + /// + Draw = 4, + /// + /// 亮显 + /// + Highlight = 8, + /// + /// 亮显取消 + /// + Unhighlight = 16, + /// + /// 显示图元 + /// + VisibleTrue = 32, + /// + /// 隐藏图元 + /// + VisibleFalse = 64, + /// + /// 平移更新,可以令ctrl+z撤回时候保证刷新 + /// + MoveZero = 128, +} + +[Flags] +public enum BrightEditor : int +{ + /// + /// 刷新屏幕,图元不生成(例如块还是旧的显示) + /// + UpdateScreen = 1, + /// + /// 刷新全图 + /// + Regen = 2, + /// + /// 清空选择集 + /// + SelectionClean = 4, + /// + /// 视口外 + /// + ViewportsFrom = 8, + /// + /// 视口内 + /// + ViewportsIn = 16, +} + +public static class RedrawEx +{ + /// + /// 刷新屏幕 + /// + /// 编辑器 + /// 图元,调用时候图元必须提权 + public static void Redraw(this Editor ed, Entity? ent = null) + { + // 刷新图元 + ent?.Redraw(BrightEntity.Draw | + BrightEntity.RecordGraphicsModified | + BrightEntity.RecomputeDimensionBlock | + BrightEntity.MoveZero); + // 刷新 + ed.Redraw(BrightEditor.UpdateScreen); + + /* + * 我发现命令加 CommandFlags.Redraw 就不需要以下处理了: + * 数据库事务和文档事务不一样,文档事务有刷新函数. + * var doc = Acap.DocumentManager.MdiActiveDocument; + * var ed = doc.Editor; + * var tm = doc.TransactionManager; + * tm.QueueForGraphicsFlush();// 如果在最外层事务结束之前需要更新图形,此句把目前为止所做的更改放入 刷新队列 + * tm.FlushGraphics(); // 将当前 刷新队列 的图形提交到显示器 + * ed.UpdateScreen(); // 仅刷新屏幕,图元不生成(例如块还是旧的显示) + */ + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + var tm = doc.TransactionManager; + tm.QueueForGraphicsFlush(); + tm.FlushGraphics(); + + // acad2014及以上要加,立即处理队列上面的消息 + System.Windows.Forms.Application.DoEvents(); + } + + /// + /// 刷新屏幕 + /// + /// 编辑器 + /// 更新的方式 + public static void Redraw(this Editor ed, BrightEditor bright) + { + if ((bright & BrightEditor.UpdateScreen) == BrightEditor.UpdateScreen) + { + // 两个函数底层差不多 + // Acap.UpdateScreen(); + ed.UpdateScreen(); + } + + if ((bright & BrightEditor.Regen) == BrightEditor.Regen) + ed.Regen(); + + if ((bright & BrightEditor.SelectionClean) == BrightEditor.SelectionClean) + ed.SetImpliedSelection(new ObjectId[0]); + + if ((bright & BrightEditor.ViewportsFrom) == BrightEditor.ViewportsFrom) + ed.UpdateTiledViewportsFromDatabase(); // 更新视口外 + + if ((bright & BrightEditor.ViewportsIn) == BrightEditor.ViewportsIn) + ed.UpdateTiledViewportsInDatabase(); // 更新视口内 + } + + /// + /// 更改图元显示 + /// + /// 图元,调用时候图元必须提权 + /// 更新的方式 + public static void Redraw(this Entity ent, BrightEntity bright) + { + // 调用时候图元必须提权,参数true表示关闭图元后进行UpData,实现局部刷新块. + if ((bright & BrightEntity.RecordGraphicsModified) == BrightEntity.RecordGraphicsModified) + ent.RecordGraphicsModified(true); + + if ((bright & BrightEntity.RecomputeDimensionBlock) == BrightEntity.RecomputeDimensionBlock) + if (ent is Dimension dim) + dim.RecomputeDimensionBlock(true); + + if ((bright & BrightEntity.Draw) == BrightEntity.Draw) + ent.Draw(); + + if ((bright & BrightEntity.Highlight) == BrightEntity.Highlight) + ent.Highlight(); + + if ((bright & BrightEntity.Unhighlight) == BrightEntity.Unhighlight) + ent.Unhighlight(); + + if ((bright & BrightEntity.VisibleTrue) == BrightEntity.VisibleTrue) + ent.Visible = true; + + if ((bright & BrightEntity.VisibleFalse) == BrightEntity.VisibleFalse) + ent.Visible = false; + + // 戴耀辉: + // 删除块内图元的时候需要刷新块, + // 用 RecordGraphicsModified 显示是没有问题, + // 但是 ctrl+z 撤销会有显示问题, + // 所以平移0可以在撤回数据库的时候刷新指定图元 + if ((bright & BrightEntity.MoveZero) == BrightEntity.MoveZero) + ent.Move(Point3d.Origin, Point3d.Origin); + } + + + #region 实体刷新 + /// + /// 刷新实体显示 + /// + /// 实体对象 + [Obsolete("此处已经被RedrawEx代替")] + public static void Flush(this Entity entity, DBTrans? trans = null) + { + trans ??= DBTrans.Top; + entity.RecordGraphicsModified(true); + trans.Transaction.TransactionManager.QueueForGraphicsFlush(); + trans.Document?.TransactionManager.FlushGraphics(); + } + + /// + /// 刷新实体显示 + /// + /// 实体id + [Obsolete("此处已经被RedrawEx代替")] + public static void Flush(this ObjectId id) + => Flush(DBTrans.Top.GetObject(id)!); + #endregion +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/Runtime/SymbolTable.cs b/src/CAD/IFox.CAD.Shared/Runtime/SymbolTable.cs similarity index 50% rename from src/IFoxCAD.Cad/Runtime/SymbolTable.cs rename to src/CAD/IFox.CAD.Shared/Runtime/SymbolTable.cs index a6d005bed2cb75faa10966f452010c6c7c6f2872..22fadac920464c96085f591d54335ebfcc6f70f4 100644 --- a/src/IFoxCAD.Cad/Runtime/SymbolTable.cs +++ b/src/CAD/IFox.CAD.Shared/Runtime/SymbolTable.cs @@ -1,3 +1,5 @@ +//using static System.Windows.Forms.AxHost; + namespace IFoxCAD.Cad; public class SymbolTable : IEnumerable @@ -29,10 +31,23 @@ public class SymbolTable : IEnumerable /// /// 事务管理器 /// 符号表id - internal SymbolTable(DBTrans tr, ObjectId tableId) + /// 默认行为:例如打开隐藏图层 + internal SymbolTable(DBTrans tr, ObjectId tableId, bool defaultBehavior = true) { DTrans = tr; - CurrentSymbolTable = DTrans.GetObject(tableId); + Database = tr.Database; + CurrentSymbolTable = DTrans.GetObject(tableId)!; + + if (!defaultBehavior) + return; + + if (CurrentSymbolTable is LayerTable layer) + { + // 层表包含隐藏的,全部显示出来 + layer = layer.IncludingHidden; + if (layer is TTable tt) + CurrentSymbolTable = tt; + } } #endregion @@ -48,9 +63,7 @@ public ObjectId this[string key] get { if (Has(key)) - { return CurrentSymbolTable[key]; - } return ObjectId.Null; } } @@ -99,20 +112,18 @@ private ObjectId Add(TRecord record) /// 符号表记录名 /// 符号表记录处理函数的无返回值委托 /// 对象id - public ObjectId Add(string name, Action action = null) + public ObjectId Add(string name, Action? action = null) { ObjectId id = this[name]; if (id.IsNull) { - TRecord record = new() + var record = new TRecord() { Name = name }; id = Add(record); using (record.ForWrite()) - { action?.Invoke(record); - } } return id; } @@ -126,76 +137,62 @@ public ObjectId Add(string name, Action action = null) private static void Remove(TRecord record) { using (record.ForWrite()) - { record.Erase(); - } } + /// /// 删除符号表记录 /// /// 符号表记录名 public void Remove(string name) { - TRecord record = GetRecord(name); - if (record != null) - { + var record = GetRecord(name); + if (record is not null) Remove(record); - } - } + /// /// 删除符号表记录 /// /// 符号表记录对象id public void Remove(ObjectId id) { - TRecord record = GetRecord(id); - if (record != null) - { + var record = GetRecord(id); + if (record is not null) Remove(record); - } } - - #endregion #region 修改符号表记录 /// /// 修改符号表 /// - /// 符号表记录 - /// 修改委托 - private static void Change(TRecord record, Action action) - { - using (record.ForWrite()) - { - action?.Invoke(record); - } - } - /// - /// 修改符号表 - /// /// 符号表记录名 /// 修改委托 + [System.Diagnostics.DebuggerStepThrough] public void Change(string name, Action action) { var record = GetRecord(name); - if (record != null) + if (record is not null) { - Change(record, action); + using (record.ForWrite()) + action.Invoke(record); } } + /// /// 修改符号表 /// /// 符号表记录id /// 修改委托 + [System.Diagnostics.DebuggerStepThrough] public void Change(ObjectId id, Action action) { var record = GetRecord(id); - if (record != null) + if (record is not null) { - Change(record, action); + using (record.ForWrite()) + action.Invoke(record); } } #endregion @@ -205,17 +202,35 @@ public void Change(ObjectId id, Action action) /// 获取符号表记录 /// /// 符号表记录的id - /// 打开模式,默认为只读 + /// 打开模式 + /// 是否打开已删除对象,默认为不打开 + /// 是否打开锁定图层对象,默认为不打开 /// 符号表记录 - public TRecord GetRecord(ObjectId id, OpenMode openMode = OpenMode.ForRead) => id.IsNull ? null : DTrans.GetObject(id, openMode); + [System.Diagnostics.DebuggerStepThrough] + public TRecord? GetRecord(ObjectId id, + OpenMode openMode = OpenMode.ForRead, + bool openErased = false, + bool openLockedLayer = false) + { + return DTrans.GetObject(id, openMode, openErased, openLockedLayer); + } /// /// 获取符号表记录 /// /// 符号表记录名 - /// 打开模式,默认为只读 + /// 打开模式 + /// 是否打开已删除对象,默认为不打开 + /// 是否打开锁定图层对象,默认为不打开 /// 符号表记录 - public TRecord GetRecord(string name, OpenMode openMode = OpenMode.ForRead) => GetRecord(this[name], openMode); + [System.Diagnostics.DebuggerStepThrough] + public TRecord? GetRecord(string name, + OpenMode openMode = OpenMode.ForRead, + bool openErased = false, + bool openLockedLayer = false) + { + return GetRecord(this[name], openMode, openErased, openLockedLayer); + } /// /// 获取符号表记录 @@ -223,14 +238,20 @@ public void Change(ObjectId id, Action action) /// 符号表记录集合 public IEnumerable GetRecords() { - return this.Select(id => GetRecord(id)); + foreach (var item in this) + { + var record = GetRecord(item); + if (record is not null) + yield return record; + } } /// /// 获取符号表记录的名字集合 /// /// 记录的名字集合 - public IEnumerable GetRecordNames() => this.Select(id => GetRecord(id).Name); + public IEnumerable GetRecordNames() => GetRecords().Select(record => record.Name); + /// /// 获取符合过滤条件的符号表记录名字集合 /// @@ -238,13 +259,11 @@ public IEnumerable GetRecords() /// 记录的名字集合 public IEnumerable GetRecordNames(Func filter) { - foreach (var id in this) + foreach (var item in this) { - var record = GetRecord(id); - if (filter.Invoke(record)) - { + var record = GetRecord(item); + if (record is not null && filter.Invoke(record)) yield return record.Name; - } } } @@ -257,22 +276,24 @@ public IEnumerable GetRecordNames(Func filter) /// 对象id public ObjectId GetRecordFrom(SymbolTable table, string name, bool over) { - if (table is null) - { - throw new ArgumentNullException(nameof(table)); - } + //if (table is null) + // throw new ArgumentNullException(nameof(table), "对象为null"); + table.NotNull(nameof(table)); ObjectId rid = this[name]; bool has = rid != ObjectId.Null; if ((has && over) || !has) { ObjectId id = table[name]; - using IdMapping idm = new(); - using (ObjectIdCollection ids = new() { id }) - { - table.Database.WblockCloneObjects(ids, CurrentSymbolTable.Id, idm, DuplicateRecordCloning.Replace, false); - } - rid = idm[id].Value; + using IdMapping map = new(); + using ObjectIdCollection ids = new() { id }; + table.Database.WblockCloneObjects( + ids, + CurrentSymbolTable.Id, + map, + DuplicateRecordCloning.Replace, + false); + rid = map[id].Value; } return rid; } @@ -285,27 +306,104 @@ public ObjectId GetRecordFrom(SymbolTable table, string name, b /// 符号表记录名 /// 是否覆盖, 为覆盖, 为不覆盖 /// 对象id - internal ObjectId GetRecordFrom(Func> tableSelector, string fileName, string name, bool over) + internal ObjectId GetRecordFrom(Func> tableSelector, + string fileName, + string name, + bool over) { - using var tr = new DBTrans(fileName); + using DBTrans tr = new(fileName); return GetRecordFrom(tableSelector(tr), name, over); } #endregion - #region IEnumerable 成员 + #region 遍历 +#line hidden // 调试的时候跳过它 + /// + /// 遍历符号表,执行委托 + /// + /// 要运行的委托 + /// 打开模式,默认为只读 + /// 检查id是否删除,默认true + /// 是否打开已删除对象,默认为不打开 + /// 是否打开锁定图层对象,默认为不打开 + public void ForEach(Action task, + OpenMode openMode = OpenMode.ForRead, + bool checkIdOk = true, + bool openErased = false, + bool openLockedLayer = false) + { + ForEach((a, _, _) => { + task.Invoke(a);//由于此处是委托,所以 DebuggerStepThrough 特性会进入,改用预处理方式避免 + }, openMode, checkIdOk, openErased, openLockedLayer); + } - public IEnumerator GetEnumerator() + /// + /// 遍历符号表,执行委托(允许循环中断) + /// + /// 要执行的委托 + /// 打开模式,默认为只读 + /// 检查id是否删除,默认true + /// 是否打开已删除对象,默认为不打开 + /// 是否打开锁定图层对象,默认为不打开 + public void ForEach(Action task, + OpenMode openMode = OpenMode.ForRead, + bool checkIdOk = true, + bool openErased = false, + bool openLockedLayer = false) { + ForEach((a, b, _) => { + task.Invoke(a, b); + }, openMode, checkIdOk, openErased, openLockedLayer); + } - foreach (var id in CurrentSymbolTable) + /// + /// 遍历符号表,执行委托(允许循环中断,输出索引值) + /// + /// 要执行的委托 + /// 打开模式,默认为只读 + /// 检查id是否删除,默认true + /// 是否打开已删除对象,默认为不打开 + /// 是否打开锁定图层对象,默认为不打开 + [System.Diagnostics.DebuggerStepThrough] + public void ForEach(Action task, + OpenMode openMode = OpenMode.ForRead, + bool checkIdOk = true, + bool openErased = false, + bool openLockedLayer = false) + { + //if (task == null) + // throw new ArgumentNullException(nameof(task)); + task.NotNull(nameof(task)); + LoopState state = new();/*这种方式比Action改Func更友好*/ + int i = 0; + foreach (var id in this) { - yield return id; + if (checkIdOk && !id.IsOk()) + continue; + var record = GetRecord(id, openMode, openErased, openLockedLayer); + if (record is not null) + task.Invoke(record, state, i); + if (!state.IsRun) + break; + i++; } } +#line default + + #endregion + + #region IEnumerable 成员 + [System.Diagnostics.DebuggerStepThrough] + public IEnumerator GetEnumerator() + { + foreach (var id in CurrentSymbolTable) + yield return id; + } + [System.Diagnostics.DebuggerStepThrough] IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion -} +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Shared/Runtime/Utils.cs b/src/CAD/IFox.CAD.Shared/Runtime/Utils.cs new file mode 100644 index 0000000000000000000000000000000000000000..28c47c2fbeb1dc9b11d9ce18694860bcd18dae7e --- /dev/null +++ b/src/CAD/IFox.CAD.Shared/Runtime/Utils.cs @@ -0,0 +1,98 @@ +namespace IFoxCAD.Cad; + +using System; + +public class DBTransHelper +{ + /* + * id = db.GetObjectId(false, handle, 0); + * 参数意义: db.GetObjectId(如果没有找到就创建,句柄号,标记..将来备用) + * 在vs的输出会一直抛出: + * 引发的异常:“Autodesk.AutoCAD.Runtime.Exception”(位于 AcdbMgd.dll 中) + * "eUnknownHandle" + * 这就是为什么慢的原因,所以直接运行就好了!而Debug还是需要用arx的API替代. + */ + + [System.Security.SuppressUnmanagedCodeSecurity] + [DllImport("acdb17.dll", CallingConvention = CallingConvention.ThisCall/*08的调用约定 高版本是__cdecl*/, + EntryPoint = "?getAcDbObjectId@AcDbDatabase@@QAE?AW4ErrorStatus@Acad@@AAVAcDbObjectId@@_NABVAcDbHandle@@K@Z")] + extern static int getAcDbObjectId17x32(IntPtr db, out ObjectId id, [MarshalAs(UnmanagedType.U1)] bool createnew, ref Handle h, uint reserved); + + [System.Security.SuppressUnmanagedCodeSecurity] + [DllImport("acdb17.dll", CallingConvention = CallingConvention.ThisCall/*08的调用约定 高版本是__cdecl*/, + EntryPoint = "?getAcDbObjectId@AcDbDatabase@@QEAA?AW4ErrorStatus@Acad@@AEAVAcDbObjectId@@_NAEBVAcDbHandle@@K@Z")] + extern static int getAcDbObjectId17x64(IntPtr db, out ObjectId id, [MarshalAs(UnmanagedType.U1)] bool createnew, ref Handle h, uint reserved); + + [System.Security.SuppressUnmanagedCodeSecurity] + [DllImport("acdb18.dll", CallingConvention = CallingConvention.ThisCall/*08的调用约定 高版本是__cdecl*/, + EntryPoint = "?getAcDbObjectId@AcDbDatabase@@QAE?AW4ErrorStatus@Acad@@AAVAcDbObjectId@@_NABVAcDbHandle@@K@Z")] + extern static int getAcDbObjectId18x32(IntPtr db, out ObjectId id, [MarshalAs(UnmanagedType.U1)] bool createnew, ref Handle h, uint reserved); + + [System.Security.SuppressUnmanagedCodeSecurity] + [DllImport("acdb18.dll", CallingConvention = CallingConvention.ThisCall/*08的调用约定 高版本是__cdecl*/, + EntryPoint = "?getAcDbObjectId@AcDbDatabase@@QEAA?AW4ErrorStatus@Acad@@AEAVAcDbObjectId@@_NAEBVAcDbHandle@@K@Z")] + extern static int getAcDbObjectId18x64(IntPtr db, out ObjectId id, [MarshalAs(UnmanagedType.U1)] bool createnew, ref Handle h, uint reserved); + + /// + /// 句柄转id,NET35(08~12)专用的 + /// + /// 数据库 + /// 句柄 + /// 返回的id + /// 不存在则创建 + /// 保留,用于未来 + /// 成功0,其他值都是错误.可以强转ErrorStatus + static int GetAcDbObjectId(IntPtr db, Handle handle, out ObjectId id, bool createIfNotFound = false, uint reserved = 0) + { + id = ObjectId.Null; + switch (Acap.Version.Major) + { + case 17: + { + if (IntPtr.Size == 4) + return getAcDbObjectId17x32(db, out id, createIfNotFound, ref handle, reserved); + else + return getAcDbObjectId17x64(db, out id, createIfNotFound, ref handle, reserved); + } + case 18: + { + if (IntPtr.Size == 4) + return getAcDbObjectId18x32(db, out id, createIfNotFound, ref handle, reserved); + else + return getAcDbObjectId18x64(db, out id, createIfNotFound, ref handle, reserved); + } + } + return -1; + } + + /// + /// 句柄转id + /// + /// 数据库 + /// 句柄 + /// id + public static ObjectId TryGetObjectId(Database db, Handle handle) + { +#if !NET35 + // 高版本直接利用 + var es = db.TryGetObjectId(handle, out ObjectId id); + // if (!es) +#else + var es = GetAcDbObjectId(db.UnmanagedObject, handle, out ObjectId id); + // if (ErrorStatus.OK != (ErrorStatus)es) +#endif + return id; + } + + // public static int GetCadFileVersion(string filename) + // { + // var bytes = File.ReadAllBytes(filename); + // var headstr = Encoding.Default.GetString(bytes)[0..6]; + // if (!headstr.StartsWith("AC")) return 0; + // var vernum = int.Parse(headstr.Replace("AC", "")); + // var a = Enum.Parse(typeof(DwgVersion), "AC1800"); + // Enum.TryParse() + // return vernum + 986; + + // } +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/SelectionFilter/OpComp.cs b/src/CAD/IFox.CAD.Shared/SelectionFilter/OpComp.cs similarity index 99% rename from src/IFoxCAD.Cad/SelectionFilter/OpComp.cs rename to src/CAD/IFox.CAD.Shared/SelectionFilter/OpComp.cs index 9727b725ee72705799e43ae0822360681440b9d4..040090a873c022825fe151286663f908a8627abe 100644 --- a/src/IFoxCAD.Cad/SelectionFilter/OpComp.cs +++ b/src/CAD/IFox.CAD.Shared/SelectionFilter/OpComp.cs @@ -76,4 +76,4 @@ public override IEnumerable GetValues() yield return new TypedValue(-4, Content); yield return Value; } -} +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/SelectionFilter/OpEqual.cs b/src/CAD/IFox.CAD.Shared/SelectionFilter/OpEqual.cs similarity index 100% rename from src/IFoxCAD.Cad/SelectionFilter/OpEqual.cs rename to src/CAD/IFox.CAD.Shared/SelectionFilter/OpEqual.cs diff --git a/src/IFoxCAD.Cad/SelectionFilter/OpFilter.cs b/src/CAD/IFox.CAD.Shared/SelectionFilter/OpFilter.cs similarity index 86% rename from src/IFoxCAD.Cad/SelectionFilter/OpFilter.cs rename to src/CAD/IFox.CAD.Shared/SelectionFilter/OpFilter.cs index ec45ac9ee0e19aba640b1b4cf226e7cdcc214e43..48843f4a4e81c7de11313c9a4ff49f36240585b5 100644 --- a/src/IFoxCAD.Cad/SelectionFilter/OpFilter.cs +++ b/src/CAD/IFox.CAD.Shared/SelectionFilter/OpFilter.cs @@ -10,6 +10,11 @@ public abstract class OpFilter /// public abstract string Name { get; } + /// + /// 只读属性,表示这个过滤器取反 + /// + public OpFilter Not => new OpNot(this); + /// /// 获取TypedValue类型的值的迭代器的抽象方法,子类必须重写 /// @@ -26,14 +31,6 @@ public abstract class OpFilter return item.Not; } - /// - /// 只读属性,表示这个过滤器取反 - /// - public OpFilter Not - { - get { return new OpNot(this); } - } - /// /// 过滤器值转换为 TypedValue 类型数组 /// @@ -61,10 +58,10 @@ public static implicit operator SelectionFilter(OpFilter item) /// 字符串 public override string ToString() { - string s = ""; + var sb = new StringBuilder(); foreach (var value in GetValues()) - s += value.ToString(); - return s; + sb.Append(value); + return sb.ToString(); } /// @@ -76,12 +73,12 @@ public override string ToString() /// !(e.Dxf(0) == "line" & e.Dxf(8) == "0") /// | e.Dxf(0) != "circle" & e.Dxf(8) == "2" & e.Dxf(10) >= p); /// /// 例子2: - /// var f2 = OpFilter.Bulid( + /// var f2 = OpFilter.Build( /// e => e.Or( /// !e.And(e.Dxf(0) == "line", e.Dxf(8) == "0"), /// e.And(e.Dxf(0) != "circle", e.Dxf(8) == "2", @@ -90,9 +87,9 @@ public override string ToString() /// /// 构建过滤器的函数委托 /// 过滤器 - public static OpFilter Bulid(Func func) + public static OpFilter Build(Func func) { - return func(new OpFilter.Op()).Filter; + return func(new Op()).Filter!; } #region Operator @@ -105,11 +102,9 @@ public class Op /// /// 过滤器属性 /// - internal OpFilter Filter { get; private set; } + internal OpFilter? Filter { get; private set; } - internal Op() - { - } + internal Op() { } private Op(OpFilter filter) { @@ -121,11 +116,13 @@ private Op(OpFilter filter) /// /// 操作符类型的可变参数 /// Op对象 - public static Op And(params Op[] args) +#pragma warning disable CA1822 // 将成员标记为 static + public Op And(params Op[] args) +#pragma warning restore CA1822 // 将成员标记为 static { var filter = new OpAnd(); foreach (var op in args) - filter.Add(op.Filter); + filter.Add(op.Filter!); return new Op(filter); } @@ -134,11 +131,13 @@ public static Op And(params Op[] args) /// /// 操作符类型的可变参数 /// Op对象 - public static Op Or(params Op[] args) +#pragma warning disable CA1822 // 将成员标记为 static + public Op Or(params Op[] args) +#pragma warning restore CA1822 // 将成员标记为 static { var filter = new OpOr(); foreach (var op in args) - filter.Add(op.Filter); + filter.Add(op.Filter!); return new Op(filter); } @@ -147,7 +146,9 @@ public static Op Or(params Op[] args) /// /// 组码 /// Op对象 - public static Op Dxf(int code) +#pragma warning disable CA1822 // 将成员标记为 static + public Op Dxf(int code) +#pragma warning restore CA1822 // 将成员标记为 static { return new Op(new OpEqual(code)); } @@ -158,7 +159,9 @@ public static Op Dxf(int code) /// 组码 /// 关系运算符的值,比如">,>,=" /// Op对象 - public static Op Dxf(int code, string content) +#pragma warning disable CA1822 // 将成员标记为 static + public Op Dxf(int code, string content) +#pragma warning restore CA1822 // 将成员标记为 static { return new Op(new OpComp(content, code)); } @@ -170,7 +173,7 @@ public static Op Dxf(int code, string content) /// Op对象 public static Op operator !(Op right) { - right.Filter = !right.Filter; + right.Filter = !right.Filter!; return right; } @@ -182,7 +185,7 @@ public static Op Dxf(int code, string content) /// Op对象 public static Op operator ==(Op left, object right) { - var eq = (OpEqual)left.Filter; + var eq = (OpEqual)left.Filter!; eq.SetValue(right); return left; } @@ -195,7 +198,7 @@ public static Op Dxf(int code, string content) /// Op对象 public static Op operator !=(Op left, object right) { - var eq = (OpEqual)left.Filter; + var eq = (OpEqual)left.Filter!; eq.SetValue(right); left.Filter = eq.Not; return left; @@ -203,7 +206,7 @@ public static Op Dxf(int code, string content) private static Op GetCompOp(string content, Op left, object right) { - var eq = (OpEqual)left.Filter; + var eq = (OpEqual)left.Filter!; var comp = new OpComp(content, eq.Value.TypeCode, right); return new Op(comp); } @@ -284,8 +287,8 @@ private static Op GetCompOp(string content, Op left, object right) { var filter = new OpAnd { - left.Filter, - right.Filter + left.Filter!, + right.Filter! }; return new Op(filter); } @@ -300,8 +303,8 @@ private static Op GetCompOp(string content, Op left, object right) { var filter = new OpOr { - left.Filter, - right.Filter + left.Filter!, + right.Filter! }; return new Op(filter); } @@ -314,7 +317,7 @@ private static Op GetCompOp(string content, Op left, object right) /// Op对象 public static Op operator ^(Op left, Op right) { - var filter = new OpXor(left.Filter, right.Filter); + var filter = new OpXor(left.Filter!, right.Filter!); return new Op(filter); } diff --git a/src/IFoxCAD.Cad/SelectionFilter/OpList.cs b/src/CAD/IFox.CAD.Shared/SelectionFilter/OpList.cs similarity index 98% rename from src/IFoxCAD.Cad/SelectionFilter/OpList.cs rename to src/CAD/IFox.CAD.Shared/SelectionFilter/OpList.cs index 3a02513860c4d2e57a1bc2d9cd9de73b947f4c19..5870be92ecccc2559d74ed49971d2628b2b23efa 100644 --- a/src/IFoxCAD.Cad/SelectionFilter/OpList.cs +++ b/src/CAD/IFox.CAD.Shared/SelectionFilter/OpList.cs @@ -96,6 +96,7 @@ public void Add(DxfCode code, object value, string comp) /// 过滤器迭代器 /// /// OpFilter迭代器 + [System.Diagnostics.DebuggerStepThrough] public override IEnumerator GetEnumerator() { foreach (var value in _lst) @@ -163,4 +164,4 @@ public override void Add(OpFilter value) _lst.Add(value); } } -} +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/SelectionFilter/OpLogi.cs b/src/CAD/IFox.CAD.Shared/SelectionFilter/OpLogi.cs similarity index 92% rename from src/IFoxCAD.Cad/SelectionFilter/OpLogi.cs rename to src/CAD/IFox.CAD.Shared/SelectionFilter/OpLogi.cs index 422760860b5c634f558ad8e37738c7d5ea7da15f..bdacb864787797fc5fa97cc16dfac61e10fe78e8 100644 --- a/src/IFoxCAD.Cad/SelectionFilter/OpLogi.cs +++ b/src/CAD/IFox.CAD.Shared/SelectionFilter/OpLogi.cs @@ -25,6 +25,7 @@ public TypedValue Last /// 获取过滤条件 /// /// TypedValue迭代器 + //[System.Diagnostics.DebuggerStepThrough] public override IEnumerable GetValues() { yield return First; @@ -40,8 +41,10 @@ public override IEnumerable GetValues() /// 获取迭代器 /// /// OpFilter迭代器 + [System.Diagnostics.DebuggerStepThrough] public abstract IEnumerator GetEnumerator(); + [System.Diagnostics.DebuggerStepThrough] IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); @@ -76,6 +79,7 @@ public override string Name /// 获取迭代器 /// /// OpFilter迭代器 + [System.Diagnostics.DebuggerStepThrough] public override IEnumerator GetEnumerator() { yield return Value; @@ -120,9 +124,10 @@ public override string Name /// 获取迭代器 /// /// 选择集过滤器类型迭代器 + [System.Diagnostics.DebuggerStepThrough] public override IEnumerator GetEnumerator() { yield return Left; yield return Right; } -} +} \ No newline at end of file diff --git a/src/CAD/IFox.CAD.Source/IFox.CAD.Source.csproj b/src/CAD/IFox.CAD.Source/IFox.CAD.Source.csproj new file mode 100644 index 0000000000000000000000000000000000000000..262b59137da2e2735c93cdd5b13e78ec3d98f3ff --- /dev/null +++ b/src/CAD/IFox.CAD.Source/IFox.CAD.Source.csproj @@ -0,0 +1,49 @@ + + + + + + netstandard1.0 + true + $(AssemblyName) + $(Version) + true + + + + CS8021 + true + false + contentFiles + true + false + false + true + + + + + true + $(ContentTargetFolders)\cs\any\$(PackageId)\ + false + + + + true + + + + true + + + + + + + + + + + + + diff --git a/src/CAD/IFox.CAD.ZCAD/GlobalUsings.cs b/src/CAD/IFox.CAD.ZCAD/GlobalUsings.cs new file mode 100644 index 0000000000000000000000000000000000000000..add17740c5107dfc01d97ff5c9e08dfbabf7c28c --- /dev/null +++ b/src/CAD/IFox.CAD.ZCAD/GlobalUsings.cs @@ -0,0 +1,48 @@ +/// 系统引用 +global using System; +global using System.Collections; +global using System.Collections.Generic; +global using System.IO; +global using System.Linq; +global using System.Text; +global using System.Reflection; +global using System.Text.RegularExpressions; +global using Microsoft.Win32; +global using System.ComponentModel; +global using System.Runtime.InteropServices; +global using System.Collections.Specialized; + +global using Exception = System.Exception; + +global using Registry = Microsoft.Win32.Registry; +global using RegistryKey = Microsoft.Win32.RegistryKey; + +/// cad 引用 +global using ZwSoft.ZwCAD.ApplicationServices; +global using ZwSoft.ZwCAD.EditorInput; +global using ZwSoft.ZwCAD.Colors; +global using ZwSoft.ZwCAD.DatabaseServices; +global using ZwSoft.ZwCAD.Geometry; +global using ZwSoft.ZwCAD.Runtime; +global using Acap = ZwSoft.ZwCAD.ApplicationServices.Application; + +global using ZwSoft.ZwCAD.DatabaseServices.Filters; +global using ZwSoft.ZwCAD; + +// jig命名空间会引起Viewport/Polyline等等重义,最好逐个引入 using ZwSoft.ZwCAD.GraphicsInterface +global using ZwSoft.ZwCAD.GraphicsInterface; +global using WorldDraw = ZwSoft.ZwCAD.GraphicsInterface.WorldDraw; +global using Manager = ZwSoft.ZwCAD.GraphicsSystem.Manager; +global using Group = ZwSoft.ZwCAD.DatabaseServices.Group; +global using Viewport = ZwSoft.ZwCAD.DatabaseServices.Viewport; +global using Polyline = ZwSoft.ZwCAD.DatabaseServices.Polyline; +global using Cad_DwgFiler = ZwSoft.ZwCAD.DatabaseServices.DwgFiler; +global using Cad_DxfFiler = ZwSoft.ZwCAD.DatabaseServices.DxfFiler; +global using Cad_ErrorStatus = ZwSoft.ZwCAD.Runtime.ErrorStatus; + +/// ifoxcad.basal 引用 +global using IFoxCAD.Basal; + +#if !NewtonsoftJson +global using System.Web.Script.Serialization; +#endif \ No newline at end of file diff --git a/src/CAD/IFox.CAD.ZCAD/IFox.CAD.ZCAD.csproj b/src/CAD/IFox.CAD.ZCAD/IFox.CAD.ZCAD.csproj new file mode 100644 index 0000000000000000000000000000000000000000..e99b5ef40c1290871036477eefc774fa73580256 --- /dev/null +++ b/src/CAD/IFox.CAD.ZCAD/IFox.CAD.ZCAD.csproj @@ -0,0 +1,45 @@ + + + + NET48 + + true + true + false + MSB3270 + + + + DEBUG + + + $(Configuration);zcad + + + + runtime + + + + + + True + + + + + + + + + + + + + + + + + + + diff --git a/src/IFoxCAD.Basal/GlobalUsings.cs b/src/IFoxCAD.Basal/GlobalUsings.cs deleted file mode 100644 index bee31716cf35a9e80863bb588692258d31f0b51a..0000000000000000000000000000000000000000 --- a/src/IFoxCAD.Basal/GlobalUsings.cs +++ /dev/null @@ -1,10 +0,0 @@ -/// 系统引用 -global using System; -global using System.Collections; -global using System.Collections.Generic; -global using System.IO; -global using System.Linq; - - - - diff --git a/src/IFoxCAD.Basal/IFoxCAD.Basal.csproj b/src/IFoxCAD.Basal/IFoxCAD.Basal.csproj deleted file mode 100644 index 0e17a78a14695fdc8451bfe5e4f80abfc82594c2..0000000000000000000000000000000000000000 --- a/src/IFoxCAD.Basal/IFoxCAD.Basal.csproj +++ /dev/null @@ -1,32 +0,0 @@ - - - - net45 - true - 0.2.0 - InspireFunction - xsfhlzh;vicwjb - 基于.NET的二次开发基本类库 - InspireFunction - https://gitee.com/inspirefunction/ifoxcad - https://gitee.com/inspirefunction/ifoxcad.git - git - IFoxCAD;C#;NET;Common;Basal - 删除net35支持,升级至net45,增加c#10语法相关内容. - true - true - preview - true - LICENSE - true - - - - - True - - - - - - diff --git a/src/IFoxCAD.Basal/LoopList.cs b/src/IFoxCAD.Basal/LoopList.cs deleted file mode 100644 index 3046b38649aa0cdf067d7553bc9d97ba83408223..0000000000000000000000000000000000000000 --- a/src/IFoxCAD.Basal/LoopList.cs +++ /dev/null @@ -1,509 +0,0 @@ -namespace IFoxCAD.Basal -{ - /// - /// 环链表节点 - /// - /// - public class LoopListNode - { - /// - /// 取值 - /// - public T Value { get; set; } - - /// - /// 上一个节点 - /// - public LoopListNode Previous - { internal set; get; } - - /// - /// 下一个节点 - /// - public LoopListNode Next - { internal set; get; } - - /// - ///环链表序列 - /// - public LoopList List - { internal set; get; } - /// - /// 环链表节点构造函数 - /// - /// 节点值 - public LoopListNode(T value) - { - Value = value; - } - /// - /// 获取当前节点的临近节点 - /// - /// 搜索方向标志,为向前搜索,为向后搜索 - /// - public LoopListNode GetNext(bool forward) - { - return forward ? Next : Previous; - } - } - - /// - /// 环链表 - /// - /// - public class LoopList : - IEnumerable, IFormattable - { - /// - /// 默认构造函数 - /// - public LoopList() - { } - /// - /// 环链表构造函数 - /// - /// 节点迭代器 - public LoopList(IEnumerable values) - { - foreach (T value in values) - Add(value); - } - - /// - /// 节点数 - /// - public int Count - { get; private set; } - - /// - /// 首节点 - /// - public LoopListNode First - { get; private set; } - - /// - /// 尾节点 - /// - public LoopListNode Last - { - get { return First?.Previous; } - } - /// - /// 设置首节点 - /// - /// 节点 - /// - public bool SetFirst(LoopListNode node) - { - if (Contains(node)) - { - First = node; - return true; - } - return false; - } - - /// - /// 交换两个节点的值 - /// - /// 第一个节点 - /// 第二个节点 - public void Swap(LoopListNode node1, LoopListNode node2) - { - T value = node1.Value; - node1.Value = node2.Value; - node2.Value = value; - } - - #region Contains - /// - /// 是否包含节点 - /// - /// - /// - public bool Contains(LoopListNode node) - { - return node != null && node.List == this; - } - /// - /// 是否包含值 - /// - /// - /// - public bool Contains(T value) - { - LoopListNode node = First; - if (node == null) - return false; - - for (int i = 0; i < Count; i++) - { - if (node.Value.Equals(value)) - return true; - node = node.Next; - } - - return false; - } - /// - /// 获取节点 - /// - /// - /// - public LoopListNode GetNode(Func func) - { - LoopListNode node = First; - if (node == null) - return null; - - for (int i = 0; i < Count; i++) - { - if (func(node.Value)) - { - return node; - } - node = node.Next; - } - return null; - } - - #endregion Contains - - #region Add - - /// - /// 在首节点之前插入节点,并设置新节点为首节点 - /// - /// - /// - public LoopListNode AddFirst(T value) - { - LoopListNode node = new(value) - { - List = this - }; - if (Count == 0) - { - First = node; - First.Previous = First.Next = node; - } - else - { - LoopListNode last = Last; - First.Previous = last.Next = node; - node.Next = First; - node.Previous = last; - First = node; - } - Count++; - return First; - } - - /// - /// 在尾节点之后插入节点,并设置新节点为尾节点 - /// - /// - /// - public LoopListNode Add(T value) - { - LoopListNode node = new(value); - node.List = this; - if (Count == 0) - { - First = node; - First.Previous = First.Next = node; - } - else - { - LoopListNode last = First.Previous; - First.Previous = last.Next = node; - node.Next = First; - node.Previous = last; - } - Count++; - return Last; - } - /// - /// 前面增加节点 - /// - /// - /// - /// - public LoopListNode AddBefore(LoopListNode node, T value) - { - if (node == First) - { - return AddFirst(value); - } - else - { - LoopListNode tnode = new(value); - node.Previous.Next = tnode; - tnode.Previous = node.Previous; - node.Previous = tnode; - tnode.Next = node; - Count++; - return tnode; - } - } - /// - /// 后面增加节点 - /// - /// - /// - /// - public LoopListNode AddAfter(LoopListNode node, T value) - { - LoopListNode tnode = new(value); - node.Next.Previous = tnode; - tnode.Next = node.Next; - node.Next = tnode; - tnode.Previous = node; - Count++; - return tnode; - } - - #endregion Add - - #region Remove - - /// - /// 删除首节点 - /// - /// - public bool RemoveFirst() - { - switch (Count) - { - case 0: - return false; - - case 1: - First = null; - break; - - default: - LoopListNode last = Last; - First = First.Next; - First.Previous = last; - last.Next = First; - break; - } - Count--; - return true; - } - - /// - /// 删除尾节点 - /// - /// - public bool RemoveLast() - { - switch (Count) - { - case 0: - return false; - - case 1: - First = null; - break; - - default: - LoopListNode last = Last.Previous; - last.Next = First; - First.Previous = last; - break; - } - Count--; - return true; - } - - /// - /// 删除节点 - /// - /// - /// - public bool Remove(LoopListNode node) - { - if (Contains(node)) - { - if (Count == 1) - { - First = null; - } - else - { - if (node == First) - { - RemoveFirst(); - } - else - { - node.Next.Previous = node.Previous; - node.Previous.Next = node.Next; - } - } - Count--; - return true; - } - return false; - } - - #endregion Remove - - #region LinkTo - - /// - /// 链接两节点,并去除这两个节点间的所有节点 - /// - /// - /// - public void LinkTo(LoopListNode from, LoopListNode to) - { - if (from != to && Contains(from) && Contains(to)) - { - LoopListNode node = from.Next; - bool isFirstChanged = false; - int number = 0; - - while (node != to) - { - if (node == First) - isFirstChanged = true; - - node = node.Next; - number++; - } - - from.Next = to; - to.Previous = from; - - if (number > 0 && isFirstChanged) - First = to; - - Count -= number; - } - } - - /// - /// 链接两节点,并去除这两个节点间的所有节点 - /// - /// - /// - /// - public void LinkTo(LoopListNode from, LoopListNode to, int number) - { - if (from != to && Contains(from) && Contains(to)) - { - from.Next = to; - to.Previous = from; - First = to; - Count -= number; - } - } - - /// - /// 链接两节点,并去除这两个节点间的所有节点 - /// - /// - /// - /// - /// - public void LinkTo(LoopListNode from, LoopListNode to, int number, bool isFirstChanged) - { - if (from != to && Contains(from) && Contains(to)) - { - from.Next = to; - to.Previous = from; - if (isFirstChanged) - First = to; - Count -= number; - } - } - - #endregion LinkTo - - #region IEnumerable 成员 - - /// - /// 获取节点的查询器 - /// - /// - /// - public IEnumerable> GetNodes(LoopListNode from) - { - LoopListNode node = from; - for (int i = 0; i < Count; i++) - { - yield return node; - node = node.Next; - } - } - - /// - /// 获取节点的查询器 - /// - /// - public IEnumerable> GetNodes() - { - LoopListNode node = First; - for (int i = 0; i < Count; i++) - { - yield return node; - node = node.Next; - } - } - - /// - /// 获取节点值的查询器 - /// - /// - public IEnumerator GetEnumerator() - { - LoopListNode node = First; - for (int i = 0; i < Count; i++) - { - yield return node.Value; - node = node.Next; - } - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - #region IEnumerable 成员 - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - #endregion IEnumerable 成员 - - #endregion IEnumerable 成员 - - #region IFormattable 成员 - /// - /// 转换为字符串 - /// - /// - public override string ToString() - { - string s = "( "; - foreach (T value in this) - { - s += value.ToString() + " "; - } - return s + ")"; - } - - string IFormattable.ToString(string format, IFormatProvider formatProvider) - { - return ToString(); - } - - #endregion IFormattable 成员 - } -} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/ExtensionMethod/CollectionEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/CollectionEx.cs deleted file mode 100644 index 179c06245163eaac6155e30c6945fc84ed1fcb7f..0000000000000000000000000000000000000000 --- a/src/IFoxCAD.Cad/ExtensionMethod/CollectionEx.cs +++ /dev/null @@ -1,103 +0,0 @@ -namespace IFoxCAD.Cad; - -/// -/// 集合扩展类 -/// -public static class CollectionEx -{ - /// - /// 对象id迭代器转换为集合 - /// - /// 对象id的迭代器 - /// 对象id集合 - public static ObjectIdCollection ToCollection(this IEnumerable ids) - { - return new ObjectIdCollection(ids.ToArray()); - } - - /// - /// 实体迭代器转换为集合 - /// - /// 对象类型 - /// 实体对象的迭代器 - /// 实体集合 - public static DBObjectCollection ToCollection(this IEnumerable objs) where T : DBObject - { - DBObjectCollection objCol = new(); - foreach (T obj in objs) - objCol.Add(obj); - return objCol; - } - - /// - /// double 数值迭代器转换为 double 数值集合 - /// - /// double 数值迭代器 - /// double 数值集合 - public static DoubleCollection ToCollection(this IEnumerable doubles) - { - return new DoubleCollection(doubles.ToArray()); - } - - /// - /// 二维点迭代器转换为二维点集合 - /// - /// 二维点迭代器 - /// 二维点集合 - public static Point2dCollection ToCollection(this IEnumerable pts) - { - return new Point2dCollection(pts.ToArray()); - } - - /// - /// 三维点迭代器转换为三维点集合 - /// - /// 三维点迭代器 - /// 三维点集合 - public static Point3dCollection ToCollection(this IEnumerable pts) - { - return new Point3dCollection(pts.ToArray()); - } - - /// - /// 对象id集合转换为对象id列表 - /// - /// 对象id集合 - /// 对象id列表 - public static List ToList(this ObjectIdCollection ids) - { - return ids.Cast().ToList(); - } - - - /// - /// 遍历集合的迭代器,执行action委托 - /// - /// 集合值的类型 - /// 集合 - /// 要运行的委托 - public static void ForEach(this IEnumerable source, Action action) - { - foreach (var element in source) - { - action?.Invoke(element); - } - } - /// - /// 同时遍历集合索引和值的迭代器,执行action委托 - /// - /// 集合值的类型 - /// 集合 - /// 要运行的委托 - public static void ForEach(this IEnumerable source, Action action) - { - int i = 0; - foreach (var item in source) - { - action?.Invoke(i, item); - i++; - } - - } - -} diff --git a/src/IFoxCAD.Cad/ExtensionMethod/EntityEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/EntityEx.cs deleted file mode 100644 index 716643cf5ae9286ed00c8230d0a8d6eaa373463e..0000000000000000000000000000000000000000 --- a/src/IFoxCAD.Cad/ExtensionMethod/EntityEx.cs +++ /dev/null @@ -1,397 +0,0 @@ -namespace IFoxCAD.Cad; - -using Autodesk.AutoCAD.DatabaseServices.Filters; - -/// -/// 实体图元类扩展函数 -/// -public static class EntityEx -{ - #region 实体刷新 - /// - /// 刷新实体显示 - /// - /// 实体对象 - public static void Flush(this Entity entity, Transaction trans = null) - { - if (entity is null) - { - throw new ArgumentNullException(nameof(entity)); - } - if (trans is null) - { - trans = DBTrans.Top.Transaction; - } - entity.RecordGraphicsModified(true); - trans.TransactionManager.QueueForGraphicsFlush(); - } - - /// - /// 刷新实体显示 - /// - /// 实体id - public static void Flush(this ObjectId id) => Flush(DBTrans.Top.GetObject(id)); - #endregion - - #region 多段线端点坐标 - /// - /// 获取二维多段线的端点坐标 - /// - /// 二维多段线 - /// 事务 - /// 端点坐标集合 - public static IEnumerable GetPoints(this Polyline2d pl2d, Transaction tr = null) - { - tr ??= DBTrans.Top.Transaction; - foreach (ObjectId id in pl2d) - { - yield return ((Vertex2d)tr.GetObject(id, OpenMode.ForRead)).Position; - } - } - - /// - /// 获取三维多段线的端点坐标 - /// - /// 三维多段线 - /// 事务 - /// 端点坐标集合 - public static IEnumerable GetPoints(this Polyline3d pl3d, Transaction tr = null) - { - tr ??= DBTrans.Top.Transaction; - foreach (ObjectId id in pl3d) - { - yield return ((PolylineVertex3d)tr.GetObject(id, OpenMode.ForRead)).Position; - } - } - - /// - /// 获取多段线的端点坐标 - /// - /// 多段线 - /// 端点坐标集合 - public static IEnumerable GetPoints(this Polyline pl) - { - return - Enumerable - .Range(0, pl.NumberOfVertices) - .Select(i => pl.GetPoint3dAt(i)); - } - #endregion - - #region TransformBy - - /// - /// 移动实体 - /// - /// 实体 - /// 基点 - /// 目标点 - public static void Move(this Entity ent, Point3d from, Point3d to) - { - ent.TransformBy(Matrix3d.Displacement(to - from)); - } - - /// - /// 缩放实体 - /// - /// 实体 - /// 缩放基点坐标 - /// 缩放比例 - public static void Scale(this Entity ent, Point3d center, double scaleValue) - { - ent.TransformBy(Matrix3d.Scaling(scaleValue, center)); - } - - /// - /// 旋转实体 - /// - /// 实体 - /// 旋转中心 - /// 转角 - /// 旋转平面的法向矢量 - public static void Rotation(this Entity ent, Point3d center, double angle, Vector3d normal) - { - ent.TransformBy(Matrix3d.Rotation(angle, normal, center)); - } - - /// - /// 在XY平面内旋转实体 - /// - /// 实体 - /// 旋转中心 - /// 转角 - public static void Rotation(this Entity ent, Point3d center, double angle) - { - ent.TransformBy(Matrix3d.Rotation(angle, Vector3d.ZAxis.TransformBy(ent.Ecs), center)); - } - - /// - /// 按对称轴镜像实体 - /// - /// 实体 - /// 对称轴起点 - /// 对称轴终点 - public static void Mirror(this Entity ent, Point3d startPoint, Point3d endPoint) - { - ent.TransformBy(Matrix3d.Mirroring(new Line3d(startPoint, endPoint))); - } - - /// - /// 按对称面镜像实体 - /// - /// 实体 - /// 对称平面 - public static void Mirror(this Entity ent, Plane plane) - { - ent.TransformBy(Matrix3d.Mirroring(plane)); - } - - /// - /// 按对称点镜像实体 - /// - /// 实体 - /// 对称点 - public static void Mirror(this Entity ent, Point3d basePoint) - { - ent.TransformBy(Matrix3d.Mirroring(basePoint)); - } - - #endregion - - #region 实体范围 - /// - /// 获取实体集合的范围 - /// - /// 实体迭代器 - /// 实体集合的范围 - public static Extents3d GetExtents(this IEnumerable ents) - { - var it = ents.GetEnumerator(); - var ext = it.Current.GeometricExtents; - while (it.MoveNext()) - ext.AddExtents(it.Current.GeometricExtents); - return ext; - } - #endregion - - #region 单行文字 - - /// - /// 更正单行文字的镜像属性 - /// - /// 单行文字 - public static void ValidateMirror(this DBText txt) - { - if (!txt.Database.Mirrtext) - { - txt.IsMirroredInX = false; - txt.IsMirroredInY = false; - } - } - #endregion - - #region 多行文字 - - /// - /// 炸散多行文字 - /// - /// 存储多行文字炸散之后的对象的类型 - /// 多行文字 - /// 存储对象变量 - /// 回调函数,用于处理炸散之后的对象 - /// MTextFragment -- 多行文字炸散后的对象 - /// MTextFragmentCallbackStatus -- 回调函数处理的结果 - /// - public static void ExplodeFragments(this MText mt, T obj, Func mTextFragmentCallback) - { - mt.ExplodeFragments((f, o) => mTextFragmentCallback(f, (T)o), obj); - } - - /// - /// 获取多行文字的无格式文本 - /// - /// 多行文字 - /// 文本 - public static string GetUnFormatString(this MText mt) - { - List strs = new(); - mt.ExplodeFragments( - strs, - (f, o) => - { - o.Add(f.Text); - return MTextFragmentCallbackStatus.Continue; - }); - return string.Join("", strs.ToArray()); - } - #endregion - - #region 圆弧 - - /// - /// 根据圆心、起点和终点来创建圆弧(二维) - /// - /// 圆弧对象 - /// 起点 - /// 圆心 - /// 终点 - /// 圆弧 - public static Arc CreateArcSCE(Point3d startPoint, Point3d centerPoint, Point3d endPoint) - { - Arc arc = new(); - arc.Center = centerPoint; - arc.Radius = centerPoint.DistanceTo(startPoint); - Vector2d startVector = new(startPoint.X - centerPoint.X, startPoint.Y - centerPoint.Y); - Vector2d endVector = new(endPoint.X - centerPoint.X, endPoint.Y - centerPoint.Y); - //计算起始和终止角度 - arc.StartAngle = startVector.Angle; - arc.EndAngle = endVector.Angle; - return arc; - } - /// - /// 三点法创建圆弧(二维) - /// - /// 圆弧对象 - /// 起点 - /// 圆弧上的点 - /// 终点 - /// 圆弧 - public static Arc CreateArc(Point3d startPoint, Point3d pointOnArc, Point3d endPoint) - { - //创建一个几何类的圆弧对象 - CircularArc3d geArc = new(startPoint, pointOnArc, endPoint); - //将几何类圆弧对象的圆心和半径赋值给圆弧 - return (Arc)Curve.CreateFromGeCurve(geArc); - } - - /// - /// 根据起点、圆心和圆弧角度创建圆弧(二维) - /// - /// 圆弧对象 - /// 起点 - /// 圆心 - /// 圆弧角度 - /// 圆弧 - public static Arc CreateArc(Point3d startPoint, Point3d centerPoint, double angle) - { - Arc arc = new(); - arc.Center = centerPoint; - arc.Radius = centerPoint.DistanceTo(startPoint); - Vector2d startVector = new(startPoint.X - centerPoint.X, startPoint.Y - centerPoint.Y); - arc.StartAngle = startVector.Angle; - arc.EndAngle = startVector.Angle + angle; - return arc; - } - - #endregion - - #region 圆 - - /// - /// 两点创建圆(两点中点为圆心) - /// - /// 起点 - /// 终点 - /// - public static Circle CreateCircle(Point3d startPoint, Point3d endPoint) - { - Circle circle = new(); - circle.Center = startPoint.GetMidPointTo(endPoint); - circle.Radius = startPoint.DistanceTo(endPoint) * 0.5; - return circle; - } - - /// - /// 三点法创建圆(失败则返回Null) - /// - /// 第一点 - /// 第二点 - /// 第三点 - /// - public static Circle CreateCircle(Point3d pt1, Point3d pt2, Point3d pt3) - { - //先判断三点是否共线,得到pt1点指向pt2、pt2点的矢量 - Vector3d va = pt1.GetVectorTo(pt2); - Vector3d vb = pt1.GetVectorTo(pt3); - //如两矢量夹角为0或180度(π弧度),则三点共线. - if (va.GetAngleTo(vb) == 0 | va.GetAngleTo(vb) == Math.PI) - { - return null; - } - else - { - //创建一个几何类的圆弧对象 - CircularArc3d geArc = new(pt1, pt2, pt3); - geArc.ToCircle(); - return geArc.ToCircle(); - } - } - - #endregion - - #region 块参照 - - #region 裁剪块参照 - - private const string filterDictName = "ACAD_FILTER"; - private const string spatialName = "SPATIAL"; - - /// - /// 裁剪块参照 - /// - /// 块参照 - /// 裁剪多边形点表 - public static void ClipBlockRef(this BlockReference bref, IEnumerable pt3ds) - { - if (bref == null) - { - throw new ArgumentNullException(nameof(bref)); - } - if (pt3ds == null) - { - throw new ArgumentNullException(nameof(pt3ds)); - } - Matrix3d mat = bref.BlockTransform.Inverse(); - var pts = - pt3ds - .Select(p => p.TransformBy(mat)) - .Select(p => new Point2d(p.X, p.Y)) - .ToCollection(); - - SpatialFilterDefinition sfd = new(pts, Vector3d.ZAxis, 0.0, 0.0, 0.0, true); - using SpatialFilter sf = new() { Definition = sfd }; - var dict = bref.GetXDictionary().GetSubDictionary(true, new string[] { filterDictName }); - dict.SetAt(spatialName, sf); - //SetToDictionary(dict, spatialName, sf); - } - - /// - /// 裁剪块参照 - /// - /// 块参照 - /// 第一角点 - /// 第二角点 - public static void ClipBlockRef(this BlockReference bref, Point3d pt1, Point3d pt2) - { - if (bref == null) - { - throw new ArgumentNullException(nameof(bref)); - } - Matrix3d mat = bref.BlockTransform.Inverse(); - pt1 = pt1.TransformBy(mat); - pt2 = pt2.TransformBy(mat); - Point2dCollection pts = new() - { - new Point2d(Math.Min(pt1.X, pt2.X), Math.Min(pt1.Y, pt2.Y)), - new Point2d(Math.Max(pt1.X, pt2.X), Math.Max(pt1.Y, pt2.Y)) - }; - - SpatialFilterDefinition sfd = new(pts, Vector3d.ZAxis, 0.0, 0.0, 0.0, true); - using SpatialFilter sf = new() { Definition = sfd }; - var dict = bref.GetXDictionary().GetSubDictionary(true, new string[] { filterDictName }); - dict.SetAt(spatialName, sf); - //SetToDictionary(dict, spatialName, sf); - } - #endregion - #endregion -} diff --git a/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj b/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj deleted file mode 100644 index ef105fd29a4dd101df1b186150e44c7baeb431ff..0000000000000000000000000000000000000000 --- a/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj +++ /dev/null @@ -1,62 +0,0 @@ - - - - net45 - true - 0.2.0 - InspireFunction - xsfhlzh;vicwjb - 基于.NET的Cad二次开发类库 - InspireFunction - https://gitee.com/inspirefunction/ifoxcad - https://gitee.com/inspirefunction/ifoxcad.git - git - IFoxCAD;CAD;AutoCad;C#;NET - 删除net35支持,升级至net45,增加c#10语法相关内容. - true - true - preview - true - LICENSE - true - - - - - - - DEBUG;TRACE - - - - - - - - True - - - - - - - - - - - runtime - - - - - - - - - - - - - - - diff --git a/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj.data b/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj.data deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/src/IFoxCAD.Cad/Runtime/AcadVersion.cs b/src/IFoxCAD.Cad/Runtime/AcadVersion.cs deleted file mode 100644 index 5441a09ea04ede4a327f9573b1aea187c9297a72..0000000000000000000000000000000000000000 --- a/src/IFoxCAD.Cad/Runtime/AcadVersion.cs +++ /dev/null @@ -1,123 +0,0 @@ -namespace IFoxCAD.Cad; -using Registry = Microsoft.Win32.Registry; -/// -/// cad版本号类 -/// -public class AcadVersion -{ - /// - /// 主版本 - /// - public int Major - { private set; get; } - - /// - /// 次版本 - /// - public int Minor - { private set; get; } - - /// - /// 版本号 - /// - public double ProgId => double.Parse($"{Major}.{Minor}"); - - /// - /// 注册表名称 - /// - public string ProductName - { private set; get; } - - /// - /// 注册表位置 - /// - public string ProductRootKey - { private set; get; } - - private static readonly string _pattern = @"Autodesk\\AutoCAD\\R(\d+)\.(\d+)\\.*?"; - - private static List _versions; - - /// - /// 所有安装的cad的版本号 - /// - public static List Versions - { - get - { - if (_versions == null) - { - string[] copys = - Registry.LocalMachine - .OpenSubKey(@"SOFTWARE\Autodesk\Hardcopy") - .GetValueNames(); - _versions = new List(); - foreach (var rootkey in copys) - { - if (Regex.IsMatch(rootkey, _pattern)) - { - var gs = Regex.Match(rootkey, _pattern).Groups; - var ver = - new AcadVersion - { - ProductRootKey = rootkey, - ProductName = - Registry.LocalMachine - .OpenSubKey("SOFTWARE") - .OpenSubKey(rootkey) - .GetValue("ProductName") - .ToString(), - - Major = int.Parse(gs[1].Value), - Minor = int.Parse(gs[2].Value), - }; - - _versions.Add(ver); - } - } - } - return _versions; - } - } - - /// 已打开的cad的版本号 - /// 已打开cad的application对象 - /// cad版本号对象 - public static AcadVersion FromApp(object app) - { - if (app == null) - { - throw new ArgumentNullException(nameof(app)); - } - - string acver = - app.GetType() - .InvokeMember( - "Version", - BindingFlags.GetProperty, - null, - app, - new object[0]).ToString(); - - var gs = Regex.Match(acver, @"(\d+)\.(\d+).*?").Groups; - int major = int.Parse(gs[1].Value); - int minor = int.Parse(gs[2].Value); - foreach (var ver in Versions) - { - if (ver.Major == major && ver.Minor == minor) - return ver; - } - - return null; - } - - /// - /// 转换为字符串 - /// - /// 表示版本号的字符串 - public override string ToString() - { - return - $"名称:{ProductName}\n版本号:{ProgId}\n注册表位置:{ProductRootKey}"; - } -} diff --git a/src/IFoxCAD.Cad/Runtime/DBTrans.cs b/src/IFoxCAD.Cad/Runtime/DBTrans.cs deleted file mode 100644 index 90aa274ea6ffacfb44716b7b282197b74a9f4628..0000000000000000000000000000000000000000 --- a/src/IFoxCAD.Cad/Runtime/DBTrans.cs +++ /dev/null @@ -1,347 +0,0 @@ -namespace IFoxCAD.Cad; - -public class DBTrans : IDisposable -{ - #region 私有字段 - /// - /// 文档锁 - /// - private DocumentLock documentLock = default; - /// - /// 是否释放资源 - /// - private bool disposedValue; - /// - /// 是否提交事务 - /// - private bool _commit; - /// - /// 事务栈 - /// - private static readonly Stack dBTrans = new(); - #endregion - - #region 公开属性 - /// - /// 返回当前事务 - /// - public static DBTrans Top - { - get - { - DBTrans trans; - try - { - trans = dBTrans.Peek(); - } - catch (System.Exception) - { - trans = new DBTrans(); - } - return trans; - } - } - - /// - /// 数据库 - /// - public Database Database { get; private set; } - /// - /// 文档 - /// - public Document Document { get; private set; } - /// - /// 命令行 - /// - public Editor Editor { get; private set; } - /// - /// 事务管理器 - /// - public Transaction Transaction { get; private set; } - - #endregion - - #region 构造函数 - /// - /// 默认构造函数,默认为打开当前文档,默认提交事务 - /// - /// 要打开的文档 - /// 事务是否提交 - public DBTrans(Document doc = null, bool commit = true, bool doclock = false) - { - doc ??= Application.DocumentManager.MdiActiveDocument; - Document = doc; - Database = Document.Database; - Editor = Document.Editor; - Init(commit, doclock); - } - - /// - /// 构造函数,打开数据库,默认提交事务 - /// - /// 要打开的数据库 - /// 事务是否提交 - public DBTrans(Database database, bool commit = true) - { - Database = database; - Init(commit, false); - } - /// - /// 构造函数,打开文件,默认提交事务 - /// - /// 要打开的文件名 - /// 事务是否提交 - public DBTrans(string fileName, bool commit = true) - { - Database = new Database(false, true); - if (Path.GetExtension(fileName).ToLower().Contains("dxf")) - { - Database.DxfIn(fileName, null); - } - else - { - Database.ReadDwgFile(fileName, FileShare.Read, true, null); - } - Database.CloseInput(true); - Init(commit, false); - } - /// - /// 初始化事务及事务队列、提交模式 - /// - /// 提交模式 - private void Init(bool commit, bool doclock) - { - if (doclock) - { - documentLock = Document.LockDocument(); - } - Transaction = Database.TransactionManager.StartTransaction(); - _commit = commit; - dBTrans.Push(this); - } - - #endregion - - #region 类型转换 - /// - /// 隐式转换为Transaction - /// - /// 事务管理器 - /// 事务管理器 - public static implicit operator Transaction(DBTrans tr) - { - return tr.Transaction; - } - #endregion - - #region 符号表 - - /// - /// 块表 - /// - public SymbolTable BlockTable => new(this, Database.BlockTableId); - /// - /// 当前绘图空间 - /// - public BlockTableRecord CurrentSpace => BlockTable.GetRecord(Database.CurrentSpaceId); - /// - /// 模型空间 - /// - public BlockTableRecord ModelSpace => BlockTable.GetRecord(BlockTable.CurrentSymbolTable[BlockTableRecord.ModelSpace]); - /// - /// 图纸空间 - /// - public BlockTableRecord PaperSpace => BlockTable.GetRecord(BlockTable.CurrentSymbolTable[BlockTableRecord.PaperSpace]); - /// - /// 层表 - /// - public SymbolTable LayerTable => new(this, Database.LayerTableId); - /// - /// 文字样式表 - /// - public SymbolTable TextStyleTable => new(this, Database.TextStyleTableId); - - /// - /// 注册应用程序表 - /// - public SymbolTable RegAppTable => new(this, Database.RegAppTableId); - - /// - /// 标注样式表 - /// - public SymbolTable DimStyleTable => new(this, Database.DimStyleTableId); - - /// - /// 线型表 - /// - public SymbolTable LinetypeTable => new(this, Database.LinetypeTableId); - - /// - /// 用户坐标系表 - /// - public SymbolTable UcsTable => new(this, Database.UcsTableId); - - /// - /// 视图表 - /// - public SymbolTable ViewTable => new(this, Database.ViewTableId); - - /// - /// 视口表 - /// - public SymbolTable ViewportTable => new(this, Database.ViewportTableId); - #endregion - - #region 字典 - //TODO: 补充关于扩展字典,命名对象字典,组字典,多线样式字典等对象字典的属性 - /// - /// 命名对象字典 - /// - public DBDictionary NamedObjectsDict => GetObject(Database.NamedObjectsDictionaryId); - /// - /// 组字典 - /// - public DBDictionary GroupDict => GetObject(Database.GroupDictionaryId); - /// - /// 多重引线样式字典 - /// - public DBDictionary MLeaderStyleDict => GetObject(Database.MLeaderStyleDictionaryId); - /// - /// 多线样式字典 - /// - public DBDictionary MLStyleDict => GetObject(Database.MLStyleDictionaryId); - /// - /// 材质字典 - /// - public DBDictionary MaterialDict => GetObject(Database.MaterialDictionaryId); - /// - /// 表格样式字典 - /// - public DBDictionary TableStyleDict => GetObject(Database.TableStyleDictionaryId); - /// - /// 视觉样式字典 - /// - public DBDictionary VisualStyleDict => GetObject(Database.VisualStyleDictionaryId); - /// - /// 颜色字典 - /// - public DBDictionary ColorDict => GetObject(Database.ColorDictionaryId); - /// - /// 打印设置字典 - /// - public DBDictionary PlotSettingsDict => GetObject(Database.PlotSettingsDictionaryId); - /// - /// 打印样式表名字典 - /// - public DBDictionary PlotStyleNameDict => GetObject(Database.PlotStyleNameDictionaryId); - /// - /// 布局字典 - /// - public DBDictionary LayoutDict => GetObject(Database.LayoutDictionaryId); - /// - /// 数据链接字典 - /// - public DBDictionary DataLinkDict => GetObject(Database.DataLinkDictionaryId); -#if ac2013 - /// - /// 详细视图样式字典 - /// - public DBDictionary DetailViewStyleDict => GetObject(Database.DetailViewStyleDictionaryId); - /// - /// 剖面视图样式字典 - /// - public DBDictionary SectionViewStyleDict => GetObject(Database.SectionViewStyleDictionaryId); -#endif - #endregion - - #region 获取对象 - /// - /// 根据对象id获取图元对象 - /// - /// 要获取的图元对象的类型 - /// 对象id - /// 打开模式,默认为只读 - /// 是否打开已删除对象,默认为不打开 - /// 是否打开锁定图层对象,默认为不打开 - /// 图元对象,类型不匹配时返回 - public T GetObject(ObjectId id, - OpenMode mode = OpenMode.ForRead, - bool openErased = false, - bool forceOpenOnLockedLayer = false) where T : DBObject - { - return Transaction.GetObject(id, mode, openErased, forceOpenOnLockedLayer) as T; - } - - /// - /// 根据对象句柄字符串获取对象Id - /// - /// 句柄字符串 - /// 对象id - public ObjectId GetObjectId(string handleString) - { - var hanle = new Handle(Convert.ToInt64(handleString, 16)); - return Database.GetObjectId(false, hanle, 0); - } - - - - #endregion - - #region idispose接口相关函数 - - public void Abort() - { - Transaction.Abort(); - } - - public void Commit() - { - if (_commit) - { - Transaction.Commit(); - } - else - { - Abort(); - } - - } - - protected virtual void Dispose(bool disposing) - { - Transaction.TransactionManager.QueueForGraphicsFlush(); - if (!disposedValue) - { - if (disposing) - { - // 释放托管状态(托管对象) - Commit(); - dBTrans.Pop(); - if (!Transaction.IsDisposed) - { - Transaction.Dispose(); - } - documentLock?.Dispose(); - } - - // 释放未托管的资源(未托管的对象)并替代终结器 - // 将大型字段设置为 null - disposedValue = true; - } - } - - // 仅当“Dispose(bool disposing)”拥有用于释放未托管资源的代码时才替代终结器 - ~DBTrans() - { - // 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中 - Dispose(disposing: false); - } - - public void Dispose() - { - // 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中 - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - #endregion -} diff --git a/src/WPF/BindingErrorTraceListener.cs b/src/WPF/BindingErrorTraceListener.cs new file mode 100644 index 0000000000000000000000000000000000000000..9ba3a4b89b089ec21762575a477c06499f33ec36 --- /dev/null +++ b/src/WPF/BindingErrorTraceListener.cs @@ -0,0 +1,84 @@ +namespace IFoxCAD.WPF; +using System.Text.RegularExpressions; + +/* + xaml 需要绑定失败时候报错(vs默认是不报错的): + https://cloud.tencent.com/developer/article/1342661 + 所有的绑定输出,重写方法就可以转发,构造函数上加入: + public MainWindow() + { + PresentationTraceSources.DataBindingSource.Switch.Level = SourceLevels.Error; + PresentationTraceSources.DataBindingSource.Listeners.Add(new BindingErrorTraceListener()); + App.Current.DispatcherUnhandledException += DispatcherUnhandledException; + // InitializeComponent(); + // DataContext = new ViewModel(); + } + private void DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) + { + if (e.Exception is BindingErrorException bindingErrorException) + { + MessageBox.Show($"Binding error. {bindingErrorException.SourceObject}.{bindingErrorException.SourceProperty} {bindingErrorException.TargetElement}.{bindingErrorException.TargetProperty}"); + } + } +*/ + +/// +/// 属性绑定错误异常 +/// +public class BindingErrorException : Exception +{ + /// + /// 来源对象 + /// + public string? SourceObject { get; set; } + /// + /// 来源属性 + /// + public string? SourceProperty { get; set; } + /// + /// 目标元素 + /// + public string? TargetElement { get; set; } + /// + /// 目标属性 + /// + public string? TargetProperty { get; set; } + + public BindingErrorException() : base() + { + } + + public BindingErrorException(string message) : base(message) + { + } +} + +/// +/// 属性绑定错误侦听器 +/// +public class BindingErrorTraceListener : TraceListener +{ + const string BindingErrorPattern = + @"^BindingExpression path error(?:.+)'(.+)' property not found(?:.+)object[\s']+(.+?)'(?:.+)target element is '(.+?)'(?:.+)target property is '(.+?)'(?:.+)$"; + + public override void Write(string message) + { + Trace.WriteLine(string.Format("[Write]{0}", message)); + Debug.WriteLine(string.Format("[Write]{0}", message)); + } + + public override void WriteLine(string message) + { + var match = Regex.Match(message, BindingErrorPattern); + if (match.Success) + { + throw new BindingErrorException(message) + { + SourceObject = match.Groups[2].ToString(), + SourceProperty = match.Groups[1].ToString(), + TargetElement = match.Groups[3].ToString(), + TargetProperty = match.Groups[4].ToString() + }; + } + } +} \ No newline at end of file diff --git a/src/IFoxCAD.WPF/Converter.cs b/src/WPF/Converter.cs similarity index 97% rename from src/IFoxCAD.WPF/Converter.cs rename to src/WPF/Converter.cs index f165ad61b630b60de01bafed6db465e27da3298a..828ece9dbb93fb1f666b42ccad7ddbd2a6c77705 100644 --- a/src/IFoxCAD.WPF/Converter.cs +++ b/src/WPF/Converter.cs @@ -15,7 +15,7 @@ public class StringToIntConverter : IValueConverter /// 转换后的值。 如果该方法返回 null,则使用有效的 null 值。 public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - string a = value as string; + string? a = value as string; _ = int.TryParse(a, out int b); return b; } @@ -47,7 +47,7 @@ public class StringToDoubleConverter : IValueConverter /// 转换后的值。 如果该方法返回 null,则使用有效的 null 值。 public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - string a = value as string; + string? a = value as string; _ = double.TryParse(a, out double b); return b; } @@ -91,7 +91,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn /// 转换后的值。 如果该方法返回 null,则使用有效的 null 值。 public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { - string a = value as string; + string? a = value as string; _ = int.TryParse(a, out int b); return b; } diff --git a/src/IFoxCAD.WPF/DependencyObjectExtensions.cs b/src/WPF/DependencyObjectExtensions.cs similarity index 43% rename from src/IFoxCAD.WPF/DependencyObjectExtensions.cs rename to src/WPF/DependencyObjectExtensions.cs index f919fbba67aa5d6e136e8946e6a937082193d947..085285879e5dbbf5099273f3ac88cb68a692a406 100644 --- a/src/IFoxCAD.WPF/DependencyObjectExtensions.cs +++ b/src/WPF/DependencyObjectExtensions.cs @@ -10,25 +10,27 @@ public static class DependencyObjectExtensions /// /// 子对象 /// 依赖属性 - public static DependencyObject GetParentObject(this DependencyObject child) + public static DependencyObject? GetParentObject(this DependencyObject child) { - if (child == null) return null; + if (child is null) return null; - if (child is ContentElement contentElement) + if (child is ContentElement ce) { - DependencyObject parent = ContentOperations.GetParent(contentElement); - if (parent != null) return parent; + var parent = ContentOperations.GetParent(ce); + if (parent is not null) + return parent; - FrameworkContentElement fce = contentElement as FrameworkContentElement; + var fce = ce as FrameworkContentElement; return fce?.Parent; } - if (child is FrameworkElement frameworkElement) + if (child is FrameworkElement fe) { - DependencyObject parent = frameworkElement.Parent; - if (parent != null) return parent; + var parent = fe.Parent; + if (parent is not null) + return parent; } return VisualTreeHelper.GetParent(child); } -} +} \ No newline at end of file diff --git a/src/WPF/EnumSelection.cs b/src/WPF/EnumSelection.cs new file mode 100644 index 0000000000000000000000000000000000000000..96630e4b9fe2cb6ac62e5f849bdef5793af168e4 --- /dev/null +++ b/src/WPF/EnumSelection.cs @@ -0,0 +1,80 @@ +namespace IFoxCAD.WPF; +public class EnumSelection : INotifyPropertyChanged where T : struct, IComparable, IFormattable, IConvertible +{ + private T value; // stored value of the Enum + private readonly bool isFlagged; // Enum uses flags? + private readonly bool canDeselect; // Can be deselected? (Radio buttons cannot deselect, checkboxes can) + private readonly T blankValue; // what is considered the "blank" value if it can be deselected? + + public EnumSelection(T value) : this(value, false, default) + { + } + public EnumSelection(T value, bool canDeselect) : this(value, canDeselect, default) + { + } + public EnumSelection(T value, T blankValue) : this(value, true, blankValue) + { + } + public EnumSelection(T value, bool canDeselect, T blankValue) + { + if (!typeof(T).IsEnum) + throw new ArgumentException($"{nameof(T)} must be an enum type"); // I really wish there was a way to constrain generic types to enums... + isFlagged = typeof(T).IsDefined(typeof(FlagsAttribute), false); + + this.value = value; + this.canDeselect = canDeselect; + this.blankValue = blankValue; + } + + public T Value + { + get { return value; } + set + { + if (this.value.Equals(value)) return; + this.value = value; + OnPropertyChanged(); + OnPropertyChanged("Item[]"); // Notify that the indexer property has changed + } + } + + [IndexerName("Item")] + public bool this[T key] + { + get + { + int iKey = (int)(object)key; + return isFlagged ? ((int)(object)value & iKey) == iKey : value.Equals(key); + } + set + { + if (isFlagged) + { + int iValue = (int)(object)this.value; + int iKey = (int)(object)key; + + if ((iValue & iKey) == iKey == value) + return; + + if (value) + Value = (T)(object)(iValue | iKey); + else + Value = (T)(object)(iValue & ~iKey); + } + else + { + if (this.value.Equals(key) == value) return; + if (!value && !canDeselect) return; + + Value = value ? key : blankValue; + } + } + } + + public event PropertyChangedEventHandler? PropertyChanged; + + private void OnPropertyChanged([CallerMemberName] string propertyName = "") + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } +} \ No newline at end of file diff --git a/src/IFoxCAD.WPF/EventBindingExtension.cs b/src/WPF/EventBindingExtension.cs similarity index 51% rename from src/IFoxCAD.WPF/EventBindingExtension.cs rename to src/WPF/EventBindingExtension.cs index 4e5e623befa4cf18b5d01fc08ddf5464348eae49..ab46a128043ee2107c4d0a1e28974f4b4583e00a 100644 --- a/src/IFoxCAD.WPF/EventBindingExtension.cs +++ b/src/WPF/EventBindingExtension.cs @@ -9,11 +9,11 @@ public class EventBindingExtension : MarkupExtension /// /// 命令属性 /// - public string Command { get; set; } + public string? Command { get; set; } /// /// 命令参数属性 /// - public string CommandParameter { get; set; } + public string? CommandParameter { get; set; } /// /// 当在派生类中实现时,返回用作此标记扩展的目标属性值的对象。 /// @@ -23,69 +23,58 @@ public class EventBindingExtension : MarkupExtension /// /// /// - public override object ProvideValue(IServiceProvider serviceProvider) + public override object? ProvideValue(IServiceProvider serviceProvider) { - if (serviceProvider == null) - { + if (serviceProvider is null) throw new ArgumentNullException(nameof(serviceProvider)); - } - if (!(serviceProvider.GetService(typeof(IProvideValueTarget)) is IProvideValueTarget targetProvider)) - { - throw new InvalidOperationException(); - } + if (serviceProvider.GetService(typeof(IProvideValueTarget)) is not IProvideValueTarget targetProvider) + throw new InvalidOperationException(message: $"{nameof(ProvideValue)}:{nameof(IProvideValueTarget)}"); - if (!(targetProvider.TargetObject is FrameworkElement targetObject)) - { - throw new InvalidOperationException(); - } + if (targetProvider.TargetObject is not FrameworkElement targetObject) + throw new InvalidOperationException(message: $"{nameof(ProvideValue)}:{nameof(FrameworkElement)}"); - var memberInfo = targetProvider.TargetProperty as MemberInfo; - if (memberInfo == null) - { - throw new InvalidOperationException(); - } + if (targetProvider.TargetProperty is not MemberInfo memberInfo) + throw new InvalidOperationException(message: $"{nameof(ProvideValue)}:{nameof(MemberInfo)}"); if (string.IsNullOrWhiteSpace(Command)) { Command = memberInfo.Name.Replace("Add", ""); if (Command.Contains("Handler")) - { Command = Command.Replace("Handler", "Command"); - } else - { Command += "Command"; - } } - return CreateHandler(memberInfo, Command, targetObject.GetType()); + return CreateHandler(memberInfo, Command!, targetObject.GetType()); } - private Type GetEventHandlerType(MemberInfo memberInfo) + private Type? GetEventHandlerType(MemberInfo memberInfo) { - Type eventHandlerType = null; - if (memberInfo is EventInfo) + Type? eventHandlerType = null; + if (memberInfo is EventInfo eventInfo) { - var info = memberInfo as EventInfo; - var eventInfo = info; + // var info = memberInfo as EventInfo; + // var eventInfo = info; eventHandlerType = eventInfo.EventHandlerType; } - else if (memberInfo is MethodInfo) + else if (memberInfo is MethodInfo methodInfo) { - var info = memberInfo as MethodInfo; - var methodInfo = info; - ParameterInfo[] pars = methodInfo.GetParameters(); + // var info = memberInfo as MethodInfo; + // var methodInfo = info; + var pars = methodInfo.GetParameters(); eventHandlerType = pars[1].ParameterType; } return eventHandlerType; } - private object CreateHandler(MemberInfo memberInfo, string cmdName, Type targetType) +#pragma warning disable IDE0060 // 删除未使用的参数 + private object? CreateHandler(MemberInfo memberInfo, string cmdName, Type targetType) +#pragma warning restore IDE0060 // 删除未使用的参数 { - Type eventHandlerType = GetEventHandlerType(memberInfo); - - if (eventHandlerType == null) return null; + var eventHandlerType = GetEventHandlerType(memberInfo); + if (eventHandlerType is null) + return null; var handlerInfo = eventHandlerType.GetMethod("Invoke"); var method = new DynamicMethod("", handlerInfo.ReturnType, @@ -99,23 +88,24 @@ private object CreateHandler(MemberInfo memberInfo, string cmdName, Type targetT gen.Emit(OpCodes.Ldarg, 0); gen.Emit(OpCodes.Ldarg, 1); gen.Emit(OpCodes.Ldstr, cmdName); - if (CommandParameter == null) - { + + if (CommandParameter is null) gen.Emit(OpCodes.Ldnull); - } else - { gen.Emit(OpCodes.Ldstr, CommandParameter); - } + gen.Emit(OpCodes.Call, getMethod); gen.Emit(OpCodes.Ret); return method.CreateDelegate(eventHandlerType); } - static readonly MethodInfo getMethod = typeof(EventBindingExtension).GetMethod("HandlerIntern", new Type[] { typeof(object), typeof(object), typeof(string), typeof(string) }); + static readonly MethodInfo getMethod = typeof(EventBindingExtension) + .GetMethod("HandlerIntern", new Type[] { typeof(object), typeof(object), typeof(string), typeof(string) }); +#pragma warning disable IDE0051 // 删除未使用的私有成员 static void Handler(object sender, object args) +#pragma warning restore IDE0051 // 删除未使用的私有成员 { HandlerIntern(sender, args, "cmd", null); } @@ -126,88 +116,75 @@ static void Handler(object sender, object args) /// The arguments. /// Name of the command. /// The command parameter. - public static void HandlerIntern(object sender, object args, string cmdName, string commandParameter) + public static void HandlerIntern(object sender, object args, string cmdName, string? commandParameter) { - var fe = sender as FrameworkElement; - if (fe != null) + if (sender is FrameworkElement fe) { - ICommand cmd = GetCommand(fe, cmdName); - object commandParam = null; + var cmd = GetCommand(fe, cmdName); + object? commandParam = null; if (!string.IsNullOrWhiteSpace(commandParameter)) - { - commandParam = GetCommandParameter(fe, args, commandParameter); - } - if ((cmd != null) && cmd.CanExecute(commandParam)) - { + commandParam = GetCommandParameter(fe, args, commandParameter!); + if ((cmd is not null) && cmd.CanExecute(commandParam)) cmd.Execute(commandParam); - } } } - internal static ICommand GetCommand(FrameworkElement target, string cmdName) + internal static ICommand? GetCommand(FrameworkElement target, string cmdName) { var vm = FindViewModel(target); - if (vm == null) return null; + if (vm is null) + return null; var vmType = vm.GetType(); var cmdProp = vmType.GetProperty(cmdName); - if (cmdProp != null) - { + if (cmdProp is not null) return cmdProp.GetValue(vm) as ICommand; - } #if DEBUG throw new Exception("EventBinding path error: '" + cmdName + "' property not found on '" + vmType + "' 'DelegateCommand'"); -#endif - +#else return null; +#endif } internal static object GetCommandParameter(FrameworkElement target, object args, string commandParameter) { var classify = commandParameter.Split('.'); - object ret; - switch (classify[0]) + object ret = classify[0] switch { - case "$e": - ret = args; - break; - case "$this": - ret = classify.Length > 1 ? FollowPropertyPath(target, commandParameter.Replace("$this.", ""), target.GetType()) : target; - break; - default: - ret = commandParameter; - break; - } - + "$e" => args, + "$this" => classify.Length > 1 ? FollowPropertyPath(target, commandParameter.Replace("$this.", ""), target.GetType()) : target, + _ => commandParameter, + }; return ret; } - internal static ViewModelBase FindViewModel(FrameworkElement target) + internal static ViewModelBase? FindViewModel(FrameworkElement? target) { - if (target == null) return null; - - if (target.DataContext is ViewModelBase vm) return vm; - - var parent = target.GetParentObject() as FrameworkElement; - - return FindViewModel(parent); + if (target is null) + return null; + if (target.DataContext is ViewModelBase vm) + return vm; + return FindViewModel(target.GetParentObject() as FrameworkElement); } - internal static object FollowPropertyPath(object target, string path, Type valueType = null) + internal static object FollowPropertyPath(object target, string path, Type? valueType = null) { - if (target == null) throw new ArgumentNullException(nameof(target)); - if (path == null) throw new ArgumentNullException(nameof(path)); - - Type currentType = valueType ?? target.GetType(); - - foreach (string propertyName in path.Split('.')) + if (target is null) + throw new ArgumentNullException(nameof(target)); + if (path is null) + throw new ArgumentNullException(nameof(path)); + + valueType ??= target.GetType(); + var spls = path.Split('.'); + for (int i = 0; i < spls.Length; i++) { - PropertyInfo property = currentType.GetProperty(propertyName); - if (property == null) throw new NullReferenceException("property"); + var property = valueType.GetProperty(spls[i]); + if (property is null) + throw new NullReferenceException("property"); target = property.GetValue(target); - currentType = property.PropertyType; + valueType = property.PropertyType; } return target; } -} +} \ No newline at end of file diff --git a/src/IFoxCAD.WPF/GlobalUsings.cs b/src/WPF/GlobalUsings.cs similarity index 100% rename from src/IFoxCAD.WPF/GlobalUsings.cs rename to src/WPF/GlobalUsings.cs diff --git a/src/IFoxCAD.WPF/IFoxCAD.WPF.csproj b/src/WPF/IFox.WPF.csproj similarity index 61% rename from src/IFoxCAD.WPF/IFoxCAD.WPF.csproj rename to src/WPF/IFox.WPF.csproj index 7cc8354e58fa80ce786d820aa44a03b4503d35cd..4f84d57b3418a4bc882050335dce137115c80b45 100644 --- a/src/IFoxCAD.WPF/IFoxCAD.WPF.csproj +++ b/src/WPF/IFox.WPF.csproj @@ -1,13 +1,20 @@  - net45 + 0.4 + 开启可空类型. + + + preview + enable + NET45 true true true + ..\..\bin\$(Configuration)\ true - 0.2.0 xsfhlzh;vicwjb + xsfhlzh;vicwjb;liuqihong InspireFunction WPF的简单MVVM模式开发类库 InspireFunction @@ -16,23 +23,22 @@ https://gitee.com/inspirefunction/ifoxcad.git IFoxCAD;C#;NET;WPF;MVVM git - 增加c#10语法相关内容. - preview + True - - DEBUG;TRACE + + DEBUG;TRACE - + - - True - - + + True + + diff --git a/src/IFoxCAD.WPF/RelayCommand.cs b/src/WPF/RelayCommand.cs similarity index 90% rename from src/IFoxCAD.WPF/RelayCommand.cs rename to src/WPF/RelayCommand.cs index 70b654ff49e387ae79d78ea869fd2c4d54fd686c..5d3577760b88c9f3dba168f4acf17883b584632f 100644 --- a/src/IFoxCAD.WPF/RelayCommand.cs +++ b/src/WPF/RelayCommand.cs @@ -8,7 +8,7 @@ namespace IFoxCAD.WPF; /// public class RelayCommand : ICommand { - readonly Func _canExecute; + readonly Func? _canExecute; readonly Action _execute; /// /// 初始化 类. @@ -16,7 +16,6 @@ public class RelayCommand : ICommand /// 执行函数 public RelayCommand(Action execute) : this(execute, null) { - } /// /// 初始化 类. @@ -24,7 +23,7 @@ public RelayCommand(Action execute) : this(execute, null) /// 执行函数委托 /// 是否可执行函数委托 /// execute - public RelayCommand(Action execute, Func canExecute) + public RelayCommand(Action execute, Func? canExecute) { _execute = execute ?? throw new ArgumentNullException(nameof(execute)); _canExecute = canExecute; @@ -37,17 +36,13 @@ public event EventHandler CanExecuteChanged { add { - if (_canExecute != null) - { + if (_canExecute is not null) CommandManager.RequerySuggested += value; - } } remove { - if (_canExecute != null) - { + if (_canExecute is not null) CommandManager.RequerySuggested -= value; - } } } /// @@ -60,7 +55,7 @@ public event EventHandler CanExecuteChanged [DebuggerStepThrough] public bool CanExecute(object parameter) { - return _canExecute == null ? true : _canExecute(parameter); + return _canExecute is null || _canExecute(parameter); } /// /// 定义在调用此命令时要调用的方法。 @@ -87,7 +82,6 @@ public class RelayCommand : ICommand /// 执行函数 public RelayCommand(Action execute) : this(execute, (o) => true) { - } /// @@ -108,17 +102,13 @@ public event EventHandler CanExecuteChanged { add { - if (_canExecute != null) - { + if (_canExecute is not null) CommandManager.RequerySuggested += value; - } } remove { - if (_canExecute != null) - { + if (_canExecute is not null) CommandManager.RequerySuggested -= value; - } } } /// @@ -130,10 +120,8 @@ public event EventHandler CanExecuteChanged /// public bool CanExecute(object parameter) { - if (_canExecute == null) - { + if (_canExecute is null) return true; - } return _canExecute((T)parameter); } /// @@ -142,10 +130,8 @@ public bool CanExecute(object parameter) /// 此命令使用的数据。 如果此命令不需要传递数据,则该对象可以设置为 。 public void Execute(object parameter) { - if (_execute != null && CanExecute(parameter)) - { + if (_execute is not null && CanExecute(parameter)) _execute((T)parameter); - } } } @@ -160,14 +146,10 @@ public class EventCommand : TriggerAction /// 要执行的动作参数, 如果动作为提供参数,就设置为null protected override void Invoke(object parameter) { - if (CommandParameter != null) - { + if (CommandParameter is not null) parameter = CommandParameter; - } - if (Command != null) - { + if (Command is not null) Command.Execute(parameter); - } } /// /// 事件 @@ -196,4 +178,4 @@ public object CommandParameter /// public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(object), typeof(EventCommand), new PropertyMetadata(null)); -} +} \ No newline at end of file diff --git a/src/IFoxCAD.WPF/ViewModelBase.cs b/src/WPF/ViewModelBase.cs similarity index 88% rename from src/IFoxCAD.WPF/ViewModelBase.cs rename to src/WPF/ViewModelBase.cs index c85b8b9312ea68a36946448c03fb05a9556c5dcf..6d8908a51fcac922b9edaf827362060b8170f74f 100644 --- a/src/IFoxCAD.WPF/ViewModelBase.cs +++ b/src/WPF/ViewModelBase.cs @@ -9,14 +9,13 @@ public class ViewModelBase : INotifyPropertyChanged /// /// 属性值更改事件。 /// - public event PropertyChangedEventHandler PropertyChanged; + public event PropertyChangedEventHandler? PropertyChanged; /// /// 属性改变时调用 /// /// 属性名 public void OnPropertyChanged([CallerMemberName] string propertyName = "") { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } /// @@ -27,9 +26,10 @@ public void OnPropertyChanged([CallerMemberName] string propertyName = "") /// 属性值 /// 属性名 /// 成功返回 ,反之 - protected virtual bool Set(ref T storage, T value, [CallerMemberName] string propertyName = null) + protected virtual bool Set(ref T storage, T value, [CallerMemberName] string propertyName = "") { - if (object.Equals(storage, value)) return false; + if (object.Equals(storage, value)) + return false; storage = value; this.OnPropertyChanged(propertyName); @@ -53,6 +53,6 @@ protected RelayCommand CreateCommand(Action executeMethod) /// WPF命令 protected RelayCommand CreateCommand(Action executeMethod, Func canExecuteMethod) { - return new RelayCommand(executeMethod, canExecuteMethod); + return new(executeMethod, canExecuteMethod); } -} +} \ No newline at end of file diff --git a/tests/DBTrans.test/DBTrans.test.csproj b/tests/DBTrans.test/DBTrans.test.csproj deleted file mode 100644 index a6f4773c0403d637c64c303f52227fafd9d66693..0000000000000000000000000000000000000000 --- a/tests/DBTrans.test/DBTrans.test.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - net45 - true - true - preview - - - - - - - - diff --git a/tests/DBTrans.test/Test.cs b/tests/DBTrans.test/Test.cs deleted file mode 100644 index 99b283d8993cf44a95a5d36ce78c21d682e4e533..0000000000000000000000000000000000000000 --- a/tests/DBTrans.test/Test.cs +++ /dev/null @@ -1,628 +0,0 @@ -using Autodesk.AutoCAD.ApplicationServices; -using Autodesk.AutoCAD.Colors; -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.EditorInput; -using Autodesk.AutoCAD.Geometry; -using Autodesk.AutoCAD.Runtime; - -using IFoxCAD.Cad; - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -using test.wpf; - -namespace test; - -public class Test -{ - [CommandMethod("dbtest")] - public void Dbtest() - { - using var tr = new DBTrans(); - tr.Editor.WriteMessage("\n测试 Editor 属性是否工作!"); - tr.Editor.WriteMessage("\n----------开始测试--------------"); - tr.Editor.WriteMessage("\n测试document属性是否工作"); - if (tr.Document == Getdoc()) - { - tr.Editor.WriteMessage("\ndocument 正常"); - } - tr.Editor.WriteMessage("\n测试database属性是否工作"); - if (tr.Database == Getdb()) - { - tr.Editor.WriteMessage("\ndatabase 正常"); - } - - Line line = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - Circle circle = new(new Point3d(0, 0, 0), Vector3d.ZAxis, 2); - //var lienid = tr.AddEntity(line); - //var cirid = tr.AddEntity(circle); - //var linent = tr.GetObject(lienid); - //var lineent = tr.GetObject(cirid); - //var linee = tr.GetObject(cirid); //经测试,类型不匹配,返回null - //var dd = tr.GetObject(lienid); - //List ds = new() { linee, dd }; - } - - //add entity test - [CommandMethod("addent")] - public void Addent() - { - using var tr = new DBTrans(); - Line line = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - tr.CurrentSpace.AddEntity(line); - Line line1 = new(new Point3d(10, 10, 0), new Point3d(41, 1, 0)); - tr.ModelSpace.AddEntity(line1); - Line line2 = new(new Point3d(-10, 10, 0), new Point3d(41, 1, 0)); - tr.PaperSpace.AddEntity(line2); - } - - [CommandMethod("drawarc")] - public void drawarc() - { - using var tr = new DBTrans(); - Arc arc1 = EntityEx.CreateArcSCE(new Point3d(2, 0, 0), new Point3d(0, 0, 0), new Point3d(0, 2, 0));//起点,圆心,终点 - Arc arc2 = EntityEx.CreateArc(new Point3d(4, 0, 0), new Point3d(0, 0, 0), Math.PI / 2);//起点,圆心,弧度 - Arc arc3 = EntityEx.CreateArc(new Point3d(1, 0, 0), new Point3d(0, 0, 0), new Point3d(0, 1, 0));//起点,圆上一点,终点 - tr.CurrentSpace.AddEntity(arc1, arc2, arc3); - tr.CurrentSpace.AddArc(new Point3d(0, 0, 0), new Point3d(1, 1, 0), new Point3d(2, 0, 0));//起点,圆上一点,终点 - } - - [CommandMethod("drawcircle")] - public void draCircle() - { - using var tr = new DBTrans(); - Circle circle1 = EntityEx.CreateCircle(new Point3d(0, 0, 0), new Point3d(1, 0, 0));//起点,终点 - Circle circle2 = EntityEx.CreateCircle(new Point3d(-2, 0, 0), new Point3d(2, 0, 0), new Point3d(0, 2, 0));//三点画圆,成功 - Circle circle3 = EntityEx.CreateCircle(new Point3d(-2, 0, 0), new Point3d(0, 0, 0), new Point3d(2, 0, 0));//起点,圆心,终点,失败 - tr.CurrentSpace.AddEntity(circle1, circle2); - if (circle3 is not null) - { - tr.CurrentSpace.AddEntity(circle3); - } - else - { - tr.Editor.WriteMessage("三点画圆失败"); - } - tr.CurrentSpace.AddEntity(circle3); - tr.CurrentSpace.AddCircle(new Point3d(0, 0, 0), new Point3d(1, 1, 0), new Point3d(2, 0, 0));//三点画圆,成功 - tr.CurrentSpace.AddCircle(new Point3d(0, 0, 0), new Point3d(1, 1, 0), new Point3d(2, 2, 0));//起点,圆上一点,终点(共线) - } - - [CommandMethod("layertest")] - public void Layertest() - { - using var tr = new DBTrans(); - tr.LayerTable.Add("1"); - tr.LayerTable.Add("2", lt => - { - lt.Color = Color.FromColorIndex(ColorMethod.ByColor, 1); - lt.LineWeight = LineWeight.LineWeight030; - - }); - tr.LayerTable.Remove("3"); - tr.LayerTable.Delete("0"); - tr.LayerTable.Change("4", lt => - { - lt.Color = Color.FromColorIndex(ColorMethod.ByColor, 2); - }); - } - - - //添加图层 - [CommandMethod("layerAdd1")] - public void Layertest1() - { - using var tr = new DBTrans(); - tr.LayerTable.Add("test1", Color.FromColorIndex(ColorMethod.ByColor, 1)); - } - - //添加图层 - [CommandMethod("layerAdd2")] - public void Layertest2() - { - using var tr = new DBTrans(); - tr.LayerTable.Add("test2", 2); - //tr.LayerTable["3"] = new LayerTableRecord(); - } - //删除图层 - [CommandMethod("layerdel")] - public void LayerDel() - { - using var tr = new DBTrans(); - Env.Editor.WriteMessage(tr.LayerTable.Delete("0").ToString()); //删除图层 0 - Env.Editor.WriteMessage(tr.LayerTable.Delete("Defpoints").ToString());//删除图层 Defpoints - Env.Editor.WriteMessage(tr.LayerTable.Delete("1").ToString());//删除不存在的图层 1 - Env.Editor.WriteMessage(tr.LayerTable.Delete("2").ToString());//删除有图元的图层 2 - Env.Editor.WriteMessage(tr.LayerTable.Delete("3").ToString());//删除图层 3 - - tr.LayerTable.Remove("2"); //测试是否能强制删除 - } - - //添加直线 - [CommandMethod("linedemo1")] - public void AddLine1() - { - using var tr = new DBTrans(); - // tr.ModelSpace.AddEnt(line); - // tr.ModelSpace.AddEnts(line,circle); - - // tr.PaperSpace.AddEnt(line); - // tr.PaperSpace.AddEnts(line,circle); - - // tr.addent(btr,line); - // tr.addents(btr,line,circle); - - - // tr.BlockTable.Add(new BlockTableRecord(), line => - // { - // line. - // }); - Line line1 = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - Line line2 = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - Line line3 = new(new Point3d(1, 1, 0), new Point3d(3, 3, 0)); - Circle circle = new Circle(new Point3d(0, 0, 0), Vector3d.ZAxis, 10); - tr.CurrentSpace.AddEntity(line1); - tr.CurrentSpace.AddEntity(line2, line3, circle); - } - - //增加多段线1 - [CommandMethod("Pldemo1")] - public void AddPolyline1() - { - using var tr = new DBTrans(); - Polyline pl = new Polyline(); - pl.AddVertexAt(0, new Point2d(0, 0), 0, 0, 0); - pl.AddVertexAt(1, new Point2d(10, 10), 0, 0, 0); - pl.AddVertexAt(2, new Point2d(20, 20), 0, 0, 0); - pl.AddVertexAt(3, new Point2d(30, 30), 0, 0, 0); - pl.AddVertexAt(4, new Point2d(40, 40), 0, 0, 0); - pl.Closed = true; - pl.Color = Color.FromColorIndex(ColorMethod.ByColor, 6); - tr.CurrentSpace.AddEntity(pl); - } - - //增加多段线2 - [CommandMethod("pldemo2")] - public void Addpl2() - { - var pts = new List<(Point3d, double, double, double)> - { - (new Point3d(0,0,0),0,0,0), - (new Point3d(10,0,0),0,0,0), - (new Point3d(10,10,0),0,0,0), - (new Point3d(0,10,0),0,0,0), - (new Point3d(5,5,0),0,0,0) - }; - using var tr = new DBTrans(); - tr.CurrentSpace.AddPline(pts); - } - - //块定义 - [CommandMethod("blockdef")] - public void BlockDef() - { - using var tr = new DBTrans(); - //var line = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - tr.BlockTable.Add("test", - btr => - { - btr.Origin = new Point3d(0, 0, 0); - }, - () => //图元 - { - return new List { new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)) }; - }, - () => //属性定义 - { - var id1 = new AttributeDefinition() { Position = new Point3d(0, 0, 0), Tag = "start", Height = 0.2 }; - var id2 = new AttributeDefinition() { Position = new Point3d(1, 1, 0), Tag = "end", Height = 0.2 }; - return new List { id1, id2 }; - } - ); - //ObjectId objectId = tr.BlockTable.Add("a");//新建块 - //objectId.GetObject().AddEntity();//测试添加空实体 - } - //修改块定义 - [CommandMethod("blockdefchange")] - public void BlockDefChange() - { - using var tr = new DBTrans(); - //var line = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - tr.BlockTable.Change("test", btr => - { - btr.Origin = new Point3d(5, 5, 0); - btr.AddEntity(new Circle(new Point3d(0, 0, 0), Vector3d.ZAxis, 2)); - btr.GetEntities() - .ToList() - .ForEach(e => e.Flush()); //刷新块显示 - - }); - tr.Editor.Regen(); - } - - [CommandMethod("insertblockdef")] - public void InsertBlockDef() - { - using var tr = new DBTrans(); - var line1 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - var line2 = new Line(new Point3d(0, 0, 0), new Point3d(-1, 1, 0)); - var att1 = new AttributeDefinition() { Position = new Point3d(10, 10, 0), Tag = "tagTest1", Height = 1, TextString = "valueTest1" }; - var att2 = new AttributeDefinition() { Position = new Point3d(10, 12, 0), Tag = "tagTest2", Height = 1, TextString = "valueTest2" }; - tr.BlockTable.Add("test1", line1, line2, att1, att2); - - - var ents = new List(); - var line5 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - var line6 = new Line(new Point3d(0, 0, 0), new Point3d(-1, 1, 0)); - ents.Add(line5); - ents.Add(line6); - tr.BlockTable.Add("test44", ents); - - - var line3 = new Line(new Point3d(5, 5, 0), new Point3d(6, 6, 0)); - var line4 = new Line(new Point3d(5, 5, 0), new Point3d(-6, 6, 0)); - var att3 = new AttributeDefinition() { Position = new Point3d(10, 14, 0), Tag = "tagTest3", Height = 1, TextString = "valueTest3" }; - var att4 = new AttributeDefinition() { Position = new Point3d(10, 16, 0), Tag = "tagTest4", Height = 1, TextString = "valueTest4" }; - tr.BlockTable.Add("test2", new List { line3, line4 }, new List { att3, att4 }); - //tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test1"); // 测试默认 - //tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test2"); - //tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test3"); //测试插入不存在的块定义 - //tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test1", new Scale3d(2)); // 测试放大2倍 - //tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test1", new Scale3d(2), Math.PI / 4); // 测试放大2倍,旋转45度 - - var def1 = new Dictionary - { - { "tagTest1", "1" }, - { "tagTest2", "2" } - }; - tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test1", atts: def1); - var def2 = new Dictionary - { - { "tagTest3", "1" }, - { "tagTest4", "" } - }; - tr.CurrentSpace.InsertBlock(new Point3d(10, 10, 0), "test2", atts: def2); - tr.CurrentSpace.InsertBlock(new Point3d(-10, 0, 0), "test44"); - } - - [CommandMethod("testblocknullbug")] - public void TestBlockNullBug() - { - using var tr = new DBTrans(); - - var ents = new List(); - var line5 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - var line6 = new Line(new Point3d(0, 0, 0), new Point3d(-1, 1, 0)); - ents.Add(line5); - ents.Add(line6); - tr.BlockTable.Add("test44", ents); - tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test44"); - } - - - [CommandMethod("testclip")] - public void TestClipBlock() - { - using var tr = new DBTrans(); - tr.BlockTable.Add("test1", - btr => - { - btr.Origin = new Point3d(0, 0, 0); - btr.AddEntity(new Line(new Point3d(0, 0, 0), new Point3d(10, 10, 0)), - new Line(new Point3d(10, 10, 0), new Point3d(10, 0, 0)) - ); - } - ); - //tr.BlockTable.Add("hah"); - var id = tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test1"); - var bref = tr.GetObject(id); - var pts = new List { new Point3d(3, 3, 0), new Point3d(7, 3, 0), new Point3d(7, 7, 0), new Point3d(3, 7, 0) }; - bref.ClipBlockRef(pts); - - var id1 = tr.CurrentSpace.InsertBlock(new Point3d(20, 20, 0), "test1"); - var bref1 = tr.GetObject(id); - - bref1.ClipBlockRef(new Point3d(13, 13, 0), new Point3d(17, 17, 0)); - } - - - - - // 测试扩展数据 - [CommandMethod("addxdata")] - public void AddXdata() - { - using var tr = new DBTrans(); - var appname = "myapp"; - - tr.RegAppTable.Add(appname); // add函数会默认的在存在这个名字的时候返回这个名字的regapp的id,不存在就新建 - tr.RegAppTable.Add("myapp2"); - - var line = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)) - { - XData = new XDataList() - { - { DxfCode.ExtendedDataRegAppName, appname }, //可以用dxfcode和int表示组码 - { DxfCode.ExtendedDataAsciiString, "hahhahah" }, - {1070, 12 }, - { DxfCode.ExtendedDataRegAppName, "myapp2" }, //可以用dxfcode和int表示组码 - { DxfCode.ExtendedDataAsciiString, "hahhahah" }, - {1070, 12 } - } - }; - - tr.CurrentSpace.AddEntity(line); - } - - [CommandMethod("getxdata")] - public void GetXdata() - { - var doc = Application.DocumentManager.MdiActiveDocument; - var ed = doc.Editor; - - var res = ed.GetEntity("\n select the entity:"); - if (res.Status == PromptStatus.OK) - { - using var tr = new DBTrans(); - var data = tr.GetObject(res.ObjectId).XData; - ed.WriteMessage(data.ToString()); - } - } - - [CommandMethod("changexdata")] - public void Changexdata() - { - var doc = Application.DocumentManager.MdiActiveDocument; - var ed = doc.Editor; - var appname = "myapp"; - var res = ed.GetEntity("\n select the entity:"); - if (res.Status == PromptStatus.OK) - { - using var tr = new DBTrans(); - var data = tr.GetObject(res.ObjectId); - data.ChangeXData(appname, DxfCode.ExtendedDataAsciiString, "change"); - - ed.WriteMessage(data.XData.ToString()); - } - } - [CommandMethod("removexdata")] - public void Removexdata() - { - var doc = Application.DocumentManager.MdiActiveDocument; - var ed = doc.Editor; - var appname = "myapp"; - var res = ed.GetEntity("\n select the entity:"); - if (res.Status == PromptStatus.OK) - { - using var tr = new DBTrans(); - var data = tr.GetObject(res.ObjectId); - data.RemoveXData(appname, DxfCode.ExtendedDataAsciiString); - - ed.WriteMessage(data.XData.ToString()); - } - } - - [CommandMethod("PrintLayerName")] - public void PrintLayerName() - { - using var tr = new DBTrans(); - foreach (var layerRecord in tr.LayerTable.GetRecords()) - { - tr.Editor.WriteMessage(layerRecord.Name); - } - - } - - - [CommandMethod("testwpf")] - public void TestWPf() - { - - var test = new TestView(); - Application.ShowModalWindow(test); - } - - [CommandMethod("testpt")] - public void TestPt() - { - //var pt = Env.Editor.GetPoint("pick pt:").Value; - //var pl = Env.Editor.GetEntity("pick pl").ObjectId; - var tr1 = HostApplicationServices.WorkingDatabase.TransactionManager.TopTransaction; - using var tr2 = new DBTrans(); - var tr3 = HostApplicationServices.WorkingDatabase.TransactionManager.TopTransaction; - var tr6 = Application.DocumentManager.MdiActiveDocument.TransactionManager.TopTransaction; - Env.Print(tr2.Transaction == tr3); - Env.Print(tr3 == tr6); - using var tr4 = new DBTrans(); - var tr5 = HostApplicationServices.WorkingDatabase.TransactionManager.TopTransaction; - var tr7 = Application.DocumentManager.MdiActiveDocument.TransactionManager.TopTransaction; - Env.Print(tr4.Transaction == tr5); - Env.Print(tr5 == tr7); - var trm = HostApplicationServices.WorkingDatabase.TransactionManager; - //var ptt = tr.GetObject(pl).GetClosestPointTo(pt,false); - //var pt1 = new Point3d(0, 0.00000000000001, 0); - //var pt2 = new Point3d(0, 0.00001, 0); - //Env.Print(Tolerance.Global.EqualPoint); - //Env.Print(pt1.IsEqualTo(pt2).ToString()); - //Env.Print(pt1.IsEqualTo(pt2,new Tolerance(0.0,1e-6)).ToString()); - //Env.Print((pt1 == pt2).ToString()); - //Env.Print((pt1 != pt2).ToString()); - - - - } - - - public Database Getdb() - { - var db = Application.DocumentManager.MdiActiveDocument.Database; - return db; - } - - - public Document Getdoc() - { - var doc = Application.DocumentManager.MdiActiveDocument; - return doc; - } - - //public override void Initialize() - //{ - // Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\nload...."); - //} - - //public override void Terminate() - //{ - // Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\nunload...."); - //} -} - - -public class BlockImportClass -{ - - [CommandMethod("CBLL")] - public void cbll() - { - string filename = @"C:\Users\vic\Desktop\Drawing1.dwg"; - using var tr = new DBTrans(); - using var tr1 = new DBTrans(filename); - //tr.BlockTable.GetBlockFrom(filename, true); - string blkdefname = SymbolUtilityServices.RepairSymbolName(SymbolUtilityServices.GetSymbolNameFromPathName(filename, "dwg"), false); - tr.Database.Insert(blkdefname, tr1.Database, false); //插入了块定义,未插入块参照 - - } - - - [CommandMethod("CBL")] - public void CombineBlocksIntoLibrary() - { - Document doc = - Application.DocumentManager.MdiActiveDocument; - Editor ed = doc.Editor; - Database destDb = doc.Database; - - // Get name of folder from which to load and import blocks - - PromptResult pr = - ed.GetString("\nEnter the folder of source drawings: "); - - if (pr.Status != PromptStatus.OK) - return; - string pathName = pr.StringResult; - - // Check the folder exists - - if (!Directory.Exists(pathName)) - { - ed.WriteMessage( - "\nDirectory does not exist: {0}", pathName - ); - return; - } - - // Get the names of our DWG files in that folder - - string[] fileNames = Directory.GetFiles(pathName, "*.dwg"); - - // A counter for the files we've imported - - int imported = 0, failed = 0; - - // For each file in our list - - foreach (string fileName in fileNames) - { - // Double-check we have a DWG file (probably unnecessary) - - if (fileName.EndsWith( - ".dwg", - StringComparison.InvariantCultureIgnoreCase - ) - ) - { - // Catch exceptions at the file level to allow skipping - - try - { - // Suggestion from Thorsten Meinecke... - - string destName = - SymbolUtilityServices.GetSymbolNameFromPathName( - fileName, "dwg" - ); - - // And from Dan Glassman... - - destName = - SymbolUtilityServices.RepairSymbolName( - destName, false - ); - - // Create a source database to load the DWG into - - using (Database db = new Database(false, true)) - { - // Read the DWG into our side database - - db.ReadDwgFile(fileName, FileShare.Read, true, ""); - bool isAnno = db.AnnotativeDwg; - - // Insert it into the destination database as - // a named block definition - - ObjectId btrId = destDb.Insert( - destName, - db, - false - ); - - if (isAnno) - { - // If an annotative block, open the resultant BTR - // and set its annotative definition status - - Transaction tr = - destDb.TransactionManager.StartTransaction(); - using (tr) - { - BlockTableRecord btr = - (BlockTableRecord)tr.GetObject( - btrId, - OpenMode.ForWrite - ); - btr.Annotative = AnnotativeStates.True; - tr.Commit(); - } - } - - // Print message and increment imported block counter - - ed.WriteMessage("\nImported from \"{0}\".", fileName); - imported++; - } - } - catch (System.Exception ex) - { - ed.WriteMessage( - "\nProblem importing \"{0}\": {1} - file skipped.", - fileName, ex.Message - ); - failed++; - } - } - } - - ed.WriteMessage( - "\nImported block definitions from {0} files{1} in " + - "\"{2}\" into the current drawing.", - imported, - failed > 0 ? " (" + failed + " failed)" : "", - pathName - ); - } -} - diff --git a/tests/DBTrans.test/testConvexHull.cs b/tests/DBTrans.test/testConvexHull.cs deleted file mode 100644 index ee792c69346091ca09c1319e882ac23169433dba..0000000000000000000000000000000000000000 --- a/tests/DBTrans.test/testConvexHull.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using Autodesk.AutoCAD.Runtime; -using Autodesk.AutoCAD.Geometry; -using IFoxCAD.Cad; -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.Colors; - -namespace test -{ - public class testConvexHull - { - [CommandMethod("testch")] - public void testch() - { - //using var tr = new DBTrans(); - //var pts = new List(); - //var flag = true; - //while (flag) - //{ - // var pt = tr.Editor.GetPoint("qudian"); - // if (pt.Status == Autodesk.AutoCAD.EditorInput.PromptStatus.OK) - // { - // pts.Add(pt.Value); - // tr.CurrentSpace.AddEntity(new DBPoint(pt.Value)); - // } - // else - // { - // flag = false; - // } - - //} - - //var ptt = ConvexHull.GetConvexHull(pts); - - //Polyline pl = new Polyline(); - //for (int i = 0; i < ptt.Count; i++) - //{ - // pl.AddVertexAt(i, ptt[i].Point2d(), 0, 0, 0); - //} - ////pl.AddVertexAt(0, new Point2d(0, 0), 0, 0, 0); - ////pl.AddVertexAt(1, new Point2d(10, 10), 0, 0, 0); - ////pl.AddVertexAt(2, new Point2d(20, 20), 0, 0, 0); - ////pl.AddVertexAt(3, new Point2d(30, 30), 0, 0, 0); - ////pl.AddVertexAt(4, new Point2d(40, 40), 0, 0, 0); - //pl.Closed = true; - //pl.Color = Color.FromColorIndex(ColorMethod.ByColor, 6); - //tr.CurrentSpace.AddEntity(pl); - - //var a1 = GeometryEx.GetArea(new Point2d(0, 0), new Point2d(1, 0), new Point2d(1, 1)); - //var a2 = ConvexHull.cross(new Point3d(0, 0, 0), new Point3d(1, 0, 0), new Point3d(1, 1, 0)); - //tr.Editor.WriteMessage(a1.ToString()); - //tr.Editor.WriteMessage(a2.ToString()); - - - //var vec1 = new Vector2d(1, 1); - //var vec2 = new Vector2d(-1, 1); - - //var vec3 = vec1.GetPerpendicularVector(); - //var vec4 = vec2.GetPerpendicularVector(); - - //var area1 = vec2.DotProduct(vec1.GetPerpendicularVector()); - //var area2 = vec1.DotProduct(vec2.GetPerpendicularVector()); - - //var area3 = vec2.DotProduct(vec1); - //var area4 = vec1.DotProduct(vec2); - - var area5 = GeometryEx.GetArea(new List { new Point2d(0, 0), new Point2d(1, 1), new Point2d(-1, 1) }); - - var area6 = GeometryEx.GetArea(new List { new Point2d(0, 0), new Point2d(-1, 1), new Point2d(1, 1) }); - //Env.Editor.WriteMessage($"vec1 的法向量= {vec3} \n"); - //Env.Editor.WriteMessage($"vec2 的法向量= {vec4} \n"); - - //Env.Editor.WriteMessage($"vec2 点乘 vec1的法向量= {area1} \n"); - //Env.Editor.WriteMessage($"vec1 点乘 vec2的法向量= {area2} \n"); - - //Env.Editor.WriteMessage($"vec2 点乘 vec1= {area3} \n"); - //Env.Editor.WriteMessage($"vec1 点乘 vec2= {area4} \n"); - - Env.Editor.WriteMessage($"点集的有向面积:{area5} \n"); - Env.Editor.WriteMessage($"点集的有向面积:{area6} \n"); - } - } -} diff --git a/tests/DBTrans.test/testeditor.cs b/tests/DBTrans.test/testeditor.cs deleted file mode 100644 index 397d5c475c751a499aaf2a90781ae54d8252a5f8..0000000000000000000000000000000000000000 --- a/tests/DBTrans.test/testeditor.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Autodesk.AutoCAD.ApplicationServices; -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.EditorInput; -using Autodesk.AutoCAD.Geometry; -using Autodesk.AutoCAD.Runtime; -using IFoxCAD.Cad; -namespace test -{ - public class testeditor - { - [CommandMethod("tested")] - public void tested() - { - var pts = new List - { - new Point2d(0,0), - new Point2d(0,1), - new Point2d(1,1), - new Point2d(1,0) - }; - var res = EditorEx.GetLines(pts, false); - var res1 = EditorEx.GetLines(pts, true); - var res2 = pts.Select(pt => new TypedValue((int)LispDataType.Point2d, pt)).ToList(); - - Editor ed = Application.DocumentManager.MdiActiveDocument.Editor; - var pt = ed.GetPoint("qudiam", new Point3d(0, 0, 0)); - var d = ed.GetDouble("qudoule"); - var i = ed.GetInteger("quint"); - var s = ed.GetString("qustr"); - Env.Editor.WriteMessage(""); - } - } -} diff --git a/tests/DBTrans.test/testenv.cs b/tests/DBTrans.test/testenv.cs deleted file mode 100644 index 274d5d84d8c6dbe7fac56649d76b853c305a2ca0..0000000000000000000000000000000000000000 --- a/tests/DBTrans.test/testenv.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using IFoxCAD.Cad; -using Autodesk.AutoCAD.Runtime; -using Autodesk.AutoCAD.ApplicationServices; -using Autodesk.AutoCAD.DatabaseServices; - -namespace test -{ - public class testenv - { - [CommandMethod("testenum")] - public void testenum() - { - - Env.CmdEcho = true; - - } - [CommandMethod("testenum1")] - public void testenum1() - { - - Env.CmdEcho = false; - - } - - [CommandMethod("testdimblk")] - public void testdimblk() - { - - Env.Dimblk = Env.DimblkType.Dot; - Env.Dimblk = Env.DimblkType.Defult; - - } - [CommandMethod("testdimblk1")] - public void testdimblk1() - { - var dim = Env.Dimblk; - Env.Editor.WriteMessage(dim.ToString()); - - } - - [CommandMethod("testosmode")] - public void testosmode() - { - // 设置osmode变量,多个值用逻辑或 - Env.OSMode = Env.OSModeType.End | Env.OSModeType.Middle; - // 也可以直接写数值,进行强转 - Env.OSMode = (Env.OSModeType)5179; - // 追加模式 - Env.OSMode |= Env.OSModeType.Center; - //检查是否有某个模式 - var os = Env.OSMode.Include(Env.OSModeType.Center); - // 取消某个模式 - Env.OSMode ^= Env.OSModeType.Center; - Env.Editor.WriteMessage(Env.OSMode.ToString()); - } - [CommandMethod("testosmode1")] - public void testosmode1() - { - var dim = Env.OSMode; - Env.Editor.WriteMessage(dim.ToString()); - - } - - [CommandMethod("testzoom")] - public void testzoom() - { - using var tr = new DBTrans(); - var res = Env.Editor.GetEntity("\npick ent:"); - if (res.Status == Autodesk.AutoCAD.EditorInput.PromptStatus.OK) - { - Env.Editor.ZoomObject(res.ObjectId.GetObject()); - } - - - } - } -} diff --git a/tests/DBTrans.test/testid.cs b/tests/DBTrans.test/testid.cs deleted file mode 100644 index 91809b4cfd9caa7f24c87e96dd349ce9d8b4289f..0000000000000000000000000000000000000000 --- a/tests/DBTrans.test/testid.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using Autodesk.AutoCAD.ApplicationServices; -using Autodesk.AutoCAD.Colors; -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.EditorInput; -using Autodesk.AutoCAD.Geometry; -using Autodesk.AutoCAD.Runtime; - -using IFoxCAD.Cad; -namespace test -{ - public class Testid - { - [CommandMethod("testid")] - public void TestId() - { - using var tr = new DBTrans(); - Line line = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - tr.CurrentSpace.AddEntity(line); - tr.Dispose(); - - var res = Env.Editor.GetEntity("\npick ent:"); - if (res.Status == PromptStatus.OK) - { - res.ObjectId.Erase(); - } - //using (var tr = new DBTrans()) - //{ - // var res = Env.Editor.GetEntity("\npick ent:"); - // if(res.Status == PromptStatus.OK) - // { - // res.ObjectId.Erase(); - // } - - //} - - } - - } -} diff --git a/tests/DBTrans.test/testselectfilter.cs b/tests/DBTrans.test/testselectfilter.cs deleted file mode 100644 index 9a4289b831cdca89d008116f4ec73632be791dde..0000000000000000000000000000000000000000 --- a/tests/DBTrans.test/testselectfilter.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using Autodesk.AutoCAD.Geometry; -using Autodesk.AutoCAD.Runtime; -using Autodesk.AutoCAD.DatabaseServices; -using IFoxCAD.Cad; -using Autodesk.AutoCAD.EditorInput; - -namespace test -{ - public class testselectfilter - { - [CommandMethod("testfilter")] - public void testfilter() - { - - var p = new Point3d(10, 10, 0); - var f = OpFilter.Bulid( - e =>!(OpFilter.Op.Dxf(0) == "line" & OpFilter.Op.Dxf(8) == "0") - | OpFilter.Op.Dxf(0) != "circle" & OpFilter.Op.Dxf(8) == "2" & OpFilter.Op.Dxf(10) >= p); - - - var f2 = OpFilter.Bulid( - e => OpFilter.Op.Or( - !OpFilter.Op.And(OpFilter.Op.Dxf(0) == "line", OpFilter.Op.Dxf(8) == "0"), - OpFilter.Op.And(OpFilter.Op.Dxf(0) != "circle", OpFilter.Op.Dxf(8) == "2", - OpFilter.Op.Dxf(10) <= new Point3d(10, 10, 0)))); - - SelectionFilter f3 = f; - SelectionFilter f4 = f2; - - Env.Editor.WriteMessage(""); - } - - [CommandMethod("testselectanpoint")] - public void testselectanpoint() - { - - - var sel2 = Env.Editor.SelectAtPoint(new Point3d(0, 0, 0)); - - Env.Editor.WriteMessage(""); - } - } -} diff --git a/tests/DBTrans.test/wpf/TestViewModel.cs b/tests/DBTrans.test/wpf/TestViewModel.cs deleted file mode 100644 index dc6fce0b89686f00aa59997d5989f8decbd25e67..0000000000000000000000000000000000000000 --- a/tests/DBTrans.test/wpf/TestViewModel.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Input; - -using IFoxCAD.WPF; - -namespace test.wpf -{ - class TestViewModel : ViewModelBase - { - - private string name; - - public string Name - { - get { return name; } - set { Set(ref name, value); } - } - - private RelayCommand clickCommand; - - public RelayCommand ClickCommand - { - get - { - if (clickCommand is null) - { - clickCommand = new( - execute => Name = "hello " + Name, - can => !string.IsNullOrEmpty(Name)); - } - return clickCommand; - } - } - - private bool receiveMouseMove; - - public bool ReceiveMouseMove - { - get { return receiveMouseMove; } - set { Set(ref receiveMouseMove, value); } - } - - private string tipText; - - public string TipText - { - get { return tipText; } - set { Set(ref tipText, value); } - } - - private RelayCommand loadedCommand; - public RelayCommand LoadCommand - { - get - { - if (loadedCommand is null) - { - loadedCommand = new( - execute => MessageBox.Show("程序加载完毕")); - } - return loadedCommand; - } - } - - private RelayCommand mouseMoveCommand; - - public RelayCommand MouseMoveCommand - { - get - { - if (mouseMoveCommand is null) - { - mouseMoveCommand = new( - execute => - { - var pt = execute.GetPosition(execute.Device.Target); - var left = "左键放开"; - var mid = "中键放开"; - var right = "右键放开"; - - if (execute.LeftButton == MouseButtonState.Pressed) - { - left = "左键放下"; - } - if (execute.MiddleButton == MouseButtonState.Pressed) - { - mid = "中键放下"; - } - if (execute.RightButton == MouseButtonState.Pressed) - { - right = "右键放下"; - } - TipText = $"当前鼠标位置:X={pt.X},Y={pt.Y}。当前鼠标状态:{left}、{mid}、{right}"; - }, - can => ReceiveMouseMove); - } - return mouseMoveCommand; - } - } - - - - - - - public TestViewModel() - { - Name = "world"; - } - - - - } -} diff --git a/tests/TestAcad09plus/GlobalUsings.cs b/tests/TestAcad09plus/GlobalUsings.cs new file mode 100644 index 0000000000000000000000000000000000000000..82e3ea728deb8741d28701daa3b5eff8e481fae1 --- /dev/null +++ b/tests/TestAcad09plus/GlobalUsings.cs @@ -0,0 +1,54 @@ +/// 系统引用 +global using System; +global using System.Collections; +global using System.Collections.Generic; +global using System.IO; +global using System.Linq; +global using System.Text; +global using System.Reflection; +global using System.Text.RegularExpressions; +global using Microsoft.Win32; +global using System.ComponentModel; +global using System.Runtime.InteropServices; +global using System.Collections.Specialized; + +global using Exception = System.Exception; + +global using Registry = Microsoft.Win32.Registry; +global using RegistryKey = Microsoft.Win32.RegistryKey; + +/// autocad 引用 +global using Autodesk.AutoCAD.ApplicationServices; +global using Autodesk.AutoCAD.EditorInput; +global using Autodesk.AutoCAD.Colors; +global using Autodesk.AutoCAD.DatabaseServices; +global using Autodesk.AutoCAD.Geometry; +global using Autodesk.AutoCAD.Runtime; +global using Acap = Autodesk.AutoCAD.ApplicationServices.Application; +global using Acgi = Autodesk.AutoCAD.GraphicsInterface; + +global using Autodesk.AutoCAD.DatabaseServices.Filters; +global using Autodesk.AutoCAD; + +// jig命名空间会引起Viewport/Polyline等等重义,最好逐个引入 using Autodesk.AutoCAD.GraphicsInterface +global using WorldDraw = Autodesk.AutoCAD.GraphicsInterface.WorldDraw; +global using Manager = Autodesk.AutoCAD.GraphicsSystem.Manager; +global using Group = Autodesk.AutoCAD.DatabaseServices.Group; +global using Viewport = Autodesk.AutoCAD.DatabaseServices.Viewport; +global using Autodesk.AutoCAD.GraphicsInterface; +global using Polyline = Autodesk.AutoCAD.DatabaseServices.Polyline; +global using Cad_DwgFiler = Autodesk.AutoCAD.DatabaseServices.DwgFiler; +global using Cad_DxfFiler = Autodesk.AutoCAD.DatabaseServices.DxfFiler; +global using Cad_ErrorStatus = Autodesk.AutoCAD.Runtime.ErrorStatus; + + +/// ifoxcad +global using IFoxCAD.Cad; +global using IFoxCAD.Basal; +#if !ac2008 +global using IFoxCAD.WPF; +#endif + +#if !NewtonsoftJson +global using System.Web.Script.Serialization; +#endif \ No newline at end of file diff --git a/tests/TestAcad09plus/Properties/Resources.Designer.cs b/tests/TestAcad09plus/Properties/Resources.Designer.cs new file mode 100644 index 0000000000000000000000000000000000000000..161702059b435207c6a4ea08f19fcd8d4360891b --- /dev/null +++ b/tests/TestAcad09plus/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 +// +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 +// +//------------------------------------------------------------------------------ + +namespace Test.Properties { + using System; + + + /// + /// 一个强类型的资源类,用于查找本地化的字符串等。 + /// + // 此类是由 StronglyTypedResourceBuilder + // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 + // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen + // (以 /str 作为命令选项),或重新生成 VS 项目。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// 返回此类使用的缓存的 ResourceManager 实例。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Test.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 重写当前线程的 CurrentUICulture 属性,对 + /// 使用此强类型资源类的所有资源查找执行重写。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/tests/TestAcad09plus/Properties/Resources.resx b/tests/TestAcad09plus/Properties/Resources.resx new file mode 100644 index 0000000000000000000000000000000000000000..4fdb1b6aff69ba96d81420fab7a92b738c17f074 --- /dev/null +++ b/tests/TestAcad09plus/Properties/Resources.resx @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/tests/DBTrans.test/Properties/launchSettings.json b/tests/TestAcad09plus/Properties/launchSettings.json similarity index 90% rename from tests/DBTrans.test/Properties/launchSettings.json rename to tests/TestAcad09plus/Properties/launchSettings.json index 1ecc024ca602c378ad1a9c276e05e486e1296b68..4b464c3296d64041c377007f682aad0199fa1e8e 100644 --- a/tests/DBTrans.test/Properties/launchSettings.json +++ b/tests/TestAcad09plus/Properties/launchSettings.json @@ -1,6 +1,6 @@ { "profiles": { - "DBTrans.test": { + "Test": { "commandName": "Executable", "executablePath": "C:\\Program Files\\Autodesk\\AutoCAD 2021\\acad.exe", "commandLineArgs": "/nologo", diff --git a/tests/TestAcad09plus/TestAcad09plus.csproj b/tests/TestAcad09plus/TestAcad09plus.csproj new file mode 100644 index 0000000000000000000000000000000000000000..35a06362e9f6b69a70612160528172e0afc6c56e --- /dev/null +++ b/tests/TestAcad09plus/TestAcad09plus.csproj @@ -0,0 +1,40 @@ + + + preview + enable + + NET45 + true + true + x64 + True + + + + $(Configuration);acad;ac2015 + + + + + + + + + + TestView.xaml + + + + + + Designer + + + + + + + + + + \ No newline at end of file diff --git a/tests/TestAcad09plus/wpf/Cmd.cs b/tests/TestAcad09plus/wpf/Cmd.cs new file mode 100644 index 0000000000000000000000000000000000000000..9f8140197f37f62958433ca2df2943df35c99ffd --- /dev/null +++ b/tests/TestAcad09plus/wpf/Cmd.cs @@ -0,0 +1,11 @@ +namespace Test.wpf; + +public class Cmd +{ + [CommandMethod(nameof(Test_WPf))] + public void Test_WPf() + { + var test = new TestView(); + Acap.ShowModalWindow(test); + } +} \ No newline at end of file diff --git a/tests/DBTrans.test/wpf/TestView.xaml b/tests/TestAcad09plus/wpf/TestView.xaml similarity index 87% rename from tests/DBTrans.test/wpf/TestView.xaml rename to tests/TestAcad09plus/wpf/TestView.xaml index 42cb3042d66e18155391cba37eef9896f4c67c8a..e51e2ac54a4334535e5cd68c44e196e6b7a13905 100644 --- a/tests/DBTrans.test/wpf/TestView.xaml +++ b/tests/TestAcad09plus/wpf/TestView.xaml @@ -1,10 +1,10 @@ - +/// TestView.xaml 的交互逻辑 +/// +public partial class TestView : Window { - /// - /// TestView.xaml 的交互逻辑 - /// - public partial class TestView : Window + public TestView() { - public TestView() - { - InitializeComponent(); - DataContext = new TestViewModel(); - } + InitializeComponent(); + DataContext = new TestViewModel(); } } diff --git a/tests/TestAcad09plus/wpf/TestViewModel.cs b/tests/TestAcad09plus/wpf/TestViewModel.cs new file mode 100644 index 0000000000000000000000000000000000000000..e726029a876c62a05dfe1e8127ef3f551360bbdd --- /dev/null +++ b/tests/TestAcad09plus/wpf/TestViewModel.cs @@ -0,0 +1,82 @@ +namespace Test.wpf; + +using System.Windows; +using System.Windows.Input; + + +class TestViewModel : ViewModelBase +{ + string? name; + public string? Name + { + get { return name; } + set { Set(ref name, value); } + } + + private RelayCommand? clickCommand; + + public RelayCommand? ClickCommand + { + get + { + clickCommand ??= + new(execute => Name = "hello " + Name, + can => !string.IsNullOrEmpty(Name)); + return clickCommand; + } + } + + private bool receiveMouseMove; + public bool ReceiveMouseMove + { + get { return receiveMouseMove; } + set { Set(ref receiveMouseMove, value); } + } + + private string? tipText; + public string? TipText + { + get { return tipText; } + set { Set(ref tipText, value); } + } + + private RelayCommand? loadedCommand; + public RelayCommand? LoadCommand + { + get + { + loadedCommand ??= new(execute => MessageBox.Show("程序加载完毕")); + return loadedCommand; + } + } + + private RelayCommand? mouseMoveCommand; + public RelayCommand? MouseMoveCommand + { + get + { + mouseMoveCommand ??= new( + execute => { + var pt = execute.GetPosition(execute.Device.Target); + var left = "左键放开"; + var mid = "中键放开"; + var right = "右键放开"; + + if (execute.LeftButton == MouseButtonState.Pressed) + left = "左键放下"; + if (execute.MiddleButton == MouseButtonState.Pressed) + mid = "中键放下"; + if (execute.RightButton == MouseButtonState.Pressed) + right = "右键放下"; + TipText = $"当前鼠标位置:X={pt.X},Y={pt.Y}。当前鼠标状态:{left}、{mid}、{right}"; + }, + can => ReceiveMouseMove); + return mouseMoveCommand; + } + } + + public TestViewModel() + { + Name = "world"; + } +} diff --git "a/tests/TestAcad09plus/\346\213\211\344\274\270\345\241\253\345\205\205/01.\346\213\211\344\274\270\345\241\253\345\205\205\345\217\263\351\224\256\350\217\234\345\215\225.cs" "b/tests/TestAcad09plus/\346\213\211\344\274\270\345\241\253\345\205\205/01.\346\213\211\344\274\270\345\241\253\345\205\205\345\217\263\351\224\256\350\217\234\345\215\225.cs" new file mode 100644 index 0000000000000000000000000000000000000000..6afeef78254032837e675647c965c3a5142dba8d --- /dev/null +++ "b/tests/TestAcad09plus/\346\213\211\344\274\270\345\241\253\345\205\205/01.\346\213\211\344\274\270\345\241\253\345\205\205\345\217\263\351\224\256\350\217\234\345\215\225.cs" @@ -0,0 +1,184 @@ +using Autodesk.AutoCAD.Windows; +using System.Diagnostics; +using static IFoxCAD.Cad.PostCmd; +using MenuItem = Autodesk.AutoCAD.Windows.MenuItem; + +namespace JoinBoxAcad; +public class HatchPick +{ + [IFoxInitialize] + [CommandMethod(nameof(HatchPickInit))] + public void HatchPickInit() + { + Env.Printl($"※拉伸填充控制※\n{nameof(HatchPickSwitch)} - 切换开关\n"); + + if (Debugger.IsAttached) + Env.SetVar("hpscale", 22); + // 设定高版本双击填充启动修改面板 + // JoinBoxAcad.Menu.Cui.CuiInit(); + LoadHelper(true); + } + + // 只能命令卸载哦,因为关闭cad是不需要卸载的 + [CommandMethod(nameof(UnLoadHatchPick))] + public void UnLoadHatchPick() + { + LoadHelper(false); + } + + [CommandMethod(nameof(HatchPickSwitch))] + public void HatchPickSwitch() + { + if (HatchPickEvent.State.IsStop) + { + Env.Printl("已经 卸载 拉伸填充控制+ 用: " + nameof(HatchPickInit) + " 加载"); + return; + } + + if (HatchPickEvent.State.IsRun) + HatchPickEvent.State.Break(); + else + HatchPickEvent.State.Start(); + Env.Printl("已经 " + (HatchPickEvent.State.IsRun ? "开启" : "禁用") + " 拉伸填充控制+"); + } + + + internal static Dictionary MapDocHatchPickEvent = new(); + void LoadHelper(bool isLoad) + { + var dm = Acap.DocumentManager; + if (dm is null || dm.Count == 0) + return; + if (isLoad) + { + dm.DocumentCreated += Dm_DocumentCreated; + Dm_DocumentCreated(); // 自执行一次 + AddRightClickMenu(); + HatchPickEvent.AddInit(); + } + else + { + HatchPickEvent.RemoveInit(); + dm.DocumentCreated -= Dm_DocumentCreated; + UnDocumentCreated(); + HatchPick.RemoveRightClickMenu(); + } + } + + /// + /// 文档创建反应器 + /// + void Dm_DocumentCreated(object? sender = null, DocumentCollectionEventArgs? e = null) + { + var dm = Acap.DocumentManager; + if (dm is null || dm.Count == 0) + return; + var doc = dm.MdiActiveDocument; + if (doc is null) + return; + if (!MapDocHatchPickEvent.ContainsKey(doc)) + MapDocHatchPickEvent.Add(doc, new HatchPickEvent(doc)); + } + + /// + /// 卸载文档创建反应器 + /// + static void UnDocumentCreated() + { + var dm = Acap.DocumentManager; + if (dm is null || dm.Count == 0) + return; + var doc = dm.MdiActiveDocument; + if (doc is null) + return; + if (MapDocHatchPickEvent.ContainsKey(doc)) + { + MapDocHatchPickEvent[doc].Dispose(); + MapDocHatchPickEvent.Remove(doc); + } + } + + + + private const string V0 = "拉伸填充-开"; + private const string V1 = "拉伸填充-关";// (面板的独立填充必须关,否则致命错误) + private const string V2 = "独立填充";//(快捷,不需要关...目前还是会崩溃) + static readonly HashSet _menuItems = new() { V0, V1, V2 }; + static readonly ContextMenuExtension _contextMenu = new() { Title = "惊惊盒子" }; + /// + /// 添加右键菜单 + /// + void AddRightClickMenu() + { + // 右键菜单 + foreach (var item in _menuItems) + { + MenuItem mi = new(item); // 添加菜单项 + mi.Click += MenuItemClick; // 添加单击事件 + + //mi.MenuItems.Add(new MenuItem("改颜色1")); // 二级菜单 + _contextMenu.MenuItems.Add(mi); // 提交 + } + Acap.AddDefaultContextMenuExtension(_contextMenu);// 添加默认上下文菜单扩展,带标题的 + + //加入到某一种对象的右键菜单中 + //RXClass rxClass = Entity.GetClass(typeof(BlockReference)); + //Acap.AddObjectContextMenuExtension(rxClass, contextMenu); + //// 选择实体右键菜单才有用. 获得实体所属的RXClass类型 + // RXClass rx = RXObject.GetClass(typeof(Entity)); + // Acap.AddObjectContextMenuExtension(rx, contextMenu); // 这里为什么又可以不带标题 + } + + /// + /// 卸载右键菜单 + /// + static void RemoveRightClickMenu() + { + if (_contextMenu is null) + return; + Acap.RemoveDefaultContextMenuExtension(_contextMenu); + } + + /// + /// 右键点击触发 + /// + /// + /// + void MenuItemClick(object sender, EventArgs e) + { + // 获取发出命令的快捷菜单项 + if (sender is not MenuItem mi) + return; + + // 根据快捷菜单项的名字,分别调用对应的命令 + if (!_menuItems.Contains(mi.Text)) + return; + + switch (mi.Text) + { + case V0: + HatchPickEvent.State.Start(); + break; + case V1: + HatchPickEvent.State.Break(); + break; + case V2: + { + HatchPickEvent.State.Break(); + PromptSelectionOptions pso = new() + { + AllowDuplicates = true, // 不允许重复选择 + SingleOnly = true, // 隐含窗口选择(不需要空格确认) + }; + var ssPsr = Env.Editor.GetSelection(pso, HatchPickEvent.FilterForHatch); + if (ssPsr.Status != PromptStatus.OK) + return; + + Env.Editor.SetImpliedSelection(ssPsr.Value.GetObjectIds()); + SendCommand("-hatchedit H ", RunCmdFlag.AcedPostCommand); + HatchPickEvent.State.Start(); + } + break; + } + } +} \ No newline at end of file diff --git "a/tests/TestAcad09plus/\346\213\211\344\274\270\345\241\253\345\205\205/02.\346\213\211\344\274\270\345\241\253\345\205\205\344\272\213\344\273\266.cs" "b/tests/TestAcad09plus/\346\213\211\344\274\270\345\241\253\345\205\205/02.\346\213\211\344\274\270\345\241\253\345\205\205\344\272\213\344\273\266.cs" new file mode 100644 index 0000000000000000000000000000000000000000..f08eea85ab5352a81531811f6dbad0fc969ac15e --- /dev/null +++ "b/tests/TestAcad09plus/\346\213\211\344\274\270\345\241\253\345\205\205/02.\346\213\211\344\274\270\345\241\253\345\205\205\344\272\213\344\273\266.cs" @@ -0,0 +1,1029 @@ +using System.Drawing; +using System.Runtime.CompilerServices; +using System.Windows.Forms; +using static IFoxCAD.Cad.PostCmd; + +namespace JoinBoxAcad; + +public class HatchPickEvent : IDisposable +{ + #region 静态成员 + public static ProState State = new(); + // 选择集过滤器 + public static readonly SelectionFilter FilterForHatch = new(new TypedValue[] { new TypedValue((int)DxfCode.Start, "HATCH") }); + // 临时标记(重设选择集会触发一次选择集反应器) + static bool _selectChangedStop = false; + // 临时选择集用 + static List _hatchIds = new(); + // 获取夹点在哪个图元边界上面,是为true + static bool _pickInBo = false; + static bool _vetoProperties = false; + private Tolerance _tol = new(1e-6, 1e-6); + + public static void AddInit() + { + HatchHook.SetHook(); + // 全局事件重复+=是不需要担心的 + Acap.DocumentManager.DocumentLockModeChanged += Dm_VetoCommand; + State.Start(); + } + + public static void RemoveInit() + { + State.Stop(); + Acap.DocumentManager.DocumentLockModeChanged -= Dm_VetoCommand; + HatchHook.RemoveHook(); + } + + /// + /// 反应器->命令否决触发命令前(不可锁文档) + /// + /// + /// + public static void Dm_VetoCommand(object sender, DocumentLockModeChangedEventArgs e) + { + if (!State.IsRun) + return; + if (string.IsNullOrEmpty(e.GlobalCommandName) || e.GlobalCommandName == "#") + return; + switch (e.GlobalCommandName.ToUpper()) + { + case "PROPERTIES": // 特性面板 + { + // 事件顺序问题: + // 开cad之后第一次双击必弹出特性面板 + // 所以这里直接删除填充边界 + HatchPick.MapDocHatchPickEvent[e.Document].SetPropertiesInfoTask(); + if (_vetoProperties) + { + Debugx.Printl("Dm_VetoCommand 否决了"); + e.Veto(); + _vetoProperties = false; + // 发送编辑填充命令 + SendCommand("_hatchedit ", RunCmdFlag.AcedPostCommand); + return; + } + Debugx.Printl("Dm_VetoCommand 没否决"); + } + break; + } + } + + + #endregion + + #region 动态成员 + /// + /// 在位编辑器 记录全图选择集的填充 + /// + readonly HashSet _refeditSsgeting = new(); + /// + /// 在位编辑器 获取当前选择集做差集=>内部填充 + /// + readonly HashSet _refeditSsgeted = new(); + // map<填充id,边界转换器> + readonly Dictionary _mapHatchConv = new(); + readonly Document _doc; + public HatchPickEvent(Document doc) + { + _doc = doc; + LoadHelper(true); + } + + void LoadHelper(bool isLoad) + { + if (isLoad) + { + _doc.ImpliedSelectionChanged += Md_ImpliedSelectionChanged; + _doc.CommandWillStart += Md_CommandWillStart; + _doc.LispWillStart += Md_LispWillStart; + _doc.CommandEnded += Md_CommandEnded; + _doc.Database.ObjectErased += DB_ObjectErased; + _doc.Database.ObjectModified += DB_ObjectModified; + } + else + { + _doc.ImpliedSelectionChanged -= Md_ImpliedSelectionChanged; + _doc.CommandWillStart -= Md_CommandWillStart; + _doc.LispWillStart -= Md_LispWillStart; + _doc.CommandEnded -= Md_CommandEnded; + _doc.Database.ObjectErased -= DB_ObjectErased; + _doc.Database.ObjectModified -= DB_ObjectModified; + } + } + #endregion + + #region 事件 + /// + /// 反应器->command命令完成前 + /// + /// + /// + void Md_CommandWillStart(object sender, CommandEventArgs e) + { + if (!State.IsRun) + return; + + // 此处无法使用文档锁,否则将导致文档锁无法释放,然后ctrl+z失败 + var cmdup = e.GlobalCommandName.ToUpper(); + Debugx.Printl("Md_CommandWillStart::" + cmdup); + + switch (cmdup) + { + case "REFEDIT": + { + // 在位编辑命令,执行前,获取当前空间所有填充 + var prompt = Env.Editor.SelectAll(FilterForHatch); + if (prompt.Status != PromptStatus.OK) + return; + using DBTrans tr = new(); + GetHatchIds(prompt); + if (_hatchIds.Count == 0) + return; + for (int i = 0; i < _hatchIds.Count; i++) + _refeditSsgeting.Add(_hatchIds[i]); + } + break; + } + + // 拉伸夹点命令前触发 + if (cmdup != "GRIP_STRETCH") + { + EraseAllHatchBorders(); + } + else + { + var mp = HatchHook.MouseStartPoint; + var mouseStart = Screen.ScreenToCad(mp); + Debugx.Printl("mouseStart,屏幕点::" + mp); + Debugx.Printl("mouseStart,cad点::" + mouseStart); + + // 获取当前选择的对象,然后提取所有的夹点 + var prompt = Env.Editor.SelectImplied(); + if (prompt.Status != PromptStatus.OK) + return; + using DBTrans tr = new(); + GetHatchIds(prompt); + if (_hatchIds.Count == 0) + return; + + // TODO 屏幕像素点转cad点的误差,要随着视口高度而动态计算....这里的计算可能不太正确 + var tol = (double)Env.GetVar("viewsize") / 10; + Debugx.Printl("tol::" + tol); + + // 0x01 移动了矩形填充中间的夹点,删除边界,并且重新生成填充和边界 + // 0x02 移动了填充边界上的夹点,不处理,然后它会通过关联进行自己修改 + _pickInBo = false; + for (int i = 0; i < _hatchIds.Count; i++) + { + var hatId = _hatchIds[i]; + if (!_mapHatchConv.ContainsKey(hatId)) + continue; + + _mapHatchConv[hatId].BoundaryIds.ForEach((id, idState) => { + var boEnt = tr.GetObject(id); + if (boEnt == null) + return; + + // 获取夹点在哪个图元边界上 + HashSet boPts = new(); + if (boEnt is Circle circle) + { + // 圆形的边界夹点是: 圆心+半径 + var x = circle.Center.X; + var y = circle.Center.Y; + var z = circle.Center.Z; + var r = circle.Radius; + boPts.Add(new(x + r, y, z));//上 + boPts.Add(new(x - r, y, z));//下 + boPts.Add(new(x, y - r, z));//左 + boPts.Add(new(x, y + r, z));//右 + } + else + { + // 获取所有的边点 + // 这里圆形会获取圆心,所以剔除圆形 + var tmp = GetEntityPoint3ds(boEnt); + for (int j = 0; j < tmp.Count; j++) + boPts.Add(tmp[j]); + } + + if (boEnt is Arc arc) + { + if (!arc.StartPoint.IsEqualTo(arc.EndPoint, _tol)) + { + // 圆弧的腰点 + var arc2 = arc.GetPointAtDist(arc.GetDistAtPoint(arc.EndPoint) * 0.5); + boPts.Add(arc2); + } + } + else if (boEnt is Polyline pl) + { + for (int j = 0; j < pl.NumberOfVertices; j++) + { + var bulge = pl.GetBulgeAt(j); + if (bulge == 0.0) + continue; + // 有凸度就是有每段的中点 + var pta = pl.GetPoint2dAt(j); + Point2d ptb; + if (j + 1 < pl.NumberOfVertices) + ptb = pl.GetPoint2dAt(j + 1); + else + ptb = pl.GetPoint2dAt(0); + + var p = MathHelper.GetArcMidPoint(pta, ptb, bulge); + boPts.Add(p.Point3d()); + } + } + + boPts.ForEach((pt, ptState) => { + var dist = pt.DistanceTo(mouseStart); + //Debugx.Printl("pt::" + pt + " dist::" + dist); + if (dist < tol) + { + ptState.Break(); + _pickInBo = true; + } + }); + }); + + // 点在边界上:就不处理了,它会通过cad的关联填充反应器自动修改 + if (_pickInBo) + Debugx.Printl("夹点在边界上"); + else + Debugx.Printl("夹点不在边界上"); + } + } + } + + /// + /// 图元拉伸点 + /// + /// + /// + static List GetEntityPoint3ds(Entity ent) + { + var pts3d = new Point3dCollection(); + ent.GetStretchPoints(pts3d); + return pts3d.Cast().ToList(); + } + + + + + /// + /// 反应器->command命令完成后 + /// + /// + /// + void Md_CommandEnded(object sender, CommandEventArgs e) + { + if (!State.IsRun) + return; + + var cmdup = e.GlobalCommandName.ToUpper(); + switch (cmdup) + { + case "REFEDIT": + { + Debugx.Printl("Md_CommandEnded:: REFEDIT"); + + // 在位编辑命令,执行后,获取当前空间所有填充 + var prompt = Env.Editor.SelectAll(FilterForHatch); + if (prompt.Status != PromptStatus.OK) + return; + + using DBTrans tr = new(); + GetHatchIds(prompt); + if (_hatchIds.Count == 0) + return; + + for (int i = 0; i < _hatchIds.Count; i++) + if (!_refeditSsgeting.Contains(_hatchIds[i]))//Except + _refeditSsgeted.Add(_hatchIds[i]); + + var sb = new StringBuilder(); + foreach (var id in _refeditSsgeted) + sb.AppendLine(id.ToString()); + Env.Printl("块内填充id:" + sb.ToString()); + } + break; + case "REFSET": // 加减在位编辑图元 + { + Debugx.Printl("Md_CommandEnded:: REFSET"); + + // 命令历史的最后一行是:添加/删除 + var last = Env.GetVar("lastprompt").ToString(); + if (last is null) + return; + + // 完成后必然有上次选择集 + var prompt = Env.Editor.SelectPrevious(); + if (prompt.Status != PromptStatus.OK) + return; + using DBTrans tr = new(); + GetHatchIds(prompt); + if (_hatchIds.Count == 0) + return; + + // 就是因为无法遍历到在位编辑的块内图元,只能进行布尔运算 + if (last.Contains("添加") || last.Contains("Added"))// 中英文cad + { + for (int i = 0; i < _hatchIds.Count; i++) + { + _refeditSsgeting.Remove(_hatchIds[i]); + _refeditSsgeted.Add(_hatchIds[i]); + } + return; + } + if (last.Contains("删除") || last.Contains("Removed"))// 中英文cad + { + for (int i = 0; i < _hatchIds.Count; i++) + { + _refeditSsgeted.Remove(_hatchIds[i]); + _refeditSsgeting.Add(_hatchIds[i]); + } + return; + } + } + break; + case "REFCLOSE":// 保存块,清空集合 + { + Debugx.Printl("Md_CommandEnded:: REFCLOSE"); + _refeditSsgeted.Clear(); + _refeditSsgeting.Clear(); + } + break; + case "GRIP_STRETCH":// 拉伸夹点命令后触发 + { + // 夹点在边界上,退出 + if (_pickInBo) + return; + + // 夹点不在边界上: + // cad会平移填充,在这之后,我们删除填充边界,重建填充边界 + var prompt = Env.Editor.SelectImplied(); + if (prompt.Status != PromptStatus.OK) + return; + using DBTrans tr = new(); + GetHatchIds(prompt); + if (_hatchIds.Count == 0) + return; + + // 删除指定填充的边界,并清理关联反应器 + HashSet idsOfSsget = new(); + foreach (var hatId in _hatchIds) + { + idsOfSsget.Add(hatId); + + if (!_mapHatchConv.ContainsKey(hatId)) + continue; + bool clearFlag = false; + _mapHatchConv[hatId].BoundaryIds.ForEach(boId => { + if (!boId.IsOk()) + return; + var boEnt = tr.GetObject(boId); + if (boEnt == null) + return; + if (!HatchPickEnv.IsMeCreate(boEnt)) + return; + boId.Erase(); + clearFlag = true; + }); + + if (!clearFlag) + return; + + _mapHatchConv[hatId].BoundaryIds.Clear(); + + // 清理填充反应器 + var hatch = tr.GetObject(hatId); + if (hatch == null) + return; + using (hatch.ForWrite()) + RemoveAssociative(hatch); + CreatHatchConverter(hatch, idsOfSsget); + } + SetImpliedSelection(idsOfSsget); + } + break; + } + } + + /// + /// 获取选择集上的填充,在缓存内提取 + /// + /// + /// + static void GetHatchIds(PromptSelectionResult psr, DBTrans? tr = null) + { + tr ??= DBTrans.Top; + _hatchIds.Clear(); + var ids = psr.Value.GetObjectIds(); + for (int i = 0; i < ids.Length; i++) + { + var hatch = tr.GetObject(ids[i]); + if (hatch is not null) + _hatchIds.Add(ids[i]); + } + } + + /// + /// 反应器->lisp命令 + /// + void Md_LispWillStart(object sender, LispWillStartEventArgs e) + { + if (!State.IsRun) + return; + + using DBTrans tr = new(doclock: true); + EraseAllHatchBorders(); + } + + /// + /// 反应器->选择集 + /// + /// + /// + void Md_ImpliedSelectionChanged(object sender, EventArgs e) + { + if (!State.IsRun) + return; + + // 此处必须要文档锁 + if (_selectChangedStop) + { + _selectChangedStop = false; + return; + } + Debugx.Printl("Md_ImpliedSelectionChanged"); + + using DBTrans tr = new(doclock: true); + var prompt = Env.Editor.SelectImplied(); + if (prompt.Status != PromptStatus.OK) + { + EraseAllHatchBorders(); + return; + } + + // 获取图层锁定的记录,用于跳过 + Dictionary islocks = new(); + foreach (var layerRecord in tr.LayerTable.GetRecords()) + if (!layerRecord.IsErased)// 08符号表记录保留了这个 + islocks.Add(layerRecord.Name, layerRecord.IsLocked); + + // 遍历选择,创建边界转换器 + // 重设选择集 + HashSet idsOfSsget = new(); + foreach (var entId in prompt.Value.GetObjectIds()) + { + idsOfSsget.Add(entId); + var hatch = tr.GetObject(entId, openLockedLayer: true); + if (hatch is null) + continue; + if (islocks[hatch.Layer]) + continue; + // 重复选择 || 在位编辑外 + if (_mapHatchConv.ContainsKey(entId) || _refeditSsgeting.Contains(entId)) + continue; + CreatHatchConverter(hatch, idsOfSsget); + } + SetImpliedSelection(idsOfSsget); + } + + /// + /// 创建填充和填充边界转换器 + /// + /// + /// + /// + void CreatHatchConverter(Hatch hatch, HashSet outSsgetIds, DBTrans? tr = null) + { + tr ??= DBTrans.Top; + + var hc = new HatchConverter(hatch); + ObjectId newid; + + // 如果边界在图纸上没有删除(删除就不是关联的), + // 那就不创建新的,然后选中它们 + if (hc.BoundaryIds.Count != 0) + { + Debugx.Printl("CreatHatchConverter:: 加入了现有边界到选择集"); + + // 加入选择集 + foreach (var item in hc.BoundaryIds) + outSsgetIds.Add(item); + outSsgetIds.Add(hatch.ObjectId); + newid = hatch.ObjectId; + } + else + { + Debugx.Printl("CreatHatchConverter:: 创建新填充和边界"); + + // 创建新填充和边界 + hc.GetBoundarysData(); + newid = hc.CreateBoundarysAndHatchToMsPs(tr.CurrentSpace, trans: tr); + HatchPickEnv.SetMeXData(newid, hc.BoundaryIds); + + // 清理上次,删除边界和填充 + if (_mapHatchConv.ContainsKey(hatch.ObjectId)) + { + var boIds = _mapHatchConv[hatch.ObjectId].BoundaryIds; + for (int i = 0; i < boIds.Count; i++) + boIds[i].Erase(); + _mapHatchConv.Remove(hatch.ObjectId); + } + // 删除选中的 + hatch.ObjectId.Erase(); + } + + if (!_mapHatchConv.ContainsKey(newid)) + _mapHatchConv.Add(newid, hc); + else + _mapHatchConv[newid] = hc; + + if (newid == hatch.ObjectId) + return; + + // 优先: 块内含有旧的,就加入新的 + if (_refeditSsgeted.Contains(hatch.ObjectId)) + { + _refeditSsgeted.Remove(hatch.ObjectId); + _refeditSsgeted.Add(newid); + } + else if (_refeditSsgeting.Contains(hatch.ObjectId)) + { + _refeditSsgeting.Remove(hatch.ObjectId); + _refeditSsgeting.Add(newid); + } + } + + /// + /// 重设选择集 + /// + /// 加入选择集的成员 + void SetImpliedSelection(HashSet setImpSelect) + { + // 获取填充 + foreach (var id in _mapHatchConv.Keys) + setImpSelect.Add(id); + + // 获取填充边界 + foreach (var item in _mapHatchConv.Values) + foreach (var id in item.BoundaryIds) + setImpSelect.Add(id); + + // 设置选择集,没有标记的话会死循环 + _selectChangedStop = true; + Env.Editor.SetImpliedSelection(setImpSelect.ToArray()); + } + + /// + /// 删除全部填充边界 + /// + void EraseAllHatchBorders() + { + if (_mapHatchConv.Count == 0) + return; + foreach (var dict in _mapHatchConv) + { + dict.Value.BoundaryIds.ForEach(boId => { + if (!boId.IsOk()) + return; + using DBTrans tr = new(database: boId.Database); + var boEnt = tr.GetObject(boId, OpenMode.ForWrite); + if (boEnt == null) + return; + // 删除填充边界并清理关联反应器 + if (!HatchPickEnv.IsMeCreate(boEnt)) + return; + boEnt.Erase(); + if (dict.Key.IsOk()) + { + var hatch = tr.GetObject(dict.Key, OpenMode.ForWrite); + if (hatch == null) + return; + RemoveAssociative(hatch); + } + }); + } + _mapHatchConv.Clear(); + } + + /// + /// 移除关联反应器 + /// + /// + static void RemoveAssociative(Hatch hatch) + { + // 撤回填充,没有边界就移除关联反应器 + if (!hatch.Associative) + return; + + // 填充边界反应器 + var assIds = hatch.GetAssociatedObjectIds(); + if (assIds == null) + return; + bool isok = true; + foreach (ObjectId id in assIds) + { + if (!id.IsOk()) + { + isok = false; + break; + } + } + // 这里边界id已经删除了,所以移除会导致异常 + if (isok) + hatch.RemoveAssociatedObjectIds(); + // 取消关联反应器才能生成的正确 + hatch.Associative = false; + } + + /// + /// 撤回事件(获取删除对象) + /// + /// + /// + static void DB_ObjectErased(object sender, ObjectErasedEventArgs e) + { + if (!State.IsRun) + return; + + // object erased. + if (e.Erased) + { + return; + } + + // UNDO + if (e.DBObject is Hatch hatch) + { + if (HatchPickEnv.IsMeCreate(hatch)) + RemoveAssociative(hatch); + } + else if (e.DBObject is Entity boEnt) + { + // 撤回边界 + if (HatchPickEnv.IsMeCreate(boEnt)) + { + boEnt.Erase(); + // 通过xdata回溯填充,清理关联反应器 + if (boEnt.XData != null) + { + using DBTrans tr = new(); + var hatchId = HatchPickEnv.GetXdataHatch(boEnt); + if (hatchId.IsOk()) + { + var hatchEnt = tr.GetObject(hatchId, OpenMode.ForWrite); + if (hatchEnt != null) + RemoveAssociative(hatchEnt); + } + } + } + } + } + + /// + /// 撤回事件(更改时触发) + /// 它会获取有修改步骤的图元id + /// + /// + /// + static void DB_ObjectModified(object sender, ObjectEventArgs e) + { + if (!State.IsRun) + return; + + // 然后删除我制造的拉伸填充上面的关联反应器 + if (!e.DBObject.IsUndoing) + return; + if (e.DBObject.IsErased) + return; + // 是我生成的填充才删除关联 + if (e.DBObject is Hatch hatch) + { + if (HatchPickEnv.IsMeCreate(hatch)) + RemoveAssociative(hatch); + } + } + + void SetPropertiesInfoTask() + { + // 原有选择集 + var prompt = Env.Editor.SelectImplied(); + if (prompt.Status != PromptStatus.OK) + return; + + using DBTrans tr = new(); + + // 获取记录的边界 + HashSet boAll = new(); + foreach (var hc in _mapHatchConv.Values) + foreach (var boid in hc.BoundaryIds) + boAll.Add(boid); + + // 获取选择集上面所有的填充,如果没有填充就结束(不屏蔽特性面板) + bool hasHatch = false; + HashSet idsOfSsget = new(); + foreach (var id in prompt.Value.GetObjectIds()) + { + // 含有填充 + if (_mapHatchConv.ContainsKey(id)) + hasHatch = true; + // 排除边界的加入 + if (!boAll.Contains(id)) + idsOfSsget.Add(id); + } + if (!hasHatch) + return; + + // 删除填充边界,并清理关联反应器 + EraseAllHatchBorders(); + + // 重设选择集 提供给后续命令判断 + SetImpliedSelection(idsOfSsget); + + // 如果有填充才否决 + _vetoProperties = idsOfSsget.Count != 0; + } + #endregion + + #region IDisposable接口相关函数 + public bool IsDisposed { get; private set; } = false; + + /// + /// 手动调用释放 + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// 析构函数调用释放 + /// + ~HatchPickEvent() + { + Dispose(false); + } + + protected virtual void Dispose(bool disposing) + { + // 不重复释放 + if (IsDisposed) return; + IsDisposed = true; + + if (_doc.IsDisposed) + return; + LoadHelper(false); + } + #endregion +} + +/// +/// 填充的鼠标钩子 +/// +public static class HatchHook +{ + static readonly MouseHook MouseHook; + // 夹点拉伸前的点,拉伸后利用命令后反应器去处理"GRIP_STRETCH" + static volatile int _X; + static volatile int _Y; + public static Point MouseStartPoint { get => new(_X, _Y); } + + /// + /// 鼠标双击事件 + /// + public static event EventHandler? DoubleClick; + + static HatchHook() + { + MouseHook = new(); + } + + /// + /// 查找主线程
+ /// 代替
+ /// 托管线程和他们不一样: + ///
+ /// 主窗口 + /// 进程ID + /// 线程ID + [DllImport("user32.dll", SetLastError = true)] + static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); + + [DllImport("user32.dll")] + static extern bool IsWindowEnabled(IntPtr hWnd); + /// + /// 获取当前窗口 + /// + /// 当前窗口标识符 + [DllImport("user32.dll")] + static extern IntPtr GetForegroundWindow(); + + public static void SetHook() + { + // 如果是全局钩子会发生偶尔失效的情况,改用进程钩子反而好多了 + MouseHook.SetHook(true); + MouseHook.MouseDown += (sender, e) => { + // 此处断点时候就会使得钩子失效 + if (!IsWindowEnabled(Acap.MainWindow.Handle)) + return; + // 进程号拦截 + GetWindowThreadProcessId(GetForegroundWindow(), out uint winId); + if (MouseHook.Process.Id != winId) + return; + if (e.Button == MouseButtons.Left) + { + _X = e.X; + _Y = e.Y; + } + }; + + MouseHook.DoubleClick += (sender, e) => { + // 此处断点时候就会使得钩子失效 + if (!IsWindowEnabled(Acap.MainWindow.Handle)) + return; + // 进程号拦截 + GetWindowThreadProcessId(GetForegroundWindow(), out uint winId); + if (MouseHook.Process.Id != winId) + return; + DoubleClick?.Invoke(sender, e); + }; + } + + public static void RemoveHook() + { + MouseHook?.Dispose(); + } +} + +public static class HatchPickEnv +{ + //static readonly string _appName = nameof(JoinBox); + //static readonly string _data = nameof(CreateBoundary); + + static readonly string _appName = "JoinBox"; + static readonly string _data = "CreateBoundary"; + + /// + /// 判断图元是否由 我的转换器 创建(相对的是直接提取现有图元边界) + /// + /// 任何图元 + /// + public static bool IsMeCreate(Entity entity) + { + if (entity.XData == null) + return false; + var xl = (XDataList)entity.XData; + return xl.Contains(_appName, _data); + } + + /// + /// 我的转换器 xdata数据模板 + /// + /// + /// + /// + public static ResultBuffer GetMeBuffer(Handle hatchHandle, DBTrans? trans = null) + { + trans ??= DBTrans.Top; + trans.RegAppTable.Add(_appName); // add函数会默认的在存在这个名字的时候返回这个名字的regapp的id,不存在就新建 + ResultBuffer resBuf = new() + { + new((int)DxfCode.ExtendedDataRegAppName, _appName), + new((int)DxfCode.ExtendedDataAsciiString,_data), + new((int)DxfCode.ExtendedDataHandle, hatchHandle),//边界回溯这个填充的句柄,如果创建新填充,就需要再去改 + }; + return resBuf; + } + + /// + /// 填充和边界上面增加xdata,实现区分原生和我的数据 + /// + /// + /// + /// + public static void SetMeXData(ObjectId newHatchId, List boIds, DBTrans? trans = null) + { + trans ??= DBTrans.Top; + var hatchEnt = trans.GetObject(newHatchId); + if (hatchEnt != null) + using (hatchEnt.ForWrite()) + hatchEnt.XData = GetMeBuffer(hatchEnt.Handle, trans); // 设置xdata仅仅为debug可以通过鼠标悬停看见它数据,因此设置为自己 + + // 修改边界的xdata为新填充的 + boIds.ForEach(id => { + var boEnt = trans.GetObject(id); + if (boEnt is null) + return; + using (boEnt.ForWrite()) + { + boEnt.RemoveXData(_appName); + boEnt.XData = GetMeBuffer(newHatchId.Handle, trans); + } + }); + } + + /// + /// 通过边界ent获取填充id + /// + /// 边界图元 + /// + /// + public static ObjectId GetXdataHatch(Entity boEntity, DBTrans? trans = null) + { + if (boEntity.XData == null) + return ObjectId.Null; + XDataList data = boEntity.XData; + + if (!data.Contains(_appName, _data)) + return ObjectId.Null; + + var indexs = data.GetXdataAppIndex(_appName, new DxfCode[] { DxfCode.ExtendedDataHandle }); + if (indexs.Count == 0) + return ObjectId.Null; + + trans ??= DBTrans.Top; + return trans.GetObjectId(data[indexs[0]].Value.ToString()); + } +} + +public static class MathHelper +{ + /// + /// 圆弧的腰点 + /// + /// 圆弧点1 + /// 圆弧点3 + /// 凸度 + /// 返回腰点 + /// + [MethodImpl] + public static Point2d GetArcMidPoint(Point2d arc1, Point2d arc3, double bulge) + { + if (bulge == 0) + throw new ArgumentException("凸度为0,此线是平的"); + + var center = GetArcBulgeCenter(arc1, arc3, bulge); + var angle1 = center.GetVectorTo(arc1).GetAngle2XAxis(); + var angle3 = center.GetVectorTo(arc3).GetAngle2XAxis(); + // 利用边点进行旋转,就得到腰点,旋转角/2 + // 需要注意镜像的多段线 + double angle = angle3 - angle1; + if (bulge > 0) + { + if (angle < 0) + angle += Math.PI * 2; + } + else + { + if (angle > 0) + angle += Math.PI * 2; + } + return arc1.RotateBy(angle / 2, center); + } + /// http://bbs.xdcad.net/thread-722387-1-1.html + /// https://blog.csdn.net/jiangyb999/article/details/89366912 + /// + /// 凸度求圆心 + /// + /// 圆弧头点 + /// 圆弧尾点 + /// 凸度 + /// 圆心 + [MethodImpl] + public static Point2d GetArcBulgeCenter(Point2d arc1, Point2d arc3, double bulge) + { + if (bulge == 0) + throw new ArgumentException("凸度为0,此线是平的"); + + var x1 = arc1.X; + var y1 = arc1.Y; + var x2 = arc3.X; + var y2 = arc3.Y; + + var b = (1 / bulge - bulge) / 2; + var x = (x1 + x2 - b * (y2 - y1)) / 2; + var y = (y1 + y2 + b * (x2 - x1)) / 2; + return new Point2d(x, y); + } + + /// + /// X轴到向量的弧度,cad的获取的弧度是1PI,所以转换为2PI(上小,下大) + /// + /// 向量 + /// X轴到向量的弧度 + public static double GetAngle2XAxis(this Vector2d ve, double tolerance = 1e-6) + { + const double Tau = Math.PI + Math.PI; + // 世界重合到用户 Vector3d.XAxis->两点向量 + double al = Vector2d.XAxis.GetAngleTo(ve); + al = ve.Y > 0 ? al : Tau - al; // 逆时针为正,大于0是上半圆,小于则是下半圆,如果-负值控制正反 + al = Math.Abs(Tau - al) <= tolerance ? 0 : al; + return al; + } +} \ No newline at end of file diff --git "a/tests/TestAcad09plus/\346\213\211\344\274\270\345\241\253\345\205\205/HatchHelper.cs" "b/tests/TestAcad09plus/\346\213\211\344\274\270\345\241\253\345\205\205/HatchHelper.cs" new file mode 100644 index 0000000000000000000000000000000000000000..6d6c24312b06942b062ecdb8bb4c732a4f2d4275 --- /dev/null +++ "b/tests/TestAcad09plus/\346\213\211\344\274\270\345\241\253\345\205\205/HatchHelper.cs" @@ -0,0 +1,124 @@ +namespace JoinBoxAcad; + +public static class HatchHelper +{ + /// + /// 遍历填充每条边 + /// + /// + /// + public static void ForEach(this Hatch hatch, Action action) + { + for (int i = 0; i < hatch.NumberOfLoops; i++) + action.Invoke(hatch.GetLoopAt(i)); + } + + +#if false + /// + /// 分离填充边界 + /// 将外边界和包含的边界成为一个集 + /// + /// 填充的边界(只有多段线和圆) + /// 多个id集,id中第一个是外边界 + public static IEnumerable[] SeparationBorder(this IEnumerable bianjie) + { + IEnumerable[] objectIds = null; + if (bianjie.Length < 1) return null; + + Database db = bianjie[0].Database; + + // 首先获取一个图元,这个图元默认成边界,看它是否包含第二个,如果是,加入a集 + // 如果不是,它是否把自己包含了, + // 如果是,把它加入a集,并下次使用它来成为外边界 + // 如果不是,则为新边界,把它加入b集 + List alist = new List(); // 边界包含 + List blist = new List(); // 有另外的边界 + List> clist = new List>();// 边界集 + + using (Transaction tr = db.TransactionManager.StartTransaction()) + { + while (true) + { + for (int i = 0; i < bianjie.Length; i++) + { + Entity ent = bianjie[i].ObjectIdToEntity(false); + ent.UpgradeOpen(); + for (int j = i + 1; j < bianjie.Length; j++) + { + if (ent is Polyline polyline)// 多段线 + { + } + else if (ent is Circle circle) // 圆 + { + } + } + ent.DowngradeOpen(); + } + + if (blist.Count == 0)// 没有其他边界就结束循环 + { + break; + } + // 把blist的第一个用来作为新的外边界 + } + } + return objectIds; + } + + /// + /// 判断边界是否包含图元 布尔算法... + /// + /// 边界 + /// 包含的图元 + /// 是true,否false + public static bool BorderIntoCollect(this ObjectId border, ObjectId include) + { + bool flag = false; + Database db = border.Database; + using (Transaction tr = db.TransactionManager.StartTransaction()) + { + Entity ent = border.ObjectIdToEntity(false); + Entity ent2 = include.ObjectIdToEntity(false); + + if (ent is Polyline polyline)// 多段线边界 + { + if (ent2 is Polyline polyline2)// 多段线 + { + } + else if (ent2 is Circle circle2) // 圆 + { + // 判断圆心在多段线内 + if (circle2.Center.RayCasting(polyline.GetPolylinePoints()) != 3) + { + if (true)// 半径若大于多段线最长那段,表示包含不到,是圆包含了多段线(含有弧度就错了) + { + flag = true; + } + } + else // 圆心不在边界内,判断边界是否有交点 + { + } + } + } + else if (ent is Circle circle) // 圆边界 + { + if (ent2 is Polyline polyline2)// 多段线 + { + } + else if (ent2 is Circle circle2) // 圆 + { + // 填充边界不存在交集 + // 两个圆心的距离>两个圆的半径和=两圆分离 + double length = circle.Center.GetDistanceBetweenTwoPoint(circle2.Center); + if (length < circle.Radius + circle2.Radius) + { + flag = true; + } + } + } + return flag; + } + } +#endif +} \ No newline at end of file diff --git "a/tests/TestAcad09plus/\346\213\211\344\274\270\345\241\253\345\205\205/\345\261\217\345\271\225\345\235\220\346\240\207\350\275\254cad\345\235\220\346\240\207.cs" "b/tests/TestAcad09plus/\346\213\211\344\274\270\345\241\253\345\205\205/\345\261\217\345\271\225\345\235\220\346\240\207\350\275\254cad\345\235\220\346\240\207.cs" new file mode 100644 index 0000000000000000000000000000000000000000..1856a61a7f4fe036cabfdd69c3fe7d2695b6cee3 --- /dev/null +++ "b/tests/TestAcad09plus/\346\213\211\344\274\270\345\241\253\345\205\205/\345\261\217\345\271\225\345\235\220\346\240\207\350\275\254cad\345\235\220\346\240\207.cs" @@ -0,0 +1,133 @@ +//#define cpp +namespace JoinBoxAcad; + +using System.Drawing; +using static IFoxCAD.Basal.WindowsAPI; + +public partial class Screen +{ + [CommandMethod(nameof(GetScreenToCadxx))] + public static void GetScreenToCadxx() + { + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + var ed = doc.Editor; + var ucsPoint = GetScreenToCad(); + ed.WriteMessage(ucsPoint.ToString() + "\n"); + } + + /// + /// 屏幕坐标转cad坐标 + /// + public static Point3d GetScreenToCad() + { + // 两种获取方式都可以 + var cursorPos = System.Windows.Forms.Control.MousePosition; + // GetCursorPos(out Point cursorPos); + return ScreenToCad(cursorPos); + } + + /// + /// 屏幕像素点转cad图纸坐标点 + /// + /// 屏幕像素点 + /// 返回ucs的点 + public static Point3d ScreenToCad(Point cursorPos) + { + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + var ed = doc.Editor; + var mid = WindowsAPI.GetParent(doc.Window.Handle); + + ScreenToClient(mid, ref cursorPos); + var vn = ed.GetViewportNumber(cursorPos);// System.Windows.Forms.Control.MousePosition + var wcsPoint = ed.PointToWorld(cursorPos, vn); + var ucsPoint = wcsPoint.TransformBy(doc.Editor.CurrentUserCoordinateSystem.Inverse()); + return ucsPoint; + } + + /// + /// 屏幕坐标到客户区坐标转换 + /// + /// 窗口句柄 + /// 点结构,返回屏幕坐标 + /// + [DllImport("user32.dll")] + public static extern bool ScreenToClient(IntPtr hWnd, ref Point lpPoint); + [DllImport("user32.dll")] + static extern bool ClientToScreen(IntPtr hWnd, ref Point lpPoint); + + public static Point CadToScreen(Point3d pt3d, Point mousePosition) + { + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + var ed = doc.Editor; + var mid = WindowsAPI.GetParent(doc.Window.Handle); + + var vn = ed.GetViewportNumber(mousePosition);//System.Windows.Forms.Control.MousePosition + var ptScr = Env.Editor.PointToScreen(pt3d, vn);// 高版本这个不一样,转为客户区 + var ptScrWin = new Point((int)ptScr.X, (int)ptScr.Y); + ClientToScreen(mid, ref ptScrWin); + return ptScrWin; + } + + //#if NET35 + // [DllImport("acad.exe", EntryPoint = "?acedGetAcadDwgView@@YAPAVCView@@XZ")] //acad08 + //#else + // [DllImport("acad.exe", EntryPoint = "?acedGetAcadDwgView@@YAPEAVCView@@XZ")]//acad21 + //#endif + // static extern IntPtr AcedGetAcadDwgview(); + + delegate IntPtr DelegateAcedGetAcadDwgview(); + static DelegateAcedGetAcadDwgview? acedGetAcadDwgView; + /// + /// 获取视口指针 + /// + public static IntPtr AcedGetAcadDwgview() + { + if (acedGetAcadDwgView is null) + { + acedGetAcadDwgView = AcadPeInfo + .GetDelegate( + nameof(acedGetAcadDwgView), AcadPeEnum.AcadExe); + } + if (acedGetAcadDwgView is not null) + return acedGetAcadDwgView.Invoke();// 调用方法 + return IntPtr.Zero; + } + + delegate int DelegateAcedGetWinNum(int x, int y); + static DelegateAcedGetWinNum? acedGetWinNum; + /// + /// 获取窗口数字 + /// + public static int AcedGetWinNum(int x, int y) + { + if (acedGetWinNum is null) + acedGetWinNum = AcadPeInfo + .GetDelegate( + nameof(acedGetWinNum), AcadPeEnum.ExeAndCore); + if (acedGetWinNum is not null) + return acedGetWinNum.Invoke(x, y);// 调用方法 + return 0; + } + + /// + /// 将坐标从绘图窗口转换为活动视口坐标系 + /// + /// + /// + /// + /// +#if NET35 + // 此处都是acad08这个有重载,不知道PeInfo能不能正常运行 + [DllImport("acad.exe", EntryPoint = "?acedCoordFromPixelToWorld@@YAHHVCPoint@@QAN@Z")] + static extern int AcedCoordFromPixelToWorld(int windnum, Point pt, out Point3D ptOut); + + [DllImport("acad.exe", EntryPoint = "?acedCoordFromPixelToWorld@@YAXABVCPoint@@QAN@Z")]//这个重载参数不知道 + static extern int AcedCoordFromPixelToWorld(Point pt, out Point3D ptOut); +#else + [DllImport("accore.dll", EntryPoint = "?acedCoordFromPixelToWorld@@YAHHVCPoint@@QEAN@Z")] + static extern int AcedCoordFromPixelToWorld(int windnum, Point pt, out Point3D ptOut); +#endif +} \ No newline at end of file diff --git "a/tests/TestAcad09plus/\346\213\211\344\274\270\345\241\253\345\205\205/\350\277\207\347\250\213\346\226\207\344\273\266/\344\270\200\344\270\252\346\227\240\346\263\225\347\247\273\345\212\250\347\272\242\350\211\262\345\234\206\347\232\204\344\276\213\345\255\220.cs" "b/tests/TestAcad09plus/\346\213\211\344\274\270\345\241\253\345\205\205/\350\277\207\347\250\213\346\226\207\344\273\266/\344\270\200\344\270\252\346\227\240\346\263\225\347\247\273\345\212\250\347\272\242\350\211\262\345\234\206\347\232\204\344\276\213\345\255\220.cs" new file mode 100644 index 0000000000000000000000000000000000000000..4e45d212f21d85d6b68830a38f677a676d012d04 --- /dev/null +++ "b/tests/TestAcad09plus/\346\213\211\344\274\270\345\241\253\345\205\205/\350\277\207\347\250\213\346\226\207\344\273\266/\344\270\200\344\270\252\346\227\240\346\263\225\347\247\273\345\212\250\347\272\242\350\211\262\345\234\206\347\232\204\344\276\213\345\255\220.cs" @@ -0,0 +1,94 @@ +// 一个无法移动红色圆的例子 https://www.keanw.com/2008/08/rolling-back-th.html +#if true2 +namespace JoinBoxAcad; + +public class CmdReactor +{ + Document? _doc; + ObjectIdCollection _ids = new(); + Point3dCollection _pts = new(); + + [CommandMethod(nameof(Test_REACTOR))] + public void Test_REACTOR() + { + _doc = Acap.DocumentManager.MdiActiveDocument; + _doc.CommandWillStart += Doc_CommandWillStart; + } + + /// + /// 挂载一个命令反应器,如果出现了move就挂载一个 + /// + /// + /// + void Doc_CommandWillStart(object sender, CommandEventArgs e) + { + if (e.GlobalCommandName == "MOVE") + { + _ids.Clear(); + _pts.Clear(); + + if (_doc is null) + return; + _doc.Database.ObjectOpenedForModify += Db_ObjectOpenedForModify; + _doc.CommandCancelled += Doc_CommandEnded; + _doc.CommandEnded += Doc_CommandEnded; + _doc.CommandFailed += Doc_CommandEnded; + } + } + + /// + /// 卸载一堆反应器 + /// + void RemoveEventHandlers() + { + if (_doc is null) + return; + _doc.CommandCancelled -= Doc_CommandEnded; + _doc.CommandEnded -= Doc_CommandEnded; + _doc.CommandFailed -= Doc_CommandEnded; + _doc.Database.ObjectOpenedForModify -= Db_ObjectOpenedForModify; + } + + void Doc_CommandEnded(object sender, CommandEventArgs e) + { + // 在恢复位置之前删除数据库reactor + RemoveEventHandlers(); + RollbackLocations(); + } + + /// + /// 颜色是1的圆加入集合 + /// + /// + /// + void Db_ObjectOpenedForModify(object sender, ObjectEventArgs e) + { + if (e.DBObject is Circle circle && circle.ColorIndex == 1)// 如果颜色是1 + { + // 不含有就加入集合 + if (!_ids.Collection.Contains(circle.ObjectId)) + { + _ids.Add(circle.ObjectId); + _pts.Add(circle.Center); + } + } + } + + /// + /// 修改圆心 + /// + void RollbackLocations() + { + Debug.Assert(_ids.Count == _pts.Count, "预计相同数量的ID和位置"); + _doc?.Database.Action(tr => { + int i = 0; + foreach (ObjectId id in _ids.Collection) + { + var circle = tr.GetObject(id, OpenMode.ForWrite) as Circle; + if (circle is not null) + circle.Center = _pts[i++]; + } + }); + } +} +#endif \ No newline at end of file diff --git "a/tests/TestAcad09plus/\346\213\211\344\274\270\345\241\253\345\205\205/\350\277\207\347\250\213\346\226\207\344\273\266/\350\207\252\345\256\232\344\271\211\345\233\276\345\205\203\345\241\253\345\205\205\350\276\271\347\225\214.txt" "b/tests/TestAcad09plus/\346\213\211\344\274\270\345\241\253\345\205\205/\350\277\207\347\250\213\346\226\207\344\273\266/\350\207\252\345\256\232\344\271\211\345\233\276\345\205\203\345\241\253\345\205\205\350\276\271\347\225\214.txt" new file mode 100644 index 0000000000000000000000000000000000000000..50ccab0d7cc1a64306f9d6064b7b0ad89ac0131d --- /dev/null +++ "b/tests/TestAcad09plus/\346\213\211\344\274\270\345\241\253\345\205\205/\350\277\207\347\250\213\346\226\207\344\273\266/\350\207\252\345\256\232\344\271\211\345\233\276\345\205\203\345\241\253\345\205\205\350\276\271\347\225\214.txt" @@ -0,0 +1,104 @@ +拉伸填充bug: +1号: 不闭合的多段线,新建边界会丢失一个不和头点重叠的倒数第二个点. +与头点重叠是ok的 +闭合是ok的 +解决:因为倒数第二个点要从 curve.EndPoint 获取. + +2号: +如果画了一个矩形,然后填充,再删除边界,再点选填充,那么通过生成出来关联标注, +移动第一个点的时候,是没有响应的,移动第二次的时候才会响应. + +两种想法解决这个问题, +a:生成之后,用移动矩阵影响一下关联反应器. + 答:不会触发的,即使我分成两个事务分别提交. +b:先克隆一个填充,再创建关联边界. + 答:可行的,但是会导致3号问题的发生. + +3号: +由于我是生成填充的,所以在在位编辑的时候会在长事务内部: + +【贵妃】惊惊 2019/7/13 17:52:02 +我遇到了一个问题,如果是在位编辑的时候,当前空间是模型空间,那么我用函数克隆一个块外的东西到模型空间,实际上会克隆到在位编辑的内部... +我都不知道怎么处理这个情况了..莫非要关闭用户的在位编辑状态么.. + +【才人】edata 2019/7/13 17:57:15 +在位编辑是这样的. + +【贵妃】惊惊 2019/7/13 17:58:08 +那桌子是怎么控制在位编辑-减出去块外的? + +【才人】edata 2019/7/13 17:58:20 +在位编辑实际上是对当前空间的修改,然后移动回块定义.. + +【才人】edata 2019/7/13 17:58:44 +这个就不是很清楚了... + +【贵妃】惊惊 2019/7/13 17:58:47 +也就是长事务上面记录了要移动回去的id? +如果减选了就是剔除了id? + +【才人】edata 2019/7/13 17:59:27 +你能卡到这个长事务吗? + +【贵妃】惊惊 2019/7/13 17:59:39 +net貌似无法控制长事务呀 +应该桌子有考虑到的,只是我还没有挖出来具体怎么处理的.. + +【才人】edata 2019/7/13 18:08:59 +用命令去移除当前在位编辑. + +我用了命令去移除块外的填充和边界,这样操作是可行的, +然后又产生了一个问题,如果是块内的填充,我并不想减去. +想法: 用命令反应器在在位编辑前获取当前空间所有的图元,然后在位编辑时候就知道两个集合的交叉部分了 +答:证明是可以分类出来的,但是会引起一个问题,在位编辑复制的填充id不在任何一个表上. + +判断执行的时候不是选择前的填充均执行产生边界(这样就不用复制反应器了 +再检测命令 _refset 加减三个集合的填充 +但是命令反应器无法检测二级命令,只能通过 lastprompt 获取最后一行命令,判断添加或者删除 + +命令完成后,执行选择上次选择,便是_refset命令的选择 +然后把id改成加减到判断的两个集合中 + + + + + +U 大写命令,用户回滚的时会导致集合信息不符: +之前koz找到的方法,先选择在位编辑命令触发时的所有图元,再选择触发后的所有图元,分别建立两个集合储存,然后进行差集运算, +得出那些是块内图元,这个方法是可行的,然后我发现在使用+加入块内和-减出块内这也都可以判断, +问题就是....如果是使用了U回滚,那么我将不知道如何修改这两个集合.... + +检测在位编辑命令启动之后,而无结束命令的时候, +期间如果用户使用了u就提示用户是否禁止拉伸填充. + +挂载一个图元的反应器,如果使用了命令U,那么判断填充是否被更改了-> ObjectOpenedForModify 反应器 +如果更改了,更改方式无法知道?? 可能是改颜色,也可能是会在块内外加减操作更改. +无解决方案! + + + +拉伸非圆的时候,如果中心移动,那么边界不会跟随 +发生了拉伸命令的时候, +答: 选择的对象有填充,就克隆填充,并且删除原有的边界,及填充 + + + + +频闪控制 +grips=0 + + + + +编辑填充 +样条曲线边界生成 + +在cad2008设计一条填充边界 +填充边界的自定义图元名称叫做: HatchBoundary +设定HatchBoundary变量,0为使用,1为不使用边界 +想法破产:不会自定义图元. + + + +在位编辑的锁定图元的方法,该不是一个隐藏图层导致的吧. +不是,但是在位编辑的时候出现了一个图层 0-RefEdit0 \ No newline at end of file diff --git "a/tests/TestAcad09plus/\346\213\211\344\274\270\345\241\253\345\205\205/\350\277\207\347\250\213\346\226\207\344\273\266/\350\216\267\345\217\226\345\244\271\347\202\271\344\276\213\345\255\220.cs" "b/tests/TestAcad09plus/\346\213\211\344\274\270\345\241\253\345\205\205/\350\277\207\347\250\213\346\226\207\344\273\266/\350\216\267\345\217\226\345\244\271\347\202\271\344\276\213\345\255\220.cs" new file mode 100644 index 0000000000000000000000000000000000000000..a2d7a98ae0e355589fbbbdb634ca5be75cd5e3e6 --- /dev/null +++ "b/tests/TestAcad09plus/\346\213\211\344\274\270\345\241\253\345\205\205/\350\277\207\347\250\213\346\226\207\344\273\266/\350\216\267\345\217\226\345\244\271\347\202\271\344\276\213\345\255\220.cs" @@ -0,0 +1,100 @@ +#if !ac2008 +namespace GripOverruleTest; + +// https://through-the-interface.typepad.com/through_the_interface/2009/08/knowing-when-an-autocad-object-is-grip-edited-using-overrules-in-net.html +public class GripVectorOverrule : GripOverrule +{ + // A static pointer to our overrule instance + static public GripVectorOverrule theOverrule = new(); + // A flag to indicate whether we're overruling + static bool overruling = false; + // A single set of grips would not have worked in + // the case where multiple objects were selected. + static Dictionary _gripDict = new(); + + private string GetKey(Entity e) + { + // Generate a key based on the name of the object's type + // and its geometric extents + // (We cannot use the ObjectId, as this is null during + // grip-stretch operations.) + return e.GetType().Name + ":" + e.GeometricExtents.ToString(); + } + // Save the locations of the grips for a particular entity + private void StoreGripInfo(Entity e, Point3dCollection grips) + { + string key = GetKey(e); + if (_gripDict.ContainsKey(key)) + { + // Clear the grips if any already associated + Point3dCollection grps = _gripDict[key]; + using (grps) + grps.Clear(); + _gripDict.Remove(key); + } + // Now we add our grips + Point3d[] pts = new Point3d[grips.Count]; + grips.CopyTo(pts, 0); + Point3dCollection gps = new(pts); + _gripDict.Add(key, gps); + } + // Get the locations of the grips for an entity + private Point3dCollection? RetrieveGripInfo(Entity e) + { + Point3dCollection? grips = null; + string key = GetKey(e); + if (_gripDict.ContainsKey(key)) + grips = _gripDict[key]; + return grips; + } + public override void GetGripPoints(Entity e, Point3dCollection grips, IntegerCollection snaps, IntegerCollection geomIds) + { + base.GetGripPoints(e, grips, snaps, geomIds); + StoreGripInfo(e, grips); + } + public override void MoveGripPointsAt(Entity e, IntegerCollection indices, Vector3d offset) + { + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + var ed = doc.Editor; + + var grips = RetrieveGripInfo(e); + if (grips != null) + { + // Could get multiple points moved at once, + // hence the integer collection + foreach (int i in indices) + { + // Get the grip point from our internal state + Point3d pt = grips[i]; + // Draw a vector from the grip point to the newly + // offset location, using the index into the + // grip array as the color (excluding colours 0 and 7). + // These vectors don't getting cleared, which makes + // for a fun effect. + ed.DrawVector( + pt, + pt + offset, + (i >= 6 ? i + 2 : i + 1), // exclude colours 0 and 7 + false + ); + } + } + base.MoveGripPointsAt(e, indices, offset); + } + [CommandMethod(nameof(GripOverruleOnOff))] + public static void GripOverruleOnOff() + { + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + var ed = doc.Editor; + if (overruling) + RemoveOverrule(GetClass(typeof(Entity)), theOverrule); + else + AddOverrule(GetClass(typeof(Entity)), theOverrule, true); + overruling = !overruling; + Overruling = overruling; + ed.WriteMessage("\nGrip overruling turned {0}.", overruling ? "on" : "off"); + } +} +#endif \ No newline at end of file diff --git a/src/IFoxCAD.Cad/GlobalUsings.cs b/tests/TestConsole/GlobalUsings.cs similarity index 51% rename from src/IFoxCAD.Cad/GlobalUsings.cs rename to tests/TestConsole/GlobalUsings.cs index 08bd0b723b0d3fc72dde43fd6a3912b64c570093..6328a6745828c422b1cd7873beb6a2c9206e73bd 100644 --- a/src/IFoxCAD.Cad/GlobalUsings.cs +++ b/tests/TestConsole/GlobalUsings.cs @@ -8,12 +8,7 @@ global using System.Reflection; global using System.Text.RegularExpressions; global using Microsoft.Win32; - - -/// autocad 引用 -global using Autodesk.AutoCAD.ApplicationServices; -global using Autodesk.AutoCAD.EditorInput; -global using Autodesk.AutoCAD.Colors; -global using Autodesk.AutoCAD.DatabaseServices; -global using Autodesk.AutoCAD.Geometry; -global using Autodesk.AutoCAD.Runtime; +global using System.ComponentModel; +global using System.Runtime.CompilerServices; + +global using IFoxCAD.Basal; \ No newline at end of file diff --git a/tests/TestConsole/Helper.cs b/tests/TestConsole/Helper.cs new file mode 100644 index 0000000000000000000000000000000000000000..6fba76627d029ef735d722370f22e42fd05a86be --- /dev/null +++ b/tests/TestConsole/Helper.cs @@ -0,0 +1,22 @@ +namespace TestConsole; + +public static class Helper +{ + public static void ForEach(this IEnumerable ints, Action action) + { + LoopState state = new(); + foreach (var item in ints) + { + action(item, state); + if (!state.IsRun) + break; + } + + // int forNum = 5; + // var result = Parallel.For(0, forNum, (int i, ParallelLoopState pls) => { + // if (i > 2) + // pls.Break(); + // Task.Delay(10).Wait(); + // }); + } +} \ No newline at end of file diff --git a/tests/TestConsole/Info.cs b/tests/TestConsole/Info.cs new file mode 100644 index 0000000000000000000000000000000000000000..7c41b7159846f14a1bc063d8ed0ade64a03c398d --- /dev/null +++ b/tests/TestConsole/Info.cs @@ -0,0 +1,43 @@ +using System.Runtime.InteropServices; + +namespace TestConsole; + +[Flags] +public enum PlugIn +{ + [Description("惊惊")] + JoinBox = 1, + [Description("源泉")] + YuanQuan = 2, + [Description("迷你")] + IMinCad = 4, + + Lisp = JoinBox | YuanQuan | IMinCad, + + DOCBAR = 8, + DUOTAB = 16, + + All = Lisp | DOCBAR | DUOTAB +} + + +[Flags] +public enum PlugIn2 +{ + [Description("惊惊")] + JoinBox = 1, + [Description("源泉")] + YuanQuan = 2, + [Description("迷你")] + IMinCad = 4, + + [Description("*Lisp*")] + Lisp = JoinBox | YuanQuan | IMinCad, + + DOCBAR = 8, + DUOTAB = 16, + + // all == *Lisp*|DOCBAR|DUOTAB + // 采取的行为是:注释的行为是特殊的,就按照注释的,否则,遍历子元素提取注释 + All = Lisp | DOCBAR | DUOTAB +} \ No newline at end of file diff --git a/tests/TestConsole/Program.cs b/tests/TestConsole/Program.cs new file mode 100644 index 0000000000000000000000000000000000000000..514c252e7de44180183b1049a1f3e379bbf0a114 --- /dev/null +++ b/tests/TestConsole/Program.cs @@ -0,0 +1,176 @@ +// See https://aka.ms/new-console-template for more information +using System; +using System.Runtime.InteropServices; +using System.Runtime.Serialization.Formatters.Binary; +using System.Text; +using TestConsole; +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +#if true +namespace CalculatorDemo +{ + class Program + { + static void Main(string[] args) + { +#if false + int nResult = AddTwoNumbers(10, 20); + Console.WriteLine(nResult); + + AddTwoNumbers22((a, b) => { + Console.WriteLine(a + b); + }); + + var a = new int[] { 1, 2, 3, 4, 5, 6, 78, 9, 92, }; + a.ForEach(a => { + Console.WriteLine(a); + }); +#endif + + var aa = new int[] { 1, 2, 3, 4, 5, 6, 78, 9, 92, }; + Console.WriteLine(aa[1..^2]); + + + var time = Timer.RunTime(() => { + for (int i = 0; i < 10000000; i++) + i++; + }, Timer.TimeEnum.Second); + Console.WriteLine($"代码执行的时间:{time}"); + } + + private static int addtuple((int ,int ) b) + { + return b.Item1 + b.Item2; + } + + + [DebuggerHidden] + private static int AddTwoNumbers(int nNum1, int nNum2) + { + return Add(nNum1, nNum2); + } + private static int Add(int op1, int op2) + { + return op1 + op2; + } + + [DebuggerHidden] + private static void AddTwoNumbers22(Action action) + { + action(10, 20); + } + } + + public static class Fors + { + /// + /// 遍历集合,执行委托 + /// + /// 集合值的类型 + /// 集合 + /// 委托 + [DebuggerHidden] + public static void ForEach(this IEnumerable source, Action action) + { + foreach (var element in source) + { + action.Invoke(element); + } + } + } +} +#endif + +#if true2 + +Console.WriteLine("***************************************************"); + + +List list = new List(); +list.Add(1); +list.Add(2); +list.Add(3); +list.Add(4); +list.Add(5); +list.ForEach((x, loop) => { + if (x == 3) + loop.Break(); + Console.WriteLine(x); +}); + + + +// 乱序 +Console.WriteLine(PlugIn.JoinBox.PrintNote()); +Console.WriteLine(PlugIn.Lisp.PrintNote());// 这里先交换顺序来试试能不能成功 +Console.WriteLine(PlugIn.IMinCad.PrintNote()); +Console.WriteLine(PlugIn.YuanQuan.PrintNote()); +Console.WriteLine(PlugIn.All.PrintNote()); +Console.WriteLine(PlugIn.DOCBAR.PrintNote()); +Console.WriteLine(PlugIn.DUOTAB.PrintNote()); +Console.WriteLine("***************************************************"); +// 乱序2 +Console.WriteLine(PlugIn2.JoinBox.PrintNote()); +Console.WriteLine(PlugIn2.Lisp.PrintNote());// 这里先交换顺序来试试能不能成功 +Console.WriteLine(PlugIn2.IMinCad.PrintNote()); +Console.WriteLine(PlugIn2.YuanQuan.PrintNote()); +Console.WriteLine(PlugIn2.All.PrintNote()); +Console.WriteLine(PlugIn2.DOCBAR.PrintNote()); +Console.WriteLine(PlugIn2.DUOTAB.PrintNote()); + +EnumEx.CleanCache(); + +// 表达式树例子 +TestConsole.Test_Expression.Demo3(); +// TestConsole.Test_Expression.Demo1(); + +#region 元组测试 +var valuetuple = (1, 2); + +Console.WriteLine(valuetuple.ToString()); + +int[] someArray = new int[5] { 1, 2, 3, 4, 5 }; +int lastElement = someArray[^1]; // lastElement = 5 +Console.WriteLine(lastElement); +int midElement = someArray[^3]; +Console.WriteLine(midElement); +var range = someArray[1..3]; +foreach (var item in range) + Console.WriteLine(item); +#endregion + +Console.ReadLine(); + + +#region 测试遍历枚举 +// Season a = Season.Autumn; +// Console.WriteLine($"Integral value of {a} is {(int)a}"); // output: Integral value of Autumn is 2 +// foreach (var enumItem in Enum.GetValues(typeof(Season))) +// Console.WriteLine((byte)enumItem); + +var sb = new StringBuilder(); +/*因为 net framework 没写好的原因,导致直接使用迭代器反而更慢,到了NET60就迭代器比foreach更快*/ +var enums = Enum.GetValues(typeof(Season)).GetEnumerator(); +while (enums.MoveNext()) +{ + sb.Append(((byte)enums.Current).ToString()); + sb.Append(","); +} +Console.WriteLine(sb); + +sb.Remove(sb.Length - 1, 1);// 剔除末尾, +// 因为有返回值所以容易理解成 sb = sb.Remove(sb.Length - 1, 1); +Console.WriteLine(sb); + +public enum Season : byte +{ + Spring, + Summer, + Autumn, + Winter +} +#endregion +#endif \ No newline at end of file diff --git a/tests/TestConsole/RuntimeHelpers.cs b/tests/TestConsole/RuntimeHelpers.cs new file mode 100644 index 0000000000000000000000000000000000000000..fb91a153f3911d3b0694bb0cc694ddc94d6e6778 --- /dev/null +++ b/tests/TestConsole/RuntimeHelpers.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// 如果要用range的语法比如 a[1..3],那么需要将本文件复制到你的项目里 +#if true +namespace System.Runtime.CompilerServices +{ + internal static class RuntimeHelpers + { + /// + /// Slices the specified array using the specified range. + /// + public static T[] GetSubArray(T[] array, Range range) + { + if (array == null) + { + throw new ArgumentNullException(); + } + + (int offset, int length) = range.GetOffsetAndLength(array.Length); + + if (default(T)! != null || typeof(T[]) == array.GetType()) // NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757) + { + // We know the type of the array to be exactly T[]. + + if (length == 0) + { + // return Array.Empty(); + return new T[0]; + } + + var dest = new T[length]; + Array.Copy(array, offset, dest, 0, length); + return dest; + } + else + { + // The array is actually a U[] where U:T. + T[] dest = (T[])Array.CreateInstance(array.GetType().GetElementType()!, length); + Array.Copy(array, offset, dest, 0, length); + return dest; + } + } + } +} +#endif \ No newline at end of file diff --git a/tests/TestConsole/TestConsole.csproj b/tests/TestConsole/TestConsole.csproj new file mode 100644 index 0000000000000000000000000000000000000000..673e547532c24179549526d919d56d9cd550aab2 --- /dev/null +++ b/tests/TestConsole/TestConsole.csproj @@ -0,0 +1,31 @@ + + + + preview + enable + + Exe + NET45 + enable + preview + + + + + + + NET40 + enable + preview + NOINDEX;NORANGE;NOVALUETUPLE + + + + + + + + + + + diff --git a/tests/TestConsole/TestSerialize.cs b/tests/TestConsole/TestSerialize.cs new file mode 100644 index 0000000000000000000000000000000000000000..413764d8766b0927c7af76a9e2c65ab506d56fd0 --- /dev/null +++ b/tests/TestConsole/TestSerialize.cs @@ -0,0 +1,48 @@ +using System.Runtime.Serialization; + +namespace TestConsole; + +[Serializable] +public struct StructDemo +{ + public char[] Chars1; + public char[] Chars2; +} + +[Serializable] +public struct StructDemo2 : ISerializable +{ + public char[] Chars1; + public char[] Chars2; + + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("i", Chars1); + info.AddValue("j", Chars2); + } +} + +// 二进制序列化,存在元数据 +//{ +// StructDemo structDemo = new(); +// structDemo.Chars1 = "aaa".ToCharArray(); +// structDemo.Chars2 = "bbb".ToCharArray(); + +// using MemoryStream stream = new(); +// BinaryFormatter formatter = new(); +// formatter.Serialize(stream, structDemo); +// var str = Encoding.ASCII.GetString(stream.ToArray()); +// Console.WriteLine(str); +//} + +//{ +// StructDemo2 structDemo = new(); +// structDemo.Chars1 = "aaa".ToCharArray(); +// structDemo.Chars2 = "bbb".ToCharArray(); + +// using MemoryStream stream = new(); +// BinaryFormatter formatter = new(); +// formatter.Serialize(stream, structDemo); +// var str = Encoding.ASCII.GetString(stream.ToArray()); +// Console.WriteLine(str); +//} \ No newline at end of file diff --git "a/tests/TestConsole/\350\241\250\350\276\276\345\274\217\346\240\221.cs" "b/tests/TestConsole/\350\241\250\350\276\276\345\274\217\346\240\221.cs" new file mode 100644 index 0000000000000000000000000000000000000000..4c706ddf96ee66567ba18edfdf4e06ce887ebb34 --- /dev/null +++ "b/tests/TestConsole/\350\241\250\350\276\276\345\274\217\346\240\221.cs" @@ -0,0 +1,142 @@ +namespace TestConsole; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Linq.Expressions; + +/// +/// 表达式树 +/// MSDN链接 +/// +public class Test_Expression +{ + public static void Demo1() + { + // 官方例子:表达式体内只有一个式子 + // 创建表达式树 + Expression> exprTree = num => num < 5; + + // 分解表达式树 + ParameterExpression param = exprTree.Parameters[0];// num + BinaryExpression operation = (BinaryExpression)exprTree.Body;// 函数体 {(num < 5)} + ParameterExpression left = (ParameterExpression)operation.Left;// 左节点 num + ConstantExpression right = (ConstantExpression)operation.Right;// 右表达式 5 + + Console.WriteLine("表达式树例子: {0} => {1} {2} {3}", + param.Name, left.Name, operation.NodeType, right.Value); + Console.Read(); + } + + + public static void Demo2() + { + // 这里是会报错的!! 原因就是体内有多个例子需要分解!! + // Expression> exprTree = x => x > 5 && x < 50; + // + // // 分解表达式树 + // ParameterExpression param = exprTree.Parameters[0];// x + // BinaryExpression operation = (BinaryExpression)exprTree.Body;// 函数体 {((x > 5) AndAlso (x < 50))} + // + // ParameterExpression left = (ParameterExpression)operation.Left;// 左节点.......这里报错 + // ConstantExpression right = (ConstantExpression)operation.Right;// 右表达式.....这里报错 + // + // Console.WriteLine("表达式树例子: {0} => {1} {2} {3}", + // param.Name, left.Name, operation.NodeType, right.Value); + } + + + // 博客园例子,表达式体内有多个式子 + public static void Demo3() + { + List names = new() { "Cai", "Edward", "Beauty" }; + + Console.WriteLine("******************一个表达式"); + Expression> lambda2 = name => name.Length > 2 && name.Length < 4; + var method2 = ReBuildExpression(lambda2); + var query2 = names.Where(method2); + foreach (string n in query2) + Console.WriteLine(n); + + Console.WriteLine("******************二个表达式"); + Expression> lambda0 = item => item.Length > 2; + Expression> lambda1 = item => item.Length < 4; + var method = ReBuildExpression(lambda0, lambda1); + var query = names.Where(method); + foreach (string n in query) + Console.WriteLine(n); + Console.WriteLine("******************表达式结束"); + Console.Read(); + } + + + static Func ReBuildExpression(Expression> lambda) + { + MyExpressionVisitor my = new() + { + Parameter = Expression.Parameter(typeof(string), "name") + }; + + Expression left = my.Modify(lambda.Body); + // 构造一个新的表达式 + var newLambda = Expression.Lambda>(left, my.Parameter); + return newLambda.Compile(); + } + + + + /// + /// 重构表达式_合并 + /// + /// 匿名函数表达式1 + /// 匿名函数表达式2 + /// + static Func ReBuildExpression(Expression> lambda0, + Expression> lambda1) + { + MyExpressionVisitor my = new() + { + Parameter = Expression.Parameter(typeof(string), "name") + }; + + Expression left = my.Modify(lambda0.Body); + Expression right = my.Modify(lambda1.Body); + var expression = Expression.AndAlso(left, right);// 就是 && 合并两个匿名函数 + + // 构造一个新的表达式 + var newLambda = Expression.Lambda>(expression, my.Parameter); + return newLambda.Compile(); + } +} + + +/// +/// 表达式参数分解 +/// 博客园链接 +/// +public class MyExpressionVisitor : ExpressionVisitor +{ + /// + /// 公共参数 + /// + public ParameterExpression? Parameter; + /// + /// 返回替换后的参数表达式 + /// + /// + /// + public Expression Modify(Expression exp) + { + return Visit(exp); + } + /// + /// 重写参数 + /// + /// + /// + protected override Expression? VisitParameter(ParameterExpression p) + { + return Parameter; + } +} diff --git a/tests/TestShared/CmdINI.cs b/tests/TestShared/CmdINI.cs new file mode 100644 index 0000000000000000000000000000000000000000..0eeff285c443700900062d4ad821ecee303928da --- /dev/null +++ b/tests/TestShared/CmdINI.cs @@ -0,0 +1,188 @@ +//#define givePeopleTest +#if false +using System.Diagnostics; + +namespace Test; + +/// +/// 注册中心(自动执行接口): +/// +/// 继承虚函数后才能使用
+/// 0x01 netload加载之后自动执行,写入启动注册表,下次就不需要netload了
+/// 0x02 反射调用特性和接口
+/// 启动cad后的执行顺序为:
+/// 1:构造函数
+/// 2:特性..多个
+/// 3:接口..多个
+/// 4:本类的构造函数
+/// +/// **** 警告 **** +/// 如果不写一个 储存这个对象, +/// 而是直接写卸载命令在此, +/// 第一次加载的时候会初始化完成,然后这个类生命就结束了, +/// 第二次通过命令进入,会引发构造函数再次执行,留意构造函数的打印信息即可发现 +/// +///
+///
+public class AutoRegAssemEx : AutoRegAssem +{ + public AutoRegAssemEx() : base(AutoRegConfig.All) + { + CmdInit.AutoRegAssemEx = this; +#if givePeopleTest +#if Debug + // 此处用来反射本程序集,检查是否存在重复命令 + AutoReflection.DebugCheckCmdRecurrence(); +#endif + Env.Printl($"{nameof(AutoRegAssemEx)}构造函数,开始自动执行\r\n"); +#endif + } +} + + +public class CmdInit +{ + public static AutoRegAssemEx? AutoRegAssemEx; + + /// 如果netload之后用 删除注册表, + /// 由于不是也不能卸载dll,再netload是无法执行自动接口的, + /// 所以此时会产生无法再注册的问题...因此需要暴露此注册函数(硬来) + [CommandMethod(nameof(IFoxAddReg))] + public void IFoxAddReg() + { + Env.Printl($"加入注册表"); + + AutoRegAssemEx ??= new(); + AutoRegAssemEx.RegApp(); + } + + /// + /// 卸载注册表信息 + /// + [CommandMethod(nameof(IFoxRemoveReg))] + public void IFoxRemoveReg() + { + Env.Printl($"卸载注册表"); + + // 防止卸载两次,不然会报错的 + AutoRegAssemEx?.UnRegApp(); + AutoRegAssemEx = null; + } + + [CommandMethod(nameof(Debugx))] + public void Debugx() + { + var flag = Environment.GetEnvironmentVariable("debugx", EnvironmentVariableTarget.User); + if (flag == null || flag == "0") + { + Environment.SetEnvironmentVariable("debugx", "1", EnvironmentVariableTarget.User); + Env.Printl($"vs输出 -- 已启用"); + } + else + { + Environment.SetEnvironmentVariable("debugx", "0", EnvironmentVariableTarget.User); + Env.Printl($"vs输出 -- 已禁用"); + } + } +} + +#if givePeopleTest +/* + * 自动执行:特性 + */ +public class Cmd_IFoxInitialize +{ + int TestInt = 0; + + [IFoxInitialize] + public void Initialize() + { + Env.Printl($"开始自动执行,可以分开多个类和多个函数:{nameof(Cmd_IFoxInitialize)}.{nameof(Initialize)}+{TestInt}"); + } + + [IFoxInitialize] + public void Initialize2() + { + Env.Printl($"开始自动执行,可以分开多个类和多个函数,又一次测试:{nameof(Cmd_IFoxInitialize)}.{nameof(Initialize2)}"); + } + + //[IFoxInitialize(isInitialize: false)] + //public void Terminate() + //{ + // try + // { + // // 注意此时编辑器已经回收,所以此句引发错误 + // // 您可以写一些其他的释放动作,例如资源回收之类的 + // Env.Printl($"\n 结束自动执行 Terminate \r\n"); + // // 改用 + // Debugx.Printl($"\n 结束自动执行 Terminate \r\n"); + // } + // catch (System.Exception e) + // { + // System.Windows.Forms.MessageBox.Show(e.Message); + // } + //} + + [IFoxInitialize] + public static void StaticInitialize() + { + Env.Printl($"开始自动执行,静态调用:{nameof(Cmd_IFoxInitialize)}.{nameof(StaticInitialize)}"); + } +} + + +/* + * 自动执行:接口 + */ +public class Cmd_IFoxInitializeInterface : IFoxAutoGo +{ + int TestInt = 0; + public Cmd_IFoxInitializeInterface() + { + Env.Printl($"开始自动执行,{nameof(IFoxAutoGo)}接口调用:{nameof(Cmd_IFoxInitializeInterface)}::{TestInt}"); + } + + public Sequence SequenceId() + { + return Sequence.Last; + } + + public void Initialize() + { + Env.Printl($"开始自动执行,{nameof(IFoxAutoGo)}接口调用:{nameof(Initialize)}::{TestInt}"); + } + + public void Terminate() + { + Debugx.Printl($"开始自动执行,{nameof(IFoxAutoGo)}接口调用:{nameof(Terminate)}::{TestInt}"); + // try + // { + // // 注意此时编辑器已经回收,所以此句没用,并引发错误 + // Env.Printl($"结束自动执行 {nameof(Cmd_IFoxInitializeInterface)}.Terminate \r\n"); + // } + // catch (System.Exception e) + // { + // System.Windows.Forms.MessageBox.Show(e.Message); + // } + } +} +#endif +#endif + +public class Init : AutoLoad +{ + public override void Initialize() + { + + Env.Print("loading..."); + // 将程序的目录加入信任路径 + AppendSupportPath(CurrentDirectory.FullName); + } + + public override void Terminate() + { + // 这里不能调用输出函数,因为这个函数执行的时候,已经没有editor对象了。 + // 所以如果不是想要在cad关闭的时候清理某些东西,这里不用写任何的代码。 + + } +} \ No newline at end of file diff --git a/tests/TestShared/Copyclip.cs b/tests/TestShared/Copyclip.cs new file mode 100644 index 0000000000000000000000000000000000000000..cc4558c58a8e472009135d06f83299e7e0960cd2 --- /dev/null +++ b/tests/TestShared/Copyclip.cs @@ -0,0 +1,877 @@ +#define test +#define COPYCLIP +#define PASTECLIP + +namespace Test; +using Autodesk.AutoCAD.DatabaseServices; +using System; +using System.Diagnostics; +using System.Drawing.Imaging; +using System.Threading; +using System.Windows; + +/* + * 0x01 (已完成) + * 跨cad复制,由于高版本会保存为当前dwg格式,所以我们将所有都保存为07格式(有动态块), + * 就可以多个版本cad相互复制粘贴了 + * + * 0x02 + * 设置一个粘贴板栈,用tmp.config储存(路径和粘贴基点), + * ctrl+shfit+v v v 就是三次前的剪贴板内容;也可以制作一个剪贴板窗口更好给用户交互 + * + * 0x03 + * 天正图元的复制粘贴出错原因 + * + * 引用技术贴: + * https://forums.autodesk.com/t5/net/paste-list-of-objects-from-clipboard-on-dwg-file-using-c-net/td-p/6797606 + */ +public class Copyclip +{ + #region 命令 +#if test + static bool _IsRunIFoxCopyClip = false; + [IFoxInitialize] // 惊惊: 遇到了高版本无法导出WMF,放弃此功能,等待有缘人 + public void Init() + { + Acap.DocumentManager.DocumentLockModeChanged += Dm_VetoCommand; + Env.Printl($"※剪贴板控制※\n{nameof(Copyclip_Switch)} - 切换开关\n"); + } + + [CommandMethod(nameof(Copyclip_Switch))] + public void Copyclip_Switch() + { + _IsRunIFoxCopyClip = !_IsRunIFoxCopyClip; + Env.Printl("已经 " + (_IsRunIFoxCopyClip ? "开启" : "禁用") + " 剪贴板+"); + } + + /// + /// 反应器->命令否决触发命令前(不可锁文档) + /// + /// + /// + void Dm_VetoCommand(object sender, DocumentLockModeChangedEventArgs e) + { + if (string.IsNullOrEmpty(e.GlobalCommandName) || e.GlobalCommandName == "#") + return; + if (!_IsRunIFoxCopyClip) + return; + + var up = e.GlobalCommandName.ToUpper(); + + string? cmd = null; +#if COPYCLIP + if (up == "COPYCLIP")// 复制 + { + e.Veto(); + cmd = nameof(IFoxCopyClip); + } + else if (up == "COPYBASE") //ctrl+shift+c 带基点复制 + { + e.Veto(); + cmd = nameof(IFoxCopyBase); + } + else if (up == "CUTCLIP") // 剪切 + { + e.Veto(); + cmd = nameof(IFoxCutclip); + } +#endif +#if PASTECLIP + if (up == "PASTECLIP")// 粘贴 + { + // === 完成之后此处将会移除 + // 粘贴文本的生成单行文字/多行文字,这些还需要自己去实现 + var getClip = ClipTool.GetClipboard(ClipboardEnv.CadVer, out TagClipboardInfo tag); + if (!getClip) + return; + //=== 完成之后此处将会移除 + + e.Veto(); + cmd = nameof(IFoxPasteClip); + } + else if (up == "PASTEBLOCK") //ctrl+shift+v 粘贴为块 + { + // === 完成之后此处将会移除 + var getClip = ClipTool.GetClipboard(ClipboardEnv.CadVer, out TagClipboardInfo tag); + if (!getClip) + return; + //=== 完成之后此处将会移除 + + e.Veto(); + cmd = nameof(IFoxPasteBlock); + } +#endif + if (cmd != null) + { + var dm = Acap.DocumentManager; + if (dm.Count == 0) + return; + var doc = dm.MdiActiveDocument; + // 发送命令是因为com导出WMF需要命令形式,否则将报错 + // 但是发送命令会导致选择集被取消了,那么就需要设置 CommandFlags.Redraw + doc.SendStringToExecute(cmd + "\n", true, false, false); + } + } + + + /// + /// 复制 + /// + [CommandMethod(nameof(IFoxCopyClip), CommandFlags.UsePickSet | CommandFlags.Redraw)] + public void IFoxCopyClip() + { + Copy(false); + } + /// + /// 带基点复制 + /// + [CommandMethod(nameof(IFoxCopyBase), CommandFlags.UsePickSet | CommandFlags.Redraw)] + public void IFoxCopyBase() + { + Copy(true); + } + /// + /// 剪切 + /// + [CommandMethod(nameof(IFoxCutclip), CommandFlags.UsePickSet | CommandFlags.Redraw)] + public void IFoxCutclip() + { + Copy(false, true); + } + + + /// + /// 粘贴 + /// + [CommandMethod(nameof(IFoxPasteClip))] + public void IFoxPasteClip() + { + Paste(false); + } + /// + /// 粘贴为块 + /// + [CommandMethod(nameof(IFoxPasteBlock))] + public void IFoxPasteBlock() + { + Paste(true); + } +#endif + #endregion + + // 想要重启cad之后还可以继续用剪贴板,那么就不要这个: + // [IFoxInitialize(isInitialize: false)] + // 会出现永远存在临时文件夹的情况: + // 0x01 复制的时候,无法删除占用中的, + // 0x02 调试期间直接退出 acad.exe + public void Terminate() + { + // 此处要先去删除tmp文件夹的上次剪贴板产生的dwg文件 + for (int i = _delFile.Count - 1; i >= 0; i--) + { + try + { + if (File.Exists(_delFile[i])) + File.Delete(_delFile[i]); + _delFile.RemoveAt(i); + } + catch { Env.Printl("无法删除(是否占用):" + _delFile[i]); } + } + } + + + /// + /// 读写锁,当资源处于写入模式时,
+ /// 其他线程写入需要等待本次写入结束之后才能继续写入 + /// 参考链接 + ///
+ static ReaderWriterLockSlim _rwLock = new(); + + /// + /// 储存准备删除的文件 + /// 也可以用txt代替 + /// 如果删除出错(占用),将一直在这个集合中,直到cad关闭 + /// + readonly List _delFile = new(); + + /// + /// 复制 + /// + /// + void Copy(bool getPoint, bool isEraseSsget = false) + { + try + { + if (!_rwLock.IsWriteLockHeld) + _rwLock.EnterWriteLock(); // 进入写入锁 + + var dm = Acap.DocumentManager; + if (dm.Count == 0) + return; + var doc = dm.MdiActiveDocument; + if (doc == null) + return; + + ObjectId[] idArray; +#if true + var psr = doc.Editor.SelectImplied();// 预选 + if (psr.Status != PromptStatus.OK) + psr = doc.Editor.GetSelection();// 手选 + if (psr.Status != PromptStatus.OK) + return; + idArray = psr.Value.GetObjectIds(); +#endif + + // 设置基点 + Point3d pt = Point3d.Origin; + var tempFile = CreateTempFileName(); + while (File.Exists(tempFile) || + File.Exists(Path.ChangeExtension(tempFile, "wmf"))) + { + tempFile = CreateTempFileName(); + Thread.Sleep(1); + } + + using DBTrans tr = new(); + + + #region 写入 WMF 数据 + /* + * 通过cadCom导出wmf(免得自己转换每种图元), + * 再将wmf转为emf,然后才能写入剪贴板 + * wmf生成(win32api): https://www.cnblogs.com/5iedu/p/4706324.html + */ + IntPtr hMetaFile = IntPtr.Zero; + { + var wmf = Path.ChangeExtension(tempFile, "wmf"); + Env.Editor.ComExportWMF(wmf, idArray); + if (File.Exists(wmf)) + { + hMetaFile = PlaceableMetaHeader.Wmf2Emf(wmf);//emf文件句柄 + try { File.Delete(wmf); } + catch (Exception e) { Env.Printl(e); } + } + else + { + Env.Printl("没有创建wmf,失败"); + } + } + if (hMetaFile != IntPtr.Zero) + { + // 克隆一个,并写入描述...此处尚未完成 + // EmfTool.SetEnhMetaFileDescriptionEx(ref hMetaFile, "这是阿惊的emf"); + } + + // 保存文件 + //var emfsave = Path.ChangeExtension(cadClipType.File, ".emf"); + //EmfTool.Save(emf, emfsave); + #endregion + + #region 写入 AutoCAD.R17 数据 + if (getPoint) + { + var pr = doc.Editor.GetPoint("\n选择基点"); + if (pr.Status != PromptStatus.OK) + return; + pt = pr.Value; + } + else + { + // 遍历块内 + // 获取左下角点作为基点 + double minx = double.MaxValue; + double miny = double.MaxValue; + double minz = double.MaxValue; + foreach (var id in idArray) + { + var ent = tr.GetObject(id); + if (ent == null) + continue; + var info = ent.GetBoundingBoxEx(); + if (ent is BlockReference brf) + info.Move(brf.Position, Point3d.Origin); + minx = minx > info.MinX ? info.MinX : minx; + miny = miny > info.MinY ? info.MinY : miny; + minz = minz > info.MinZ ? info.MinZ : minz; + } + pt = new(minx, miny, minz); + } + + var cadClipType = new TagClipboardInfo(tempFile, pt); + + // 克隆到目标块表内 + using (DBTrans fileTr = new(cadClipType.File)) + { + fileTr.Task(() => { + using IdMapping map = new(); + using ObjectIdCollection ids = new(idArray); + tr.Database.WblockCloneObjects( + ids, + fileTr.ModelSpace.ObjectId, + map, + DuplicateRecordCloning.Replace, + false); + }); + + // 大于dwg07格式的,保存为07,以实现高低版本通用剪贴板 + // 小于dwg07格式的,本工程没有支持cad06dll + if ((int)DwgVersion.Current >= 27) + fileTr.SaveFile((DwgVersion)27, false); + else + throw new ArgumentException($"版本过低,无法保存,版本号:{DwgVersion.Current}"); + } + #endregion + + // 必须一次性写入剪贴板,详见 OpenClipboardTask + var cadClipFormat = ClipTool.RegisterClipboardFormat(ClipboardEnv.CadVer); + + // 剪贴板需要的指针: 克隆一个新的,不释放内存,不锁定内存(否则高频触发时候卡死) + var cadClipData = cadClipType.CloneToPtr(); + + bool getFlag = ClipTool.OpenClipboardTask(true, () => { + // 写入剪贴板: cad图元 + ClipTool.SetClipboardData(cadClipFormat, cadClipData); + + // 写入剪贴板: wmf,使得在粘贴链接的时候可以用 + if (hMetaFile != IntPtr.Zero) + ClipTool.SetClipboardData((uint)ClipboardFormat.CF_ENHMETAFILE, hMetaFile); + }); + if (hMetaFile != IntPtr.Zero) + EmfTool.DeleteEnhMetaFile(hMetaFile); + + // 成功拷贝就删除上一次的临时文件 + if (getFlag) + Terminate(); + + // 加入删除队列,下次删除 + if (!_delFile.Contains(cadClipType.File)) + _delFile.Add(cadClipType.File); + + // 剪切时候删除 + if (isEraseSsget) + { + idArray.ForEach(id => { + id.Erase(); + }); + } + } + catch (Exception e) + { + Debugger.Break(); + throw e; + } + finally + { + if (_rwLock.IsWriteLockHeld) + _rwLock.ExitWriteLock(); // 退出写入锁 + } + } + + + /// + /// 粘贴 + /// + /// + void Paste(bool isBlock) + { + try + { + if (!_rwLock.IsWriteLockHeld) + _rwLock.EnterWriteLock(); // 进入写入锁 + + var dm = Acap.DocumentManager; + if (dm.Count == 0) + return; + + var getClip = ClipTool.GetClipboard(ClipboardEnv.CadVer, out TagClipboardInfo tag); + if (!getClip) + { + // 在没有安装插件的高版本cad中复制,此时剪贴板是当前版本的, + // 那么在安装了插件的cad中需要识别这个同版本的剪贴板内容 + // 例如天正只在某个启动的cad中加载插件,而不是全部 + getClip = ClipTool.GetClipboard(ClipboardEnv.CadCurrentVer, out tag); + if (!getClip) + return; + } + + var cadClipType = tag; + Env.Print("粘贴来源: " + cadClipType.File); + + if (!File.Exists(cadClipType.File)) + { + Env.Print("文件不存在"); + return; + } + + // 获取临时文件的图元id + var fileEntityIds = new List(); + using (DBTrans fileTr = new(cadClipType.File, commit: false, + fileOpenMode: FileOpenMode.OpenForReadAndAllShare)) + { + fileTr.ModelSpace.ForEach(id => { + if (id.IsOk()) + fileEntityIds.Add(id); + }); + } + if (fileEntityIds.Count == 0) + return; + + using DBTrans tr = new(); + tr.Editor?.SetImpliedSelection(new ObjectId[0]); // 清空选择集 + + // 新建块表记录 + var btr = CreateBlockTableRecord(tr, cadClipType.File); + if (btr == null) + return; + + /// 克隆进块表记录 + /// 动态块粘贴之后,用ctrl+z导致动态块特性无法恢复, + /// 是因为它: + using IdMapping map = new(); + using ObjectIdCollection idc = new(fileEntityIds.ToArray()); + tr.Task(() => { + tr.Database.WblockCloneObjects( + idc, + btr.ObjectId, // tr.Database.BlockTableId, // 粘贴目标 + map, + DuplicateRecordCloning.Ignore, + false); + }); + + // 移动块内,从基点到原点 + foreach (var id in btr) + { + if (!id.IsOk()) + { + Env.Printl("jig预览块内有克隆失败的脏东西,是否天正克隆期间导致?"); + continue; + } + var ent = tr.GetObject(id); + if (ent == null) + continue; + using (ent.ForWrite()) + ent.Move(cadClipType.Point, Point3d.Origin); + } + + // 预览并获取交互点 + // 天正此处可能存在失败:天正图元不给你jig接口调用之类的 + using var moveJig = new JigEx((mousePoint, drawEntitys) => { + var brf = new BlockReference(Point3d.Origin, btr.ObjectId); + brf.Move(Point3d.Origin, mousePoint); + drawEntitys.Enqueue(brf); + }); + var jppo = moveJig.SetOptions(cadClipType.Point); + jppo.Keywords.Add(" ", " ", "<空格取消>"); + jppo.Keywords.Add("A", "A", "引线点粘贴(A)"); + + var dr = moveJig.Drag(); + Point3d moveTo = Point3d.Origin; + if (dr.Status == PromptStatus.Keyword) + moveTo = cadClipType.Point; + else if (dr.Status == PromptStatus.OK) + moveTo = moveJig.MousePointWcsLast; + else + { + // 删除jig预览的块表记录 + using (btr.ForWrite()) + btr.Erase(); + return; + } + + if (isBlock) + { + PasteIsBlock(tr, moveJig.Entitys, moveJig.MousePointWcsLast, moveTo); + } + else + { + PasteNotBlock(tr, btr, Point3d.Origin, moveTo); + // 删除jig预览的块表记录 + using (btr.ForWrite()) + btr.Erase(); + } + + try + { + #region 读取剪贴板WMF + var msg = new StringBuilder(); + + int a3 = 0; + a3 = 2 | 4; + if ((a3 & 1) == 1) + { + // win32api 不成功 + ClipTool.OpenClipboardTask(false, () => { + // 剪贴板数据保存目标数据列表 + List _bytes = new(); + var cf = (uint)ClipboardFormat.CF_ENHMETAFILE; + var clipTypeData = ClipTool.GetClipboardData(cf); + if (clipTypeData == IntPtr.Zero) + { + Env.Printl("失败:粘贴剪贴板emf1"); + return; + } + + // 无法锁定剪贴板emf内存,也无法获取GlobalSize + bool locked = WindowsAPI.GlobalLockTask(clipTypeData, prt => { + uint size = WindowsAPI.GlobalSize(prt); + if (size > 0) + { + var buffer = new byte[size]; + Marshal.Copy(prt, buffer, 0, buffer.Length); + _bytes.Add(buffer); + } + }); + if (!locked) + Env.Printl("锁定内存失败"); + }); + } + if ((a3 & 2) == 2) + { + ClipTool.OpenClipboardTask(false, () => { + // 无法锁定剪贴板emf内存,也无法获取GlobalSize + // 需要直接通过指针跳转到指定emf结构位置 + var cf = (uint)ClipboardFormat.CF_ENHMETAFILE; + var clipTypeData = ClipTool.GetClipboardData(cf); + if (clipTypeData == IntPtr.Zero) + { + Env.Printl("失败:粘贴剪贴板emf2"); + return; + } + + int a4 = 1; + //int a4 = 1 | 2 | 4; + if ((a4 & 1) == 1) + { + // 获取描述 + var desc = EmfTool.GetEnhMetaFileDescriptionEx(clipTypeData); + if (!string.IsNullOrEmpty(desc)) + msg.AppendLine("DescriptionEx::" + desc); + } + if ((a4 & 2) == 2) + { + // 获取文件信息 + var obj = EnhMetaHeader.Create(clipTypeData); + msg.AppendLine("EnhMetaHeader::" + obj.ToString()); + } + if ((a4 & 4) == 4) + { + // 保存文件 + //var emfsave = Path.ChangeExtension(cadClipType.File, ".emf"); + //EmfTool.Save(clipTypeData, emfsave); + } + }); + } + if ((a3 & 4) == 4) + { + // c# 读取成功,win32直接读取剪贴板的话是不成功的 + if (Clipboard.ContainsData(DataFormats.EnhancedMetafile)) + { + var iData = Clipboard.GetDataObject();//从剪切板获取数据 + if (!iData.GetDataPresent(DataFormats.EnhancedMetafile)) + return; + var metafile = (Metafile)iData.GetData(DataFormats.EnhancedMetafile); + /* + // 为什么序列化失败了呢 + var formatter = new BinaryFormatter(); + //using MemoryStream stream = new(); + Stream stream = new FileStream("MyFile.bin", FileMode.Create, FileAccess.Write, FileShare.None); + formatter.Serialize(stream, metafile); + stream.Close(); + //Metafile obj = (Metafile)formatter.Deserialize(stream); + */ + msg.AppendLine($"c# Metafile::{metafile.Size}"); + } + } + + if (msg.Length != 0) + Env.Printl(msg); + #endregion + } + catch (Exception e) + { + Debugger.Break(); + Debugx.Printl(e); + } + } + catch (Exception e)//{"剪贴板上的数据无效 (异常来自 HRESULT:0x800401D3 (CLIPBRD_E_BAD_DATA))"} + { + Debugger.Break(); + Debugx.Printl(e); + } + finally + { + if (_rwLock.IsWriteLockHeld) + _rwLock.ExitWriteLock(); // 退出写入锁 + } + } + + /// + /// 粘贴为块 + /// + /// + /// + /// + /// + static void PasteIsBlock(DBTrans tr, Entity[] entitys, Point3d move, Point3d moveTo) + { + if (!move.IsEqualTo(moveTo, new Tolerance(1e-6, 1e-6))) + { + entitys.ForEach(ent => { + ent.Move(move, moveTo); + }); + } + tr.CurrentSpace.AddEntity(entitys); + } + + /// + /// 直接粘贴(不为块参照) + /// + /// + /// + /// 它总是为 + /// 目标点 + static void PasteNotBlock(DBTrans tr, BlockTableRecord btr, Point3d move, Point3d moveTo) + { + using ObjectIdCollection ids = new(); + foreach (var id in btr) + { + if (!id.IsOk()) + continue; + ids.Add(id); + } + + // 深度克隆,然后平移到当前目标点位置 + using IdMapping map = new(); + tr.CurrentSpace.DeepCloneEx(ids, map); + + map.GetValues().ForEach(id => { + if (!id.IsOk()) + return; + var ent = tr.GetObject(id); + if (ent == null) + return; + using (ent.ForWrite()) + ent.Move(move, moveTo); + }); + } + + /// + /// 创建块表记录 + /// + /// + /// 此名称若已在块表存在,就会自动用时间名称代替 + /// + BlockTableRecord? CreateBlockTableRecord(DBTrans tr, string tempFile) + { + var blockNameNew = Path.GetFileNameWithoutExtension(tempFile); + while (tr.BlockTable.Has(blockNameNew)) + { + tempFile = CreateTempFileName(); + blockNameNew = Path.GetFileNameWithoutExtension(tempFile); + Thread.Sleep(1); + } + var btrIdNew = tr.BlockTable.Add(blockNameNew); + return tr.GetObject(btrIdNew); + } + + /// + /// 创建临时路径的时间文件名 + /// + /// 格式,X是16进制 + /// + static string CreateTempFileName(string format = "X") + { + var t1 = DateTime.Now.ToString("yyyyMMddHHmmssfffffff"); + t1 = Convert.ToInt32(t1.GetHashCode()).ToString(format); + var t2 = Convert.ToInt32(t1.GetHashCode()).ToString(format);// 这里是为了满足长度而做的 + return Path.GetTempPath() + "A$" + t1 + t2[0] + ".DWG"; + } +} + +#if !ac2008 +public class TestImageFormat +{ + public ImageFormat GetFormat(string filename) + { + string ext = Path.GetExtension(filename).ToLower(); + var imf = ext switch + { + ".bmp" => ImageFormat.Bmp, + ".gif" => ImageFormat.Gif, + ".jpg" => ImageFormat.Jpeg, + ".tif" => ImageFormat.Tiff, + ".wmf" => ImageFormat.Wmf, + ".png" => ImageFormat.Png, + _ => throw new NotImplementedException(), + }; + return imf; + } + + // 此处相当于截图,后台没有doc不可用 + // https://www.cnblogs.com/shangdishijiao/p/15166499.html + [CommandMethod(nameof(CreatePreviewImage))] + public void CreatePreviewImage() + { + using DBTrans tr = new(); + if (tr.Document == null) + return; + + var doc = tr.Document; + + var size = doc.Window.DeviceIndependentSize; + using var bmp = doc.CapturePreviewImage( + Convert.ToUInt32(size.Width), + Convert.ToUInt32(size.Height)); + + //保存wmf会变png,看二进制签名 + var outFile = Path.ChangeExtension(tr.Database.Filename, ".bmp"); + bmp.Save(outFile, GetFormat(outFile)); + Env.Printl($"保存文件:{outFile}"); + Env.Printl($"保存后缀:{GetFormat(outFile)}"); + + // 利用winAPI截图 + bool getFlag = ClipTool.OpenClipboardTask(true, () => { + BitmapTool.CaptureWndImage(doc.Window.Handle, bitmapHandle => { + // 写入剪贴板: BMP位图,这是截图,不是WMF转BMP,不对 + ClipTool.SetClipboardData((uint)ClipboardFormat.CF_BITMAP, bitmapHandle); + }); + }); + } +} +#endif + +public class OleTestClass +{ + // https://adndevblog.typepad.com/autocad/2012/04/update-linked-ole-object-from-net.html + + /// + /// 更新ole链接 + /// 如果 OLE 对象在另一个应用程序中打开,则上面的代码似乎不会更新该对象 + /// 例如,位图在 Microsoft 画图中打开 + /// + /// + /// + [DllImport("mfc90u.dll", CallingConvention = CallingConvention.ThisCall, + EntryPoint = "#6766")] + public static extern int COleClientItem_UpdateLink(IntPtr thisClientItem); + + [DllImport("acdb18.dll", CallingConvention = CallingConvention.ThisCall, + EntryPoint = "?getOleClientItem@AcDbOle2Frame@@QBEPAVCOleClientItem@@XZ")] + public static extern IntPtr AcDbOle2Frame_getOleClientItem(IntPtr thisOle2Frame); + + [CommandMethod(nameof(UpdateOleClient))] + public void UpdateOleClient() + { + var per = Env.Editor.GetEntity("\n选择OLE更新链接"); + if (per.Status != PromptStatus.OK) + return; + + using DBTrans tr = new(); + var ole2frame = tr.GetObject(per.ObjectId); + if (ole2frame == null) + return; + using (ole2frame.ForWrite()) + { + IntPtr ptrClientItem = AcDbOle2Frame_getOleClientItem(ole2frame.UnmanagedObject); + COleClientItem_UpdateLink(ptrClientItem); + } + } + + + // https://adndevblog.typepad.com/autocad/2012/06/iterating-ole-linked-entities.html + // 获取OLE路径 + [CommandMethod(nameof(GetOlePath))] + public void GetOlePath() + { + using DBTrans tr = new(); + foreach (ObjectId id in tr.CurrentSpace) + { + if (!id.IsOk()) + continue; + + var ole2frame = tr.GetObject(id); + if (ole2frame == null) + continue; + switch (ole2frame.Type) + { + case Ole2Frame.ItemType.Static: + Env.Editor.WriteMessage("\n" + "Static"); + break; + case Ole2Frame.ItemType.Embedded: + Env.Editor.WriteMessage("\n" + "Embedded"); + break; + case Ole2Frame.ItemType.Link: + Env.Editor.WriteMessage("\n" + "Link"); + Env.Editor.WriteMessage("\n" + ole2frame.LinkPath); + break; + } + } + } + + [CommandMethod(nameof(SetOle))] + public void SetOle() + { + //var pr = Env.Editor.GetPoint("\n选择基点"); + //if (pr.Status != PromptStatus.OK) + // return; + //var pt = pr.Value; + + using DBTrans tr = new(); + + // https://forums.autodesk.com/t5/net/how-to-create-an-ole-object-with-ole2frame/td-p/3203222 + var oo = new Ole2Frame(); + oo.SetDatabaseDefaults(); + //oo.CopyFrom() + oo.Position2d = new(0, 0, 100, 100); + + // 打印质量? https://learn.microsoft.com/zh-cn/dotnet/api/system.printing.outputquality?view=windowsdesktop-6.0 + //oo.OutputQuality = + //oo.AutoOutputQuality = + oo.Rotation = 0.0; + //oo.WcsWidth = ; + //oo.WcsHeight = ; + //oo.ScaleWidth = ; + //oo.ScaleHeight = ; + + //宽高比锁定 + oo.LockAspect = true; + } + +#if true2 + // https://forums.autodesk.com/t5/net/how-can-i-read-data-from-ole2frame-object-excel-through-api/td-p/7944241 + public void GetOleForOffice() + { + using DBTrans tr = new(); + foreach (ObjectId id in tr.CurrentSpace) + { + if (!id.IsOk()) + continue; + + var ole2frame = tr.GetObject(id); + if (ole2frame == null) + continue; + + var wb = (Microsoft.Office.Interop.Excel.Workbook)ole2frame.OleObject; + var ws = (Microsoft.Office.Interop.Excel.Worksheet)wb.ActiveSheet; + var range = (Microsoft.Office.Interop.Excel.Range)ws.UsedRange; + + tb.SetSize(range.Rows.Count, range.Columns.Count); + tb.SetRowHeight(range.RowHeight); + tb.SetColumnWidth(range.ColumnWidth); + tb.Position = new Point3d(ole2frame.Location.X, ole2frame.Location.Y, ole2frame.Location.Z); + for (int row = 0; row < range.Rows.Count; row++) + { + for (int col = 0; col < range.Columns.Count; col++) + { + tb.Cells[row, col].TextHeight = 1; + var aa = (Microsoft.Office.Interop.Excel.Range)range.Cells[row + 1, col + 1]; + var bb = Convert.ToString(aa.Value2); + tb.SetTextString(row, col, bb ?? ""); + tb.Cells[row, col].Alignment = CellAlignment.MiddleCenter; + } + } + tb.GenerateLayout(); + } + } +#endif +} \ No newline at end of file diff --git a/tests/TestShared/TestAOP.cs b/tests/TestShared/TestAOP.cs new file mode 100644 index 0000000000000000000000000000000000000000..872bf35646c1cc67b634688bf5624a324c2c32cd --- /dev/null +++ b/tests/TestShared/TestAOP.cs @@ -0,0 +1,68 @@ +namespace Test; + +// 被注入的函数将不能使用断点, +// 因此用户要充分了解才能使用 +#if false +/* + * 类库用户想侵入的命名空间是用户的, + * 所以需要用户手动进行AOP.Run(), + * 默认情况不侵入用户的命令,必须用户手动启用此功能; + * 启动执行策略之后,侵入命名空间下的命令, + * 此时有拒绝特性的策略保证豁免,因为用户肯定是想少写一个事务注入的特性; + */ +public class AutoAOP +{ + [IFoxInitialize] + public void Initialize() + { + AOP.Run(nameof(Test_)); + } +} + +namespace Test +{ + /* + * 天秀的事务注入,让你告别事务处理 + * https://www.cnblogs.com/JJBox/p/16157578.html + */ + public class AopTestClass + { + // 类不拒绝,这里拒绝 + [IFoxRefuseInjectionTransaction] + [CommandMethod(nameof(IFoxRefuseInjectionTransaction))] + public void IFoxRefuseInjectionTransaction() + { + } + + // 不拒绝 + [CommandMethod(nameof(InjectionTransaction))] + public void InjectionTransaction() + { + // 怎么用事务呢? + // 直接用 DBTrans.Top + var dBTrans = new DBTrans(); + dBTrans.Commit(); + } + } + + // 拒绝注入事务,写类上,则方法全都拒绝 + [IFoxRefuseInjectionTransaction] + public class AopTestClassRefuseInjection + { + // 此时这个也是拒绝的..这里加特性只是无所谓 + [IFoxRefuseInjectionTransaction] + [CommandMethod(nameof(IFoxRefuseInjectionTransaction2))] + public void IFoxRefuseInjectionTransaction2() + { + // 拒绝注入就要自己开事务,通常用在循环提交事务上面. + // 另见 报错0x02 https://www.cnblogs.com/JJBox/p/10798940.html + using DBTrans tr = new(); + } + + [CommandMethod(nameof(InjectionTransaction2))] + public void InjectionTransaction2() + { + } + } +} +#endif \ No newline at end of file diff --git a/tests/TestShared/TestAddEntity.cs b/tests/TestShared/TestAddEntity.cs new file mode 100644 index 0000000000000000000000000000000000000000..9b1dcd8e7319518567445ad7b6000cccb42fd30f --- /dev/null +++ b/tests/TestShared/TestAddEntity.cs @@ -0,0 +1,410 @@ +using System.Diagnostics; +using System.Web.UI.WebControls; + +namespace Test; + +public partial class Test +{ + [CommandMethod(nameof(Test_DBTrans))] + public void Test_DBTrans() + { + using DBTrans tr = new(); + if (tr.Editor is null) + return; + tr.Editor.WriteMessage("\n测试 Editor 属性是否工作!"); + tr.Editor.WriteMessage("\n----------开始测试--------------"); + tr.Editor.WriteMessage("\n测试document属性是否工作"); + if (tr.Document == Getdoc()) + { + tr.Editor.WriteMessage("\ndocument 正常"); + } + tr.Editor.WriteMessage("\n测试database属性是否工作"); + if (tr.Database == Getdb()) + { + tr.Editor.WriteMessage("\ndatabase 正常"); + } + + Line line = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + Circle circle = new(new Point3d(0, 0, 0), Vector3d.ZAxis, 2); + // var lienid = tr.AddEntity(line); + // var cirid = tr.AddEntity(circle); + // var linent = tr.GetObject(lienid); + // var lineent = tr.GetObject(cirid); + // var linee = tr.GetObject(cirid); // 经测试,类型不匹配,返回null + // var dd = tr.GetObject(lienid); + // List ds = new() { linee, dd }; + // tr.CurrentSpace.AddEntity(line,tr); + } + + // add entity test + [CommandMethod(nameof(Test_Addent))] + public void Test_Addent() + { + using DBTrans tr = new(); + Line line = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + tr.CurrentSpace.AddEntity(line); + Line line1 = new(new Point3d(10, 10, 0), new Point3d(41, 1, 0)); + tr.ModelSpace.AddEntity(line1); + Line line2 = new(new Point3d(-10, 10, 0), new Point3d(41, 1, 0)); + tr.PaperSpace.AddEntity(line2); + } + + [CommandMethod(nameof(Test_Drawarc))] + public void Test_Drawarc() + { + using DBTrans tr = new(); + Arc arc1 = ArcEx.CreateArcSCE(new Point3d(2, 0, 0), new Point3d(0, 0, 0), new Point3d(0, 2, 0));// 起点,圆心,终点 + Arc arc2 = ArcEx.CreateArc(new Point3d(4, 0, 0), new Point3d(0, 0, 0), Math.PI / 2); // 起点,圆心,弧度 + Arc arc3 = ArcEx.CreateArc(new Point3d(1, 0, 0), new Point3d(0, 0, 0), new Point3d(0, 1, 0)); // 起点,圆上一点,终点 + tr.CurrentSpace.AddEntity(arc1, arc2, arc3); + tr.CurrentSpace.AddArc(new Point3d(0, 0, 0), new Point3d(1, 1, 0), new Point3d(2, 0, 0));// 起点,圆上一点,终点 + } + + [CommandMethod(nameof(Test_DrawCircle))] + public void Test_DrawCircle() + { + using DBTrans tr = new(); + var circle1 = CircleEx.CreateCircle(new Point3d(0, 0, 0), new Point3d(1, 0, 0)); // 起点,终点 + var circle2 = CircleEx.CreateCircle(new Point3d(-2, 0, 0), new Point3d(2, 0, 0), new Point3d(0, 2, 0));// 三点画圆,成功 + var circle3 = CircleEx.CreateCircle(new Point3d(-2, 0, 0), new Point3d(0, 0, 0), new Point3d(2, 0, 0));// 起点,圆心,终点,失败 + tr.CurrentSpace.AddEntity(circle1, circle2!); + if (circle3 is not null) + tr.CurrentSpace.AddEntity(circle3); + else + tr.Editor?.WriteMessage("三点画圆失败"); + tr.CurrentSpace.AddEntity(circle3!); + tr.CurrentSpace.AddCircle(new Point3d(0, 0, 0), new Point3d(1, 1, 0), new Point3d(2, 0, 0));// 三点画圆,成功 + tr.CurrentSpace.AddCircle(new Point3d(0, 0, 0), new Point3d(1, 1, 0), new Point3d(2, 2, 0));// 起点,圆上一点,终点(共线) + } + + [CommandMethod(nameof(Test_LayerAdd0))] + public void Test_LayerAdd0() + { + using DBTrans tr = new(); + tr.LayerTable.Add("1"); + tr.LayerTable.Add("2", lt => { + lt.Color = Color.FromColorIndex(ColorMethod.ByColor, 1); + lt.LineWeight = LineWeight.LineWeight030; + }); + tr.LayerTable.Remove("3"); + tr.LayerTable.Delete("0"); + tr.LayerTable.Change("4", lt => { + lt.Color = Color.FromColorIndex(ColorMethod.ByColor, 2); + }); + } + + + // 添加图层 + [CommandMethod(nameof(Test_LayerAdd1))] + public void Test_LayerAdd1() + { + using DBTrans tr = new(); + tr.LayerTable.Add("test1", Color.FromColorIndex(ColorMethod.ByColor, 1)); + } + + // 添加图层 + [CommandMethod(nameof(Test_LayerAdd2))] + public void Test_LayerAdd2() + { + using DBTrans tr = new(); + tr.LayerTable.Add("test2", 2); + // tr.LayerTable["3"] = new LayerTableRecord(); + } + // 删除图层 + [CommandMethod(nameof(Test_LayerDel))] + public void Test_LayerDel() + { + using DBTrans tr = new(); + Env.Editor.WriteMessage(tr.LayerTable.Delete("0").ToString()); // 删除图层 0 + Env.Editor.WriteMessage(tr.LayerTable.Delete("Defpoints").ToString());// 删除图层 Defpoints + Env.Editor.WriteMessage(tr.LayerTable.Delete("1").ToString()); // 删除不存在的图层 1 + Env.Editor.WriteMessage(tr.LayerTable.Delete("2").ToString()); // 删除有图元的图层 2 + Env.Editor.WriteMessage(tr.LayerTable.Delete("3").ToString()); // 删除图层 3 + + tr.LayerTable.Remove("2"); // 测试是否能强制删除 + } + + // 添加直线 + [CommandMethod(nameof(Test_AddLine1))] + public void Test_AddLine1() + { + using DBTrans tr = new(); + // tr.ModelSpace.AddEnt(line); + // tr.ModelSpace.AddEnts(line,circle); + + // tr.PaperSpace.AddEnt(line); + // tr.PaperSpace.AddEnts(line,circle); + + // tr.addent(btr,line); + // tr.addents(btr,line,circle); + + + // tr.BlockTable.Add(new BlockTableRecord(), line => + // { + // line. + // }); + Line line1 = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + Line line2 = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + Line line3 = new(new Point3d(1, 1, 0), new Point3d(3, 3, 0)); + Circle circle = new(new Point3d(0, 0, 0), Vector3d.ZAxis, 10); + tr.CurrentSpace.AddEntity(line1); + tr.CurrentSpace.AddEntity(line2, line3, circle); + } + + // 增加多段线1 + [CommandMethod(nameof(Test_AddPolyline1))] + public void Test_AddPolyline1() + { + using DBTrans tr = new(); + Polyline pl = new(); + pl.SetDatabaseDefaults(); + pl.AddVertexAt(0, new Point2d(0, 0), 0, 0, 0); + pl.AddVertexAt(1, new Point2d(10, 10), 0, 0, 0); + pl.AddVertexAt(2, new Point2d(20, 20), 0, 0, 0); + pl.AddVertexAt(3, new Point2d(30, 30), 0, 0, 0); + pl.AddVertexAt(4, new Point2d(40, 40), 0, 0, 0); + pl.Closed = true; + pl.Color = Color.FromColorIndex(ColorMethod.ByColor, 6); + tr.CurrentSpace.AddEntity(pl); + } + + // 增加多段线2 + [CommandMethod(nameof(Test_AddPolyline2))] + public void Test_AddPolyline2() + { + var pts = new List<(Point3d, double, double, double)> + { + (new Point3d(0,0,0),0,0,0), + (new Point3d(10,0,0),0,0,0), + (new Point3d(10,10,0),0,0,0), + (new Point3d(0,10,0),0,0,0), + (new Point3d(5,5,0),0,0,0) + }; + using DBTrans tr = new(); + tr.CurrentSpace.AddPline(pts); + } + + + + + // 测试扩展数据 + static readonly string _appname = "myapp2"; + // 增 + [CommandMethod(nameof(Test_AddXdata))] + public void Test_AddXdata() + { + using DBTrans tr = new(); + var appname = "myapp2"; + + tr.RegAppTable.Add("myapp1"); + tr.RegAppTable.Add(appname); // add函数会默认的在存在这个名字的时候返回这个名字的regapp的id,不存在就新建 + tr.RegAppTable.Add("myapp3"); + tr.RegAppTable.Add("myapp4"); + + var line = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)) + { + XData = new XDataList() + { + { DxfCode.ExtendedDataRegAppName, "myapp1" }, // 可以用dxfcode和int表示组码 + { DxfCode.ExtendedDataAsciiString, "xxxxxxx" }, + {1070, 12 }, + { DxfCode.ExtendedDataRegAppName, appname }, // 可以用dxfcode和int表示组码,移除中间的测试 + { DxfCode.ExtendedDataAsciiString, "要移除的我" }, + {1070, 12 }, + { DxfCode.ExtendedDataRegAppName, "myapp3" }, // 可以用dxfcode和int表示组码 + { DxfCode.ExtendedDataAsciiString, "aaaaaaaaa" }, + {1070, 12 }, + { DxfCode.ExtendedDataRegAppName, "myapp4" }, // 可以用dxfcode和int表示组码 + { DxfCode.ExtendedDataAsciiString, "bbbbbbbbb" }, + {1070, 12 } + } + }; + + tr.CurrentSpace.AddEntity(line); + } + // 删 + [CommandMethod(nameof(Test_RemoveXdata))] + public void Test_RemoveXdata() + { + var res = Env.Editor.GetEntity("\n select the entity:"); + if (res.Status == PromptStatus.OK) + { + using DBTrans tr = new(); + var ent = tr.GetObject(res.ObjectId); + if (ent == null || ent.XData == null) + return; + + Env.Printl("\n移除前:" + ent.XData.ToString()); + + ent.RemoveXData(_appname, DxfCode.ExtendedDataAsciiString); + Env.Printl("\n移除成员后:" + ent.XData.ToString()); + + ent.RemoveXData(_appname); + Env.Printl("\n移除appName后:" + ent.XData.ToString()); + } + } + // 查 + [CommandMethod(nameof(Test_GetXdata))] + public void Test_GetXdata() + { + using DBTrans tr = new(); + tr.RegAppTable.ForEach(id => + id.GetObject()?.Name.Print()); + tr.RegAppTable.GetRecords().ForEach(rec => rec.Name.Print()); + tr.RegAppTable.GetRecordNames().ForEach(name => name.Print()); + tr.RegAppTable.ForEach(reg => reg.Name.Print(), checkIdOk: false); + + // var res = ed.GetEntity("\n select the entity:"); + // if (res.Status == PromptStatus.OK) + // { + // using DBTrans tr = new(); + // tr.RegAppTable.ForEach(id => id.GetObject().Print()); + // var data = tr.GetObject(res.ObjectId).XData; + // ed.WriteMessage(data.ToString()); + // } + + // 查询appName里面是否含有某个 + + var res = Env.Editor.GetEntity("\n select the entity:"); + if (res.Status == PromptStatus.OK) + { + var ent = tr.GetObject(res.ObjectId); + if (ent == null || ent.XData == null) + return; + + XDataList data = ent.XData; + if (data.Contains(_appname)) + Env.Printl("含有appName:" + _appname); + else + Env.Printl("不含有appName:" + _appname); + + var str = "要移除的我"; + if (data.Contains(_appname, str)) + Env.Printl("含有内容:" + str); + else + Env.Printl("不含有内容:" + str); + } + } + // 改 + [CommandMethod(nameof(Test_ChangeXdata))] + public void Test_ChangeXdata() + { + var res = Env.Editor.GetEntity("\n select the entity:"); + if (res.Status != PromptStatus.OK) + return; + using DBTrans tr = new(); + var data = tr.GetObject(res.ObjectId)!; + data.ChangeXData(_appname, DxfCode.ExtendedDataAsciiString, "change"); + + if (data.XData == null) + return; + Env.Printl(data.XData.ToString()); + } + + + + [CommandMethod(nameof(Test_PrintLayerName))] + public void Test_PrintLayerName() + { + using DBTrans tr = new(); + foreach (var layerRecord in tr.LayerTable.GetRecords()) + { + tr.Editor?.WriteMessage(layerRecord.Name); + } + foreach (var layerRecord in tr.LayerTable.GetRecords()) + { + tr.Editor?.WriteMessage(layerRecord.Name); + break; + } + } + + + [CommandMethod(nameof(Test_Rec))] + public void Test_Rec() + { + Point2d p1 = new(10000.2, 100000.5); + Point2d p2 = new(15000.9, 100000.5); + Point2d p3 = new(15000.9, 105000.7); + Point2d p4 = new(10000.2, 105000.7); + + var p12 = p2 - p1; + var p23 = p3 - p2; + var p34 = p4 - p3; + var p41 = p1 - p4; + var p13 = p3 - p1; + var p24 = p4 - p2; + + + const double pi90 = Math.PI / 2; + pi90.Print(); + + Tools.TestTimes(1000000, "对角线", () => { + var result = false; + if (Math.Abs(p13.Length - p24.Length) <= 1e8) + { + result = p41.IsParallelTo(p12); + } + }); + +#pragma warning disable CS0219 // 变量已被赋值,但从未使用过它的值 + Tools.TestTimes(1000000, "三次点乘", () => { + var result = false; + if (Math.Abs(p12.DotProduct(p23)) < 1e8 && + Math.Abs(p23.DotProduct(p34)) < 1e8 && + Math.Abs(p34.DotProduct(p41)) < 1e8) + result = true; + }); + + Tools.TestTimes(1000000, "三次垂直", () => { + var result = false; + if (p12.IsParallelTo(p23) && + p23.IsParallelTo(p34) && + p34.IsParallelTo(p41)) + result = true; + }); +#pragma warning restore CS0219 // 变量已被赋值,但从未使用过它的值 + } + + public Database Getdb() + { + var db = Acap.DocumentManager.MdiActiveDocument.Database; + return db; + } + + public Document Getdoc() + { + var doc = Acap.DocumentManager.MdiActiveDocument; + return doc; + } + + + [CommandMethod(nameof(Test_EntRoration))] + public void Test_EntRoration() + { + var line = new Line(new(0, 0, 0), new(100, 0, 0)); + + using DBTrans tr = new(); + tr.CurrentSpace.AddEntity(line); + var line2 = (Line)line.Clone(); + tr.CurrentSpace.AddEntity(line2); + line2.Rotation(new(100, 0, 0), Math.PI / 2); + } + + [CommandMethod(nameof(Test_TypeSpeed))] + public void Test_TypeSpeed() + { + var line = new Line(); + var line1 = line as Entity; + Tools.TestTimes(100000, "is 匹配:", () => { + var t = line1 is Line; + }); + Tools.TestTimes(100000, "name 匹配:", () => { + // var t = line.GetType().Name; + var tt = line1.GetType().Name == nameof(Line); + }); + Tools.TestTimes(100000, "dxfname 匹配:", () => { + // var t = line.GetType().Name; + var tt = line1.GetRXClass().DxfName == nameof(Line); + }); + } +} \ No newline at end of file diff --git a/tests/TestShared/TestBlock.cs b/tests/TestShared/TestBlock.cs new file mode 100644 index 0000000000000000000000000000000000000000..75403c346d899dcff30fde2f4ee8352b62437286 --- /dev/null +++ b/tests/TestShared/TestBlock.cs @@ -0,0 +1,649 @@ +namespace Test; + +public class TestBlock +{ + // 一个命令就把块编辑搞定,减少用户记忆命令 + [CommandMethod(nameof(Test_Refedit), CommandFlags.Redraw | CommandFlags.Session)] + public void Test_Refedit() + { + Env.Printl($"{nameof(Test_Refedit)}-在位编辑块/在位保存块"); + + // 全部用lisp发送命令是为了空格还是本命令 + // 打开了块编辑器,就关闭掉,保存提示 + if ((short)Env.GetVar("BlockEditor") == 1) + { + Env.Editor.RunLisp("(command \"_.bclose\")"); + return; + } + // 0x01 非在位编辑状态: 先选择块参照,然后在位编辑 + // 0x02 在位编辑状态: 关闭并保存 + if (Env.GetVar("RefEditName").ToString() == "")//显示正在编辑的参照名称 + Env.Editor.RunLisp("(command \"_.refedit\")");//直接点选可以有嵌套层次 + else + Env.Editor.RunLisp("(command \"_.refclose\" \"s\")"); + } + + [CommandMethod(nameof(Test_GetBoundingBoxEx))] + public void Test_GetBoundingBoxEx() + { + using DBTrans tr = new(); + var ents = Env.Editor.SSGet()?.Value?.GetEntities(); + if (ents == null) + return; + foreach (var item in ents) + { + if (item is null) + continue; + var box = item.GetBoundingBoxEx(); + Env.Print("min:" + box.Min + ";max" + box.Max); + } + } + + // 前台块定义 + [CommandMethod(nameof(Test_BlockDef))] + public void Test_BlockDef() + { + using DBTrans tr = new(); + // var line = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + tr.BlockTable.Add("test", + btr => { + btr.Origin = new Point3d(0, 0, 0); + }, + () => // 图元 + { + return new List { new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)) }; + }, + () => // 属性定义 + { + var id1 = new AttributeDefinition() { Position = new Point3d(0, 0, 0), Tag = "start", Height = 0.2 }; + var id2 = new AttributeDefinition() { Position = new Point3d(1, 1, 0), Tag = "end", Height = 0.2 }; + return new List { id1, id2 }; + } + ); + // ObjectId objectId = tr.BlockTable.Add("a");// 新建块 + // objectId.GetObject().AddEntity();// 测试添加空实体 + tr.BlockTable.Add("test1", + btr => { + btr.Origin = new Point3d(0, 0, 0); + }, + () => { + var line = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + var acText = new TextInfo("123", Point3d.Origin, AttachmentPoint.BaseLeft) + .AddDBTextToEntity(); + + return new List { line, acText }; + }); + } + + // 后台块定义 + [CommandMethod(nameof(Test_BlockDefbehind))] + public void Test_BlockDefbehind() + { + using DBTrans tr = new(@"C:\Users\vic\Desktop\test.dwg"); + // var line = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + tr.BlockTable.Add("test", + btr => { + btr.Origin = new Point3d(0, 0, 0); + }, + () => // 图元 + { + return new List { new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)) }; + }, + () => // 属性定义 + { + var id1 = new AttributeDefinition() { Position = new Point3d(0, 0, 0), Tag = "start", Height = 0.2 }; + var id2 = new AttributeDefinition() { Position = new Point3d(1, 1, 0), Tag = "end", Height = 0.2 }; + return new List { id1, id2 }; + } + ); + // ObjectId objectId = tr.BlockTable.Add("a");// 新建块 + // objectId.GetObject().AddEntity();// 测试添加空实体 + tr.BlockTable.Add("test1", + btr => { + btr.Origin = new Point3d(0, 0, 0); + }, + () => { + var line = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + var acText = new TextInfo("12345", Point3d.Origin, AttachmentPoint.BaseLeft) + .AddDBTextToEntity(); + + return new List { line, acText }; + }); + tr.SaveDwgFile(); + } + + + + // 修改块定义 + [CommandMethod(nameof(Test_BlockDefChange))] + public void Test_BlockDefChange() + { + using DBTrans tr = new(); + // var line = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + // tr.BlockTable.Change("test", btr => + // { + // btr.Origin = new Point3d(5, 5, 0); + // btr.AddEntity(new Circle(new Point3d(0, 0, 0), Vector3d.ZAxis, 2)); + // btr.GetEntities() + // .ToList() + // .ForEach(e => e.Flush()); // 刷新块显示 + + // }); + + + tr.BlockTable.Change("test", btr => { + foreach (var id in btr) + { + var ent = tr.GetObject(id); + using (ent!.ForWrite()) + { + if (ent is Dimension dBText) + { + dBText.DimensionText = "234"; + dBText.RecomputeDimensionBlock(true); + } + if (ent is Hatch hatch) + hatch.ColorIndex = 0; + } + } + }); + tr.Editor?.Regen(); + } + + [CommandMethod(nameof(Test_InsertBlockDef))] + public void Test_InsertBlockDef() + { + using DBTrans tr = new(); + var line1 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + var line2 = new Line(new Point3d(0, 0, 0), new Point3d(-1, 1, 0)); + var att1 = new AttributeDefinition() { Position = new Point3d(10, 10, 0), Tag = "tagTest1", Height = 1, TextString = "valueTest1" }; + var att2 = new AttributeDefinition() { Position = new Point3d(10, 12, 0), Tag = "tagTest2", Height = 1, TextString = "valueTest2" }; + tr.BlockTable.Add("test1", line1, line2, att1, att2); + + + var ents = new List(); + var line5 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + var line6 = new Line(new Point3d(0, 0, 0), new Point3d(-1, 1, 0)); + ents.Add(line5); + ents.Add(line6); + tr.BlockTable.Add("test44", ents); + + + var line3 = new Line(new Point3d(5, 5, 0), new Point3d(6, 6, 0)); + var line4 = new Line(new Point3d(5, 5, 0), new Point3d(-6, 6, 0)); + var att3 = new AttributeDefinition() { Position = new Point3d(10, 14, 0), Tag = "tagTest3", Height = 1, TextString = "valueTest3" }; + var att4 = new AttributeDefinition() { Position = new Point3d(10, 16, 0), Tag = "tagTest4", Height = 1, TextString = "valueTest4" }; + tr.BlockTable.Add("test2", new List { line3, line4 }, new List { att3, att4 }); + // tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test1"); // 测试默认 + // tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test2"); + // tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test3"); // 测试插入不存在的块定义 + // tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test1", new Scale3d(2)); // 测试放大2倍 + // tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test1", new Scale3d(2), Math.PI / 4); // 测试放大2倍,旋转45度 + + var def1 = new Dictionary + { + { "tagTest1", "1" }, + { "tagTest2", "2" } + }; + tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test1", atts: def1); + var def2 = new Dictionary + { + { "tagTest3", "1" }, + { "tagTest4", "" } + }; + tr.CurrentSpace.InsertBlock(new Point3d(10, 10, 0), "test2", atts: def2); + tr.CurrentSpace.InsertBlock(new Point3d(-10, 0, 0), "test44"); + } + + [CommandMethod(nameof(Test_AddAttsDef))] + public void Test_AddAttsDef() + { + using DBTrans tr = new(); + var blockid = Env.Editor.GetEntity("pick block:").ObjectId; + var btf = tr.GetObject(blockid); + if (btf is null) + return; + var att1 = new AttributeDefinition() { Position = new Point3d(20, 20, 0), Tag = "addtagTest1", Height = 1, TextString = "valueTest1" }; + var att2 = new AttributeDefinition() { Position = new Point3d(10, 12, 0), Tag = "tagTest2", Height = 1, TextString = "valueTest2" }; + tr.BlockTable.AddAttsToBlocks(btf.BlockTableRecord, new() { att1, att2 }); + } + + [CommandMethod(nameof(Test_BlockNullBug))] + public void Test_BlockNullBug() + { + using DBTrans tr = new(); + + var ents = new List(); + var line5 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + var line6 = new Line(new Point3d(0, 0, 0), new Point3d(-1, 1, 0)); + ents.Add(line5); + ents.Add(line6); + tr.BlockTable.Add("test44", ents); + tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test44"); + } + + [CommandMethod(nameof(Test_BlockFile))] + public void Test_BlockFile() + { + using DBTrans tr = new(); + var id = tr.BlockTable.GetBlockFrom(@"C:\Users\vic\Desktop\test.dwg", false); + tr.CurrentSpace.InsertBlock(Point3d.Origin, id); + } + + + [CommandMethod(nameof(Test_ClipBlock))] + public void Test_ClipBlock() + { + using DBTrans tr = new(); + tr.BlockTable.Add("test1", btr => { + btr.Origin = new Point3d(0, 0, 0); + btr.AddEntity(new Line(new Point3d(0, 0, 0), new Point3d(10, 10, 0)), + new Line(new Point3d(10, 10, 0), new Point3d(10, 0, 0))); + }); + // tr.BlockTable.Add("hah"); + var id = tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test1"); + var brf1 = tr.GetObject(id)!; + var pts = new List { new Point3d(3, 3, 0), new Point3d(7, 3, 0), new Point3d(7, 7, 0), new Point3d(3, 7, 0) }; + brf1.ClipBlockRef(pts); + + var id1 = tr.CurrentSpace.InsertBlock(new Point3d(20, 20, 0), "test1"); + var brf2 = tr.GetObject(id); + brf2?.ClipBlockRef(new Point3d(13, 13, 0), new Point3d(17, 17, 0)); + } + + // 给用户的测试程序,不知道对错 + [CommandMethod(nameof(Test_Block_ej))] + public void Test_Block_ej() + { + using (DBTrans tr = new()) + { + // Point3d.Origin.AddBellowToModelSpace(100, 100, 5, 3, 30);// 画波纹管 + + // Database db2 = new Database(false, true); + // string fullFileName = @".\MyBlockDwgFile\001.dwg"; + // db2.ReadDwgFile(fullFileName, System.IO.FileShare.Read, true, null); + // db2.CloseInput(true); + // string blockName = "test"; + // if (!tr.BlockTable.Has(blockName)) + // { + // // tr.Database.Insert(blockName, db2, false);// 插入块 + // db.Insert(blockName, db2, false); + + // } + + string fullFileName = @"C:\Users\vic\Desktop\001.dwg"; + var blockdef = tr.BlockTable.GetBlockFrom(fullFileName, false); + + tr.Database.Clayer = tr.LayerTable["0"];// 当前图层切换为0图层 + tr.LayerTable.Change(tr.Database.Clayer, ltr => { + ltr.Color = Color.FromColorIndex(ColorMethod.ByAci, 2); // ColorMethod.ByAci可以让我们使用AutoCAD ACI颜色索引……这里为2(表示黄色) + }); + + var id = tr.ModelSpace.InsertBlock(Point3d.Origin, blockdef);// 插入块参照 + var brf = tr.GetObject(id); + brf?.Draw(); + } + + using DBTrans tr2 = new(); + PromptEntityOptions peo = new("\n请选择一个块"); + peo.SetRejectMessage("\n对象必须是块"); + peo.AddAllowedClass(typeof(BlockReference), true); + + var per = Env.Editor.GetEntity(peo); + if (per.Status != PromptStatus.OK) + return; + + var brf2 = tr2.GetObject(per.ObjectId)!; + // var BTR = tr.GetObject(Bref.BlockTableRecord, OpenMode.ForWrite); + //// 如果知道块名字BTRName + // BlockTableRecord BTR = tr.GetObject(tr.BlockTable[blockName], OpenMode.ForWrite); + + var btr = tr2.BlockTable[brf2.Name]; + + tr2.BlockTable.Change(btr, ltr => { + foreach (ObjectId oid in ltr) + { + var ent = tr2.GetObject(oid); + if (ent is MText mText) + { + using (ent.ForWrite()) + switch (mText.Text) + { + case "$$A": + mText.Contents = "hahaha"; + break; + case "$$B": + break; + default: + break; + } + } + else if (ent is DBText dBText) + { + using (ent.ForWrite()) + dBText.TextString = "haha"; + } + else if (ent is Dimension dimension) + { + using (ent.ForWrite()) + switch (dimension.DimensionText) + { + case "$$pipeLen": + dimension.DimensionText = "350"; + dimension.RecomputeDimensionBlock(true); + break; + default: + break; + } + } + } + }); + tr2.Editor?.Regen(); + } + + [CommandMethod(nameof(Test_QuickBlockDef2))] + public void Test_QuickBlockDef2() + { + // Database db = HostApplicationServices.WorkingDatabase; + Editor ed = Acap.DocumentManager.MdiActiveDocument.Editor; + PromptSelectionOptions promptOpt = new() + { + MessageForAdding = "请选择需要快速制作块的对象" + }; + string blockName = "W_BLOCK_" + DateTime.Now.ToString("yyyyMMdd_HHmmss"); + // var rss = ed.GetSelection(promptOpt); + var rss = Env.Editor.GetSelection(promptOpt); + using DBTrans tr = new(); + if (rss.Status == PromptStatus.OK) + { + // SelectionSet ss = rss.Value; + // ObjectId[] ids = ss.GetObjectIds(); + // var ents = new List>(); + // var extents = new Extents3d(); + // foreach (var id in ids) + // { + // Entity ent = tr.GetObject(id); + // if (ent is null) + // continue; + // try + // { + // extents.AddExtents(ent.GeometricExtents); + // var order = id.Handle.Value; + // var newEnt = ent.Clone() as Entity; + // ents.Add(new KeyValuePair(newEnt, order)); + // ent.UpgradeOpen(); + // ent.Erase(); + // ent.DowngradeOpen(); + // } + // catch (System.Exception exc) + // { + // ed.WriteMessage(exc.Message); + // } + // } + // ents = ents.OrderBy(x => x.Value).ToList(); + var ents = rss.Value.GetEntities(); + // ents.ForEach(ent => extents.AddExtents(ent.GeometricExtents)); + var extents = ents!.GetExtents(); + Point3d pt = extents.MinPoint; + Matrix3d matrix = Matrix3d.Displacement(Point3d.Origin - pt); + // var newEnts = new List(); + // foreach (var ent in ents) + // { + // var newEnt = ent.Key; + // newEnt.TransformBy(matrix); + // newEnts.Add(newEnt); + // } + // if (tr.BlockTable.Has(blockName)) + // { + // Acap.ShowAlertDialog(Environment.NewLine + "块名重复,程序退出!"); + // return; + // } + ents.ForEach(ent => + ent?.ForWrite(e => e?.TransformBy(matrix))); + // var newents = ents.Select(ent => + // { + // var maping = new IdMapping(); + // return ent.DeepClone(ent, maping, true) as Entity; + // }); + var newents = ents.Select(ent => ent?.Clone() as Entity); + + // ents.ForEach(ent => ent.ForWrite(e => e.Erase(true))); // 删除实体就会卡死,比较奇怪,估计是Clone()函数的问题 + // 经过测试不是删除的问题 + var btrId = tr.BlockTable.Add(blockName, newents!); + ents.ForEach(ent => ent?.ForWrite(e => e?.Erase(true))); + var bId = tr.CurrentSpace.InsertBlock(pt, blockName); + // tr.GetObject(bId, OpenMode.ForWrite).Move(Point3d.Origin, Point3d.Origin); + // var ed = Acap.DocumentManager.MdiActiveDocument.Editor; + // ed.Regen(); + // tr.Editor.Regen(); + // 调用regen() 卡死 + } + // tr.Editor.Regen(); + // ed.Regen(); + // using (var tr = new DBTrans()) + // { + // tr.CurrentSpace.InsertBlock(Point3d.Origin, blockName); + // tr.Editor.Regen(); + // } + } + + [CommandMethod(nameof(Test_QuickBlockDef1))] + public void Test_QuickBlockDef1() + { + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + var db = doc.Database; + var ed = doc.Editor; + + PromptSelectionOptions promptOpt = new() + { + MessageForAdding = "请选择需要快速制作块的对象" + }; + string blockName = "W_BLOCK_" + DateTime.Now.ToString("yyyyMMdd_HHmmss"); + var rss = Env.Editor.GetSelection(promptOpt); + if (rss.Status != PromptStatus.OK) + return; + + using var tr = db.TransactionManager.StartTransaction(); + var ids = rss.Value.GetObjectIds(); + var bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable; + var btr = new BlockTableRecord + { + Name = blockName + }; + foreach (var item in ids) + { + var ent = tr.GetObject(item, OpenMode.ForRead) as Entity; + btr.AppendEntity(ent!.Clone() as Entity); + ent.ForWrite(e => e.Erase(true)); + } + bt!.UpgradeOpen(); + bt.Add(btr); + tr.AddNewlyCreatedDBObject(btr, true); + bt.DowngradeOpen(); + // tr.Commit(); + // } + + // using (var tr1 = db.TransactionManager.StartTransaction()) + // { + // var bt = tr1.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable; + var btr1 = tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite) as BlockTableRecord; + var brf = new BlockReference(Point3d.Origin, bt[blockName]) + { + ScaleFactors = default + }; + btr1!.AppendEntity(brf); + tr.AddNewlyCreatedDBObject(brf, true); + btr1.DowngradeOpen(); + ed.Regen(); + tr.Commit(); + // ed.Regen(); + } + + void Wblock() + { + var curdb = HostApplicationServices.WorkingDatabase; + PromptSelectionOptions opts = new() + { + MessageForAdding = "选择对象" + }; + var ss = Env.Editor.GetSelection(opts).Value; + using ObjectIdCollection ids = new(ss.GetObjectIds()); + var db = curdb.Wblock(ids, Point3d.Origin); + db.SaveAs(@"c:\test.dwg", DwgVersion.Current); + } + + void ChangeDynameicBlock() + { + var pro = new Dictionary + { + { "haha", 1 } + }; + var blockid = Env.Editor.GetEntity("选择个块").ObjectId; + using DBTrans tr = new(); + var brf = tr.GetObject(blockid)!; + brf.ChangeBlockProperty(pro); + // 这是第一个函数的用法 + } + + [CommandMethod(nameof(Test_Back))] + public void Test_Back() + { + string dir = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory); + string dwg = dir + "\\test.dwg"; + if (!File.Exists(dwg)) + { + System.Windows.Forms.MessageBox.Show(dwg, "你还没有创建此文件"); + return; + } + + using DBTrans tr = new(dwg); + tr.ModelSpace.GetEntities().ForEach(ent => { + ent.ForWrite(e => e.ColorIndex = 3); + }); + tr.Database.SaveAs(dwg, DwgVersion.Current); + + tr.ModelSpace.GetEntities().ForEach(ent => { + ent.ForWrite(e => e.ColorIndex = 4); + }); + tr.Database.SaveAs(dwg, DwgVersion.Current); + } +} + +public class BlockImportClass +{ + [CommandMethod(nameof(Test_Cbll))] + public void Test_Cbll() + { + string filename = @"C:\Users\vic\Desktop\Drawing1.dwg"; + using DBTrans tr = new(); + using DBTrans tr1 = new(filename); + // tr.BlockTable.GetBlockFrom(filename, true); + string blkdefname = SymbolUtilityServices.RepairSymbolName(SymbolUtilityServices.GetSymbolNameFromPathName(filename, "dwg"), false); + tr.Database.Insert(blkdefname, tr1.Database, false); // 插入了块定义,未插入块参照 + } + +#if !NET35 + [CommandMethod(nameof(Test_CombineBlocksIntoLibrary))] + public void Test_CombineBlocksIntoLibrary() + { + Document doc = Acap.DocumentManager.MdiActiveDocument; + Editor ed = doc.Editor; + Database destDb = doc.Database; + + PromptResult pr = ed.GetString("\nEnter the folder of source drawings: "); + + if (pr.Status != PromptStatus.OK) + return; + string pathName = pr.StringResult; + if (!Directory.Exists(pathName)) + { + ed.WriteMessage("\nDirectory does not exist: {0}", pathName); + return; + } + string[] fileNames = Directory.GetFiles(pathName, "*.dwg"); + int imported = 0, failed = 0; + foreach (string fileName in fileNames) + { + if (fileName.EndsWith(".dwg", + StringComparison.InvariantCultureIgnoreCase)) + { + // Catch exceptions at the file level to allow skipping + try + { + // Suggestion from Thorsten Meinecke... + + string destName = + SymbolUtilityServices.GetSymbolNameFromPathName( + fileName, "dwg" + ); + + // And from Dan Glassman... + + destName = + SymbolUtilityServices.RepairSymbolName( + destName, false + ); + + // Create a source database to load the DWG into + + using Database db = new(false, true); + // Read the DWG into our side database + + db.ReadDwgFile(fileName, FileShare.Read, true, ""); + bool isAnno = db.AnnotativeDwg; + + // Insert it into the destination database as + // a named block definition + + ObjectId btrId = destDb.Insert( + destName, + db, + false + ); + + if (isAnno) + { + // If an annotative block, open the resultant BTR + // and set its annotative definition status + + Transaction tr = + destDb.TransactionManager.StartTransaction(); + using (tr) + { + BlockTableRecord btr = + (BlockTableRecord)tr.GetObject( + btrId, + OpenMode.ForWrite + ); + btr.Annotative = AnnotativeStates.True; + tr.Commit(); + } + } + + // Print message and increment imported block counter + + ed.WriteMessage("\nImported from \"{0}\".", fileName); + imported++; + } + catch (System.Exception ex) + { + ed.WriteMessage( + "\nProblem importing \"{0}\": {1} - file skipped.", + fileName, ex.Message + ); + failed++; + } + } + } + + ed.WriteMessage( + "\nImported block definitions from {0} files{1} in " + + "\"{2}\" into the current drawing.", + imported, + failed > 0 ? " (" + failed + " failed)" : "", + pathName + ); + } +#endif +} \ No newline at end of file diff --git a/tests/TestShared/TestCadFilePath.cs b/tests/TestShared/TestCadFilePath.cs new file mode 100644 index 0000000000000000000000000000000000000000..4a32dd520a763fd659e283429ffd4d8deb82697c --- /dev/null +++ b/tests/TestShared/TestCadFilePath.cs @@ -0,0 +1,30 @@ + +namespace Test; + +public class TestCadFilePath +{ + [CommandMethod("TestCadFilePath")] + public void TestCadFilePathfun() + { + string key = HostApplicationServices.Current.UserRegistryProductRootKey; + // 计算机\HKEY_CURRENT_USER\SOFTWARE\Autodesk\AutoCAD\R24.0\ACAD-4101:804 + RegistryKey ackey = Registry.CurrentUser.OpenSubKey(key); + var profileskey = ackey.OpenSubKey("Profiles"); + + var listkey = profileskey.GetSubKeyNames(); + foreach (var item in listkey) + { + var acadkey = profileskey.OpenSubKey($@"{item}\General",true); + var name = "ACAD"; + var str = acadkey.GetValue(name)?.ToString(); + if (str is not null && !str.Contains("nihao")) + { + Env.Print(str); + acadkey.SetValue(name, $@"{str}\nihao;", RegistryValueKind.String); + } + + + } + + } +} diff --git a/tests/TestShared/TestConvexHull.cs b/tests/TestShared/TestConvexHull.cs new file mode 100644 index 0000000000000000000000000000000000000000..380bc4453f65b6bda27c8a79e6f4bbf703cd72c8 --- /dev/null +++ b/tests/TestShared/TestConvexHull.cs @@ -0,0 +1,76 @@ +namespace Test; + + +public class TestConvexHull +{ + [CommandMethod(nameof(Test_ConvexHull))] + public void Test_ConvexHull() + { + // using DBTrans tr = new(); + // var pts = new List(); + // var flag = true; + // while (flag) + // { + // var pt = tr.Editor.GetPoint("qudian"); + // if (pt.Status == PromptStatus.OK) + // { + // pts.Add(pt.Value); + // tr.CurrentSpace.AddEntity(new DBPoint(pt.Value)); + // } + // else + // { + // flag = false; + // } + + // } + + // var ptt = ConvexHull.GetConvexHull(pts); + + // Polyline pl = new Polyline(); + // for (int i = 0; i < ptt.Count; i++) + // { + // pl.AddVertexAt(i, ptt[i].Point2d(), 0, 0, 0); + // } + //// pl.AddVertexAt(0, new Point2d(0, 0), 0, 0, 0); + //// pl.AddVertexAt(1, new Point2d(10, 10), 0, 0, 0); + //// pl.AddVertexAt(2, new Point2d(20, 20), 0, 0, 0); + //// pl.AddVertexAt(3, new Point2d(30, 30), 0, 0, 0); + //// pl.AddVertexAt(4, new Point2d(40, 40), 0, 0, 0); + // pl.Closed = true; + // pl.Color = Color.FromColorIndex(ColorMethod.ByColor, 6); + // tr.CurrentSpace.AddEntity(pl); + + // var a1 = GeometryEx.GetArea(new Point2d(0, 0), new Point2d(1, 0), new Point2d(1, 1)); + // var a2 = ConvexHull.cross(new Point3d(0, 0, 0), new Point3d(1, 0, 0), new Point3d(1, 1, 0)); + // tr.Editor.WriteMessage(a1.ToString()); + // tr.Editor.WriteMessage(a2.ToString()); + + + // var vec1 = new Vector2d(1, 1); + // var vec2 = new Vector2d(-1, 1); + + // var vec3 = vec1.GetPerpendicularVector(); + // var vec4 = vec2.GetPerpendicularVector(); + + // var area1 = vec2.DotProduct(vec1.GetPerpendicularVector()); + // var area2 = vec1.DotProduct(vec2.GetPerpendicularVector()); + + // var area3 = vec2.DotProduct(vec1); + // var area4 = vec1.DotProduct(vec2); + + var area5 = GeometryEx.GetArea(new List { new Point2d(0, 0), new Point2d(1, 1), new Point2d(-1, 1) }); + + var area6 = GeometryEx.GetArea(new List { new Point2d(0, 0), new Point2d(-1, 1), new Point2d(1, 1) }); + // Env.Editor.WriteMessage($"vec1 的法向量= {vec3} \n"); + // Env.Editor.WriteMessage($"vec2 的法向量= {vec4} \n"); + + // Env.Editor.WriteMessage($"vec2 点乘 vec1的法向量= {area1} \n"); + // Env.Editor.WriteMessage($"vec1 点乘 vec2的法向量= {area2} \n"); + + // Env.Editor.WriteMessage($"vec2 点乘 vec1= {area3} \n"); + // Env.Editor.WriteMessage($"vec1 点乘 vec2= {area4} \n"); + + Env.Editor.WriteMessage($"点集的有向面积:{area5} \n"); + Env.Editor.WriteMessage($"点集的有向面积:{area6} \n"); + } +} \ No newline at end of file diff --git a/tests/TestShared/TestCurve.cs b/tests/TestShared/TestCurve.cs new file mode 100644 index 0000000000000000000000000000000000000000..803d7603d764eceb30fc3e6faa7b8f2c162a6caa --- /dev/null +++ b/tests/TestShared/TestCurve.cs @@ -0,0 +1,150 @@ +namespace Test; + +public class TestGraph +{ + [CommandMethod(nameof(Test_PointInDict))] + public void Test_PointInDict() + { + var pt1 = new Point3d(0.0255, 0.452, 0); + var pt2 = new Point3d(0.0255001, 0.452003, 0); + var pt3 = new Point3d(0.0255002, 0.4520001, 0); + var pt4 = new Point3d(0.0255450, 0.45287893, 0); + var pt5 = new Point3d(0.02554935, 0.452092375, 0); + var dict = new Dictionary + { + { pt1, 1 }, + { pt2, 2 }, + { pt3, 3 }, + { pt4, 4 }, + { pt5, 5 } + }; + Env.Print(dict[pt1]); + } + + [CommandMethod(nameof(Test_Graph1))] + public void Test_Graph1() + { + using DBTrans tr = new(); + var ents = Env.Editor.SSGet()?.Value?.GetEntities(); + if (ents == null) + return; + Tools.TestTimes2(1, "new", () => { + var res = ents!.GetAllCycle(); + + // res.ForEach((i, t) => t.ForWrite(e => e.ColorIndex = i + 1)); + Env.Print(res.Count()); + tr.CurrentSpace.AddEntity(res); + }); + } + + [CommandMethod(nameof(Test_Graphspeed))] + public void Test_Graphspeed() + { + using DBTrans tr = new(); + var ents = Env.Editor.SSGet()?.Value?.GetEntities(); + if (ents == null) + return; + + var graph = new IFoxCAD.Cad.Graph(); // 为了调试先把图的访问改为internal + foreach (var curve in ents) + { +#if NET35 + graph.AddEdge(curve!.ToCurve3d()!); +#else + graph.AddEdge(curve!.GetGeCurve()); +#endif + } + + // 新建 dfs + var dfs = new DepthFirst(); +#if true + Tools.TestTimes2(100, "new", () => { + // 查询全部的 闭合环 + dfs.FindAll(graph); + }); + Tools.TestTimes2(1000, "new", () => { + // 查询全部的 闭合环 + dfs.FindAll(graph); + }); +#else + Tools.TestTimes2(100, "old", () => { + // 查询全部的 闭合环 + dfs.FindAll(graph); + }); + Tools.TestTimes2(1000, "old", () => { + // 查询全部的 闭合环 + dfs.FindAll(graph); + }); +#endif + // res.ForEach((i, t) => t.ForWrite(e => e.ColorIndex = i + 1)); + + // tr.CurrentSpace.AddEntity(res); + } +} + + + + +public class TestCurve +{ + [CommandMethod(nameof(Test_BreakCurve))] + public void Test_BreakCurve() + { + using DBTrans tr = new(); + var ents = Env.Editor.SSGet()?.Value.GetEntities(); + if (ents is null) + return; + var tt = CurveEx.BreakCurve(ents.ToList()!); + tt.ForEach(t => t.ForWrite(e => e.ColorIndex = 1)); + tr.CurrentSpace.AddEntity(tt); + } + + [CommandMethod(nameof(Test_CurveCurveIntersector3d))] + public void Test_CurveCurveIntersector3d() + { + using DBTrans tr = new(); + var ents = Env.Editor.SSGet()? + .Value.GetEntities() + .Select(e => e?.ToCompositeCurve3d()).ToList(); + if (ents == null) + return; + + var cci3d = new CurveCurveIntersector3d(); + for (int i = 0; i < ents.Count; i++) + { + var gc1 = ents[i]; + var int1 = gc1?.GetInterval(); + // var pars1 = paramss[i]; + for (int j = i; j < ents.Count; j++) + { + var gc2 = ents[j]; + // var pars2 = paramss[j]; + var int2 = gc2?.GetInterval(); + cci3d.Set(gc1, gc2, int1, int2, Vector3d.ZAxis); + var d = cci3d.OverlapCount(); + var a = cci3d.GetIntersectionRanges(); + Env.Print($"{a[0].LowerBound}-{a[0].UpperBound} and {a[1].LowerBound}-{a[1].UpperBound}"); + for (int m = 0; m < d; m++) + { + var b = cci3d.GetOverlapRanges(m); + Env.Print($"{b[0].LowerBound}-{b[0].UpperBound} and {b[1].LowerBound}-{b[1].UpperBound}"); + } + + for (int k = 0; k < cci3d.NumberOfIntersectionPoints; k++) + { + // var a = cci3d.GetOverlapRanges(k); + // var b = cci3d.IsTangential(k); + // var c = cci3d.IsTransversal(k); + // var d = cci3d.OverlapCount(); + // var e = cci3d.OverlapDirection(); + var pt = cci3d.GetIntersectionParameters(k); + var pts = cci3d.GetIntersectionPoint(k); + Env.Print(pts); + } + } + } + // var tt = CurveEx.Topo(ents.ToList()); + // tt.ForEach(t => t.ForWrite(e => e.ColorIndex = 1)); + // tr.CurrentSpace.AddEntity(tt); + } +} \ No newline at end of file diff --git a/tests/TestShared/TestDBTrans.cs b/tests/TestShared/TestDBTrans.cs new file mode 100644 index 0000000000000000000000000000000000000000..0dc60500b8abcff9d9ca5164c86a5a5c451388c4 --- /dev/null +++ b/tests/TestShared/TestDBTrans.cs @@ -0,0 +1,160 @@ +namespace Test; + +public class TestTrans +{ + [CommandMethod(nameof(CmdTest_DBTransActiveOpenDwg), CommandFlags.Session)] + public static void CmdTest_DBTransActiveOpenDwg() + { + using DBTrans tr = new(@"D:\桌面\AA.dwg", activeOpen: true); + } + + [CommandMethod(nameof(CmdTest_ForEachDemo))] + public static void CmdTest_ForEachDemo() + { + using DBTrans tr = new(); + + // 泛型扩展(用变量名来使用它) + tr.BlockTable.ForEach(action: (id) => { + //Debugger.Break();// 为什么cad工程不能断点进入呢? + id.Print(); + Console.WriteLine(id); + }); + + //tr.BlockTable.ForEach(asdad); + //void asdad(object id) + //{ + // id.Print(); + //} + + tr.BlockTable.ForEach(action: (id, state) => { + id.Print(); + }); + tr.BlockTable.ForEach(action: (id, state, index) => { + id.Print(); + }); + + // 符号表扩展(会顶替泛型扩展) + tr.BlockTable.ForEach((btr) => { // 预处理设置不进入ForEach函数体内 + btr.Print();// 此处可以设置断点 + }, OpenMode.ForRead, checkIdOk: true); + tr.BlockTable.ForEach((btr, state) => {// 预处理设置不进入ForEach函数体内 + btr.Print();// 此处可以设置断点 + }, OpenMode.ForRead, checkIdOk: true); + tr.BlockTable.ForEach((btr, state, index) => { // 预处理设置不进入ForEach函数体内 + btr.Print();// 此处可以设置断点 + }, OpenMode.ForRead, checkIdOk: true); + + // 修改:此处有缺陷:cad08会获取已经删除的块表记录,需要检查id.IsOk(),用ForEach代替 + // tr.BlockTable.Change("块表记录", btr => { + // }); + + // 修改:此处无缺陷 + tr.BlockTable.Change(tr.ModelSpace.ObjectId, modelSpace => { // 特性设置不进入函数体内 + var ents = modelSpace.GetEntities(); // 此处不会检查id.IsOk() + + modelSpace.ForEach(id => { // 利用遍历检查id.IsOk() + if (id.IsOk()) + id.Print(); + }); + }); + } + + + + // 后台:不存在路径的dwg会在桌面进行临时保存 + [CommandMethod(nameof(FileNotExist))] + public void FileNotExist() + { + using DBTrans tr = new("test.dwg"); + tr.SaveFile((DwgVersion)24, false); + } + + // 前台:由于是弹出面板,此时路径不会起任何作用 + [CommandMethod(nameof(FileNotExist2))] + public void FileNotExist2() + { + using DBTrans tr = new(); + tr.SaveFile(saveAsFile: "D:\\"); + } + + // 后台:只有路径,没有文件名 + [CommandMethod(nameof(FileNotExist3))] + public void FileNotExist3() + { + using DBTrans tr = new("D:\\"); + tr.SaveDwgFile(); + + using DBTrans tr2 = new("D:\\"); + tr2.SaveFile(saveAsFile: "D:\\"); + } + + + [CommandMethod(nameof(Test_SaveDwgFile))] + public void Test_SaveDwgFile() + { + string filename = @"C:\Users\vic\Desktop\test.dwg"; + using DBTrans tr = new(filename); + tr.ModelSpace.AddCircle(new Point3d(10, 10, 0), 20); + // tr.Database.SaveAs(filename,DwgVersion.Current); + tr.SaveDwgFile(); + } + [CommandMethod(nameof(Test_DBTransAbort))] + public void Test_DBTransAbort() + { + using DBTrans tr = new(); + tr.ModelSpace.AddCircle(new Point3d(0, 0, 0), 20); + tr.Abort(); + // tr.Commit(); + } + + // AOP 应用 预计示例: + // 1. 无参数 + // [AOP] + // [CommandMethod(nameof(Test_AOP1))] + // public void TestAOP1() + // { + // // 不用 using DBTrans tr = new(); + // var tr = DBTrans.Top; + // tr.ModelSpace.AddCircle(new Point3d(0, 0, 0), 20); + // } + + // 2. 有参数 + // [AOP("file")] + // [CommandMethod(nameof(Test_AOP2))] + // public void TestAOP2() + // { + // // 不用 using var tr = new DBTrans(file); + // var tr = DBTrans.Top; + // tr.ModelSpace.AddCircle(new Point3d(0, 0, 0), 20); + // } + + + [CommandMethod(nameof(Test_TopTransaction))] + public void Test_TopTransaction() + { + // var pt = Env.Editor.GetPoint("pick pt:").Value; + // var pl = Env.Editor.GetEntity("pick pl").ObjectId; + + var tr1 = HostApplicationServices.WorkingDatabase.TransactionManager.TopTransaction; + using DBTrans tr2 = new(); + var tr3 = HostApplicationServices.WorkingDatabase.TransactionManager.TopTransaction; + var tr6 = Acap.DocumentManager.MdiActiveDocument.TransactionManager.TopTransaction; + Env.Print(tr2.Transaction == tr3); + Env.Print(tr3 == tr6); + using DBTrans tr4 = new(); + var tr5 = HostApplicationServices.WorkingDatabase.TransactionManager.TopTransaction; + var tr7 = Acap.DocumentManager.MdiActiveDocument.TransactionManager.TopTransaction; + Env.Print(tr4.Transaction == tr5); + Env.Print(tr5 == tr7); + var trm = HostApplicationServices.WorkingDatabase.TransactionManager; + + // var ptt = tr.GetObject(pl).GetClosestPointTo(pt,false); + // var pt1 = new Point3d(0, 0.00000000000001, 0); + // var pt2 = new Point3d(0, 0.00001, 0); + // Env.Print(Tolerance.Global.EqualPoint); + // Env.Print(pt1.IsEqualTo(pt2).ToString()); + // Env.Print(pt1.IsEqualTo(pt2,new Tolerance(0.0,1e-6)).ToString()); + // Env.Print((pt1 == pt2).ToString()); + // Env.Print((pt1 != pt2).ToString()); + } +} \ No newline at end of file diff --git a/tests/TestShared/TestDwgFilerEx.cs b/tests/TestShared/TestDwgFilerEx.cs new file mode 100644 index 0000000000000000000000000000000000000000..d2267af46b95cdba1d5cc7cdeeda3d75940a5958 --- /dev/null +++ b/tests/TestShared/TestDwgFilerEx.cs @@ -0,0 +1,158 @@ +namespace Test; + +using DxfFiler = IFoxCAD.Cad.DxfFiler; + +public class CmdTestDwgFilerEx +{ + [CommandMethod(nameof(CmdTest_DwgFilerEx))] + public static void CmdTest_DwgFilerEx() + { + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + var db = doc.Database; + var ed = doc.Editor; + ed.WriteMessage("\n****测试,序列化图元"); + + var ssPsr = ed.SelectImplied();// 预选 + if (ssPsr.Status != PromptStatus.OK) + { + ssPsr = ed.GetSelection();// 手选 这里输入al会变成all,无法删除ssget的all关键字 + if (ssPsr.Status != PromptStatus.OK) + return; + } + + using DBTrans tr = new(); + var ids = ssPsr.Value.GetObjectIds(); + foreach (var id in ids) + { + if (!id.IsOk()) + continue; + var ent = tr.GetObject(id, OpenMode.ForRead); + if (ent is null) + continue; + var dwgFilerEx = new DwgFilerEx(ent); + ed.WriteMessage(Environment.NewLine + dwgFilerEx.ToString()); + } + } + + [CommandMethod(nameof(CmdTest_EntDxfout))] + public static void CmdTest_EntDxfout() + { + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + var db = doc.Database; + var ed = doc.Editor; + + // 定义选择集选项 + var pso = new PromptSelectionOptions + { + RejectObjectsOnLockedLayers = true, // 不选择锁定图层对象 + AllowDuplicates = true, // 不允许重复选择 + }; + var ssPsr = ed.GetSelection(pso);// 手选 这里输入al会变成all,无法删除ssget的all关键字 + if (ssPsr.Status != PromptStatus.OK) + return; + + using DBTrans tr = new(); + var ids = ssPsr.Value.GetObjectIds(); + foreach (var id in ids) + { + if (!id.IsOk()) + continue; + var ent = tr.GetObject(id, OpenMode.ForRead); + if (ent is null) + continue; + // ResultBuffer rbDxf = new(); + var filer = new DxfFiler(ent.UnmanagedObject, true);/// 这里有问题 + ent.DxfOut(filer); + } + } + + + [CommandMethod(nameof(CmdTest_TextOut))] + public static void CmdTest_TextOut() + { + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + var db = doc.Database; + var ed = doc.Editor; + +#if true + var peo1 = new PromptEntityOptions(Environment.NewLine + "点选源TCH_WIREDIM2:") + { + AllowObjectOnLockedLayer = false, + AllowNone = false + }; + var gt1 = ed.GetEntity(peo1); + if (gt1.Status != PromptStatus.OK) + return; +#else + var peo2 = new PromptEntityOptions(Environment.NewLine + "点选目标TCH_WIREDIM2:") + { + AllowObjectOnLockedLayer = false, + AllowNone = false + }; + var gt2 = ed.GetEntity(peo2); + if (gt2.Status != PromptStatus.OK) + return; +#endif + + using DBTrans tr = new(); + var dwgFilerEx = new DwgFilerEx(); + var bText = tr.GetObject(gt1.ObjectId, OpenMode.ForRead); + if (bText is null) + return; + + // DwgFilerEx.StringList[0] = "1@2@3@4@5@6@7@"; + // 复制 TCH_WIREDIM2 不行,TEXT 也不行,直接崩溃。line等线就没事 + bText.DwgOut(dwgFilerEx.DwgFiler); + + int testNum = 1 | 2 | 4 | 8; + + if ((testNum & 1) == 1) + { + // 错误,原地克隆也是不行的,它会生成在了模型中. + var sIds = new List + { + bText.ObjectId + }; + // 克隆到目标块表内 + using ObjectIdCollection bindIds = new(sIds.ToArray()); + using IdMapping map = new(); + + tr.CurrentSpace.DeepCloneEx(bindIds, map); + var newTexts = map.GetValues().GetObject(); + newTexts.ForEach(nText => { + if (nText == null) + return; + // 通过上面的克隆就已经在块表上面了.所以下面的设置也跟设置到已有图元上一样报错. + nText.UpgradeOpen(); + nText.DwgIn(dwgFilerEx); + tr.CurrentSpace.AddEntity(nText); + nText.DowngradeOpen(); + }); + } + if ((testNum & 2) == 2) + { + // 出错 + // 直接设置 + bText.DwgIn(dwgFilerEx); + } + if ((testNum & 4) == 4) + { + // 出错 + // 此时是内存中对象.... + var nText = (DBText)bText.Clone(); + nText.DwgIn(dwgFilerEx); + tr.CurrentSpace.AddEntity(nText); + } + if ((testNum & 8) == 8) + { + // 新对象相当于克隆,是ok的 + DBText nText = new(); + nText.SetDatabaseDefaults(); + nText.DwgIn(dwgFilerEx); + tr.CurrentSpace.AddEntity(nText); + } + } +} \ No newline at end of file diff --git a/tests/TestShared/TestEditor.cs b/tests/TestShared/TestEditor.cs new file mode 100644 index 0000000000000000000000000000000000000000..656ddaa8ceadb82a34cc341f8146c86dd2753d1c --- /dev/null +++ b/tests/TestShared/TestEditor.cs @@ -0,0 +1,78 @@ +namespace Test; + +public class Testeditor +{ + [CommandMethod(nameof(Test_Editor))] + public void Test_Editor() + { + var pts = new List + { + new Point2d(0,0), + new Point2d(0,1), + new Point2d(1,1), + new Point2d(1,0) + }; + var res = EditorEx.GetLines(pts, false); + var res1 = EditorEx.GetLines(pts, true); + var res2 = pts.Select(pt => new TypedValue((int)LispDataType.Point2d, pt)).ToList(); + + Editor ed = Acap.DocumentManager.MdiActiveDocument.Editor; + var pt = ed.GetPoint("qudiam", new Point3d(0, 0, 0)); + var d = ed.GetDouble("qudoule"); + var i = ed.GetInteger("quint"); + var s = ed.GetString("qustr"); + Env.Editor.WriteMessage(""); + } + [CommandMethod(nameof(Test_Zoom))] + public void Test_Zoom() + { + using DBTrans tr = new(); + var res = Env.Editor.GetEntity("\npick ent:"); + if (res.Status == PromptStatus.OK) + Env.Editor.ZoomObject(res.ObjectId.GetObject()!); + } + [CommandMethod(nameof(Test_ZoomExtents))] + public void Test_ZoomExtents() + { + // using DBTrans tr = new(); + // var res = Env.Editor.GetEntity("\npick ent:"); + // if (res.Status == PromptStatus.OK) + // { + // Env.Editor.ZoomObject(res.ObjectId.GetObject()); + // } + + Env.Editor.ZoomExtents(); + } + + [CommandMethod(nameof(Test_Ssget))] + public void Test_Ssget() + { + var action_a = () => { Env.Print("this is a"); }; + var action_b = () => { Env.Print("this is b"); }; + + var keyword = new Dictionary + { + { "A", action_a }, + { "B", action_b } + }; + + var ss = Env.Editor.SSGet(":S", messages: new string[2] { "get", "del" }, + keywords: keyword); + Env.Print(ss!); + } + + [CommandMethod(nameof(Test_ExportWMF), CommandFlags.Modal | CommandFlags.UsePickSet)] + public void Test_ExportWMF() + { + var psr = Env.Editor.SelectImplied();// 预选 + if (psr.Status != PromptStatus.OK) + psr = Env.Editor.GetSelection();// 手选 + if (psr.Status != PromptStatus.OK) + return; + + var ids = psr.Value.GetObjectIds(); + // acad21(acad08没有)先选择再执行..会让你再选择一次 + // 而且只发生在启动cad之后第一次执行. + Env.Editor.ComExportWMF("D:\\桌面\\aaa.dwg", ids); + } +} \ No newline at end of file diff --git a/tests/TestShared/TestEnv.cs b/tests/TestShared/TestEnv.cs new file mode 100644 index 0000000000000000000000000000000000000000..8b9d2f2056313474b8429929fb82820ae6474355 --- /dev/null +++ b/tests/TestShared/TestEnv.cs @@ -0,0 +1,127 @@ +namespace Test; + +public class Testenv +{ + [CommandMethod(nameof(Test_Enum))] + public void Test_Enum() + { + Env.CmdEcho = true; + } + [CommandMethod(nameof(Test_Enum1))] + public void Test_Enum1() + { + Env.CmdEcho = false; + } + + [CommandMethod(nameof(Test_Dimblk))] + public void Test_Dimblk() + { + Env.Dimblk = Env.DimblkType.Dot; + Env.Print(Env.Dimblk); + Env.Print(Env.GetDimblkId(Env.DimblkType.Dot)); + Env.Dimblk = Env.DimblkType.Defult; + Env.Print(Env.Dimblk); + Env.Print(Env.GetDimblkId(Env.DimblkType.Defult)); + Env.Dimblk = Env.DimblkType.Oblique; + Env.Print(Env.Dimblk); + Env.Print(Env.GetDimblkId(Env.DimblkType.Oblique)); + Env.Dimblk = Env.DimblkType.ArchTick; + Env.Print(Env.Dimblk); + Env.Print(Env.GetDimblkId(Env.DimblkType.ArchTick)); + } + [CommandMethod(nameof(Test_Dimblk1))] + public void Test_Dimblk1() + { + var dim = Env.Dimblk; + Env.Editor.WriteMessage(dim.ToString()); + } + + [CommandMethod(nameof(Test_Osmode))] + public void Test_Osmode() + { + // 设置osmode变量,多个值用逻辑或 + Env.OSMode = Env.OSModeType.End | Env.OSModeType.Middle; + // 也可以直接写数值,进行强转 + Env.OSMode = (Env.OSModeType)5179; + // 追加模式 + Env.OSMode |= Env.OSModeType.Center; + // 检查是否有某个模式 + var os = Env.OSMode.Include(Env.OSModeType.Center); + // 取消某个模式 + Env.OSMode ^= Env.OSModeType.Center; + Env.Editor.WriteMessage(Env.OSMode.ToString()); + } + [CommandMethod(nameof(Test_Osmode1))] + public void Test_Osmode1() + { + var dim = Env.OSMode; + Env.Editor.WriteMessage(dim.ToString()); + } + + [CommandMethod(nameof(Test_Cadver))] + public void Test_Cadver() + { + // Env.Print(AcadVersion.Versions); + AcadVersion.Versions.ForEach(v => Env.Print(v)); + AcadVersion.FromApp(Acap.AcadApplication)?.Print(); + 1.Print(); + "1".Print(); + } + + [CommandMethod(nameof(Test_GetVar))] + public void Test_GetVar() + { + // test getvar + var a = Env.GetVar("dbmod"); + a.Print(); + Env.SetVar("dbmod1", 1); + } + + + + //[CommandMethod(nameof(Test_DwgVersion))] + //public void TestDwgVersion() + //{ + // string filename = @"C:\Users\vic\Desktop\test.dwg"; + // var a = Helper.GetCadFileVersion(filename); + // a.Print(); + // ((DwgVersion)a).Print(); + //} + + +#if !NET35 && !NET40 + // 通过此功能获取全部变量,尚不清楚此处如何设置,没有通过测试 + [CommandMethod(nameof(Test_GetvarAll))] + public static void Test_GetvarAll() + { + GetvarAll(); + } + + public static Dictionary GetvarAll() + { + var dict = new Dictionary(); + var en = new SystemVariableEnumerator(); + while (en.MoveNext()) + { + Console.WriteLine(en.Current.Name + "-----" + en.Current.Value);// Value会出现异常 + dict.Add(en.Current.Name, en.Current.Value); + } + return dict; + } +#endif + + [CommandMethod(nameof(Test_GetEnv))] + public static void Test_GetEnv() + { + var dir = Env.GetEnv("PrinterConfigDir"); + Env.Printl("pc3打印机位置:" + dir); + + Env.SetEnv("abc", "656"); + + var obj = Env.GetEnv("abc"); + Env.Printl("GetEnv:" + obj); + + Env.Printl("GetEnv:" + Env.GetEnv("abc")); + Env.Printl("GetEnv PATH:" + Env.GetEnv("PATH")); + } +} \ No newline at end of file diff --git a/tests/TestShared/TestFileDatabase.cs b/tests/TestShared/TestFileDatabase.cs new file mode 100644 index 0000000000000000000000000000000000000000..e5c58c326c09c5c083598fcbe1995940e2df70c0 --- /dev/null +++ b/tests/TestShared/TestFileDatabase.cs @@ -0,0 +1,28 @@ +namespace Test; + +/************************************************************** +*作者:Leon +*创建时间:2022/2/11 9:55:32 +**************************************************************/ + +public class TestFileDatabase +{ + [CommandMethod(nameof(Test_FileDatabaseInit))] + public void Test_FileDatabaseInit() + { + try + { + var fileName = @"C:\Users\Administrator\Desktop\合并详图测试BUG.dwg"; + using DBTrans trans = new(fileName); + trans.ModelSpace.AddEntity(new Line(new(0, 0, 0), new(1000, 1000, 0))); + if (trans.Document is not null && trans.Document.IsActive) + trans.Document.SendStringToExecute("_qsave\n", false, true, true); + else + trans.Database.SaveAs(fileName, (DwgVersion)27); + } + catch (System.Exception e) + { + System.Windows.MessageBox.Show(e.Message); + } + } +} \ No newline at end of file diff --git a/tests/TestShared/TestHatchinfo.cs b/tests/TestShared/TestHatchinfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..52dbfaf9bc282807a79faf5b9de60852152f26cd --- /dev/null +++ b/tests/TestShared/TestHatchinfo.cs @@ -0,0 +1,18 @@ +namespace Test; + +public class TestHatchinfo +{ + [CommandMethod(" TestHatchInfo")] + public void TestHatchInfo() + { + using var tr = new DBTrans(); + var sf = new SelectionFilter(new TypedValue[] { new TypedValue(0, "*line,circle,arc") }); + var ids = Env.Editor.SSGet(null, sf).Value?.GetObjectIds(); + if (ids.Count() > 0) + { + HatchInfo hf = new HatchInfo(ids, false, null, 1, 0).Mode2UserDefined(); + hf.Build(tr.CurrentSpace); + } + } +} + diff --git a/tests/TestShared/TestId.cs b/tests/TestShared/TestId.cs new file mode 100644 index 0000000000000000000000000000000000000000..57f19cf512f635112ceef6637b3c36250ee02df5 --- /dev/null +++ b/tests/TestShared/TestId.cs @@ -0,0 +1,72 @@ +namespace Test; + +public class Testid +{ + [CommandMethod(nameof(Test_Id))] + public void Test_Id() + { + using DBTrans tr = new(); + Line line = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + tr.CurrentSpace.AddEntity(line); + tr.Dispose(); + + var res = Env.Editor.GetEntity("\npick ent:"); + if (res.Status == PromptStatus.OK) + { + res.ObjectId.Erase(); + } + // using (var tr = new DBTrans()) + // { + // var res = Env.Editor.GetEntity("\npick ent:"); + // if(res.Status == PromptStatus.OK) + // { + // res.ObjectId.Erase(); + // } + + // } + } + + [CommandMethod(nameof(Test_MyCommand))] + public void Test_MyCommand() + { + using DBTrans dbtrans = new(Env.Document, true, false); + using var trans = Env.Database.TransactionManager.StartTransaction(); + + var l1 = new Line(new Point3d(0, 0, 0), new Point3d(100, 100, 0)); + var blkred = trans.GetObject(Env.Database.CurrentSpaceId, OpenMode.ForWrite) as BlockTableRecord; + blkred?.AppendEntity(l1); + trans.AddNewlyCreatedDBObject(l1, true); + trans.Commit(); + // dbtrans.Dispose(); + } + [CommandMethod(nameof(Test_TextStyle))] + public void Test_TextStyle() + { + using DBTrans tr = new(); + tr.TextStyleTable.Add("宋体", "宋体.ttf", 0.8); + + tr.TextStyleTable.Add("宋体1", FontTTF.宋体, 0.8); + tr.TextStyleTable.Add("仿宋体", FontTTF.仿宋, 0.8); + tr.TextStyleTable.Add("fsgb2312", FontTTF.仿宋GB2312, 0.8); + tr.TextStyleTable.Add("arial", FontTTF.Arial, 0.8); + tr.TextStyleTable.Add("romas", FontTTF.Romans, 0.8); + + + + tr.TextStyleTable.Add("daziti", ttr => { + ttr.FileName = "ascii.shx"; + ttr.BigFontFileName = "gbcbig.shx"; + }); + } + + [CommandMethod(nameof(Test_TextStyleChange))] + public void Test_TextStyleChange() + { + using DBTrans tr = new(); + + + tr.TextStyleTable.AddWithChange("宋体1", "simfang.ttf", height: 5); + tr.TextStyleTable.AddWithChange("仿宋体", "宋体.ttf"); + tr.TextStyleTable.AddWithChange("fsgb2312", "Romans", "gbcbig"); + } +} \ No newline at end of file diff --git a/tests/TestShared/TestJig.cs b/tests/TestShared/TestJig.cs new file mode 100644 index 0000000000000000000000000000000000000000..6db1f386f18a6ab5ec537e0328ccf947a3ad5f03 --- /dev/null +++ b/tests/TestShared/TestJig.cs @@ -0,0 +1,314 @@ +namespace Test; + +using System.Windows.Forms; + +public class Commands_Jig +{ + // 已在数据库的图元如何进入jig + [CommandMethod(nameof(Test_Jig33))] + public static void Test_Jig33() + { + using DBTrans tr = new(); + var per = tr.Editor?.GetEntity("\n点选圆形:"); + if (per?.Status != PromptStatus.OK) + return; + var cir = tr.GetObject(per.ObjectId, OpenMode.ForWrite); + if (cir == null) + return; + var oldSp = cir.StartPoint; + JigEx? moveJig = null; + moveJig = new JigEx((mousePoint, drawEntitys) => { + moveJig!.SetOptions(oldSp);// 回调过程中也可以修改基点 + // cir.UpgradeOpen();// 已经提权了,所以这里不需要提权 + cir.Move(cir.StartPoint, mousePoint); + // cir.DowngradeOpen(); + + // 此处会Dispose图元, + // 所以此处不加入已经在数据库的图元,而是加入new Entity的. + // drawEntitys.Enqueue(cir); + }); + moveJig.SetOptions(cir.GeometricExtents.MinPoint, orthomode: true); + + // 此处详见方法注释 + moveJig.DatabaseEntityDraw(draw => { + draw.RawGeometry.Draw(cir); + }); + + while (true) + { + var prDrag = moveJig.Drag(); + if (prDrag.Status == PromptStatus.OK) + break; + } + moveJig.Dispose(); + } + + + // 不在数据库的图元如何进入jig + [CommandMethod(nameof(Test_Jig44))] + public void Test_Jig44() + { + using DBTrans tr = new(); + var per = Env.Editor.GetEntity("\n请选择一条多段线:"); + if (per.Status != PromptStatus.OK) + return; + var ent = tr.GetObject(per.ObjectId, OpenMode.ForWrite); + if (ent is not Polyline pl) + return; + + /* + * 鼠标采样器执行时修改鼠标基点 + * 原因: 多段线与鼠标垂直点作为 BasePoint,jig鼠标点为确定点 + * 所以需要先声明再传入指针,但是我发现null也可以. + */ + JigPromptPointOptions? options = null; + using var jig = new JigEx((mousePoint, drawEntitys) => { + var closestPt = pl.GetClosestPointTo(mousePoint, false); + + // 回调过程中SetOptions会覆盖配置,所以如果想增加关键字或者修改基点, + // 不要这样做: jig.SetOptions(closestPt) 而是使用底层暴露 + options!.BasePoint = closestPt; + + // 需要避免重复加入同一个关键字 + if (!options.Keywords.Contains("A")) + options.Keywords.Add("A"); + + // 生成文字 + var dictString = (pl.GetDistAtPoint(closestPt) * 0.001).ToString("0.00"); + var acText = new TextInfo(dictString, closestPt, AttachmentPoint.BaseLeft, textHeight: 200) + .AddDBTextToEntity(); + + // 加入刷新队列 + drawEntitys.Enqueue(acText); + }); + + options = jig.SetOptions(per.PickedPoint); + + // 如果没有这个,那么空格只会是 PromptStatus.None 而不是 PromptStatus.Keyword + // options.Keywords.Add(" ", " ", "空格结束啊"); + // jig.SetSpaceIsKeyword(); + + bool flag = true; + while (flag) + { + var pr = jig.Drag(); + if (pr.Status == PromptStatus.Keyword) + { + switch (pr.StringResult) + { + case "A": + tr.Editor?.WriteMessage($"\n 您触发了关键字{pr.StringResult}"); + flag = false; + break; + case " ": + tr.Editor?.WriteMessage("\n 触发关键字空格"); + flag = false; + break; + } + } + else if (pr.Status != PromptStatus.OK)// PromptStatus.None == 右键,空格,回车,都在这里结束 + { + tr.Editor?.WriteMessage(Environment.NewLine + pr.Status.ToString()); + return; + } + else + flag = false; + } + tr.CurrentSpace.AddEntity(jig.Entitys); + } + + [CommandMethod(nameof(Test_MessageFilter))] + public void Test_MessageFilter() + { + var dm = Acap.DocumentManager; + var ed = dm.MdiActiveDocument.Editor; + + // Create and add our message filter + MyMessageFilter filter = new(); + System.Windows.Forms.Application.AddMessageFilter(filter); + // Start the loop + while (true) + { + // Check for user input events + System.Windows.Forms.Application.DoEvents(); + // Check whether the filter has set the flag + if (filter.bCanceled == true) + { + ed.WriteMessage("\nLoop cancelled."); + break; + } + ed.WriteMessage($"\nInside while loop...and {filter.Key}"); + } + // We're done - remove the message filter + System.Windows.Forms.Application.RemoveMessageFilter(filter); + } + // Our message filter class + public class MyMessageFilter : IMessageFilter + { + public const int WM_KEYDOWN = 0x0100; + public bool bCanceled = false; + public Keys Key { get; private set; } + public bool PreFilterMessage(ref Message m) + { + if (m.Msg == WM_KEYDOWN) + { + // Check for the Escape keypress + Keys kc = (Keys)(int)m.WParam & Keys.KeyCode; + if (m.Msg == WM_KEYDOWN && kc == Keys.Escape) + bCanceled = true; + Key = kc; + // Return true to filter all keypresses + return true; + } + // Return false to let other messages through + return false; + } + } + + + [CommandMethod(nameof(Test_QuickText))] + static public void Test_QuickText() + { + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + var db = doc.Database; + var ed = doc.Editor; + + PromptStringOptions pso = new("\nEnter text string") + { + AllowSpaces = true + }; + var pr = ed.GetString(pso); + if (pr.Status != PromptStatus.OK) + return; + + using var tr = doc.TransactionManager.StartTransaction(); + var btr = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite); + // Create the text object, set its normal and contents + + var acText = new TextInfo(pr.StringResult, + Point3d.Origin, + AttachmentPoint.BaseLeft, textHeight: 200) + .AddDBTextToEntity(); + + acText.Normal = ed.CurrentUserCoordinateSystem.CoordinateSystem3d.Zaxis; + btr.AppendEntity(acText); + tr.AddNewlyCreatedDBObject(acText, true); + + // Create our jig + var pj = new TextPlacementJig(tr, db, acText); + // Loop as we run our jig, as we may have keywords + var state = PromptStatus.Keyword; + while (state == PromptStatus.Keyword) + { + var res = ed.Drag(pj); + state = res.Status; + if (state != PromptStatus.OK && state != PromptStatus.Keyword) + return; + } + tr.Commit(); + } + +#if true + class TextPlacementJig : EntityJig + { + // Declare some internal state + readonly Database _db; + readonly Transaction _tr; + + Point3d _position; + double _angle, _txtSize; + + // Constructor + public TextPlacementJig(Transaction tr, Database db, Entity ent) : base(ent) + { + _db = db; + _tr = tr; + _angle = 0; + _txtSize = 1; + } + + protected override SamplerStatus Sampler(JigPrompts jp) + { + // We acquire a point but with keywords + JigPromptPointOptions po = new("\nPosition of text") + { + UserInputControls = + UserInputControls.Accept3dCoordinates | + UserInputControls.NullResponseAccepted | + UserInputControls.NoNegativeResponseAccepted | + UserInputControls.GovernedByOrthoMode + }; + po.SetMessageAndKeywords( + "\nSpecify position of text or " + + "[Bold/Italic/LArger/Smaller/" + + "ROtate90/LEft/Middle/RIght]: ", + "Bold Italic LArger Smaller " + + "ROtate90 LEft Middle RIght" + ); + + PromptPointResult ppr = jp.AcquirePoint(po); + if (ppr.Status == PromptStatus.Keyword) + { + switch (ppr.StringResult) + { + case "Bold": + break; + case "Italic": + break; + case "LArger": + { + // Multiple the text size by two + _txtSize *= 2; + break; + } + case "Smaller": + { + // Divide the text size by two + _txtSize /= 2; + break; + } + case "ROtate90": + { + // To rotate clockwise we subtract 90 degrees and + // then normalise the angle between 0 and 360 + _angle -= Math.PI / 2; + while (_angle < Math.PI * 2) + { + _angle += Math.PI * 2; + } + break; + } + case "LEft": + break; + case "RIght": + break; + case "Middle": + break; + } + return SamplerStatus.OK; + } + else if (ppr.Status == PromptStatus.OK) + { + // Check if it has changed or not (reduces flicker) + if (_position.DistanceTo(ppr.Value) < Tolerance.Global.EqualPoint) + return SamplerStatus.NoChange; + + _position = ppr.Value; + return SamplerStatus.OK; + } + return SamplerStatus.Cancel; + } + + protected override bool Update() + { + // Set properties on our text object + DBText txt = (DBText)Entity; + txt.Position = _position; + txt.Height = _txtSize; + txt.Rotation = _angle; + return true; + } + } +#endif +} \ No newline at end of file diff --git a/tests/TestShared/TestJigExTransient.cs b/tests/TestShared/TestJigExTransient.cs new file mode 100644 index 0000000000000000000000000000000000000000..37611712007e06d7044392c180d5af52be54bcc8 --- /dev/null +++ b/tests/TestShared/TestJigExTransient.cs @@ -0,0 +1,89 @@ +#if !ac2008 +namespace Test; + +public partial class Test +{ + [CommandMethod(nameof(Test_JigExTransient))] + public void Test_JigExTransient() + { + // 先取1点,建2个圆 + var getpt = Env.Editor.GetPoint("\n选择点"); + if (getpt.Status != PromptStatus.OK) + return; + var pt = getpt.Value.Ucs2Wcs(); + + var c1 = new Circle(pt, Vector3d.ZAxis, 100); + var c2 = new Circle(pt.Polar(0, 300), Vector3d.ZAxis, 100); + + // 创建瞬态容器 + using JigExTransient jet = new(); + + // 将c1以默认模式,c2以亮显模式加到瞬态容器,即在图纸上显示 + jet.Add(c1); + jet.Add(c2, Acgi.TransientDrawingMode.Highlight); + + // 再取一点,再建一个圆c3 + var r2 = Env.Editor.GetPoint("\n选择下一点"); + if (r2.Status != PromptStatus.OK) + return; + var pt2 = r2.Value.Ucs2Wcs(); + + // 将c1从瞬态容器中移除,将c2修改颜色,c3加入瞬态容器 + jet.Remove(c1); + + c2.ColorIndex = 1; + var c3 = new Circle(pt2, Vector3d.ZAxis, 150); + jet.Add(c3); + + // 由于c2进行了修改,所以需要更新, + // 可以单个更新或更新整个瞬态容器 + jet.Update(c2); + // jet.UpdateAll(); + + Env.Editor.GetPoint("\n此拾取无意义,仅为了暂停查看"); + + // 加到图纸中,为测试瞬态容器可以自行dispose消失,所以未全部加入 + using DBTrans tr = new(); + tr.CurrentSpace.AddEntity(c3); + + // 若想将容器中所有图元全部加入提供了Entities属性 + // tr.CurrentSpace.AddEntity(jet.Entities); + } + + [CommandMethod(nameof(Test_JigExTransentDim))] + public static void Test_JigExTransentDim() + { + PromptPointOptions ppo = new("") + { + AppendKeywordsToMessage = false, + }; + List pts = new(); + for (int i = 0; i < 3; i++) + { + ppo.Message = $"\n选择标注点{i + 1}"; + var ppr = Env.Editor.GetPoint(ppo); + if (ppr.Status != PromptStatus.OK) + return; + pts.Add(ppr.Value); + } + + using DBTrans tr = new(); + + using RotatedDimension dimension = new(); + dimension.SetDatabaseDefaults();// cad16没有这个不显示 + dimension.Rotation = 0; + dimension.XLine1Point = pts[0]; + dimension.XLine2Point = pts[1]; + dimension.DimLinePoint = pts[2]; + dimension.DimensionText = "<>"; + dimension.DimensionStyle = tr.Database.Dimstyle; + + using JigExTransient jet = new(); + jet.Add(dimension, TransientDrawingMode.Highlight); + jet.UpdateAll(); + + Env.Editor.GetPoint("\n此拾取无意义,仅为了暂停查看"); + tr.CurrentSpace.AddEntity(dimension); + } +} +#endif \ No newline at end of file diff --git a/tests/TestShared/TestJson.cs b/tests/TestShared/TestJson.cs new file mode 100644 index 0000000000000000000000000000000000000000..9205679c53a0bc6a302eefd3076e92d455f8fef0 --- /dev/null +++ b/tests/TestShared/TestJson.cs @@ -0,0 +1,25 @@ +namespace TestShared; + +public class TestJson +{ + /* + * 需要引入: + * + * + * + * + */ + [CommandMethod(nameof(JavaScriptSerializer))] + public void JavaScriptSerializer() + { + var RegisteredUsers = new List(); + RegisteredUsers.Add(0); + RegisteredUsers.Add(1); + RegisteredUsers.Add(2); + RegisteredUsers.Add(3); + + var serializer = new System.Web.Script.Serialization.JavaScriptSerializer(); + var serializedResult = serializer.Serialize(RegisteredUsers); + var deserializedResult = serializer.Deserialize>(serializedResult); + } +} \ No newline at end of file diff --git a/tests/TestShared/TestLisp.cs b/tests/TestShared/TestLisp.cs new file mode 100644 index 0000000000000000000000000000000000000000..8f79a39e91f1a2c737415d2fc4363244656c0a48 --- /dev/null +++ b/tests/TestShared/TestLisp.cs @@ -0,0 +1,121 @@ +namespace Test; + +public class TestLisp +{ + // 定义lisp函数 + [LispFunction(nameof(LispTest_RunLisp))] + public static object LispTest_RunLisp(ResultBuffer rb) + { + CmdTest_RunLisp(); + return null!; + } + + // 模态命令,只有当CAD发出命令提示或当前没有其他的命令或程序活动的时候才可以被触发 + [CommandMethod("CmdTest_RunLisp1")] + // 透明命令,可以在一个命令提示输入的时候触发例如正交切换,zoom等 + [CommandMethod("CmdTest_RunLisp2", CommandFlags.Transparent)] + // 选择图元之后执行命令将可以从 获取图元 + [CommandMethod("CmdTest_RunLisp3", CommandFlags.UsePickSet)] + // 命令执行前已选中部分实体.在命令执行过程中这些标记不会被清除 + [CommandMethod("CmdTest_RunLisp4", CommandFlags.Redraw)] + // 命令不能在透视图中使用 + [CommandMethod("CmdTest_RunLisp5", CommandFlags.NoPerspective)] + // 命令不能通过 MULTIPLE命令 重复触发 + [CommandMethod("CmdTest_RunLisp6", CommandFlags.NoMultiple)] + // 不允许在模型空间使用命令 + [CommandMethod("CmdTest_RunLisp7", CommandFlags.NoTileMode)] + // 不允许在布局空间使用命令 + [CommandMethod("CmdTest_RunLisp8", CommandFlags.NoPaperSpace)] + // 命令不能在OEM产品中使用 + [CommandMethod("CmdTest_RunLisp9", CommandFlags.NoOem)] + // 不能直接使用命令名调用,必须使用 组名.全局名 调用 + [CommandMethod("CmdTest_RunLisp10", CommandFlags.Undefined)] + // 定义lisp方法.已废弃 请使用lispfunction + [CommandMethod("CmdTest_RunLisp11", CommandFlags.Defun)] + // 命令不会被存储在新的命令堆上 + [CommandMethod("CmdTest_RunLisp12", CommandFlags.NoNewStack)] + // 命令不能被内部锁定(命令锁) + [CommandMethod("CmdTest_RunLisp13", CommandFlags.NoInternalLock)] + // 调用命令的文档将会被锁定为只读 + [CommandMethod("CmdTest_RunLisp14", CommandFlags.DocReadLock)] + // 调用命令的文档将会被锁定,类似document.lockdocument + [CommandMethod("CmdTest_RunLisp15", CommandFlags.DocExclusiveLock)] + // 命令在CAD运行期间都能使用,而不只是在当前文档 + [CommandMethod("CmdTest_RunLisp16", CommandFlags.Session)] + // 获取用户输入时,可以与属性面板之类的交互 + [CommandMethod("CmdTest_RunLisp17", CommandFlags.Interruptible)] + // 命令不会被记录在命令历史记录 + [CommandMethod("CmdTest_RunLisp18", CommandFlags.NoHistory)] + // 命令不会被 UNDO取消 + [CommandMethod("CmdTest_RunLisp19", CommandFlags.NoUndoMarker)] + // 不能在参照块中使用命令 + [CommandMethod("CmdTest_RunLisp20", CommandFlags.NoBlockEditor)] +#if !ac2008 + // acad09增,不会被动作录制器 捕捉到 + [CommandMethod("CmdTest_RunLisp21", CommandFlags.NoActionRecording)] + // acad09增,会被动作录制器捕捉 + [CommandMethod("CmdTest_RunLisp22", CommandFlags.ActionMacro)] +#endif +#if !NET35 + // 推断约束时不能使用命令 + [CommandMethod("CmdTest_RunLisp23", CommandFlags.NoInferConstraint)] + // 命令允许在选择图元时临时显示动态尺寸 + [CommandMethod("CmdTest_RunLisp24", CommandFlags.TempShowDynDimension)] +#endif + public static void CmdTest_RunLisp() + { + // 测试方法1: (command "CmdTest_RunLisp1") + // 测试方式2: (LispTest_RunLisp) + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + var ed = doc.Editor; + + var sb = new StringBuilder(); + foreach (var item in Enum.GetValues(typeof(EditorEx.RunLispFlag))) + { + sb.Append((byte)item); + sb.Append(','); + } + sb.Remove(sb.Length - 1, 1); + var option = new PromptIntegerOptions($"\n输入RunLispFlag枚举值:[{sb}]"); + var ppr = ed.GetInteger(option); + + if (ppr.Status != PromptStatus.OK) + return; + var flag = (EditorEx.RunLispFlag)ppr.Value; + + if (flag == EditorEx.RunLispFlag.AdsQueueexpr) + { + // 同步 + Env.Editor.RunLisp("(setq a 10)(princ)", + EditorEx.RunLispFlag.AdsQueueexpr); + Env.Editor.RunLisp("(princ a)", + EditorEx.RunLispFlag.AdsQueueexpr);// 成功输出 + } + else if (flag == EditorEx.RunLispFlag.AcedEvaluateLisp) + { + // 使用(command "CmdTest_RunLisp1")发送,然后 !b 查看变量,acad08是有值的,高版本是null + var strlisp0 = "(setq b 20)"; + var res0 = Env.Editor.RunLisp(strlisp0, + EditorEx.RunLispFlag.AcedEvaluateLisp); // 有lisp的返回值 + + var strlisp1 = "(defun f1( / )(princ \"aa\"))"; + var res1 = Env.Editor.RunLisp(strlisp1, + EditorEx.RunLispFlag.AcedEvaluateLisp); // 有lisp的返回值 + + var strlisp2 = "(defun f2( / )(command \"line\"))"; + var res2 = Env.Editor.RunLisp(strlisp2, + EditorEx.RunLispFlag.AcedEvaluateLisp); // 有lisp的返回值 + } + else if (flag == EditorEx.RunLispFlag.SendStringToExecute) + { + // 测试异步 + // (command "CmdTest_RunLisp1")和(LispTest_RunLisp)4都是异步 + var str = "(setq c 40)(princ)"; + Env.Editor.RunLisp(str, + EditorEx.RunLispFlag.SendStringToExecute); // 异步,后发送 + Env.Editor.RunLisp("(princ c)", + EditorEx.RunLispFlag.AdsQueueexpr); // 同步,先发送了,输出是null + } + } +} \ No newline at end of file diff --git a/tests/TestShared/TestLoop.cs b/tests/TestShared/TestLoop.cs new file mode 100644 index 0000000000000000000000000000000000000000..438253ae5a8b7e03c3a0660e9d31409db966afb8 --- /dev/null +++ b/tests/TestShared/TestLoop.cs @@ -0,0 +1,25 @@ +namespace Test; +public class TestLoop +{ + [CommandMethod(nameof(Test_LoopList))] + public void Test_LoopList() + { + var loop = new LoopList + { + 0, + 1, + 2, + 3, + 4, + 5 + }; + + Env.Print(loop); + + loop.SetFirst(loop.Last!); + Env.Print(loop); + Env.Print(loop.Min()); + loop.SetFirst(new LoopListNode(loop.Min(), loop)); + Env.Print(loop); + } +} \ No newline at end of file diff --git a/tests/TestShared/TestMarshal.cs b/tests/TestShared/TestMarshal.cs new file mode 100644 index 0000000000000000000000000000000000000000..834fe12349a21bdd8bc0e126dff683674e08e231 --- /dev/null +++ b/tests/TestShared/TestMarshal.cs @@ -0,0 +1,123 @@ +using System.Diagnostics; +using static IFoxCAD.Basal.WindowsAPI; + +namespace TestShared; + +public class TestMarshal +{ + [CommandMethod(nameof(TestToBytes))] + public void TestToBytes() + { + var ptA = new Point3d(123, 456, 789); + var bytes = StructToBytes(ptA); + var ptB = BytesToStruct(bytes); + Env.Printl(ptB); + } + + [CommandMethod(nameof(Test_ChangeLinePoint))] + public void Test_ChangeLinePoint() + { + var prs = Env.Editor.SSGet("\n 选择直线"); + if (prs.Status != PromptStatus.OK) + return; + + using DBTrans tr = new(); + + prs.Value.GetObjectIds().ForEach(id => { + var line = id.GetObject(); + if (line == null) + return; + using (line.ForWrite()) + unsafe + { + // 不允许直接 &这个表达式进行取址(因为它是属性,不是字段) + // 实际上就是默认拷贝一份副本出来 + var p1 = line.StartPoint; + ((Point3D*)&p1)->X = 0; + ((Point3D*)&p1)->Y = 0; + ((Point3D*)&p1)->Z = 0; + line.StartPoint = p1;// 又放回去,节省了一个变量 + + var p2 = line.EndPoint; + ((Point3D*)&p2)->X = 100; + ((Point3D*)&p2)->Y = 100; + ((Point3D*)&p2)->Z = 0; + line.EndPoint = p2; + } + }); + } + + [CommandMethod(nameof(Test_ImplicitPoint3D))] + public void Test_ImplicitPoint3D() + { + // 无法用指针转换类型,所以隐式转换是无法不new的, + // 貌似是因为 + // 如果发生了获取对象的成员引用指针,没有new的话,会发生引用不计数...造成GC释放失效... + // 而微软没有提供一种计数转移的方法...造成我无法实现此操作... + unsafe + { + Point3d pt1 = new(1, 56, 89); + var a1 = (Point3D*)&pt1; + Debugx.Printl("指针类型转换,获取x::" + a1->X); + + var pt2 = Point3D.Create(new IntPtr(&pt1)); + Debugx.Printl("pt1地址::" + (int)&pt1); + Debugx.Printl("pt2地址::" + (int)&pt2); + Debug.Assert(&pt1 == &pt2);//不相等,是申请了新内存 + } + } + + [CommandMethod(nameof(Test_Marshal))] + public void Test_Marshal() + { + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + var ed = doc.Editor; + // 0x01 如何修改Point3d内容? + Point3d pt = new(100, 50, 0); + ed.WriteMessage("\n原始:" + pt.ToString()); + + // 0x02 最佳方法: + // 将Point3d内存转为Point3D,以此避开get保护,实现修改内部值 + // 为了避免在安全类型中转换,多了栈帧(无法内联),直接用指针处理 + unsafe + { + ((Point3D*)&pt)->X = 12345;//必须强转成这个指针类型,不然它为(Point3d*) + } + ed.WriteMessage("\n指针法:" + pt.ToString()); + + // 0x03 此方法仍然需要不安全操作,而且多了几个函数调用... + unsafe + { + var p = new IntPtr(&pt); + var result2 = Point3D.Create(p); + result2.X = 220; + result2.ToPtr(p); + } + "封送法:".Print(); + pt.Print(); + + // 拷贝到数组,还原指针到结构,最后将内存空间转换为目标结构体 + // 浪费内存,这不闹嘛~ + int typeSize = Marshal.SizeOf(pt); + byte[] bytes = new byte[typeSize]; + IntPtr structPtr = Marshal.AllocHGlobal(Marshal.SizeOf(pt)); + Marshal.StructureToPtr(pt, structPtr, true); + Marshal.Copy(structPtr, bytes, 0, typeSize); + var result = (Point3d)Marshal.PtrToStructure(structPtr, typeof(Point3d)); + "内存拷贝:".Print(); + result.Print(); + + //这个是不对的,会获取类型的指针,替换了就错误了 + //RuntimeTypeHandle handle = structObj.GetType().TypeHandle; + //IntPtr ptr = handle.Value; + //var result3 = (Point3D)Marshal.PtrToStructure(ptr, typeof(Point3D)); + //result3.SetX(330); + //Marshal.StructureToPtr(result3, ptr, true); + //"打印D:".Print(); + //structObj.Print(); + + // 释放内存 + Marshal.FreeHGlobal(structPtr); + } +} \ No newline at end of file diff --git a/tests/TestShared/TestMirrorFile.cs b/tests/TestShared/TestMirrorFile.cs new file mode 100644 index 0000000000000000000000000000000000000000..20503f7427cb80e79047273586111f070ba86f71 --- /dev/null +++ b/tests/TestShared/TestMirrorFile.cs @@ -0,0 +1,75 @@ +using Autodesk.AutoCAD.DatabaseServices; + +namespace Test; + +public class MirrorFile +{ + const string file = "D:/JX.dwg"; + const string fileSave = "D:/JX222.dwg"; + + /// + /// 测试:后台打开图纸,镜像文字是否存在文字偏移 + /// 答案:不存在 + /// + [CommandMethod(nameof(CmdTest_MirrorFile))] + public static void CmdTest_MirrorFile() + { + var yaxis = new Point3d(0, 1, 0); + using DBTrans tr = new(file, fileOpenMode: FileOpenMode.OpenForReadAndReadShare); + tr.BlockTable.Change(tr.ModelSpace.ObjectId, modelSpace => { + modelSpace.ForEach(entId => { + var dbText = tr.GetObject(entId, OpenMode.ForRead)!; + if (dbText is null) + return; + + dbText.UpgradeOpen(); + var pos = dbText.Position; + // text.Move(pos, Point3d.Origin); + // Y轴 + dbText.Mirror(Point3d.Origin, yaxis); + // text.Move(Point3d.Origin, pos); + dbText.DowngradeOpen(); + }); + }); + var ver = (DwgVersion)27;/*AC1021 AutoCAD 2007/2008/2009.*/ + tr.Database.SaveAs(fileSave, ver); + } + + /// + /// 测试:后台设置 dbText.IsMirroredInX 属性会令文字偏移 + /// 答案:存在,并提出解决方案 + /// + [CommandMethod(nameof(CmdTest_MirrorFile2))] + public static void CmdTest_MirrorFile2() + { + using DBTrans tr = new(file); + + tr.Task(() => { + var yaxis = new Point3d(0, 1, 0); + tr.BlockTable.Change(tr.ModelSpace.ObjectId, modelSpace => { + modelSpace.ForEach(entId => { + var entity = tr.GetObject(entId, OpenMode.ForWrite)!; + if (entity is DBText dbText) + { + dbText.Mirror(Point3d.Origin, yaxis); + dbText.IsMirroredInX = true; // 这句将导致文字偏移 + + // 指定文字的垂直对齐方式 + if (dbText.VerticalMode == TextVerticalMode.TextBase) + dbText.VerticalMode = TextVerticalMode.TextBottom; + + // 指定文字的水平对齐方式 + dbText.HorizontalMode = dbText.HorizontalMode switch + { + TextHorizontalMode.TextLeft => TextHorizontalMode.TextRight, + TextHorizontalMode.TextRight => TextHorizontalMode.TextLeft, + _ => dbText.HorizontalMode + }; + dbText.AdjustAlignment(tr.Database); + } + }); + }); + }); + tr.Database.SaveAs(fileSave, (DwgVersion)27 /*AC1021 AutoCAD 2007/2008/2009.*/); + } +} \ No newline at end of file diff --git a/tests/TestShared/TestPoint.cs b/tests/TestShared/TestPoint.cs new file mode 100644 index 0000000000000000000000000000000000000000..f3ba101225a06a3293c5fe92374bb85a01f047c0 --- /dev/null +++ b/tests/TestShared/TestPoint.cs @@ -0,0 +1,149 @@ +namespace Test; + + +using System.Diagnostics; + + +public class TestPoint +{ + /// + /// 红黑树排序点集 + /// + [CommandMethod(nameof(Test_PtSortedSet))] + public void Test_PtSortedSet() + { + var ss1 = new SortedSet + { + new Point2d(1, 1), + new Point2d(4.6, 2), + new Point2d(8, 3), + new Point2d(4, 3), + new Point2d(5, 40), + new Point2d(6, 5), + new Point2d(1, 6), + new Point2d(7, 6), + new Point2d(9, 6) + }; + + /*判断区间,超过就中断*/ + foreach (var item in ss1) + { + if (item.X > 3 && item.X < 7) + Debugx.Printl(item); + else if (item.X >= 7) + break; + } + } + + + + [CommandMethod(nameof(Test_PtGethash))] + public void Test_PtGethash() + { + // test + var pt = Env.Editor.GetPoint("pick pt").Value; + // Tools.TestTimes2(1_000_000, "新语法", () => { + // pt.GetHashString2(); + // }); + Tools.TestTimes2(1_000_000, "旧语法", () => { + pt.GetHashString(); + }); + } + + [CommandMethod(nameof(Test_Point3dHash))] + public void Test_Point3dHash() + { + Env.Print($"4位小数的hash:{new Point3d(0.0_001, 0.0_002, 0.0).GetHashCode()}"); + Env.Print($"5位小数的hash:{new Point3d(0.00_001, 0.00_002, 0.0).GetHashCode()}"); + Env.Print($"6位小数的hash:{new Point3d(0.000_001, 0.000_002, 0.0).GetHashCode()}"); + Env.Print($"7位小数的hash:{new Point3d(0.000_0_001, 0.000_0_002, 0.0).GetHashCode()}"); + Env.Print($"8位小数的hash:{new Point3d(0.000_00_001, 0.000_00_002, 0.0).GetHashCode()}"); + Env.Print($"9位小数的hash:{new Point3d(0.000_000_001, 0.000_000_002, 0.0).GetHashCode()}"); + Env.Print($"10位小数的hash:{new Point3d(0.000_000_0001, 0.000_000_0002, 0.0).GetHashCode()}"); + Env.Print($"10位小数的hash:{new Point3d(0.000_000_0001, 0.000_000_0001, 0.0).GetHashCode()}"); + + Env.Print($"11位小数的hash:{new Point3d(0.000_000_000_01, 0.000_000_000_02, 0.0).GetHashCode()}"); + Env.Print($"11位小数的hash:{new Point3d(0.000_000_000_01, 0.000_000_000_01, 0.0).GetHashCode()}"); + + Env.Print($"12位小数的hash:{new Point3d(0.000_000_000_001, 0.000_000_000_002, 0.0).GetHashCode()}"); + Env.Print($"12位小数的hash:{new Point3d(0.000_000_000_001, 0.000_000_000_001, 0.0).GetHashCode()}"); + + Env.Print($"13位小数的hash:{new Point3d(0.000_000_000_0001, 0.000_000_000_0002, 0.0).GetHashCode()}"); + Env.Print($"13位小数的hash:{new Point3d(0.000_000_000_0001, 0.000_000_000_0001, 0.0).GetHashCode()}"); + + Env.Print($"14位小数的hash:{new Point3d(0.000_000_000_000_01, 0.000_000_000_000_02, 0.0).GetHashCode()}"); + Env.Print($"14位小数的hash:{new Point3d(0.000_000_000_000_01, 0.000_000_000_000_01, 0.0).GetHashCode()}"); + + Env.Print($"15位小数的hash:{new Point3d(0.000_000_000_000_001, 0.000_000_000_000_002, 0.0).GetHashCode()}"); + Env.Print($"15位小数的hash:{new Point3d(0.000_000_000_000_001, 0.000_000_000_000_001, 0.0).GetHashCode()}"); + + Env.Print($"16位小数的hash:{new Point3d(0.000_000_000_000_000_1, 0.000_000_000_000_000_2, 0.0).GetHashCode()}"); + Env.Print($"16位小数的hash:{new Point3d(0.000_000_000_000_000_1, 0.000_000_000_000_000_1, 0.0).GetHashCode()}"); + + Env.Print($"17位小数的hash:{new Point3d(0.000_000_000_000_000_01, 0.000_000_000_000_000_02, 0.0).GetHashCode()}"); + Env.Print($"17位小数的hash:{new Point3d(0.000_000_000_000_000_01, 0.000_000_000_000_000_01, 0.0).GetHashCode()}"); + + Env.Print($"18位小数的hash:{new Point3d(0.000_000_000_000_000_001, 0.000_000_000_000_000_002, 0.0).GetHashCode()}"); + Env.Print($"18位小数的hash:{new Point3d(0.000_000_000_000_000_001, 0.000_000_000_000_000_001, 0.0).GetHashCode()}"); + + Env.Print($"19位小数的hash:{new Point3d(0.000_000_000_000_000_000_1, 0.000_000_000_000_000_000_2, 0.0).GetHashCode()}"); + Env.Print($"19位小数的hash:{new Point3d(0.000_000_000_000_000_000_1, 0.000_000_000_000_000_000_1, 0.0).GetHashCode()}"); + + Env.Print($"20位小数的hash:{new Point3d(0.000_000_000_000_000_000_01, 0.000_000_000_000_000_000_02, 0.0).GetHashCode()}"); + Env.Print($"20位小数的hash:{new Point3d(0.000_000_000_000_000_000_01, 0.000_000_000_000_000_000_01, 0.0).GetHashCode()}"); + } + + [CommandMethod(nameof(Test_ListEqualspeed))] + public void Test_ListEqualspeed() + { + var lst1 = new List { 1, 2, 3, 4 }; + var lst2 = new List { 1, 2, 3, 4 }; + lst1.EqualsAll(null!); + Tools.TestTimes2(1000000, "eqaulspeed:", () => { + lst1.EqualsAll(lst2); + }); + } + + [CommandMethod(nameof(Test_Contains))] + public void Test_Contains() + { + // test list and dict contains speed + var lst = new List { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 }; + var hashset = new HashSet { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 }; + var dict = new Dictionary + { + { 1, 0 }, + { 2, 1 }, + { 3, 2 }, + { 4, 3 }, + { 5, 4 }, + { 6, 5 }, + { 7, 6 }, + { 8, 7 }, + { 9, 8 }, + { 10, 9 }, + { 11, 11 }, + { 12, 12 }, + { 13, 13 }, + { 14, 14 }, + { 15, 15 }, + { 16, 16 }, + { 17, 17 }, + { 18, 18 }, + { 19, 19 }, + { 20, 20 }, + }; + + Tools.TestTimes2(100_0000, "list:", () => { + lst.Contains(20); + }); + + Tools.TestTimes2(100_0000, "hashset:", () => { + hashset.Contains(20); + }); + + Tools.TestTimes2(100_0000, "dict:", () => { + dict.ContainsKey(20); + }); + } +} \ No newline at end of file diff --git a/tests/TestShared/TestQuadTree.cs b/tests/TestShared/TestQuadTree.cs new file mode 100644 index 0000000000000000000000000000000000000000..74cd52dc3a5137dcaf38f2e30c888d0b9b62296c --- /dev/null +++ b/tests/TestShared/TestQuadTree.cs @@ -0,0 +1,459 @@ +namespace Test; + +#pragma warning disable CS8632 // 只能在 "#nullable" 注释上下文内的代码中使用可为 null 的引用类型的注释。 +/* + * 这里属于用户调用例子, + * 调用时候必须要继承它,再提供给四叉树 + * 主要是用户可以扩展属性 + */ +public class CadEntity : QuadEntity +{ + public ObjectId ObjectId; + // 这里加入其他字段 + public List? Link;// 碰撞链 + public System.Drawing.Color Color; + public double Angle; + public CadEntity(ObjectId objectId, Rect box) : base(box) + { + ObjectId = objectId; + } + public int CompareTo(CadEntity? other) + { + if (other == null) + return -1; + return GetHashCode() ^ other.GetHashCode(); + } + public override int GetHashCode() + { + return (base.GetHashCode(), ObjectId.GetHashCode()).GetHashCode(); + } +} +#pragma warning restore CS8632 // 只能在 "#nullable" 注释上下文内的代码中使用可为 null 的引用类型的注释。 + + + + + +public partial class TestQuadTree +{ + QuadTree? _quadTreeRoot; + #region 四叉树创建并加入 + [CommandMethod(nameof(Test_QuadTree))] + public void Test_QuadTree() + { + using DBTrans tr = new(); + + Rect dbExt; + // 使用数据库边界来进行 + var dbExtent = tr.Database.GetValidExtents3d(); + if (dbExtent == null) + { + // throw new ArgumentException("画一个矩形"); + + // 这个初始值的矩形是很有意义, + // 主要是四叉树分裂过程中产生多个Rect,Rect内有很多重复的double值,是否可以内存复用,以此减少内存大小? + // 接着想了一下,Rect可以是int,long,这样可以利用位运算它扩展和缩小, + // 最小就是1,并且可以控制四叉树深度,不至于无限递归. + // 而且指针长度跟值是一样的,所以就不需要复用了,毕竟跳转一个函数地址挺麻烦的. + // 但是因为啊惊懒的原因,并没有单独制作这样的矩形, + // 而且非常糟糕的是,c#不支持模板约束运算符,使得值类型之间需要通过一层接口来委婉处理,拉低了效率..引用类型倒是无所谓.. + // 要么忍着,要么换c++去搞四叉树吧 + dbExt = new Rect(0, 0, 1 << 10, 1 << 10); + } + else + { + var a = new Point2d(dbExtent.Value.MinPoint.X, dbExtent.Value.MinPoint.Y); + var b = new Point2d(dbExtent.Value.MaxPoint.X, dbExtent.Value.MaxPoint.Y); + dbExt = new Rect(a, b); + } + + // 创建四叉树 + _quadTreeRoot = new QuadTree(dbExt); + + // 数据库边界 + var pl = dbExt.ToPoints(); + var databaseBoundary = new List<(Point3d, double, double, double)> + { + (new Point3d(pl[0].X,pl[0].Y,0),0,0,0), + (new Point3d(pl[1].X,pl[1].Y,0),0,0,0), + (new Point3d(pl[2].X,pl[2].Y,0),0,0,0), + (new Point3d(pl[3].X,pl[3].Y,0),0,0,0), + }; + tr.CurrentSpace.AddPline(databaseBoundary); + + // 生成多少个图元,导致cad会令undo出错(八叉树深度过大 treemax) + // int maximumItems = 30_0000; + int maximumItems = 1000; + + // 随机图元生成 + List ces = new(); // 用于随机获取图元 + Tools.TestTimes(1, "画圆消耗时间:", () => { + // 生成外边界和随机圆形 + var grc = GenerateRandomCircle(maximumItems, dbExt); + foreach (var ent in grc) + { + // 初始化图元颜色 + ent!.ColorIndex = 1; // Color.FromRgb(0, 0, 0);// 黑色 + var edge = ent.GeometricExtents; + // 四叉树数据 + var entRect = new Rect(edge.MinPoint.X, edge.MinPoint.Y, edge.MaxPoint.X, edge.MaxPoint.Y); + var entId = tr.CurrentSpace.AddEntity(ent); + + var ce = new CadEntity(entId, entRect) + { + Color = RandomEx.NextColor() + }; + ces.Add(ce); + /*加入随机点*/ + var p = edge.MinPoint + new Vector3d(10, 10, 0); + entRect = new Rect(p.Point2d(), p.Point2d()); + entId = tr.CurrentSpace.AddEntity(new DBPoint(p)); + var dbPointCe = new CadEntity(entId, entRect); + ces.Add(dbPointCe); + } + });// 30万图元±3秒.cad2021 + + // 测试只加入四叉树的时间 + Tools.TestTimes(1, "插入四叉树时间:", () => { + for (int i = 0; i < ces.Count; i++) + _quadTreeRoot.Insert(ces[i]); + });// 30万图元±0.7秒.cad2021 + + tr.Editor?.WriteMessage($"\n加入图元数量:{maximumItems}"); + } + + /// + /// 创建随机圆形 + /// + /// 创建数量 + /// 数据库边界 + static IEnumerable GenerateRandomCircle(int createNumber, Rect dbExt) + { + var x1 = (int)dbExt.X; + var x2 = (int)(dbExt.X + dbExt.Width); + var y1 = (int)dbExt.Y; + var y2 = (int)(dbExt.Y + dbExt.Height); + + var rand = RandomEx.GetRandom(); + for (int i = 0; i < createNumber; i++) + { + var x = rand.Next(x1, x2) + rand.NextDouble(); + var y = rand.Next(y1, y2) + rand.NextDouble(); + yield return CircleEx.CreateCircle(new Point3d(x, y, 0), rand.Next(1, 100)); // 起点,终点 + } + } + + /* 啊惊: 有点懒不想改了*/ +#if true2 + + // 选择加入到四叉树 + [CommandMethod(nameof(CmdTest_QuadTree21))] + public void CmdTest_QuadTree21() + { + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + var db = doc.Database; + var ed = doc.Editor; + ed.WriteMessage("\n选择单个图元加入已有的四叉树"); + + var ss = ed.Ssget(); + if (ss.Count == 0) + return; + + AddQuadTreeRoot(db, ed, ss); + } + + // 自动加入全图到四叉树 + [CommandMethod(nameof(CmdTest_QuadTree20))] + public void CmdTest_QuadTree20() + { + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + var db = doc.Database; + var ed = doc.Editor; + ed.WriteMessage("\n自动加入全图到四叉树"); + + var ss = new List(); + int entnum = 0; + var time1 = Timer.RunTime(() => { + db.Action(tr => { + db.TraverseBlockTable(tr, btRec => { + if (!btRec.IsLayout)// 布局跳过 + return false; + + foreach (var item in btRec) + { + // var ent = item.ToEntity(tr); + ss.Add(item); + ++entnum;// 图元数量:100000, 遍历全图时间:0.216秒 CmdTest_QuadTree2 + } + return false; + }); + }); + }); + ed.WriteMessage($"\n图元数量:{entnum}, 遍历全图时间:{time1 / 1000.0}秒"); + + // 清空原有的 + _quadTreeRoot = null; + AddQuadTreeRoot(db, ed, ss); + } + + void AddQuadTreeRoot(Database db, Editor ed, List ss) + { + if (_quadTreeRoot is null) + { + ed.WriteMessage("\n四叉树是空的,重新初始化"); + + Rect dbExt; + // 使用数据库边界来进行 + var dbExtent = db.GetValidExtents3d(); + if (dbExtent == null) + { + // throw new ArgumentException("画一个矩形"); + + // 测试时候画个矩形,在矩形内画随机坐标的圆形 + dbExt = new Rect(0, 0, 32525, 32525); + } + else + { + dbExt = new Rect(dbExtent.Value.MinPoint.Point2d(), dbExtent.Value.MaxPoint.Point2d()); + } + _quadTreeRoot = new(dbExt); + } + + /* 测试: + * 为了测试删除内容释放了分支,再重复加入是否报错 + * 先创建 CmdTest_QuadTree1 + * 再减去 CmdTest_QuadTree0 + * 然后原有黑色边界,再生成边界 CmdTest_Create00,对比删除效果. + * 然后加入 CmdTest_QuadTree2 + * 然后原有黑色边界,再生成边界 CmdTest_Create00,对比删除效果. + */ + + List ces = new(); + db.Action(tr => { + ss.ForEach(entId => { + var ent = entId.ToEntity(tr); + if (ent is null) + return; + var edge = new EdgeEntity(ent); + // 四叉树数据 + var ce = new CadEntity(entId, edge.Edge) + { + Color = Utility.RandomColor + }; + ces.Add(ce); + + edge.Dispose(); + }); + }); + + var time2 = Timer.RunTime(() => { + _quadTreeRoot.Insert(ces); + }); + ed.WriteMessage($"\n图元数量:{ces.Count}, 加入四叉树时间:{time2 / 1000.0}秒"); + } +#endif + + #endregion + + /* 啊惊: 有点懒不想改了*/ +#if true2 + + #region 节点边界显示 + // 四叉树减去节点 + [CommandMethod(nameof(CmdTest_QuadTree0))] + public void CmdTest_QuadTree0() + { + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + // var db = doc.Database; + var ed = doc.Editor; + ed.WriteMessage("\n四叉树减区"); + + if (_quadTreeRoot is null) + { + ed.WriteMessage("\n四叉树是空的"); + return; + } + var rect = GetCorner(ed); + if (rect is null) + return; + _quadTreeRoot.Remove(rect); + } + + // 创建节点边界 + [CommandMethod(nameof(CmdTest_CreateNodesRect))] + public void CmdTest_CreateNodesRect() + { + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + var db = doc.Database; + var ed = doc.Editor; + ed.WriteMessage("\n创建边界"); + + if (_quadTreeRoot is null) + { + ed.WriteMessage("\n四叉树是空的"); + return; + } + + // 此处发现了一个事务处理的bug,提交数量过多的时候,会导致 ctrl+z 无法回滚, + // 需要把事务放在循环体内部 + // 报错: 0x6B00500A (msvcr80.dll)处(位于 acad.exe 中)引发的异常: 0xC0000005: 写入位置 0xFFE00000 时发生访问冲突。 + // 画出所有的四叉树节点边界,因为事务放在外面引起 + var nodeRects = new List(); + _quadTreeRoot.ForEach(node => { + nodeRects.Add(node); + return false; + }); + var rectIds = new List(); + foreach (var item in nodeRects)// Count = 97341 当数量接近这个量级 + { + db.Action(tr => { + var pts = item.ToPoints(); + var rec = EntityAdd.AddPolyLineToEntity(pts.ToPoint2d()); + rec.ColorIndex = 250; + rectIds.Add(tr.AddEntityToMsPs(db, rec)); + }); + } + db.Action(tr => { + db.CoverGroup(tr, rectIds); + }); + + // 获取四叉树深度 + int dep = 0; + _quadTreeRoot.ForEach(node => { + dep = dep > node.Depth ? dep : node.Depth; + return false; + }); + ed.WriteMessage($"\n四叉树深度是: {dep}"); + } + #endregion + +#endif + + #region 四叉树查询节点 + // 选择范围改图元颜色 + [CommandMethod(nameof(CmdTest_QuadTree3))] + public void CmdTest_QuadTree3() + { + Ssget(QuadTreeSelectMode.IntersectsWith); + } + + [CommandMethod(nameof(CmdTest_QuadTree4))] + public void CmdTest_QuadTree4() + { + Ssget(QuadTreeSelectMode.Contains); + } + + /// + /// 改颜色 + /// + /// + void Ssget(QuadTreeSelectMode mode) + { + if (_quadTreeRoot is null) + return; + + using DBTrans tr = new(); + if (tr.Editor is null) + return; + var rect = GetCorner(tr.Editor); + if (rect is null) + return; + + tr.Editor.WriteMessage("选择模式:" + mode); + + // 仿选择集 + var ces = _quadTreeRoot.Query(rect, mode); + ces.ForEach(item => { + var ent = tr.GetObject(item.ObjectId, OpenMode.ForWrite); + ent!.Color = Color.FromColor(item.Color); + ent.DowngradeOpen(); + ent.Dispose(); + }); + } + + /// + /// 交互获取 + /// + /// + /// + public static Rect? GetCorner(Editor ed) + { + var optionsA = new PromptPointOptions($"{Environment.NewLine}起点位置:"); + var pprA = ed.GetPoint(optionsA); + if (pprA.Status != PromptStatus.OK) + return null; + var optionsB = new PromptCornerOptions(Environment.NewLine + "输入矩形角点2:", pprA.Value) + { + UseDashedLine = true,// 使用虚线 + AllowNone = true,// 回车 + }; + var pprB = ed.GetCorner(optionsB); + if (pprB.Status != PromptStatus.OK) + return null!; + + return new Rect(new Point2d(pprA.Value.X, pprA.Value.Y), + new Point2d(pprB.Value.X, pprB.Value.Y), + true); + } + #endregion +} + +// public partial class TestQuadTree +// { +// public void Cmd_tt6() +// { +// using DBTrans tr = new(); +// var ed = tr.Editor; +// // 创建四叉树,默认参数无所谓 +// var TreeRoot = new QuadTree(new Rect(0, 0, 32525, 32525)); + +// var fil = OpFilter.Build(e => e.Dxf(0) == "LINE"); +// var psr = ed.SSGet("\n 选择需要连接的直线", fil); +// if (psr.Status != PromptStatus.OK) return; +// var LineEnts = new List(psr.Value.GetEntities(OpenMode.ForWrite)!); +// // 将实体插入到四岔树 +// foreach (var line in LineEnts) +// { +// var edge = line.GeometricExtents; +// var entRect = new Rect(edge.MinPoint.X, edge.MinPoint.Y, edge.MaxPoint.X, edge.MaxPoint.Y); +// var ce = new CadEntity(line.Id, entRect) +// { +// // 四叉树数据 +// Angle = line.Angle +// }; +// TreeRoot.Insert(ce); +// } + +// var ppo = new PromptPointOptions(Environment.NewLine + "\n指定标注点:<空格退出>") +// { +// AllowArbitraryInput = true,// 任意输入 +// AllowNone = true // 允许回车 +// }; +// var ppr = ed.GetPoint(ppo);// 用户点选 +// if (ppr.Status != PromptStatus.OK) +// return; +// var rect = new Rect(ppr.Value.Point2d(), 100, 100); +// tr.CurrentSpace.AddEntity(rect.ToPolyLine());// 显示选择靶标范围 + +// var nent = TreeRoot.FindNearEntity(rect);// 查询最近实体,按逆时针 +// var ent = tr.GetObject(nent.ObjectId, OpenMode.ForWrite);// 打开实体 +// ent.ColorIndex = Utility.GetRandom().Next(1, 256);// 1~256随机色 +// ent.DowngradeOpen();// 实体降级 +// ent.Dispose(); + +// var res = TreeRoot.Query(rect, QuadTreeSelectMode.IntersectsWith);// 查询选择靶标范围相碰的ID +// res.ForEach(item => { +// if (item.Angle == 0 || item.Angle == Math.PI) // 过滤直线角度为0或180的直线 +// { +// var ent = tr.GetObject(item.ObjectId, OpenMode.ForWrite); +// ent.ColorIndex = Utility.GetRandom().Next(1, 7); +// ent.DowngradeOpen(); +// ent.Dispose(); +// } +// }); +// } +// } \ No newline at end of file diff --git a/tests/TestShared/TestSelectfilter.cs b/tests/TestShared/TestSelectfilter.cs new file mode 100644 index 0000000000000000000000000000000000000000..ff47d0001f03802e3388f0b8266d99c0062587e7 --- /dev/null +++ b/tests/TestShared/TestSelectfilter.cs @@ -0,0 +1,32 @@ +namespace Test; + +public class Testselectfilter +{ + [CommandMethod(nameof(Test_Filter))] + public void Test_Filter() + { + var p = new Point3d(10, 10, 0); + var f = OpFilter.Build( + e => !(e.Dxf(0) == "line" & e.Dxf(8) == "0") + | e.Dxf(0) != "circle" & e.Dxf(8) == "2" & e.Dxf(10) >= p); + + + var f2 = OpFilter.Build( + e => e.Or( + !e.And(e.Dxf(0) == "line", e.Dxf(8) == "0"), + e.And(e.Dxf(0) != "circle", e.Dxf(8) == "2", + e.Dxf(10) <= new Point3d(10, 10, 0)))); + + SelectionFilter f3 = f; + SelectionFilter f4 = f2; + + Env.Editor.WriteMessage(""); + } + + [CommandMethod(nameof(Test_Selectanpoint))] + public void Test_Selectanpoint() + { + var sel2 = Env.Editor.SelectAtPoint(new Point3d(0, 0, 0)); + Env.Editor.WriteMessage(""); + } +} \ No newline at end of file diff --git a/tests/TestShared/TestShared.projitems b/tests/TestShared/TestShared.projitems new file mode 100644 index 0000000000000000000000000000000000000000..bd01c5c24e4e74d281e2e9d34ffe54f2312751ee --- /dev/null +++ b/tests/TestShared/TestShared.projitems @@ -0,0 +1,40 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + ced63d2d-0af6-4831-806d-5e8e9b0d0a07 + + + TestShared + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/TestShared/TestShared.shproj b/tests/TestShared/TestShared.shproj new file mode 100644 index 0000000000000000000000000000000000000000..31ea779a25d8b662fbe52cf640dbcceff700c326 --- /dev/null +++ b/tests/TestShared/TestShared.shproj @@ -0,0 +1,13 @@ + + + + ced63d2d-0af6-4831-806d-5e8e9b0d0a07 + 14.0 + + + + + + + + diff --git a/tests/TestShared/TestXRecord.cs b/tests/TestShared/TestXRecord.cs new file mode 100644 index 0000000000000000000000000000000000000000..313bf8c5aecb2ba000399429afd421661bb512d9 --- /dev/null +++ b/tests/TestShared/TestXRecord.cs @@ -0,0 +1,281 @@ +//#define ExtendedDataBinaryChunk +#define XTextString + +#if NewtonsoftJson +using System.Diagnostics; +using Newtonsoft.Json; +using static IFoxCAD.Cad.WindowsAPI; + +namespace Test_XRecord; + +public class TestCmd_XRecord +{ + [CommandMethod(nameof(TestSerializeSetXRecord))] + public void TestSerializeSetXRecord() + { + var prs = Env.Editor.SSGet("\n 序列化,选择多段线:"); + if (prs.Status != PromptStatus.OK) + return; + + using var tr = new DBTrans(); + var pls = prs.Value.GetEntities(); + Tools.TestTimes(1, nameof(TestSerializeSetXRecord), () => { + foreach (var pl in pls) + { + if (pl == null) + continue; + + TestABCList datas = new(); + for (int i = 0; i < 1000; i++) + { + datas.Add(new() + { + AAA = i, + BBB = i.ToString(), + CCCC = i * 0.5, + DDDD = i % 2 != 0, + EEEE = new(0, i, 0) + }); + } + + using (pl.ForWrite()) + pl.SerializeToXRecord(datas); + } + }); + } + + [CommandMethod(nameof(TestDeserializeGetXRecord))] + public void TestDeserializeGetXRecord() + { + var prs = Env.Editor.GetEntity("\n 反序列化,选择多段线:"); + if (prs.Status != PromptStatus.OK) + return; + + using var tr = new DBTrans(); + + TestABCList? datas = null; + Tools.TestTimes(1, nameof(TestDeserializeGetXRecord), () => { + var pl = prs.ObjectId.GetObject(); + if (pl == null) + return; +#if XTextString + datas = pl.DeserializeToXRecord(); +#endif + +#if ExtendedDataBinaryChunk + // 这里有数据容量限制,而且很小 + var xd = pl.GetXDictionary(); + if (xd == null) + return; + if (xd.XData == null) + return; + + XDataList data = xd.XData; + var sb = new StringBuilder(); + data.ForEach(a => { + if (a.TypeCode == (short)DxfCode.ExtendedDataBinaryChunk) + if (a.Value is byte[] bytes) + sb.Append(Encoding.UTF8.GetString(bytes)); + }); + datas = JsonConvert.DeserializeObject(sb.ToString(), XRecordHelper._sset); +#endif + }); + if (datas == null) + { + Env.Printl("没有反序列的东西"); + return; + } + + var sb = new StringBuilder(); + for (int i = 0; i < datas.Count; i++) + sb.Append(datas[i]); + Env.Printl(sb); + } +} + +public static class XRecordHelper +{ + #region 序列化方式 + internal static JsonSerializerSettings _sset = new() + { + Formatting = Formatting.Indented, + TypeNameHandling = TypeNameHandling.Auto + }; + + /// + /// 设定信息 + /// + /// 储存对象 + /// 储存数据 + public static void SerializeToXRecord(this DBObject dbo, T data) + { + var xd = dbo.GetXDictionary(); + if (xd == null) + return; + + // XRecordDataList 不能超过2G大小 + const int GigaByte2 = 2147483647; + // 单条只能 16KiBit => 2048 * 16 == 32768 + const int KiBit16 = (2048 * 16) - 1; + + // 就算这个写法支持,计算机也不一定有那么多内存,所以遇到此情况最好换成内存拷贝 + var json = JsonConvert.SerializeObject(data, _sset);// 此时内存占用了2G + var buffer = Encoding.UTF8.GetBytes(json); // 此时内存又占用了2G.. + + BytesTask(buffer, GigaByte2, bts => { +#if XTextString + XRecordDataList datas = new(); + BytesTask(buffer, KiBit16, bts => { + datas.Add(DxfCode.XTextString, Encoding.UTF8.GetString(bts)); // 这对的 + // datas.Add(DxfCode.XTextString, bts);//这样 bts 变成 "System.Byte[]" + }); + xd.SetXRecord(typeof(T).FullName, datas); +#endif + +#if ExtendedDataBinaryChunk + // 这里有数据容量限制,而且很小 + var appname = typeof(T).FullName; + DBTrans.Top.RegAppTable.Add(appname); + + XDataList datas = new(); + datas.Add(DxfCode.ExtendedDataRegAppName, appname); + BytesTask(buffer, KiBit16, bts => { + datas.Add(DxfCode.ExtendedDataBinaryChunk, bts); + }); + using (xd.ForWrite()) + xd.XData = datas; // Autodesk.AutoCAD.Runtime.Exception:“eXdataSizeExceeded” +#endif + }); + } + + [DebuggerHidden] + static void BytesTask(byte[] buffer, int max, Action action) + { + int index = 0; + while (index < buffer.Length) + { + // 每次 max,然后末尾剩余就单独 + byte[] bts; + if (buffer.Length - index > max) + bts = new byte[max]; + else + bts = new byte[buffer.Length - index]; + + for (int i = 0; i < bts.Length; i++) + bts[i] = buffer[index++]; + + action.Invoke(bts); + } + } + + /// + /// 提取信息 + /// + /// 类型 + /// 储存对象 + /// 提取数据生成的对象 + public static T? DeserializeToXRecord(this DBObject dbo) + { + var xd = dbo.GetXDictionary(); + if (xd == null) + return default; + + var datas = xd.GetXRecord(typeof(T).FullName); + if (datas == null) + return default; + + var sb = new StringBuilder(); + for (int i = 0; i < datas.Count; i++) + sb.Append(datas[i].Value); + + return JsonConvert.DeserializeObject(sb.ToString(), _sset); + } + #endregion + +#if NET35 + /// + /// 设置描述(容量无限) + /// + /// + /// + /// + public static void SetSummaryInfoAtt(this Database db, string key, string value) + { + var info = new DatabaseSummaryInfoBuilder(db.SummaryInfo); + if (!info.CustomProperties.ContainsKey(key)) + info.CustomProperties.Add(key, value); + else + info.CustomProperties[key] = value; + db.SummaryInfo = info.ToDatabaseSummaryInfo(); + } + /// + /// 获取描述 + /// + /// + /// + /// + public static object? GetSummaryInfoAtt(this Database db, string key) + { + var info = new DatabaseSummaryInfoBuilder(db.SummaryInfo); + if (info.CustomProperties.ContainsKey(key)) + return info.CustomProperties[key]; + return null; + } +#else + /// + /// 设置描述(容量无限) + /// + /// + /// + /// + public static void SetSummaryInfoAtt(this Database db, string key, object value) + { + var info = new DatabaseSummaryInfoBuilder(db.SummaryInfo); + if (!info.CustomPropertyTable.Contains(key)) + info.CustomPropertyTable.Add(key, value); + else + info.CustomPropertyTable[key] = value; + db.SummaryInfo = info.ToDatabaseSummaryInfo(); + } + /// + /// 获取描述 + /// + /// + /// + /// + public static object? GetSummaryInfoAtt(this Database db, string key) + { + var info = new DatabaseSummaryInfoBuilder(db.SummaryInfo); + if (info.CustomPropertyTable.Contains(key)) + return info.CustomPropertyTable[key]; + return null; + } +#endif +} + +public class TestABCList : List +{ +} + +[ComVisible(true)] +[Serializable] +[DebuggerDisplay("{DebuggerDisplay,nq}")] +[DebuggerTypeProxy(typeof(TestABC))] +[StructLayout(LayoutKind.Sequential, Pack = 4)] +public class TestABC +{ + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private string DebuggerDisplay => ToString(); + + public int AAA; + public string? BBB; + public double CCCC; + public bool DDDD; + public Point3D EEEE; + + public override string ToString() + { + return JsonConvert.SerializeObject(this, XRecordHelper._sset); + } +} +#endif \ No newline at end of file diff --git a/tests/TestShared/TestXrefEx.cs b/tests/TestShared/TestXrefEx.cs new file mode 100644 index 0000000000000000000000000000000000000000..81422006a11439cf8c03803e2f43027051ef8ad5 --- /dev/null +++ b/tests/TestShared/TestXrefEx.cs @@ -0,0 +1,24 @@ +namespace Test; + +public class TestCmd_BindXrefs +{ + //后台绑定 + [CommandMethod(nameof(Test_Bind1))] + public static void Test_Bind1() + { + string fileName = @"D:\Test.dwg"; + using var tr = new DBTrans(fileName, + fileOpenMode: FileOpenMode.OpenForReadAndAllShare/*后台绑定特别注意*/); + tr.XrefFactory(XrefModes.Bind); + tr.SaveDwgFile(); + } + + //前台绑定 + [CommandMethod(nameof(Test_Bind2))] + public static void Test_Bind2() + { + using var tr = new DBTrans(); + tr.XrefFactory(XrefModes.Bind); + tr.SaveDwgFile(); + } +} \ No newline at end of file