freecad-preview-extractor 0.1.0

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Andrew Kondratev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,143 @@
1
+ # FreeCAD preview extractor
2
+
3
+ [![Tests and Lint](https://github.com/andruhon/freecad-preview-extractor/actions/workflows/unit-tests.yml/badge.svg)](https://github.com/andruhon/freecad-preview-extractor/actions/workflows/unit-tests.yml) [![Integration Tests](https://github.com/andruhon/freecad-preview-extractor/actions/workflows/integration-tests.yml/badge.svg?event=push)](https://github.com/andruhon/freecad-preview-extractor/actions/workflows/integration-tests.yml)
4
+
5
+ A CLI tool for extracting and generating preview thumbnails from FreeCAD files.
6
+
7
+ Preview is extracted for each `FILENAME.FCStd` file as `FILENAME-preview.png`
8
+
9
+ ## Features
10
+
11
+ - **Extract existing previews**: Quickly extract embedded thumbnail images from `.FCStd` files
12
+ - **Generate new previews**: Use FreeCAD to create fresh previews with isometric view and fit-to-view
13
+ - **Batch processing**: Process all FreeCAD files in a directory recursively
14
+ - **Single file mode**: Extract or generate previews for specific files
15
+ - **Ignore patterns**: Use an ignore config file (like `.gitignore`) to exclude specific files from batch processing
16
+
17
+ ## Installation
18
+ ```bash
19
+ git clone https://github.com/andruhon/freecad-preview-extractor.git
20
+ cd ./freecad-preview-extractor
21
+ npm ci
22
+ sudo npm install -g ./
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ ### Extract existing previews from all FreeCAD files
28
+ ```bash
29
+ fcxtc
30
+ ```
31
+
32
+ or
33
+
34
+ ```bash
35
+ freecad-preview-extractor
36
+ ```
37
+
38
+ ### Extract preview from a specific file
39
+ ```bash
40
+ fcxtc filename.FCStd
41
+ ```
42
+
43
+ ### Generate preview with FreeCAD before extraction (requires FreeCAD and desktop environment)
44
+
45
+ Adding `--fit` argument will trigger `src/isofit.FCMacro` FreeCAD macros for each file,
46
+ the macros sets model to the isometric view, does "fit into view", and saves the file.
47
+
48
+ ```bash
49
+ fcxtc --fit
50
+ ```
51
+
52
+ or for a specific file:
53
+
54
+ ```bash
55
+ fcxtc --fit filename.FCStd
56
+ ```
57
+
58
+ ### Ignore specific files during batch processing
59
+ ```bash
60
+ fcxtc --ignore-config .myignore
61
+ ```
62
+
63
+ Create an ignore file (e.g., `.myignore`) with patterns to exclude:
64
+ ```
65
+ # Ignore all files in the archived directory
66
+ archived/*.FCStd
67
+
68
+ # Ignore specific files
69
+ test-model.FCStd
70
+
71
+ # Ignore files with specific naming pattern
72
+ *backup*.FCStd
73
+ ```
74
+
75
+ The `--fit` option will:
76
+ 1. Open each FreeCAD file with FreeCAD
77
+ 2. Run the `isofit.FCMacro` macro to set Isometric View and Fit All
78
+ 3. Save the file with the updated preview
79
+ 4. Extract the preview image as usual
80
+
81
+ The `--ignore-config` option allows you to exclude specific files from batch processing using pattern matching similar to `.gitignore`:
82
+ - Supports wildcards (`*`, `?`)
83
+ - Supports `**` for recursive matching
84
+ - Supports character ranges (`[abc]`)
85
+ - Lines starting with `#` are treated as comments
86
+ - Empty lines are ignored
87
+
88
+ **Note:** The `--fit` option requires:
89
+ - FreeCAD installed and available in your system PATH
90
+ - A desktop environment / X server (cannot run headless)
91
+ - UI access for FreeCAD to render the view
92
+
93
+ **Note:** The `--ignore-config` option only works in batch processing mode (when no specific file is provided). It is ignored in single-file mode.
94
+
95
+ ## Testing
96
+
97
+ The project has comprehensive test suites covering unit logic and integration scenarios.
98
+
99
+ ### Unit Tests
100
+ Run unit tests for core logic (path handling, ignore patterns, etc.):
101
+ ```bash
102
+ npm test
103
+ ```
104
+
105
+ ### Integration Tests
106
+ Integration tests are split into two categories based on whether they require a local FreeCAD installation.
107
+
108
+ **1. Standard Integration Tests (No FreeCAD required)**
109
+ These tests check file extraction, CLI behavior, and ignore patterns without invoking FreeCAD.
110
+ ```bash
111
+ npm run test:integration-no-freecad
112
+ ```
113
+
114
+ **2. Full Integration Tests (FreeCAD required)**
115
+ These tests involve the `--fit` flag and spawning the FreeCAD process. They require FreeCAD to be installed and available in the system PATH.
116
+ ```bash
117
+ npm run test:integration
118
+ ```
119
+
120
+ **Note for Developers:**
121
+ - Tests ending in `.test.js` do not require FreeCAD.
122
+ - Tests ending in `.test-freecad.js` require FreeCAD.
123
+
124
+ ## Utilities
125
+
126
+ The project exports utility functions that can be used programmatically:
127
+
128
+ - `loadIgnoreConfig(filePath, cwd)` - Load ignore patterns from a file
129
+ - `filterIgnoredFiles(filePaths, rootDir, patterns, enabled)` - Filter files based on patterns
130
+ - `shouldIgnoreFile(filePath, rootDir, patterns, enabled)` - Check if a single file should be ignored
131
+
132
+ ## Project Files
133
+
134
+ Key files of the project (not including all files):
135
+
136
+ - `src/index.js` - Main CLI entry point
137
+ - `src/extract-png-from-fcstd.js` - Thumbnail extraction logic
138
+ - `src/isofit.FCMacro` - FreeCAD macro for isometric view and fit (used by `--fit` option)
139
+ - `tests/` - Unit tests for ignore functionality
140
+
141
+ ## License
142
+
143
+ MIT
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "freecad-preview-extractor",
3
+ "version": "0.1.0",
4
+ "description": "Script extracting freecad previews",
5
+ "keywords": [
6
+ "FreeCAD"
7
+ ],
8
+ "license": "MIT",
9
+ "author": "Andrew Kondratev",
10
+ "type": "module",
11
+ "main": "src/index.js",
12
+ "bin": {
13
+ "fcxtc": "src/index.js",
14
+ "freecad-preview-extractor": "src/index.js"
15
+ },
16
+ "scripts": {
17
+ "test": "node --test --test-concurrency=1 tests/*.js",
18
+ "test:integration": "node --test --test-concurrency=1 integration-tests/*.js",
19
+ "test:integration-no-freecad": "node --test --test-concurrency=1 integration-tests/*.test.js"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/andruhon/freecad-preview-extractor.git"
24
+ },
25
+ "bugs": {
26
+ "url": "https://github.com/andruhon/freecad-preview-extractor/issues"
27
+ },
28
+ "homepage": "https://github.com/andruhon/freecad-preview-extractor#readme",
29
+ "files": [
30
+ "src",
31
+ "LICENSE",
32
+ "README.md"
33
+ ],
34
+ "engines": {
35
+ "node": ">=24.0.0"
36
+ },
37
+ "dependencies": {
38
+ "glob": "^13.0.0",
39
+ "yauzl": "^3.2.0",
40
+ "minimatch": "^10.1.1"
41
+ }
42
+ }
@@ -0,0 +1,120 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import yauzl from 'yauzl';
4
+
5
+ // Constants
6
+ const THUMBNAIL_PATH = 'thumbnails/Thumbnail.png';
7
+
8
+ // Helper to promisify yauzl's zip opening
9
+ function openZipPromise(filename) {
10
+ return new Promise((resolve, reject) => {
11
+ yauzl.open(filename, { lazyEntries: true }, (err, zipfile) => {
12
+ if (err) return reject(err);
13
+ resolve(zipfile);
14
+ });
15
+ });
16
+ }
17
+
18
+ /**
19
+ * Extract thumbnail from a FreeCAD file
20
+ * @param {string} zipFilePath - Path to the .FCStd file
21
+ * @param {string} outputPath - Path where to save the extracted PNG
22
+ * @returns {Promise<boolean>} True if thumbnail was found and extracted
23
+ */
24
+ export async function extractThumbnailFromFCStd(zipFilePath, outputPath) {
25
+ const zipfile = await openZipPromise(zipFilePath);
26
+ let foundThumbnail = false;
27
+
28
+ return new Promise((resolve, reject) => {
29
+ zipfile.on('entry', (entry) => {
30
+ // Look specifically for thumbnails/Thumbnail.png
31
+ if (entry.fileName === THUMBNAIL_PATH) {
32
+ console.log(`✅ Found Thumbnail in ${path.basename(zipFilePath)}`);
33
+ foundThumbnail = true;
34
+
35
+ zipfile.openReadStream(entry, (err, readStream) => {
36
+ if (err) {
37
+ zipfile.close();
38
+ return reject(err);
39
+ }
40
+
41
+ // Create directory if it doesn't exist
42
+ const outputDir = path.dirname(outputPath);
43
+ if (!fs.existsSync(outputDir)) {
44
+ fs.mkdirSync(outputDir, { recursive: true });
45
+ }
46
+
47
+ const writeStream = fs.createWriteStream(outputPath);
48
+ readStream.pipe(writeStream);
49
+
50
+ // Handle readStream errors to prevent resource leaks
51
+ readStream.on('error', (err) => {
52
+ zipfile.close();
53
+ reject(err);
54
+ });
55
+
56
+ writeStream.on('finish', () => {
57
+ console.log(`✅ Extracted thumbnail to: ${outputPath}`);
58
+ zipfile.close();
59
+ resolve(true);
60
+ });
61
+
62
+ writeStream.on('error', (err) => {
63
+ zipfile.close();
64
+ reject(err);
65
+ });
66
+ });
67
+ } else {
68
+ zipfile.readEntry();
69
+ }
70
+ });
71
+
72
+ zipfile.on('end', () => {
73
+ if (!foundThumbnail) {
74
+ console.log(`⚠️ No thumbnail found in ${path.basename(zipFilePath)}`);
75
+ resolve(false);
76
+ }
77
+ });
78
+
79
+ zipfile.on('error', (err) => {
80
+ reject(err);
81
+ });
82
+
83
+ zipfile.readEntry();
84
+ });
85
+ }
86
+
87
+ /**
88
+ * Process a single .FCStd file to extract its preview
89
+ * @param {string} filePath - Path to the .FCStd file
90
+ * @returns {Promise<boolean>} True if processing was successful
91
+ */
92
+ export async function processSingleFile(filePath) {
93
+ try {
94
+ // Verify file exists
95
+ if (!fs.existsSync(filePath)) {
96
+ console.log(`❌ File not found: ${filePath}`);
97
+ return false;
98
+ }
99
+
100
+ // Verify it's a .FCStd file
101
+ const ext = path.extname(filePath).toLowerCase();
102
+ if (ext !== '.fcstd') {
103
+ console.log(`❌ Not a .FCStd file: ${filePath}`);
104
+ return false;
105
+ }
106
+
107
+ // Prepare output path - same directory, same name but with .png extension
108
+ const dir = path.dirname(filePath);
109
+ const baseName = path.basename(filePath, path.extname(filePath));
110
+ const pngPath = path.join(dir, `${baseName}-preview.png`);
111
+
112
+ // Try to extract thumbnail from the file
113
+ await extractThumbnailFromFCStd(filePath, pngPath);
114
+ return true;
115
+
116
+ } catch (err) {
117
+ console.log(`❌ Error processing ${filePath}: ${err.message}`);
118
+ return false;
119
+ }
120
+ }
@@ -0,0 +1,97 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { minimatch } from 'minimatch';
3
+ import path from 'node:path';
4
+
5
+ /**
6
+ * Load ignore patterns from a file
7
+ * @param {string} ignoreFilePath - Path to the ignore file
8
+ * @returns {string[]} Array of ignore patterns
9
+ */
10
+ export function loadIgnorePatterns(ignoreFilePath) {
11
+ if (!existsSync(ignoreFilePath)) {
12
+ // Return empty array if file doesn't exist
13
+ return [];
14
+ }
15
+
16
+ try {
17
+ const content = readFileSync(ignoreFilePath, 'utf-8');
18
+ const patterns = content
19
+ .split('\n')
20
+ .map((line) => line.trim())
21
+ .filter((line) => line.length > 0 && !line.startsWith('#'));
22
+
23
+ return patterns;
24
+ } catch (error) {
25
+ console.warn(`⚠️ Warning: Could not read ignore file ${ignoreFilePath}: ${error.message}`);
26
+ return [];
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Check if a file path should be ignored based on patterns
32
+ * @param {string} filePath - The file path to check
33
+ * @param {string} rootDir - The root directory for relative pattern matching
34
+ * @param {string[] | undefined} customPatterns - Optional custom patterns to use
35
+ * @param {boolean} enabled - Whether ignore functionality is enabled
36
+ * @returns {boolean} True if the file should be ignored, false otherwise
37
+ */
38
+ export function shouldIgnoreFile(filePath, rootDir, customPatterns = undefined, enabled = true) {
39
+ if (!enabled) {
40
+ return false;
41
+ }
42
+
43
+ const patterns = customPatterns ?? [];
44
+
45
+ if (patterns.length === 0) {
46
+ return false;
47
+ }
48
+
49
+ // Convert file path to relative path for pattern matching
50
+ const relativePath = path.relative(rootDir, filePath);
51
+
52
+ // Check if any pattern matches
53
+ for (const pattern of patterns) {
54
+ try {
55
+ if (minimatch(relativePath, pattern, { dot: true })) {
56
+ return true;
57
+ }
58
+ } catch (error) {
59
+ console.warn(`⚠️ Warning: Error matching pattern '${pattern}': ${error.message}`);
60
+ }
61
+ }
62
+
63
+ return false;
64
+ }
65
+
66
+ /**
67
+ * Filter an array of file paths based on ignore patterns
68
+ * @param {string[]} filePaths - Array of file paths to filter
69
+ * @param {string} rootDir - The root directory for relative pattern matching
70
+ * @param {string[] | undefined} customPatterns - Optional custom patterns to use
71
+ * @param {boolean} enabled - Whether ignore functionality is enabled
72
+ * @returns {string[]} Filtered array of file paths that should not be ignored
73
+ */
74
+ export function filterIgnoredFiles(filePaths, rootDir, customPatterns = undefined, enabled = true) {
75
+ if (!enabled) {
76
+ return filePaths;
77
+ }
78
+
79
+ return filePaths.filter((filePath) => !shouldIgnoreFile(filePath, rootDir, customPatterns, enabled));
80
+ }
81
+
82
+ /**
83
+ * Load ignore patterns and resolve to absolute paths if needed
84
+ * @param {string} ignoreFilePath - Path to the ignore config file
85
+ * @param {string} cwd - Current working directory for relative resolution
86
+ * @returns {string[]} Array of patterns
87
+ */
88
+ export function loadIgnoreConfig(ignoreFilePath, cwd = process.cwd()) {
89
+ if (!existsSync(ignoreFilePath)) {
90
+ console.warn(`⚠️ Warning: Ignore config file not found: ${ignoreFilePath}`);
91
+ return [];
92
+ }
93
+
94
+ const patterns = loadIgnorePatterns(ignoreFilePath);
95
+ console.log(`🔍 Loaded ${patterns.length} ignore patterns from ${ignoreFilePath}`);
96
+ return patterns;
97
+ }
package/src/index.js ADDED
@@ -0,0 +1,223 @@
1
+ #!/usr/bin/env node
2
+ import path from "path";
3
+ import { fileURLToPath } from "url";
4
+ import { spawn } from "child_process";
5
+ import { glob } from "glob";
6
+ import {
7
+ processSingleFile,
8
+ extractThumbnailFromFCStd,
9
+ } from "./extract-png-from-fcstd.js";
10
+ import { loadIgnoreConfig, filterIgnoredFiles } from "./ignore-utils.js";
11
+ import { readFileSync } from "fs";
12
+
13
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
+
15
+ // Get root directory for pattern matching
16
+ function getRootDir(cwd = process.cwd()) {
17
+ return cwd;
18
+ }
19
+
20
+ // Run FreeCAD with isofit macro to generate preview
21
+ async function runFreeCADIsofit(fcstdFile) {
22
+ const macroPath = path.join(__dirname, "isofit.FCMacro");
23
+
24
+ return new Promise((resolve, reject) => {
25
+ console.log(
26
+ `🔧 Running FreeCAD with Isometric, Fit All macro on ${fcstdFile}...`,
27
+ );
28
+
29
+ const freecad = spawn("freecad", [fcstdFile, macroPath]);
30
+
31
+ freecad.stdout.on("data", (data) => {
32
+ // Log FreeCAD output if needed for debugging
33
+ // console.log(`FreeCAD: ${data}`);
34
+ });
35
+
36
+ freecad.stderr.on("data", (data) => {
37
+ // Log errors but don't fail immediately
38
+ // console.error(`FreeCAD stderr: ${data}`);
39
+ });
40
+
41
+ freecad.on("close", (code) => {
42
+ if (code === 0) {
43
+ console.log(`✅ FreeCAD processing complete for ${fcstdFile}`);
44
+ resolve();
45
+ } else {
46
+ reject(new Error(`FreeCAD exited with code ${code}`));
47
+ }
48
+ });
49
+
50
+ freecad.on("error", (err) => {
51
+ reject(
52
+ new Error(
53
+ `Failed to start FreeCAD: ${err.message}. Make sure FreeCAD is installed and available in PATH.`,
54
+ ),
55
+ );
56
+ });
57
+ });
58
+ }
59
+
60
+ // Process all .FCStd files in current directory and subdirectories
61
+ async function processAllFiles(runFit = false, ignoreConfig = null) {
62
+ try {
63
+ // Find all .FCStd files in the current directory and subdirectories (case insensitive)
64
+ let fcstdFiles = await glob("**/*.FCStd", { nocase: true });
65
+
66
+ if (fcstdFiles.length === 0) {
67
+ console.log("❌ No .FCStd files found");
68
+ return;
69
+ }
70
+
71
+ // Apply ignore patterns if config is provided
72
+ let ignorePatterns = [];
73
+ if (ignoreConfig) {
74
+ const rootDir = getRootDir();
75
+ ignorePatterns = loadIgnoreConfig(ignoreConfig, rootDir);
76
+ if (ignorePatterns.length > 0) {
77
+ const originalCount = fcstdFiles.length;
78
+ fcstdFiles = filterIgnoredFiles(fcstdFiles, rootDir, ignorePatterns, true);
79
+ const ignoredCount = originalCount - fcstdFiles.length;
80
+ if (ignoredCount > 0) {
81
+ console.log(`🔍 Ignored ${ignoredCount} files based on ${ignoreConfig}`);
82
+ }
83
+ }
84
+ }
85
+
86
+ if (fcstdFiles.length === 0) {
87
+ console.log("❌ All files were filtered out by ignore patterns");
88
+ return;
89
+ }
90
+
91
+ console.log(`✅ Found ${fcstdFiles.length} .FCStd files to check`);
92
+
93
+ let processedCount = 0;
94
+ let successCount = 0;
95
+ let failureCount = 0;
96
+
97
+ for (const file of fcstdFiles) {
98
+ try {
99
+ // If --fit flag is set, run FreeCAD with isofit macro first
100
+ if (runFit) {
101
+ await runFreeCADIsofit(file);
102
+ }
103
+
104
+ // Prepare output path - same directory, same name but with .png extension
105
+ const dir = path.dirname(file);
106
+ const baseName = path.basename(file, path.extname(file));
107
+ const pngPath = path.join(dir, `${baseName}-preview.png`);
108
+
109
+ // Try to extract thumbnail from the file
110
+ await extractThumbnailFromFCStd(file, pngPath);
111
+ successCount++;
112
+ } catch (err) {
113
+ console.log(`❌ Error processing ${file}: ${err.message}`);
114
+ failureCount++;
115
+ }
116
+ processedCount++;
117
+ console.log(`📊 Progress: ${processedCount}/${fcstdFiles.length} (${successCount} succeeded, ${failureCount} failed)`);
118
+ }
119
+
120
+ if (failureCount > 0) {
121
+ console.log(`⚠️ Processing completed with ${failureCount} failures out of ${processedCount} files`);
122
+ return false; // Indicate partial failure
123
+ } else {
124
+ console.log("✅ Processing complete - all files processed successfully");
125
+ return true; // Indicate success
126
+ }
127
+ } catch (err) {
128
+ console.error("❌ Error:", err);
129
+ return false; // Indicate failure
130
+ }
131
+ }
132
+
133
+ // Main entry point
134
+ async function main() {
135
+ const args = process.argv.slice(2);
136
+
137
+ // Check for --version or --help flags
138
+ if (args.includes("--version") || args.includes("-v")) {
139
+ const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url)));
140
+ console.log(pkg.version);
141
+ process.exit(0);
142
+ }
143
+
144
+ if (args.includes("--help") || args.includes("-h")) {
145
+ console.log(`FreeCAD Preview Extractor v0.0.2-alpha
146
+
147
+ Usage: fcxtc [options] [file]
148
+
149
+ Options:
150
+ --fit Run FreeCAD with isometric fit macro before extracting
151
+ --ignore-config Path to ignore config file (e.g., .fcignore)
152
+ --version, -v Show version number
153
+ --help, -h Show this help message
154
+
155
+ Examples:
156
+ fcxtc Extract previews from all .FCStd files in current directory
157
+ fcxtc myfile.FCStd Extract preview from specific file
158
+ fcxtc --fit Run isometric fit macro on all files before extracting
159
+ fcxtc --ignore-config .fcignore Use ignore config file
160
+ fcxtc --fit --ignore-config .fcignore Combine flags`);
161
+ process.exit(0);
162
+ }
163
+
164
+ // Check for --fit flag
165
+ const fitIndex = args.indexOf("--fit");
166
+ const runFit = fitIndex !== -1;
167
+
168
+ // Check for --ignore-config flag
169
+ let ignoreConfig = null;
170
+ const ignoreConfigIndex = args.indexOf("--ignore-config");
171
+ if (ignoreConfigIndex !== -1 && ignoreConfigIndex + 1 < args.length) {
172
+ ignoreConfig = args[ignoreConfigIndex + 1];
173
+ }
174
+
175
+ // Filter out --fit, --ignore-config and its value to get file arguments
176
+ const fileArgs = args.filter((arg, index) => {
177
+ if (arg === "--fit") return false;
178
+ if (arg === "--ignore-config") return false;
179
+ if (ignoreConfigIndex !== -1 && index === ignoreConfigIndex + 1) return false;
180
+ return true;
181
+ });
182
+
183
+ if (fileArgs.length === 0) {
184
+ // No arguments - process all files
185
+ if (runFit) {
186
+ console.log(
187
+ "🔍 Running FreeCAD isofit and extracting images from all FreeCAD files in current directory...",
188
+ );
189
+ } else {
190
+ console.log(
191
+ "🔍 Extracting images from all FreeCAD files in current directory...",
192
+ );
193
+ }
194
+ if (ignoreConfig) {
195
+ console.log(`🔍 Using ignore config: ${ignoreConfig}`);
196
+ }
197
+ const success = await processAllFiles(runFit, ignoreConfig);
198
+ if (!success) {
199
+ process.exit(1); // Exit with error code if batch processing failed
200
+ }
201
+ } else {
202
+ // Process specific file(s)
203
+ const file = fileArgs[0];
204
+
205
+ // For single file mode, ignore config is not applied
206
+ if (runFit) {
207
+ console.log(`🔧 Running FreeCAD isofit on: ${file}`);
208
+ await runFreeCADIsofit(file);
209
+ }
210
+
211
+ console.log(`🔍 Extracting image from: ${file}`);
212
+ await processSingleFile(file);
213
+ }
214
+ }
215
+
216
+ export { processAllFiles, runFreeCADIsofit };
217
+ export { loadIgnoreConfig, filterIgnoredFiles } from "./ignore-utils.js";
218
+
219
+ // Run the script
220
+ main().catch((err) => {
221
+ console.error("❌ Fatal error:", err);
222
+ process.exit(1);
223
+ });
@@ -0,0 +1,18 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ # Macro Begin: isofit.FCMacro +++++++++++++++++++++++++++++++++++++++++++++++++
4
+ import FreeCAD
5
+
6
+ try:
7
+ view = Gui.activeDocument().activeView()
8
+ view.setAnimationEnabled(False)
9
+ view.viewIsometric()
10
+ Gui.SendMsgToActiveView("ViewFit")
11
+ Gui.updateGui()
12
+ Gui.SendMsgToActiveView("Save")
13
+ Gui.getMainWindow().close()
14
+ except Exception as e:
15
+ print("Error in isofit macro: {}".format(e))
16
+ Gui.getMainWindow().close()
17
+
18
+ # Macro End: isofit.FCMacro +++++++++++++++++++++++++++++++++++++++++++++++++