pyaltiumlib 0.2__tar.gz → 0.4__tar.gz
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.
- {pyaltiumlib-0.2/src/pyaltiumlib.egg-info → pyaltiumlib-0.4}/PKG-INFO +1 -1
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib/__init__.py +1 -6
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib/base.py +42 -44
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib/datatypes/__init__.py +3 -2
- pyaltiumlib-0.4/src/pyaltiumlib/datatypes/binaryreader.py +193 -0
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib/datatypes/coordinate.py +130 -50
- pyaltiumlib-0.4/src/pyaltiumlib/datatypes/mapping.py +39 -0
- pyaltiumlib-0.4/src/pyaltiumlib/datatypes/parametercollection.py +270 -0
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib/datatypes/pcbmapping.py +7 -7
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib/datatypes/schematicmapping.py +8 -8
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib/libcomponent.py +41 -43
- pyaltiumlib-0.4/src/pyaltiumlib/pcblib/footprint.py +103 -0
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib/pcblib/lib.py +36 -19
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib/pcblib/records/PCBArc.py +25 -33
- pyaltiumlib-0.4/src/pyaltiumlib/pcblib/records/PCBComponentBody.py +24 -0
- pyaltiumlib-0.4/src/pyaltiumlib/pcblib/records/PCBFill.py +97 -0
- pyaltiumlib-0.4/src/pyaltiumlib/pcblib/records/PCBPad.py +313 -0
- pyaltiumlib-0.4/src/pyaltiumlib/pcblib/records/PCBRegion.py +118 -0
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib/pcblib/records/PCBString.py +23 -70
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib/pcblib/records/PCBTrack.py +24 -53
- pyaltiumlib-0.4/src/pyaltiumlib/pcblib/records/PCBVia.py +143 -0
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib/pcblib/records/__init__.py +6 -2
- pyaltiumlib-0.4/src/pyaltiumlib/pcblib/records/base.py +191 -0
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib/schlib/lib.py +16 -7
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib/schlib/records/SchArc.py +26 -30
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib/schlib/records/SchBezier.py +34 -40
- pyaltiumlib-0.4/src/pyaltiumlib/schlib/records/SchComponent.py +45 -0
- pyaltiumlib-0.4/src/pyaltiumlib/schlib/records/SchDesignator.py +78 -0
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib/schlib/records/SchEllipse.py +25 -28
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib/schlib/records/SchEllipticalArc.py +28 -32
- pyaltiumlib-0.4/src/pyaltiumlib/schlib/records/SchImplementationList.py +20 -0
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib/schlib/records/SchLabel.py +26 -21
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib/schlib/records/SchLine.py +27 -19
- pyaltiumlib-0.4/src/pyaltiumlib/schlib/records/SchParameter.py +27 -0
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib/schlib/records/SchPin.py +31 -25
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib/schlib/records/SchPolygon.py +19 -29
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib/schlib/records/SchPolyline.py +25 -34
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib/schlib/records/SchRectangle.py +26 -31
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib/schlib/records/SchRoundRectangle.py +26 -34
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib/schlib/records/__init__.py +1 -1
- pyaltiumlib-0.4/src/pyaltiumlib/schlib/records/base.py +160 -0
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib/schlib/symbol.py +48 -21
- {pyaltiumlib-0.2 → pyaltiumlib-0.4/src/pyaltiumlib.egg-info}/PKG-INFO +1 -1
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib.egg-info/SOURCES.txt +3 -2
- pyaltiumlib-0.2/src/pyaltiumlib/datatypes/binaryreader.py +0 -129
- pyaltiumlib-0.2/src/pyaltiumlib/datatypes/mapping.py +0 -13
- pyaltiumlib-0.2/src/pyaltiumlib/datatypes/parametercollection.py +0 -157
- pyaltiumlib-0.2/src/pyaltiumlib/datatypes/schematicpin.py +0 -55
- pyaltiumlib-0.2/src/pyaltiumlib/datatypes/svg_utils.py +0 -39
- pyaltiumlib-0.2/src/pyaltiumlib/pcblib/footprint.py +0 -93
- pyaltiumlib-0.2/src/pyaltiumlib/pcblib/records/PCBComponentBody.py +0 -25
- pyaltiumlib-0.2/src/pyaltiumlib/pcblib/records/PCBPad.py +0 -322
- pyaltiumlib-0.2/src/pyaltiumlib/pcblib/records/base.py +0 -62
- pyaltiumlib-0.2/src/pyaltiumlib/schlib/records/SchComponent.py +0 -83
- pyaltiumlib-0.2/src/pyaltiumlib/schlib/records/SchDesignator.py +0 -42
- pyaltiumlib-0.2/src/pyaltiumlib/schlib/records/SchImplementationList.py +0 -42
- pyaltiumlib-0.2/src/pyaltiumlib/schlib/records/SchParameter.py +0 -32
- pyaltiumlib-0.2/src/pyaltiumlib/schlib/records/base.py +0 -137
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/LICENSE.txt +0 -0
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/README.md +0 -0
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/setup.cfg +0 -0
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/setup.py +0 -0
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib/datatypes/parametercolor.py +0 -0
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib/datatypes/parameterfont.py +0 -0
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib/datatypes/pcblayerdefinition.py +0 -0
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib/pcblib/__init__.py +0 -0
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib/schlib/__init__.py +0 -0
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib.egg-info/dependency_links.txt +0 -0
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib.egg-info/requires.txt +0 -0
- {pyaltiumlib-0.2 → pyaltiumlib-0.4}/src/pyaltiumlib.egg-info/top_level.txt +0 -0
|
@@ -7,7 +7,7 @@ AUTHOR_NAME = 'Chris Hoyer'
|
|
|
7
7
|
AUTHOR_EMAIL = 'info@chrishoyer.de'
|
|
8
8
|
CYEAR = '2024-2025'
|
|
9
9
|
|
|
10
|
-
__version__ = "0.
|
|
10
|
+
__version__ = "0.4"
|
|
11
11
|
__author__ = "Chris Hoyer <info@chrishoyer.de>"
|
|
12
12
|
|
|
13
13
|
import os
|
|
@@ -51,8 +51,3 @@ def read(filepath: str) -> Union[SchLib, PcbLib]:
|
|
|
51
51
|
else:
|
|
52
52
|
logger.error(f"Invalid file type: {filepath}.")
|
|
53
53
|
raise
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from pyaltiumlib.datatypes import ParameterColor
|
|
2
2
|
|
|
3
|
+
import os
|
|
3
4
|
import olefile
|
|
4
5
|
from typing import List, Optional, Dict, Any
|
|
5
6
|
|
|
@@ -17,45 +18,44 @@ class GenericLibFile:
|
|
|
17
18
|
:param string filepath: The path to the library file
|
|
18
19
|
|
|
19
20
|
:raises FileNotFoundError: If file is not a supported file.
|
|
20
|
-
"""
|
|
21
|
-
|
|
22
|
-
LibType = None
|
|
23
|
-
"""
|
|
24
|
-
`string` that specifies the type of the library.
|
|
25
|
-
"""
|
|
26
|
-
|
|
27
|
-
LibHeader = ''
|
|
28
|
-
"""`string` that contains the file path to the library.
|
|
29
|
-
"""
|
|
30
|
-
|
|
31
|
-
FilePath = ''
|
|
32
|
-
"""
|
|
33
|
-
`string` that stores the header information of the library.
|
|
34
|
-
"""
|
|
35
|
-
|
|
36
|
-
ComponentCount = 0
|
|
37
|
-
"""
|
|
38
|
-
`int` with total number of components in the library.
|
|
39
|
-
"""
|
|
40
|
-
|
|
41
|
-
Parts = []
|
|
42
|
-
"""
|
|
43
|
-
`List[any]` is a collection of components derived from :class:`pyaltiumlib.libcomponent.LibComponent` in their specific class
|
|
44
|
-
contained in the library.
|
|
45
|
-
"""
|
|
21
|
+
"""
|
|
46
22
|
|
|
47
23
|
def __init__(self, filepath: str):
|
|
48
|
-
|
|
49
|
-
Initialize a GenericLibFile object.
|
|
50
|
-
"""
|
|
24
|
+
|
|
51
25
|
if not olefile.isOleFile( filepath ):
|
|
52
|
-
logger.error(f"{filepath} is not a supported file.")
|
|
26
|
+
logger.error(f"'{filepath}' is not a supported file.")
|
|
53
27
|
raise
|
|
54
28
|
|
|
55
|
-
self.LibType = type(self)
|
|
29
|
+
self.LibType = type(self)
|
|
30
|
+
"""
|
|
31
|
+
`string` that specifies the type of the library.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
self.LibHeader = ""
|
|
35
|
+
"""
|
|
36
|
+
`string` that stores the header information of the library.
|
|
37
|
+
"""
|
|
38
|
+
|
|
56
39
|
self.FilePath = filepath
|
|
40
|
+
"""
|
|
41
|
+
`string` that contains the file path to the library.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
self.FileName = os.path.basename(filepath)
|
|
45
|
+
"""
|
|
46
|
+
`string` that contains the name of the library.
|
|
47
|
+
"""
|
|
48
|
+
|
|
57
49
|
self.ComponentCount = 0
|
|
50
|
+
"""
|
|
51
|
+
`int` with total number of components in the library.
|
|
52
|
+
"""
|
|
53
|
+
|
|
58
54
|
self.Parts = []
|
|
55
|
+
"""
|
|
56
|
+
`List[any]` is a collection of components derived from :class:`pyaltiumlib.libcomponent.LibComponent` in their specific class
|
|
57
|
+
contained in the library.
|
|
58
|
+
"""
|
|
59
59
|
|
|
60
60
|
self._olefile = None
|
|
61
61
|
self._olefile_open = False
|
|
@@ -66,19 +66,19 @@ class GenericLibFile:
|
|
|
66
66
|
self._BackgroundColor = ParameterColor.from_hex("#6D6A69")
|
|
67
67
|
|
|
68
68
|
|
|
69
|
-
def __repr__(self) ->
|
|
69
|
+
def __repr__(self) -> str:
|
|
70
70
|
"""
|
|
71
|
-
Converts public attributes of the high level file to a
|
|
71
|
+
Converts public attributes of the high level file to a string.
|
|
72
72
|
|
|
73
|
-
:return: A
|
|
74
|
-
:rtype:
|
|
73
|
+
:return: A string representation of the content of the object
|
|
74
|
+
:rtype: str
|
|
75
75
|
"""
|
|
76
|
-
return self.read_meta()
|
|
76
|
+
return str( self.read_meta() )
|
|
77
77
|
|
|
78
78
|
# =============================================================================
|
|
79
79
|
# External access
|
|
80
80
|
# =============================================================================
|
|
81
|
-
|
|
81
|
+
|
|
82
82
|
def read_meta(self) -> Dict:
|
|
83
83
|
"""
|
|
84
84
|
Converts public attributes of the high level file to a dictionary.
|
|
@@ -92,8 +92,7 @@ class GenericLibFile:
|
|
|
92
92
|
if not key.startswith("_")
|
|
93
93
|
}
|
|
94
94
|
return public_attributes
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
|
|
97
96
|
def list_parts(self) -> List[str]:
|
|
98
97
|
"""
|
|
99
98
|
List the names of all parts in the library.
|
|
@@ -102,8 +101,7 @@ class GenericLibFile:
|
|
|
102
101
|
:rtype: List[str]
|
|
103
102
|
"""
|
|
104
103
|
return [x.Name for x in self.Parts]
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
|
|
107
105
|
def get_part(self, name: str) -> Optional[Any]:
|
|
108
106
|
"""
|
|
109
107
|
Get a part of the library by its name.
|
|
@@ -128,13 +126,13 @@ class GenericLibFile:
|
|
|
128
126
|
Open the library file for reading.
|
|
129
127
|
"""
|
|
130
128
|
if self._olefile_open:
|
|
131
|
-
raise ValueError(f"file: {
|
|
129
|
+
raise ValueError(f"file: '{self.FilePath}'. Already open!")
|
|
132
130
|
|
|
133
131
|
try:
|
|
134
132
|
self._olefile = olefile.OleFileIO( self.FilePath )
|
|
135
133
|
self._olefile_open = True
|
|
136
134
|
except Exception as e:
|
|
137
|
-
logger.error(f"Failed to open file: {self.FilePath}. Error: {e}")
|
|
135
|
+
logger.error(f"Failed to open file: '{self.FilePath}'. Error: {e}")
|
|
138
136
|
raise
|
|
139
137
|
|
|
140
138
|
def _OpenStream(self, container: str, stream: str) -> Any:
|
|
@@ -149,7 +147,7 @@ class GenericLibFile:
|
|
|
149
147
|
Any: The opened stream.
|
|
150
148
|
"""
|
|
151
149
|
if not self._olefile_open:
|
|
152
|
-
logger.error(f"file: {
|
|
150
|
+
logger.error(f"file: '{self.FilePath}'. File not opened!")
|
|
153
151
|
raise
|
|
154
152
|
|
|
155
153
|
if not container == "":
|
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
from .parametercolor import ParameterColor
|
|
7
7
|
from .parameterfont import ParameterFont
|
|
8
8
|
from .binaryreader import BinaryReader
|
|
9
9
|
from .coordinate import Coordinate, CoordinatePoint
|
|
10
|
+
from .parametercollection import ParameterCollection
|
|
10
11
|
|
|
11
12
|
# Schematic related
|
|
12
|
-
from .
|
|
13
|
+
from .parametercollection import SchematicPin
|
|
13
14
|
from .schematicmapping import (
|
|
14
15
|
SchematicLineWidth, SchematicLineStyle, SchematicLineShape,
|
|
15
16
|
SchematicPinSymbol, SchematicPinElectricalType, SchematicTextOrientation,
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
from pyaltiumlib.datatypes.coordinate import Coordinate, CoordinatePoint
|
|
2
|
+
|
|
3
|
+
# Configure logging
|
|
4
|
+
import logging
|
|
5
|
+
logger = logging.getLogger(__name__)
|
|
6
|
+
|
|
7
|
+
class BinaryReader:
|
|
8
|
+
"""
|
|
9
|
+
A utility class for reading binary data with structured methods.
|
|
10
|
+
|
|
11
|
+
:param bytes data: The binary data to be read.
|
|
12
|
+
"""
|
|
13
|
+
def __init__(self, data):
|
|
14
|
+
self.data = data
|
|
15
|
+
self.offset = 0
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
def from_stream(cls, stream, size_length=4):
|
|
19
|
+
"""
|
|
20
|
+
Reads a binary block from a stream and initializes a BinaryReader.
|
|
21
|
+
|
|
22
|
+
:param file-like object stream: The binary stream to read from.
|
|
23
|
+
:param int optional size_length: Number of bytes indicating the block size.
|
|
24
|
+
:return: An instance of BinaryReader.
|
|
25
|
+
:rtype: BinaryReader
|
|
26
|
+
"""
|
|
27
|
+
length = int.from_bytes( stream.read( size_length ), "little" )
|
|
28
|
+
data = stream.read( length )
|
|
29
|
+
|
|
30
|
+
if len(data) != length:
|
|
31
|
+
logger.warning("Stream does not match the declared block length.")
|
|
32
|
+
|
|
33
|
+
return cls( data )
|
|
34
|
+
|
|
35
|
+
def has_content(self):
|
|
36
|
+
"""
|
|
37
|
+
Checks if there is remaining data to read.
|
|
38
|
+
|
|
39
|
+
:return: True if data exists, False otherwise.
|
|
40
|
+
:rtype: bool
|
|
41
|
+
"""
|
|
42
|
+
return not len(self.data) == 0
|
|
43
|
+
|
|
44
|
+
def length(self):
|
|
45
|
+
"""
|
|
46
|
+
Returns the length of the binary data.
|
|
47
|
+
|
|
48
|
+
:return: The length of the data.
|
|
49
|
+
:rtype: int
|
|
50
|
+
"""
|
|
51
|
+
return len(self.data)
|
|
52
|
+
|
|
53
|
+
def read(self, length):
|
|
54
|
+
"""
|
|
55
|
+
Reads a specified number of bytes from the binary data.
|
|
56
|
+
|
|
57
|
+
:param int length: The number of bytes to read.
|
|
58
|
+
:return: The read bytes.
|
|
59
|
+
:rtype: bytes
|
|
60
|
+
"""
|
|
61
|
+
if self.offset + length > len(self.data):
|
|
62
|
+
logger.warning("Not enough data to read the requested length.")
|
|
63
|
+
|
|
64
|
+
result = self.data[self.offset:self.offset + length]
|
|
65
|
+
self.offset += length
|
|
66
|
+
return result
|
|
67
|
+
|
|
68
|
+
def read_byte(self):
|
|
69
|
+
"""
|
|
70
|
+
Reads a single byte from the binary data.
|
|
71
|
+
|
|
72
|
+
:return: The read byte.
|
|
73
|
+
:rtype: bytes
|
|
74
|
+
"""
|
|
75
|
+
return self.read(1)
|
|
76
|
+
|
|
77
|
+
def read_int8(self, signed=False):
|
|
78
|
+
"""
|
|
79
|
+
Reads an 8-bit integer.
|
|
80
|
+
|
|
81
|
+
:param bool optional signed: Whether to interpret the value as signed.
|
|
82
|
+
:return: The integer value.
|
|
83
|
+
:rtype: int
|
|
84
|
+
"""
|
|
85
|
+
return int.from_bytes(self.read_byte(), signed=signed)
|
|
86
|
+
|
|
87
|
+
def read_int16(self, signed=False):
|
|
88
|
+
"""
|
|
89
|
+
Reads a 16-bit integer.
|
|
90
|
+
|
|
91
|
+
:param bool optional signed: Whether to interpret the value as signed.
|
|
92
|
+
:return: The integer value.
|
|
93
|
+
:rtype: int
|
|
94
|
+
"""
|
|
95
|
+
return int.from_bytes(self.read(2), byteorder="little", signed=signed)
|
|
96
|
+
|
|
97
|
+
def read_int32(self, signed=False):
|
|
98
|
+
"""
|
|
99
|
+
Reads a 32-bit integer.
|
|
100
|
+
|
|
101
|
+
:param bool optional signed: Whether to interpret the value as signed.
|
|
102
|
+
:return: The integer value.
|
|
103
|
+
:rtype: int
|
|
104
|
+
"""
|
|
105
|
+
return int.from_bytes(self.read(4), byteorder="little", signed=signed)
|
|
106
|
+
|
|
107
|
+
def read_double(self):
|
|
108
|
+
"""
|
|
109
|
+
Reads an IEEE 754 double-precision floating point value.
|
|
110
|
+
|
|
111
|
+
:return: The double value.
|
|
112
|
+
:rtype: float
|
|
113
|
+
"""
|
|
114
|
+
value = int.from_bytes(self.read(8), byteorder='little', signed=False)
|
|
115
|
+
sign = (value >> 63) & 0x1
|
|
116
|
+
exponent = (value >> 52) & 0x7FF
|
|
117
|
+
mantissa = value & ((1 << 52) - 1)
|
|
118
|
+
|
|
119
|
+
if exponent == 0x7FF:
|
|
120
|
+
return float('inf') if mantissa == 0 else float('nan')
|
|
121
|
+
|
|
122
|
+
if exponent == 0:
|
|
123
|
+
result = (mantissa / (1 << 52)) * (2 ** (-1022))
|
|
124
|
+
else:
|
|
125
|
+
result = (1 + (mantissa / (1 << 52))) * (2 ** (exponent - 1023))
|
|
126
|
+
|
|
127
|
+
return -result if sign == 1 else result
|
|
128
|
+
|
|
129
|
+
def read_string_block(self, size_string=1):
|
|
130
|
+
"""
|
|
131
|
+
Reads a length-prefixed string.
|
|
132
|
+
|
|
133
|
+
:param int optional size_string: Number of bytes specifying the string length.
|
|
134
|
+
|
|
135
|
+
:return: The decoded string.
|
|
136
|
+
:rtype: str
|
|
137
|
+
"""
|
|
138
|
+
length_string = int.from_bytes(self.read(size_string), "little")
|
|
139
|
+
string_data = self.read( length_string )
|
|
140
|
+
|
|
141
|
+
if len(string_data) != length_string:
|
|
142
|
+
logger.warning("String does not match the declared string length.")
|
|
143
|
+
|
|
144
|
+
return string_data.decode('windows-1252')
|
|
145
|
+
|
|
146
|
+
def read_bin_coord(self, scaley=-1.0, double=False, double_scaling = 1/10000.0):
|
|
147
|
+
"""
|
|
148
|
+
Reads a binary coordinate pair and returns a CoordinatePoint.
|
|
149
|
+
|
|
150
|
+
:param float scaley: Scaling factor for the Y coordinate.
|
|
151
|
+
:param bool optional double: Read coordinates as double
|
|
152
|
+
:param float optional float: Scaling of double values
|
|
153
|
+
|
|
154
|
+
:return: A CoordinatePoint object.
|
|
155
|
+
:rtype: CoordinatePoint
|
|
156
|
+
"""
|
|
157
|
+
if double:
|
|
158
|
+
x = (self.read_double() * double_scaling)
|
|
159
|
+
y = (self.read_double() * double_scaling) * scaley
|
|
160
|
+
return CoordinatePoint( Coordinate(x), Coordinate(y))
|
|
161
|
+
|
|
162
|
+
else:
|
|
163
|
+
x = self.read(4)
|
|
164
|
+
y = self.read(4)
|
|
165
|
+
return CoordinatePoint( Coordinate.parse_bin(x), Coordinate.parse_bin(y, scale=scaley))
|
|
166
|
+
|
|
167
|
+
def read_unicode_text(self, length=32, encoding='utf-16-le'):
|
|
168
|
+
"""
|
|
169
|
+
Reads a Unicode string of fixed length.
|
|
170
|
+
|
|
171
|
+
:param int optional length: Maximum length of the string in bytes.
|
|
172
|
+
:param str optional encoding: The encoding format.
|
|
173
|
+
|
|
174
|
+
:return: The decoded Unicode string.
|
|
175
|
+
:rtype: str
|
|
176
|
+
"""
|
|
177
|
+
pos = self.offset
|
|
178
|
+
data = []
|
|
179
|
+
while len(data) < length:
|
|
180
|
+
|
|
181
|
+
if self.offset + 2 > len(self.data):
|
|
182
|
+
logger.warning("Not enough data to read.")
|
|
183
|
+
|
|
184
|
+
unicode_char = self.read(2)
|
|
185
|
+
if unicode_char == b'\x00\x00': # Null terminator
|
|
186
|
+
break
|
|
187
|
+
data.extend(unicode_char)
|
|
188
|
+
|
|
189
|
+
# Ensure we skip the remaining bytes to read exactly `length` bytes
|
|
190
|
+
self.offset = pos + length
|
|
191
|
+
|
|
192
|
+
return bytes(data).decode(encoding)
|
|
193
|
+
|
|
@@ -1,30 +1,64 @@
|
|
|
1
1
|
import math
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
# Set up logging
|
|
4
|
+
import logging
|
|
5
|
+
logger = logging.getLogger(__name__)
|
|
6
6
|
|
|
7
7
|
class Coordinate:
|
|
8
|
+
"""
|
|
9
|
+
Represents a single coordinate value.
|
|
10
|
+
|
|
11
|
+
This class provides parsing functions for extracting coordinates from
|
|
12
|
+
different data formats, as well as mathematical operations.
|
|
13
|
+
|
|
14
|
+
:param value: The numerical value of the coordinate.
|
|
15
|
+
:type value: int or float
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
**Operations:**
|
|
19
|
+
|
|
20
|
+
Supports addition, subtraction, multiplication, and division with both
|
|
21
|
+
integers, floats, and other `Coordinate` instances. Can be compared
|
|
22
|
+
using `<`, `>`, `<=`, `>=` with both `Coordinate` instances and numerical values.
|
|
23
|
+
"""
|
|
24
|
+
|
|
8
25
|
def __init__(self, value):
|
|
9
26
|
self.value = value
|
|
10
|
-
|
|
27
|
+
|
|
11
28
|
@classmethod
|
|
12
|
-
def parse_dpx(cls, key, data, scale=1.0):
|
|
29
|
+
def parse_dpx(cls, key, data, scale=1.0):
|
|
30
|
+
"""
|
|
31
|
+
Parses a coordinate from a parameter collection data dictionary (schematic file).
|
|
32
|
+
|
|
33
|
+
:param str key: The key corresponding to the coordinate.
|
|
34
|
+
:param dict data: The dictionary containing coordinate data.
|
|
35
|
+
:param float, optional scale: Scaling factor for the parsed coordinate.
|
|
36
|
+
|
|
37
|
+
:return: A Coordinate instance with the parsed value.
|
|
38
|
+
:rtype: Coordinate
|
|
39
|
+
"""
|
|
13
40
|
num = int(data.get(key, 0))
|
|
14
41
|
frac = int(data.get(key + "_frac", 0))
|
|
15
|
-
|
|
16
42
|
coord = (num * 10.0 + frac / 10000.0)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
43
|
+
return cls(scale * coord / 10)
|
|
44
|
+
|
|
20
45
|
@classmethod
|
|
21
46
|
def parse_bin(cls, x_bytes, scale=1.0):
|
|
47
|
+
"""
|
|
48
|
+
Parses a coordinate from binary data (pcb file).
|
|
49
|
+
|
|
50
|
+
:param bytes x_bytes: Binary representation of the coordinate.
|
|
51
|
+
:param float, optional scale: Scaling factor for the parsed coordinate.
|
|
52
|
+
|
|
53
|
+
:return: A Coordinate instance with the parsed value.
|
|
54
|
+
:rtype: Coordinate
|
|
55
|
+
"""
|
|
22
56
|
x = int.from_bytes(x_bytes, byteorder="little", signed=True)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
57
|
+
return cls(scale * x / 10000.0)
|
|
58
|
+
|
|
26
59
|
def __repr__(self):
|
|
27
|
-
|
|
60
|
+
"""Returns a string representation of the coordinate."""
|
|
61
|
+
return f"{self.value}"
|
|
28
62
|
|
|
29
63
|
def __float__(self):
|
|
30
64
|
return float(self.value)
|
|
@@ -33,28 +67,31 @@ class Coordinate:
|
|
|
33
67
|
return int(self.value)
|
|
34
68
|
|
|
35
69
|
# ================== Math Functions =========================================
|
|
36
|
-
|
|
70
|
+
|
|
37
71
|
def __abs__(self):
|
|
38
|
-
return abs(
|
|
72
|
+
return abs(int(self.value))
|
|
39
73
|
|
|
40
74
|
def __truediv__(self, other):
|
|
41
75
|
if isinstance(other, (int, float)):
|
|
42
76
|
if other == 0:
|
|
43
|
-
|
|
77
|
+
logger.warning("Division by zero is not allowed.")
|
|
78
|
+
raise
|
|
44
79
|
return Coordinate(self.value / other)
|
|
80
|
+
|
|
45
81
|
elif isinstance(other, Coordinate):
|
|
46
82
|
if other.value == 0:
|
|
47
|
-
|
|
83
|
+
logger.warning("Division by zero is not allowed.")
|
|
84
|
+
raise
|
|
48
85
|
return Coordinate(self.value / other.value)
|
|
49
86
|
return NotImplemented
|
|
50
|
-
|
|
87
|
+
|
|
51
88
|
def __mul__(self, other):
|
|
52
89
|
if isinstance(other, (int, float)):
|
|
53
90
|
return Coordinate(self.value * other)
|
|
54
91
|
elif isinstance(other, Coordinate):
|
|
55
92
|
return Coordinate(self.value * other.value)
|
|
56
93
|
return NotImplemented
|
|
57
|
-
|
|
94
|
+
|
|
58
95
|
def __add__(self, other):
|
|
59
96
|
if isinstance(other, (int, float)):
|
|
60
97
|
return Coordinate(self.value + other)
|
|
@@ -80,7 +117,7 @@ class Coordinate:
|
|
|
80
117
|
|
|
81
118
|
def __rsub__(self, other):
|
|
82
119
|
return self.__sub__(other)
|
|
83
|
-
|
|
120
|
+
|
|
84
121
|
def __lt__(self, other):
|
|
85
122
|
if isinstance(other, Coordinate):
|
|
86
123
|
return self.value < other.value
|
|
@@ -100,56 +137,98 @@ class Coordinate:
|
|
|
100
137
|
|
|
101
138
|
def __ge__(self, other):
|
|
102
139
|
return self > other or self == other
|
|
103
|
-
|
|
140
|
+
|
|
141
|
+
# =============================================================================
|
|
104
142
|
# =============================================================================
|
|
105
143
|
# 2D Coordinate Point
|
|
106
144
|
# =============================================================================
|
|
107
145
|
|
|
108
|
-
class CoordinatePoint:
|
|
146
|
+
class CoordinatePoint:
|
|
147
|
+
"""
|
|
148
|
+
This class encapsulates two `Coordinate` instances for the X and Y axes.
|
|
149
|
+
|
|
150
|
+
:param int, float, or Coordinate x: The X-coordinate value.
|
|
151
|
+
:param int, float, or Coordinate y: The Y-coordinate value.
|
|
152
|
+
|
|
153
|
+
**Mathematical Operations:**
|
|
154
|
+
|
|
155
|
+
Supports addition, subtraction, multiplication, and division with
|
|
156
|
+
`CoordinatePoint` instances or numerical values.
|
|
157
|
+
"""
|
|
158
|
+
|
|
109
159
|
def __init__(self, x, y):
|
|
110
160
|
if not isinstance(x, Coordinate):
|
|
111
161
|
x = Coordinate(x)
|
|
112
162
|
if not isinstance(y, Coordinate):
|
|
113
163
|
y = Coordinate(y)
|
|
114
|
-
|
|
115
164
|
self.x = x
|
|
116
165
|
self.y = y
|
|
117
|
-
|
|
166
|
+
|
|
118
167
|
def __repr__(self):
|
|
119
|
-
|
|
120
|
-
|
|
168
|
+
"""Returns a string representation of the coordinate point."""
|
|
169
|
+
return f"({self.x};{self.y})"
|
|
170
|
+
|
|
121
171
|
def to_int(self):
|
|
122
|
-
|
|
172
|
+
"""Converts the coordinate point to integer values."""
|
|
173
|
+
return CoordinatePoint(int(self.x), int(self.y))
|
|
123
174
|
|
|
124
175
|
def to_int_tuple(self):
|
|
125
|
-
|
|
126
|
-
|
|
176
|
+
"""Returns the coordinate point as a tuple of integers."""
|
|
177
|
+
return int(self.x), int(self.y)
|
|
178
|
+
|
|
127
179
|
def expand(self, size):
|
|
180
|
+
"""
|
|
181
|
+
Expands the coordinate point by a given size.
|
|
182
|
+
|
|
183
|
+
:param size: The amount to expand by.
|
|
184
|
+
:type size: int, float, or Coordinate
|
|
185
|
+
:return: A new expanded CoordinatePoint.
|
|
186
|
+
:rtype: CoordinatePoint
|
|
187
|
+
"""
|
|
128
188
|
if isinstance(size, (int, float)):
|
|
129
189
|
return CoordinatePoint(self.x + size, self.y + size)
|
|
130
190
|
if isinstance(size, (int, Coordinate)):
|
|
131
191
|
return CoordinatePoint(self.x + size.value, self.y - size.value)
|
|
132
|
-
|
|
192
|
+
|
|
133
193
|
def rotate(self, center, angle):
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
194
|
+
"""
|
|
195
|
+
Rotates the coordinate point around a given center by an angle.
|
|
196
|
+
|
|
197
|
+
:param center: The center point for rotation.
|
|
198
|
+
:type center: CoordinatePoint
|
|
199
|
+
:param angle: The rotation angle in degrees.
|
|
200
|
+
:type angle: float
|
|
201
|
+
:return: The rotated CoordinatePoint.
|
|
202
|
+
:rtype: CoordinatePoint
|
|
203
|
+
"""
|
|
204
|
+
theta = math.radians(angle)
|
|
205
|
+
x_rel = self.x - center.x
|
|
206
|
+
y_rel = self.y - center.y
|
|
207
|
+
|
|
208
|
+
x_rot = x_rel * math.cos(theta) - y_rel * math.sin(theta)
|
|
209
|
+
y_rot = x_rel * math.sin(theta) + y_rel * math.cos(theta)
|
|
210
|
+
|
|
211
|
+
self.x = x_rot + center.x
|
|
212
|
+
self.y = y_rot + center.y
|
|
213
|
+
return self
|
|
145
214
|
|
|
146
215
|
def offset(self, offset_x, offset_y):
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
216
|
+
"""
|
|
217
|
+
Offsets the coordinate point by given values.
|
|
218
|
+
|
|
219
|
+
:param offset_x: Offset for the X coordinate.
|
|
220
|
+
:type offset_x: int or float
|
|
221
|
+
:param offset_y: Offset for the Y coordinate.
|
|
222
|
+
:type offset_y: int or float
|
|
223
|
+
:return: The offset CoordinatePoint.
|
|
224
|
+
:rtype: CoordinatePoint
|
|
225
|
+
"""
|
|
226
|
+
self.x = self.x + offset_x
|
|
227
|
+
self.y = self.y + offset_y
|
|
228
|
+
return self
|
|
229
|
+
|
|
152
230
|
def copy(self):
|
|
231
|
+
"""Returns a copy of the CoordinatePoint."""
|
|
153
232
|
return CoordinatePoint(self.x, self.y)
|
|
154
233
|
|
|
155
234
|
# ================== Math Functions =========================================
|
|
@@ -171,11 +250,13 @@ class CoordinatePoint:
|
|
|
171
250
|
def __truediv__(self, other):
|
|
172
251
|
if isinstance(other, (int, float)):
|
|
173
252
|
if other == 0:
|
|
174
|
-
|
|
253
|
+
logger.warning("Division by zero is not allowed.")
|
|
254
|
+
raise
|
|
175
255
|
return CoordinatePoint(self.x / other, self.y / other)
|
|
176
256
|
elif isinstance(other, Coordinate):
|
|
177
257
|
if other.value == 0:
|
|
178
|
-
|
|
258
|
+
logger.warning("Division by zero is not allowed.")
|
|
259
|
+
raise
|
|
179
260
|
return CoordinatePoint(self.x / other.x, self.y / other.y)
|
|
180
261
|
return NotImplemented
|
|
181
262
|
|
|
@@ -193,5 +274,4 @@ class CoordinatePoint:
|
|
|
193
274
|
return self.__add__(other)
|
|
194
275
|
|
|
195
276
|
def __rsub__(self, other):
|
|
196
|
-
return self.__sub__(other)
|
|
197
|
-
|
|
277
|
+
return self.__sub__(other)
|