snapmark 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
snapmark/__init__.py ADDED
@@ -0,0 +1,165 @@
1
+ """
2
+ SnapMark - Library for marking and manipulating DXF files.
3
+
4
+ Basic Usage:
5
+ import snapmark as sm
6
+
7
+ # Quick mark by file name
8
+ sm.mark_by_name("Examples/Input")
9
+
10
+ # Mark folder
11
+ sm.Operation.process_folder("folder", mark)
12
+
13
+ # Multiple pipeline example
14
+ manager = sm.IterationManager("folder")
15
+ manager.add_operation(
16
+ sm.Aligner(),
17
+ sm.AddMark(sequence),
18
+ sm.CountHoles(sm.find_circle_by_radius(5, 10))
19
+ )
20
+ manager.execute()
21
+ """
22
+
23
+ # ========== CORE: Main operations ==========
24
+ from .operations.basic_operations import (
25
+ Operation,
26
+ AddMark,
27
+ SubstituteCircle,
28
+ AddX,
29
+ RemoveCircle,
30
+ RemoveLayer,
31
+ PrintLayers,
32
+ )
33
+
34
+ from .operations.counter import (
35
+ Counter,
36
+ CountFiles,
37
+ CountHoles,
38
+ )
39
+
40
+ from .operations.aligner import Aligner
41
+
42
+ # ========== SEQUENCE (NEW SYSTEM) ==========
43
+ from .sequence.sequence_system import (
44
+ SequenceBuilder,
45
+ from_file_name,
46
+ from_splitted_text
47
+ )
48
+
49
+ # ========== SEQUENCE (OLD - DEPRECATED) ==========
50
+ from .sequence.sequence_legacy import (
51
+ Conc,
52
+ FixSeq,
53
+ )
54
+
55
+ # ========== SHORTCUTS ==========
56
+ from .shortcuts import (
57
+ mark_by_name,
58
+ mark_by_splitted_text,
59
+ mark_with_sequence,
60
+ quick_count_holes,
61
+ single_file_pipeline,
62
+ restore_backup
63
+ )
64
+
65
+
66
+ # ========== ITERATION MANAGER ==========
67
+ from .core import IterationManager, iteration_manager # iteration_manager = alias legacy
68
+
69
+ # ========== UTILITIES ==========
70
+ from .utils.backup_manager import BackupManager
71
+ from .utils.helpers import (
72
+ count_holes,
73
+ find_all_circles,
74
+ find_circle_by_radius,
75
+ )
76
+
77
+ # ========== CHECKING/SEARCH ==========
78
+ from .checking.checking import (
79
+ find_spec_holes,
80
+ find_circle_centers,
81
+ find_longer_entity,
82
+ print_layers as print_document_layers,
83
+ print_entities,
84
+ )
85
+
86
+
87
+ # ========== METADATA ==========
88
+ __version__ = "2.0.0"
89
+ __author__ = "serg_you_lin"
90
+ __all__ = [
91
+ # Shortcuts (main API)
92
+ 'mark_by_name',
93
+ 'mark_by_splitted_text',
94
+ 'mark_with_sequence',
95
+ 'quick_count_holes',
96
+ 'single_file_pipeline',
97
+ 'restore_backup',
98
+
99
+ # Sequence Builder
100
+ 'SequenceBuilder',
101
+ 'from_file_name',
102
+ 'from_file_part',
103
+
104
+ # Operations
105
+ 'Operation',
106
+ 'AddMark',
107
+ 'AddCircle',
108
+ 'SubstituteCircle',
109
+ 'AddX',
110
+ 'RemoveCircle',
111
+ 'RemoveLayer',
112
+ 'PrintLayers',
113
+ 'Counter',
114
+ 'CountFiles',
115
+ 'CountHoles',
116
+ 'Aligner',
117
+
118
+ # Manager
119
+ 'IterationManager',
120
+ 'iteration_manager', # Alias legacy
121
+
122
+ # Utils
123
+ 'BackupManager',
124
+ 'count_holes',
125
+ 'mult_campana',
126
+ 'find_all_circles',
127
+ 'find_circle_by_radius',
128
+
129
+ # Checking
130
+ 'find_spec_holes',
131
+ 'find_circle_centers',
132
+ 'find_longer_entity',
133
+ 'print_document_layers',
134
+ 'print_entities',
135
+
136
+ # DEPRECATED (manteinence to backward compatibility)
137
+ 'Conc',
138
+ 'FixSeq',
139
+ ]
140
+
141
+
142
+
143
+ # ========== DEPRECATION WARNINGS ==========
144
+
145
+ def __getattr__(name):
146
+ """
147
+ Manages import of deprecated functions with warning.
148
+ """
149
+ import warnings
150
+
151
+ deprecated = {
152
+ 'select_files': 'Use file_pattern in process_folder() instead',
153
+ 'iter_on_a_folder': 'Use Operation.process_folder() or IterationManager',
154
+ }
155
+
156
+ if name in deprecated:
157
+ warnings.warn(
158
+ f"{name} is deprecated. {deprecated[name]}",
159
+ DeprecationWarning,
160
+ stacklevel=2
161
+ )
162
+ # Per ora solleva errore (funzioni rimosse)
163
+ raise AttributeError(f"Deprecated removed function: {name}")
164
+
165
+ raise AttributeError(f"module 'snapmark' has no attribute '{name}'")
File without changes
@@ -0,0 +1,183 @@
1
+ import sys
2
+ import os
3
+ import ezdxf
4
+ from snapmark.mark_algorithm.mark_algorithm import *
5
+ from snapmark.entities.add_entities import *
6
+
7
+
8
+ # Print the names of the layers
9
+ def print_layers(doc):
10
+ # print("Layers:")
11
+ for layer in doc.layers:
12
+ print(layer.dxf.name)
13
+ return print(layer.dxf.name)
14
+
15
+ def find_spec_holes(doc, diametro_minimo=0, diametro_massimo=float('inf')):
16
+ """
17
+ Searches for specific holes in a document based on diameter range.
18
+
19
+ Args:
20
+ doc: The document containing the entities to search.
21
+ diametro_minimo: Minimum diameter of the holes to find (default: 0).
22
+ diametro_massimo: Maximum diameter of the holes to find (default: infinity).
23
+
24
+ Returns:
25
+ A list of circular entities that match the specified diameter range.
26
+ """
27
+ holes = [] # List to store circular entities
28
+
29
+ msp = doc.modelspace() # Access the model space of the drawing
30
+
31
+ # Iterate through all entities in the model space
32
+ for entity in msp.query('CIRCLE'): # Filter only entities of type circle
33
+ diameter = entity.dxf.radius * 2 # Calculate the diameter of the circle
34
+ if diametro_minimo <= diameter <= diametro_massimo:
35
+ holes.append(entity)
36
+ # Add the circular entity to the list if it falls within the diameter range
37
+
38
+ return holes
39
+
40
+ # # Cerca fori specifici
41
+ # def find_spec_holes(doc, diametro_minimo=0, diametro_massimo=float('inf')):
42
+ # holes = [] # Lista per memorizzare le entità circolari
43
+
44
+ # msp = doc.modelspace() # Accedi al modello spaziale del disegno
45
+
46
+ # # Itera attraverso tutte le entità nel modello spaziale
47
+ # for entity in msp.query('CIRCLE'): # Filtra solo le entità di tipo cerchio
48
+ # diametro = entity.dxf.radius * 2 # Calcola il diametro del cerchio
49
+ # if diametro_minimo <= diametro <= diametro_massimo:
50
+ # holes.append(entity)
51
+ # # print(entity)# Aggiungi l'entità circolare alla lista se rientra nel range di diametri
52
+
53
+ # return holes
54
+
55
+ def find_entities(file_path, entity_type):
56
+ """Return a list of DXF entities of the given type from the specified file."""
57
+ # Load DXF file using ezdxf
58
+ doc = ezdxf.readfile(file_path)
59
+
60
+ # extract entities from model
61
+ msp = doc.modelspace()
62
+
63
+ entities = []
64
+
65
+ # Iterate through all entities of the specified type
66
+ for entity in msp.query(entity_type):
67
+ entities.append(entity)
68
+
69
+ return entities
70
+
71
+ def print_entities(msp):
72
+ for e in msp.query():
73
+ print(e)
74
+
75
+
76
+
77
+ # def find_longer_entity(entities):
78
+
79
+ # lato_piu_lungo = None
80
+ # lunghezza_lato_piu_lungo = 0
81
+ # lato_piu_lungo_is_sotto = True
82
+ # minimum_point = float('inf')
83
+ # # Itera tutte le linee nel modello
84
+ # for entity in entities:
85
+ # minimum_point = min(entity.dxf.start.y, entity.dxf.end.y, minimum_point)
86
+ # # Calcola la lunghezza della linea utilizzando il teorema di Pitagora
87
+ # lunghezza = ((entity.dxf.start.x - entity.dxf.end.x) ** 2 + (entity.dxf.start.y - entity.dxf.end.y) ** 2) ** 0.5
88
+ # # Se la lunghezza della linea è maggiore della lunghezza massima finora trovata, aggiornala
89
+ # if lunghezza > lunghezza_lato_piu_lungo:
90
+ # lunghezza_lato_piu_lungo = lunghezza
91
+ # lato_piu_lungo = entity
92
+
93
+ # # Controllare che il lato più lungo sia una linea perimetrale
94
+ # if min(lato_piu_lungo.dxf.start.y, lato_piu_lungo.dxf.end.y) > minimum_point:
95
+ # lato_piu_lungo_is_sotto = False
96
+
97
+ # return lato_piu_lungo, lato_piu_lungo_is_sotto
98
+
99
+
100
+ def find_longer_entity(entities):
101
+ """
102
+ Finds the longest entity among the given entities and checks if it is below a certain minimum point.
103
+
104
+ Args:
105
+ entities: A list of entities to evaluate.
106
+
107
+ Returns:
108
+ A tuple containing the longest entity and a boolean indicating if it is below the minimum point.
109
+ """
110
+
111
+ longest_side = None
112
+ longest_side_length = 0
113
+ longest_side_is_below = True
114
+ minimum_point = float('inf')
115
+
116
+ # Iterate through all the lines in the model
117
+ for entity in entities:
118
+ minimum_point = min(entity.dxf.start.y, entity.dxf.end.y, minimum_point)
119
+ # Calculate the length of the line using the Pythagorean theorem
120
+ length = ((entity.dxf.start.x - entity.dxf.end.x) ** 2 + (entity.dxf.start.y - entity.dxf.end.y) ** 2) ** 0.5
121
+ # If the length of the line is greater than the maximum length found so far, update it
122
+ if length > longest_side_length:
123
+ longest_side_length = length
124
+ longest_side = entity
125
+
126
+ # Check if the longest side is a perimeter line
127
+ if min(longest_side.dxf.start.y, longest_side.dxf.end.y) > minimum_point:
128
+ longest_side_is_below = False
129
+
130
+ return longest_side, longest_side_is_below
131
+
132
+
133
+
134
+ def find_circle_centers(holes_list):
135
+ """
136
+ Finds the centers of circles from a list of holes.
137
+
138
+ Args:
139
+ holes_list: A list of circular entities representing holes.
140
+
141
+ Returns:
142
+ A list of tuples containing the (x, y) coordinates of the circle centers.
143
+ """
144
+ centers = [] # List to store the centers of the circles
145
+
146
+ # Iterate through the circular entities in the holes_list
147
+ for circle in holes_list: # Use the list passed as an argument
148
+ center_x = circle.dxf.center.x # Extract the x coordinate of the circle's center
149
+ center_y = circle.dxf.center.y # Extract the y coordinate of the circle's center
150
+ centers.append((center_x, center_y)) # Add the x and y coordinates of the center to the list
151
+
152
+ return centers
153
+
154
+
155
+ def find_circle_centers_2(doc):
156
+ """
157
+ Searches for circles in a document and detects their centers.
158
+
159
+ Args:
160
+ doc: The document containing the entities to search.
161
+
162
+ Returns:
163
+ A list of tuples containing the (x, y) coordinates of the circle centers.
164
+ """
165
+ centers = [] # List to store the centers of the circles
166
+
167
+ msp = doc.modelspace() # Access the model space of the drawing
168
+
169
+ # Iterate through all entities in the model space
170
+ for circle in msp.query('CIRCLE'): # Filter only entities of type circle
171
+ center_x = circle.dxf.center.x # Extract the x coordinate of the circle's center
172
+ center_y = circle.dxf.center.y # Extract the y coordinate of the circle's center
173
+ centers.append((center_x, center_y)) # Add the x and y coordinates of the center to the list
174
+
175
+ return centers
176
+
177
+
178
+ def change_layer(entities, new_layer):
179
+ for entity in entities:
180
+ entity.set_dxf_attrib('layer', new_layer)
181
+
182
+
183
+
snapmark/core.py ADDED
@@ -0,0 +1,154 @@
1
+ """
2
+ core.py - Core components of SnapMark.
3
+
4
+ Contains IterationManager for multiple batch operations.
5
+ """
6
+ import os
7
+ import ezdxf
8
+ from pathlib import Path
9
+ from snapmark.utils.helpers import find_dxf_files
10
+
11
+ try:
12
+ from .utils.backup_manager import BackupManager
13
+ BACKUP_AVAILABLE = True
14
+ except ImportError:
15
+ BACKUP_AVAILABLE = False
16
+
17
+
18
+ class IterationManager:
19
+ """
20
+ Manages applying multiple operations in sequence on DXF files.
21
+
22
+ Example:
23
+ >>> manager = IterationManager("folder_path")
24
+ >>> manager.add_operation(AddMark(...), CountHoles(...))
25
+ >>> manager.execute()
26
+ """
27
+
28
+ def __init__(self, folder_path, use_backup_system=True):
29
+ """
30
+ Initializes the IterationManager with the specified folder path and backup option.
31
+
32
+ Args:
33
+ folder_path (str): The path of the folder containing the DXF files.
34
+ use_backup_system (bool): If True, uses .bak for backups (recommended).
35
+ """
36
+
37
+ self.folder_path = folder_path
38
+ self.operation_list = []
39
+ self.use_backup_system = use_backup_system and BACKUP_AVAILABLE
40
+
41
+ if use_backup_system and not BACKUP_AVAILABLE:
42
+ print("⚠ BackupManager not available")
43
+
44
+ def add_operation(self, *operations):
45
+ """Adds operations to the pipeline."""
46
+ for op in operations:
47
+ self.operation_list.append(op)
48
+
49
+ def execute(self, file_pattern="*.dxf", recursive=False):
50
+ """
51
+ Executes all operations on the files in the specified folder.
52
+
53
+ Args:
54
+ file_pattern (str): Pattern to filter files (e.g., "F*.dxf").
55
+ recursive (bool): If True, includes subfolders.
56
+
57
+ Returns:
58
+ dict: Statistics containing {'processed': int, 'modified': int, 'errors': list}.
59
+ """
60
+
61
+ if not self.operation_list:
62
+ print("⚠ No operations added")
63
+ return {'processed': 0, 'modified': 0, 'errors': []}
64
+
65
+ dxf_files = find_dxf_files(self.folder_path, recursive)
66
+
67
+ if self.use_backup_system:
68
+ print("🔧 Backup mode active")
69
+
70
+ # Processa file
71
+ stats = {'processed': 0, 'modified': 0, 'errors': []}
72
+
73
+ for file_path in dxf_files:
74
+ try:
75
+ modified = self._process_single_file(str(file_path))
76
+ stats['processed'] += 1
77
+ if modified:
78
+ stats['modified'] += 1
79
+ except Exception as e:
80
+ error_msg = f"Error on {file_path.name}: {str(e)}"
81
+ stats['errors'].append(error_msg)
82
+ print(f"❌ {error_msg}")
83
+
84
+ # Final messages
85
+ self._final_messages()
86
+
87
+ # Report
88
+ print(f"\n✓ Processed: {stats['processed']}")
89
+ print(f"✓ Modified: {stats['modified']}")
90
+ if stats['errors']:
91
+ print(f"❌ Errors: {len(stats['errors'])}")
92
+
93
+ return stats
94
+
95
+ def _process_single_file(self, file_path: str) -> bool:
96
+ """Applies operations to a single file."""
97
+
98
+ # Backup
99
+ if self.use_backup_system:
100
+ BackupManager.ensure_original(file_path)
101
+
102
+ # Extract folder and filename
103
+ folder = os.path.dirname(file_path)
104
+ file_name = os.path.basename(file_path)
105
+
106
+ # Open file
107
+ doc = ezdxf.readfile(file_path)
108
+
109
+ # Apply operations
110
+ should_save = False
111
+ for operation in self.operation_list:
112
+ temp_should_save = operation.execute(doc, folder, file_name)
113
+ should_save = should_save or temp_should_save
114
+ operation.message(file_name)
115
+
116
+ # Save if necessary
117
+ if should_save:
118
+ doc.saveas(file_path)
119
+ return True
120
+
121
+ return False
122
+
123
+ def _final_messages(self):
124
+ """Prints final messages (e.g., from Counter)."""
125
+
126
+ try:
127
+ from .operations.counter import Counter
128
+ for operation in self.operation_list:
129
+ if isinstance(operation, Counter):
130
+ operation.count_message()
131
+ except ImportError:
132
+ pass
133
+
134
+ def file_selection_logic(self, filter_files=None):
135
+ """DEPRECATED: Use execute() instead."""
136
+ import warnings
137
+ warnings.warn(
138
+ "file_selection_logic() is deprecated. Use execute().",
139
+ DeprecationWarning,
140
+ stacklevel=2
141
+ )
142
+
143
+ pattern = "*.dxf"
144
+ if filter_files:
145
+ if isinstance(filter_files, str):
146
+ pattern = filter_files
147
+ elif isinstance(filter_files, list) and filter_files:
148
+ pattern = filter_files[0]
149
+
150
+ return self.execute(file_pattern=pattern, recursive=False)
151
+
152
+
153
+ # Alias for backward compatibility with old scripts
154
+ iteration_manager = IterationManager
File without changes
@@ -0,0 +1,120 @@
1
+
2
+
3
+ def add_circle(doc, hole_list, radius, layer='0'):
4
+ """Adds circles at specified positions in the document."""
5
+ msp = doc.modelspace() # Access the model space of the drawing
6
+ for center_x, center_y in hole_list:
7
+ center = (center_x, center_y)
8
+ # print(center_x, center_y)
9
+ msp.add_circle(center=center, radius=radius, dxfattribs={'layer': layer})
10
+
11
+
12
+ def add_circle_with_handle(doc, center_x, center_y, radius=10, layer='0', handle=68):
13
+ """Adds a circle at a specified position with a specific handle."""
14
+ msp = doc.modelspace() # Access the model space of the drawing
15
+ center = center_x, center_y
16
+
17
+ # Create a new circle with the specified handle
18
+ circle = msp.add_circle(center=center, radius=radius, dxfattribs={'layer': layer})
19
+ circle.dxf.handle = handle # Set the specified handle
20
+
21
+ return circle
22
+
23
+
24
+ def add_x(doc, hole_list, x_size=8, layer='0'):
25
+ """Adds an 'X' shape at specified positions in the document."""
26
+ msp = doc.modelspace()
27
+ for center_x, center_y in hole_list:
28
+ # Calculate the coordinates for the 'x'
29
+ x1 = center_x - (x_size / 1.4141) /2
30
+ y1 = center_y - (x_size / 1.4141) /2
31
+ x2 = center_x + (x_size / 1.4141) /2
32
+ y2 = center_y + (x_size / 1.4141) /2
33
+
34
+ # Add diagonal lines to form an 'x'
35
+ msp.add_line(start=(x1, y1), end=(x2, y2), dxfattribs={'layer': layer})
36
+ msp.add_line(start=(x1, y2), end=(x2, y1), dxfattribs={'layer': layer})
37
+
38
+
39
+
40
+ def add_numbers_to_layer(doc, sequence, layer = '0'):
41
+ """Adds a sequence of lines to a specified layer in the document."""
42
+ msp = doc.modelspace()
43
+
44
+ for scaled_segments, position in sequence.sequence:
45
+
46
+ # Add lines based on the segments at the scaled position
47
+ scaled_position = position # The position has already been scaled
48
+ for i in range(len(scaled_segments) - 1):
49
+ start_point = (
50
+ scaled_segments[i][0] + scaled_position[0],
51
+ scaled_segments[i][1] + scaled_position[1]
52
+ )
53
+ end_point = (
54
+ scaled_segments[i + 1][0] + scaled_position[0],
55
+ scaled_segments[i + 1][1] + scaled_position[1]
56
+ )
57
+
58
+ msp.add_line(start=start_point, end=end_point, dxfattribs={'layer': layer})
59
+
60
+
61
+
62
+ def delete_circle(doc, hole_list):
63
+ """Deletes circles from the model based on the provided list."""
64
+ msp = doc.modelspace()
65
+
66
+ for hole in hole_list:
67
+ msp.delete_entity(hole)
68
+
69
+
70
+ def delete_layer(doc, layer_name):
71
+ """Deletes a specific layer and all entities associated with."""
72
+ msp = doc.modelspace()
73
+
74
+ # Find all entities belonging to the specified layer
75
+ entities_to_remove = [entity for entity in msp.query('*[layer=="{}"]'.format(layer_name))]
76
+
77
+ # Delete all entities associated with the layer
78
+ for entity in entities_to_remove:
79
+ msp.delete_entity(entity)
80
+
81
+ # Delete the layer
82
+ doc.layers.remove(layer_name)
83
+
84
+
85
+
86
+ def copy_entities_but_2(source_msp, dest_msp, holes_to_exclude=[]):
87
+ """Copies entities from source model space to target, excluding specified entities."""
88
+
89
+ for entity in source_msp.query('*'):
90
+ if entity not in holes_to_exclude:
91
+ dest_msp.add_entity(entity)
92
+
93
+
94
+
95
+ def copy_entities_but(source_msp, target_msp, entities_to_exclude=[]):
96
+ """Copies entities from source model space to target, excluding specified entities."""
97
+ for entity in source_msp.query('*'):
98
+ exclude = False
99
+ for exclude_entity in entities_to_exclude:
100
+ if entity.dxf.handle == exclude_entity.dxf.handle:
101
+ exclude = True
102
+ break
103
+ if not exclude:
104
+ target_msp.add_entity(entity.clone())
105
+
106
+ def remove_entities(msp, entities_to_remove):
107
+ """Removes specified entities from the model space."""
108
+ # Create a new list of entities excluding those to remove
109
+ new_entities = [entity for entity in msp.query('*') if entity not in entities_to_remove]
110
+
111
+ # Delete all entities from the model space
112
+ msp.delete_all_entities()
113
+
114
+ # Add the new entities to the model space
115
+ for entity in new_entities:
116
+ msp.add_entity(entity.clone())
117
+
118
+
119
+
120
+
File without changes