From 7dcdf9872603f682209e6363659c24f5fd8ed8a2 Mon Sep 17 00:00:00 2001 From: David PAGNON Date: Mon, 25 Aug 2025 10:49:46 +0200 Subject: [PATCH 01/12] Update numpy dependency version in pyproject.toml From the tests I ran (not extensive though), py-c3d works fine with numpy >= 2.0. Allowing it would help with dependency conflicts due to many newer libraries requiring later numpy versions. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b71b1c5..5039d0f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ c3d-viewer = 'c3d.scripts.c3d_viewer:main' [tool.poetry.dependencies] python = "^3.7" -numpy = "^1" +numpy = ">=1.0.0" pyglet = {version = "^1.5.21", optional = true} [tool.poetry.dev-dependencies] From e6f3106e615da6d49b3471d98277a5e4948180d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20K=C3=BCderle?= Date: Mon, 25 Aug 2025 12:50:46 +0200 Subject: [PATCH 02/12] Drop support for old Python versions --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5039d0f..dc6c381 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ c3d-metatdata = 'c3d.scripts.c3d_metatdata:main' c3d-viewer = 'c3d.scripts.c3d_viewer:main' [tool.poetry.dependencies] -python = "^3.7" +python = "^3.9" numpy = ">=1.0.0" pyglet = {version = "^1.5.21", optional = true} From a482c00154eb7a94e03341d5aeed1757a4474015 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20K=C3=BCderle?= Date: Mon, 25 Aug 2025 13:02:58 +0200 Subject: [PATCH 03/12] Updated CI Python versions --- .github/workflows/test-and-lint.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-and-lint.yml b/.github/workflows/test-and-lint.yml index defc77c..e807633 100644 --- a/.github/workflows/test-and-lint.yml +++ b/.github/workflows/test-and-lint.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.7, 3.8, 3.9] + python-version: [3.9, ¨3.10¨, 3.11, 3.12] steps: - uses: actions/checkout@v2 @@ -29,3 +29,4 @@ jobs: - name: Testing run: | poetry run python -m unittest discover . + From d2988230aee4fadc6003bf0e1a628f0f421a5240 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20K=C3=BCderle?= Date: Mon, 25 Aug 2025 13:06:37 +0200 Subject: [PATCH 04/12] porperly supported versions of actions --- .github/workflows/test-and-lint.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-and-lint.yml b/.github/workflows/test-and-lint.yml index e807633..7e1249a 100644 --- a/.github/workflows/test-and-lint.yml +++ b/.github/workflows/test-and-lint.yml @@ -15,9 +15,9 @@ jobs: python-version: [3.9, ¨3.10¨, 3.11, 3.12] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v5 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies From ad6dcf47bf20ccab3712942b75a8589302224fb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20K=C3=BCderle?= Date: Mon, 25 Aug 2025 13:11:22 +0200 Subject: [PATCH 05/12] Another try --- .github/workflows/test-and-lint.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-and-lint.yml b/.github/workflows/test-and-lint.yml index 7e1249a..f6cc5b5 100644 --- a/.github/workflows/test-and-lint.yml +++ b/.github/workflows/test-and-lint.yml @@ -12,12 +12,11 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.9, ¨3.10¨, 3.11, 3.12] - + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v5 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies From 7462c552e575048c67f22121bda1f190a3a5d058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20K=C3=BCderle?= Date: Mon, 25 Aug 2025 13:18:13 +0200 Subject: [PATCH 06/12] Fixed sample file surls --- test/zipload.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/zipload.py b/test/zipload.py index bcc9f30..4d6cefa 100644 --- a/test/zipload.py +++ b/test/zipload.py @@ -8,12 +8,12 @@ TEMP = os.path.join(tempfile.gettempdir(), 'c3d-test') ZIPS = ( ('https://www.c3d.org/data/Sample00.zip', 'sample00.zip'), - ('https://www.c3d.org/data/sample01.zip', 'sample01.zip'), - ('https://www.c3d.org/data/sample02.zip', 'sample02.zip'), - ('https://www.c3d.org/data/sample07.zip', 'sample07.zip'), - ('https://www.c3d.org/data/sample08.zip', 'sample08.zip'), - ('https://www.c3d.org/data/sample19.zip', 'sample19.zip'), - ('https://www.c3d.org/data/sample31.zip', 'sample31.zip'), + ('https://www.c3d.org/data/Sample01.zip', 'sample01.zip'), + ('https://www.c3d.org/data/Sample02.zip', 'sample02.zip'), + ('https://www.c3d.org/data/Sample07.zip', 'sample07.zip'), + ('https://www.c3d.org/data/Sample08.zip', 'sample08.zip'), + ('https://www.c3d.org/data/Sample19.zip', 'sample19.zip'), + ('https://www.c3d.org/data/Sample31.zip', 'sample31.zip'), ('https://www.c3d.org/data/Sample36.zip', 'sample36.zip'), ) From f7f857acf71b9e1bec34f6cf61cb3bdb485af369 Mon Sep 17 00:00:00 2001 From: David PAGNON Date: Mon, 25 Aug 2025 17:12:58 +0200 Subject: [PATCH 07/12] fix overflow error --- c3d/c3d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/c3d/c3d.py b/c3d/c3d.py index 0f5ec8d..8883207 100644 --- a/c3d/c3d.py +++ b/c3d/c3d.py @@ -1529,7 +1529,7 @@ def last_frame(self) -> int: # Manual refer to parsing the parameter as 2 16-bit words, generally equivalent to an uint32 # end_frame = param.uint32_value words = param.uint16_array - end_frame = words[0] + words[1] * 65536 + end_frame = int(words[0]) + int(words[1]) * 65536 if hlf <= end_frame: return end_frame param = self.get('POINT:LONG_FRAMES') From f20ce4e793ae466c86b786c79c0d098bc25e70fa Mon Sep 17 00:00:00 2001 From: David PAGNON Date: Mon, 25 Aug 2025 18:07:21 +0200 Subject: [PATCH 08/12] fixed files not found in the archive --- test/test_frame_count_encodings.py | 2 +- test/test_proc_format.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_frame_count_encodings.py b/test/test_frame_count_encodings.py index f48462b..0d21e99 100644 --- a/test/test_frame_count_encodings.py +++ b/test/test_frame_count_encodings.py @@ -52,7 +52,7 @@ class Sample19(FrameCountEncodingTest, AnalogSampleCountEncodingTest, verify.Rea frame_count = [34672] sample_count = [624096] - zip_files = ['sample19/sample19.c3d'] + zip_files = ['sample19.c3d'] class Sample31(FrameCountEncodingTest, AnalogSampleCountEncodingTest, verify.ReadTest, Base): diff --git a/test/test_proc_format.py b/test/test_proc_format.py index ca392f2..8dfa2ef 100644 --- a/test/test_proc_format.py +++ b/test/test_proc_format.py @@ -115,8 +115,8 @@ class Sample02(FormatTest, Base): ZIP = 'sample02.zip' INTEL_INT = 'pc_int.c3d' INTEL_REAL = 'pc_real.c3d' - DEC_INT = 'DEC_INT.C3D' - DEC_REAL = 'Dec_real.c3d' + DEC_INT = 'dec_int.c3d' + DEC_REAL = 'dec_real.c3d' MIPS_INT = 'sgi_int.c3d' MIPS_REAL = 'sgi_real.c3d' From 928099540ce1f6c159d766fbde17830ce04ea558 Mon Sep 17 00:00:00 2001 From: David PAGNON Date: Tue, 26 Aug 2025 10:23:16 +0200 Subject: [PATCH 09/12] fixed other overflow error (first_frame instead of last_frame) --- c3d/c3d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/c3d/c3d.py b/c3d/c3d.py index 8883207..2263d8d 100644 --- a/c3d/c3d.py +++ b/c3d/c3d.py @@ -1513,7 +1513,7 @@ def first_frame(self): # ACTUAL_START_FIELD is encoded in two 16 byte words... # return param.uint32_value words = param.uint16_array - return words[0] + words[1] * 65535 + return int(words[0]) + int(words[1]) * 65535 return self.header.first_frame @property From 053b6d41403735ccbc95e1ccd3f02dba66d2a1df Mon Sep 17 00:00:00 2001 From: David PAGNON Date: Tue, 26 Aug 2025 12:08:46 +0200 Subject: [PATCH 10/12] fixed inhomogeneous shapes error due to numpy>=1.24 --- c3d/c3d.py | 25 +++++++++++++++++++++++-- test/verify.py | 29 ++++++++++++++++++++++++----- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/c3d/c3d.py b/c3d/c3d.py index 2263d8d..1195423 100644 --- a/c3d/c3d.py +++ b/c3d/c3d.py @@ -2206,11 +2206,32 @@ def add_frames(self, frames, index=None): Insert the frame or sequence at the index (the first sequence frame will be inserted at give index). Note that the index should be relative to 0 rather then the frame number provided by read_frames()! ''' - sh = np.array(frames, dtype=object).shape + # Determine shape without using np.array() which fails on inhomogeneous data + if hasattr(frames, '__iter__') and not isinstance(frames, (str, np.ndarray)): + # Check if it looks like a sequence of frames + frames_list = list(frames) + if len(frames_list) > 0: + first_element = frames_list[0] + if isinstance(first_element, (list, tuple)) and len(first_element) == 2: + # It's a sequence of (point, analog) pairs + sh = (len(frames_list), 2) + frames = frames_list + else: + # Single frame that happens to be iterable + sh = (2,) if len(frames_list) == 2 else (len(frames_list),) + else: + # Empty sequence + sh = (0, 2) + frames = [] + else: + # Single frame (non-iterable or string/array) + sh = (2,) + frames = [frames] + # Single frame if len(sh) != 2: frames = [frames] - sh = np.shape(frames) + sh = (len(frames), 2) # Sequence of invalid shape if sh[1] != 2: raise ValueError( diff --git a/test/verify.py b/test/verify.py index 17e6d05..3fc8503 100644 --- a/test/verify.py +++ b/test/verify.py @@ -65,10 +65,18 @@ def check_zipfile(file_path): print('{} | READ: OK'.format(file)) - # Allow self.zipfiles on form: - # ['FILE', ..] - # [('FOLDER', ['FILE', ..])] - if len(np.shape(self.zip_files)) == 1: + # Check if zip_files is a simple list of files or nested folder structure + # Simple list: ['file1', 'file2', ...] + # Nested: [('folder', ['file1', 'file2']), ...] + is_simple_list = True + if self.zip_files and len(self.zip_files) > 0: + first_item = self.zip_files[0] + if isinstance(first_item, (tuple, list)) and len(first_item) == 2: + folder_name, files = first_item + if isinstance(files, (list, tuple)): + is_simple_list = False + + if is_simple_list: for file in self.zip_files: check_zipfile(file) else: @@ -130,7 +138,18 @@ def check_zipfile(file_path): print('----------------------------') print(type(self)) print('----------------------------') - if len(np.shape(self.zip_files)) == 1: + # Check if zip_files is a simple list of files or nested folder structure + # Simple list: ['file1', 'file2', ...] + # Nested: [('folder', ['file1', 'file2']), ...] + is_simple_list = True + if self.zip_files and len(self.zip_files) > 0: + first_item = self.zip_files[0] + if isinstance(first_item, (tuple, list)) and len(first_item) == 2: + folder_name, files = first_item + if isinstance(files, (list, tuple)): + is_simple_list = False + + if is_simple_list: for file in self.zip_files: check_zipfile(file) else: From cd1dac8e1206995ec2dab8343e28c8069e973e0c Mon Sep 17 00:00:00 2001 From: David PAGNON Date: Tue, 26 Aug 2025 12:33:53 +0200 Subject: [PATCH 11/12] another int16 -> int32 overflow error --- c3d/c3d.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/c3d/c3d.py b/c3d/c3d.py index 1195423..1ffff10 100644 --- a/c3d/c3d.py +++ b/c3d/c3d.py @@ -2445,7 +2445,7 @@ def _write_metadata(self, handle): # Padding self._pad_block(handle) - while handle.tell() != 512 * (self.header.data_block - 1): + while handle.tell() != 512 * (int(self.header.data_block) - 1): handle.write(b'\x00' * 512) def _write_frames(self, handle): @@ -2457,7 +2457,7 @@ def _write_frames(self, handle): Write metadata and C3D motion frames to the given file handle. The writer does not close the handle. ''' - assert handle.tell() == 512 * (self._header.data_block - 1) + assert handle.tell() == 512 * (int(self._header.data_block) - 1) scale_mag = abs(self.point_scale) is_float = self.point_scale < 0 if is_float: From 3fda0c0cd7146242a916039d18b614a12b3d9627 Mon Sep 17 00:00:00 2001 From: David PAGNON Date: Sat, 30 Aug 2025 01:40:36 +0200 Subject: [PATCH 12/12] better solved inhomogeneous shape error (setting dtype=object) --- c3d/c3d.py | 6 +++--- test/verify.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/c3d/c3d.py b/c3d/c3d.py index 2263d8d..be488eb 100644 --- a/c3d/c3d.py +++ b/c3d/c3d.py @@ -2209,7 +2209,7 @@ def add_frames(self, frames, index=None): sh = np.array(frames, dtype=object).shape # Single frame if len(sh) != 2: - frames = [frames] + frames = np.array([frames], dtype=object) sh = np.shape(frames) # Sequence of invalid shape if sh[1] != 2: @@ -2424,7 +2424,7 @@ def _write_metadata(self, handle): # Padding self._pad_block(handle) - while handle.tell() != 512 * (self.header.data_block - 1): + while handle.tell() != 512 * (int(self.header.data_block) - 1): handle.write(b'\x00' * 512) def _write_frames(self, handle): @@ -2436,7 +2436,7 @@ def _write_frames(self, handle): Write metadata and C3D motion frames to the given file handle. The writer does not close the handle. ''' - assert handle.tell() == 512 * (self._header.data_block - 1) + assert handle.tell() == 512 * (int(self._header.data_block) - 1) scale_mag = abs(self.point_scale) is_float = self.point_scale < 0 if is_float: diff --git a/test/verify.py b/test/verify.py index 17e6d05..53f21b3 100644 --- a/test/verify.py +++ b/test/verify.py @@ -68,7 +68,7 @@ def check_zipfile(file_path): # Allow self.zipfiles on form: # ['FILE', ..] # [('FOLDER', ['FILE', ..])] - if len(np.shape(self.zip_files)) == 1: + if len(np.array(self.zip_files, dtype=object).shape) == 1: for file in self.zip_files: check_zipfile(file) else: @@ -130,7 +130,7 @@ def check_zipfile(file_path): print('----------------------------') print(type(self)) print('----------------------------') - if len(np.shape(self.zip_files)) == 1: + if len(np.array(self.zip_files, dtype=object).shape) == 1: for file in self.zip_files: check_zipfile(file) else: