freecad-preview-extractor 0.1.0 → 0.1.2
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/README.md +24 -7
- package/package.json +1 -1
- package/src/extract-png-from-fcstd.js +3 -2
- package/src/fit-utils.js +47 -0
- package/src/index.js +63 -54
- package/src/{isofit.FCMacro → isometric-fit.FCMacro} +3 -3
package/README.md
CHANGED
|
@@ -15,11 +15,17 @@ Preview is extracted for each `FILENAME.FCStd` file as `FILENAME-preview.png`
|
|
|
15
15
|
- **Ignore patterns**: Use an ignore config file (like `.gitignore`) to exclude specific files from batch processing
|
|
16
16
|
|
|
17
17
|
## Installation
|
|
18
|
+
|
|
19
|
+
Install globally:
|
|
20
|
+
|
|
18
21
|
```bash
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
npm install -g freecad-preview-extractor
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Or run directly with npx:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx freecad-preview-extractor
|
|
23
29
|
```
|
|
24
30
|
|
|
25
31
|
## Usage
|
|
@@ -42,7 +48,7 @@ fcxtc filename.FCStd
|
|
|
42
48
|
|
|
43
49
|
### Generate preview with FreeCAD before extraction (requires FreeCAD and desktop environment)
|
|
44
50
|
|
|
45
|
-
Adding `--fit` argument will trigger `src/
|
|
51
|
+
Adding `--fit` argument will trigger `src/isometric-fit.FCMacro` FreeCAD macros for each file,
|
|
46
52
|
the macros sets model to the isometric view, does "fit into view", and saves the file.
|
|
47
53
|
|
|
48
54
|
```bash
|
|
@@ -74,7 +80,7 @@ test-model.FCStd
|
|
|
74
80
|
|
|
75
81
|
The `--fit` option will:
|
|
76
82
|
1. Open each FreeCAD file with FreeCAD
|
|
77
|
-
2. Run the `
|
|
83
|
+
2. Run the `isometric-fit.FCMacro` macro to set Isometric View and Fit All
|
|
78
84
|
3. Save the file with the updated preview
|
|
79
85
|
4. Extract the preview image as usual
|
|
80
86
|
|
|
@@ -129,13 +135,24 @@ The project exports utility functions that can be used programmatically:
|
|
|
129
135
|
- `filterIgnoredFiles(filePaths, rootDir, patterns, enabled)` - Filter files based on patterns
|
|
130
136
|
- `shouldIgnoreFile(filePath, rootDir, patterns, enabled)` - Check if a single file should be ignored
|
|
131
137
|
|
|
138
|
+
## Development
|
|
139
|
+
|
|
140
|
+
To set up the project for development:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
git clone https://github.com/andruhon/freecad-preview-extractor.git
|
|
144
|
+
cd ./freecad-preview-extractor
|
|
145
|
+
npm ci
|
|
146
|
+
sudo npm install -g ./
|
|
147
|
+
```
|
|
148
|
+
|
|
132
149
|
## Project Files
|
|
133
150
|
|
|
134
151
|
Key files of the project (not including all files):
|
|
135
152
|
|
|
136
153
|
- `src/index.js` - Main CLI entry point
|
|
137
154
|
- `src/extract-png-from-fcstd.js` - Thumbnail extraction logic
|
|
138
|
-
- `src/
|
|
155
|
+
- `src/isometric-fit.FCMacro` - FreeCAD macro for isometric view and fit (used by `--fit` option)
|
|
139
156
|
- `tests/` - Unit tests for ignore functionality
|
|
140
157
|
|
|
141
158
|
## License
|
package/package.json
CHANGED
|
@@ -87,9 +87,10 @@ export async function extractThumbnailFromFCStd(zipFilePath, outputPath) {
|
|
|
87
87
|
/**
|
|
88
88
|
* Process a single .FCStd file to extract its preview
|
|
89
89
|
* @param {string} filePath - Path to the .FCStd file
|
|
90
|
+
* @param {string} [customOutputPath] - Optional custom path for the output PNG
|
|
90
91
|
* @returns {Promise<boolean>} True if processing was successful
|
|
91
92
|
*/
|
|
92
|
-
export async function processSingleFile(filePath) {
|
|
93
|
+
export async function processSingleFile(filePath, customOutputPath = null) {
|
|
93
94
|
try {
|
|
94
95
|
// Verify file exists
|
|
95
96
|
if (!fs.existsSync(filePath)) {
|
|
@@ -107,7 +108,7 @@ export async function processSingleFile(filePath) {
|
|
|
107
108
|
// Prepare output path - same directory, same name but with .png extension
|
|
108
109
|
const dir = path.dirname(filePath);
|
|
109
110
|
const baseName = path.basename(filePath, path.extname(filePath));
|
|
110
|
-
const pngPath = path.join(dir, `${baseName}-preview.png`);
|
|
111
|
+
const pngPath = customOutputPath || path.join(dir, `${baseName}-preview.png`);
|
|
111
112
|
|
|
112
113
|
// Try to extract thumbnail from the file
|
|
113
114
|
await extractThumbnailFromFCStd(filePath, pngPath);
|
package/src/fit-utils.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
|
|
5
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
|
|
7
|
+
// Run FreeCAD with isometric fit macro to generate preview
|
|
8
|
+
async function runFreeCADIsometricFit(fcstdFile) {
|
|
9
|
+
const macroPath = path.join(__dirname, "isometric-fit.FCMacro");
|
|
10
|
+
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
console.log(
|
|
13
|
+
`🔧 Running FreeCAD with Isometric, Fit All macro on ${fcstdFile}...`,
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
const freecad = spawn("freecad", [fcstdFile, macroPath]);
|
|
17
|
+
|
|
18
|
+
freecad.stdout.on("data", (data) => {
|
|
19
|
+
// Log FreeCAD output if needed for debugging
|
|
20
|
+
// console.log(`FreeCAD: ${data}`);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
freecad.stderr.on("data", (data) => {
|
|
24
|
+
// Log errors but don't fail immediately
|
|
25
|
+
// console.error(`FreeCAD stderr: ${data}`);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
freecad.on("close", (code) => {
|
|
29
|
+
if (code === 0) {
|
|
30
|
+
console.log(`✅ FreeCAD processing complete for ${fcstdFile}`);
|
|
31
|
+
resolve();
|
|
32
|
+
} else {
|
|
33
|
+
reject(new Error(`FreeCAD exited with code ${code}`));
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
freecad.on("error", (err) => {
|
|
38
|
+
reject(
|
|
39
|
+
new Error(
|
|
40
|
+
`Failed to start FreeCAD: ${err.message}. Make sure FreeCAD is installed and available in PATH.`,
|
|
41
|
+
),
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { runFreeCADIsometricFit };
|
package/src/index.js
CHANGED
|
@@ -8,7 +8,8 @@ import {
|
|
|
8
8
|
extractThumbnailFromFCStd,
|
|
9
9
|
} from "./extract-png-from-fcstd.js";
|
|
10
10
|
import { loadIgnoreConfig, filterIgnoredFiles } from "./ignore-utils.js";
|
|
11
|
-
import {
|
|
11
|
+
import { runFreeCADIsometricFit } from "./fit-utils.js";
|
|
12
|
+
import fs from "fs";
|
|
12
13
|
|
|
13
14
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
14
15
|
|
|
@@ -17,46 +18,6 @@ function getRootDir(cwd = process.cwd()) {
|
|
|
17
18
|
return cwd;
|
|
18
19
|
}
|
|
19
20
|
|
|
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
21
|
// Process all .FCStd files in current directory and subdirectories
|
|
61
22
|
async function processAllFiles(runFit = false, ignoreConfig = null) {
|
|
62
23
|
try {
|
|
@@ -95,10 +56,21 @@ async function processAllFiles(runFit = false, ignoreConfig = null) {
|
|
|
95
56
|
let failureCount = 0;
|
|
96
57
|
|
|
97
58
|
for (const file of fcstdFiles) {
|
|
59
|
+
let fileToProcess = file;
|
|
60
|
+
let tempFile = null;
|
|
61
|
+
|
|
98
62
|
try {
|
|
99
|
-
// If --fit flag is set, run FreeCAD with
|
|
63
|
+
// If --fit flag is set, run FreeCAD with isometric-fit macro first
|
|
100
64
|
if (runFit) {
|
|
101
|
-
|
|
65
|
+
const ext = path.extname(file);
|
|
66
|
+
const dir = path.dirname(file);
|
|
67
|
+
const baseName = path.basename(file, ext);
|
|
68
|
+
tempFile = path.join(dir, `${baseName}_temp_${Date.now()}${ext}`);
|
|
69
|
+
|
|
70
|
+
fs.copyFileSync(file, tempFile);
|
|
71
|
+
fileToProcess = tempFile;
|
|
72
|
+
|
|
73
|
+
await runFreeCADIsometricFit(fileToProcess);
|
|
102
74
|
}
|
|
103
75
|
|
|
104
76
|
// Prepare output path - same directory, same name but with .png extension
|
|
@@ -107,11 +79,20 @@ async function processAllFiles(runFit = false, ignoreConfig = null) {
|
|
|
107
79
|
const pngPath = path.join(dir, `${baseName}-preview.png`);
|
|
108
80
|
|
|
109
81
|
// Try to extract thumbnail from the file
|
|
110
|
-
await extractThumbnailFromFCStd(
|
|
82
|
+
await extractThumbnailFromFCStd(fileToProcess, pngPath);
|
|
111
83
|
successCount++;
|
|
112
84
|
} catch (err) {
|
|
113
85
|
console.log(`❌ Error processing ${file}: ${err.message}`);
|
|
114
86
|
failureCount++;
|
|
87
|
+
} finally {
|
|
88
|
+
// Clean up temp file
|
|
89
|
+
if (tempFile && fs.existsSync(tempFile)) {
|
|
90
|
+
try {
|
|
91
|
+
fs.unlinkSync(tempFile);
|
|
92
|
+
} catch (e) {
|
|
93
|
+
console.log(`⚠️ Could not delete temp file ${tempFile}: ${e.message}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
115
96
|
}
|
|
116
97
|
processedCount++;
|
|
117
98
|
console.log(`📊 Progress: ${processedCount}/${fcstdFiles.length} (${successCount} succeeded, ${failureCount} failed)`);
|
|
@@ -136,7 +117,7 @@ async function main() {
|
|
|
136
117
|
|
|
137
118
|
// Check for --version or --help flags
|
|
138
119
|
if (args.includes("--version") || args.includes("-v")) {
|
|
139
|
-
const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url)));
|
|
120
|
+
const pkg = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url)));
|
|
140
121
|
console.log(pkg.version);
|
|
141
122
|
process.exit(0);
|
|
142
123
|
}
|
|
@@ -184,7 +165,7 @@ Examples:
|
|
|
184
165
|
// No arguments - process all files
|
|
185
166
|
if (runFit) {
|
|
186
167
|
console.log(
|
|
187
|
-
"🔍 Running FreeCAD
|
|
168
|
+
"🔍 Running FreeCAD isometric-fit and extracting images from all FreeCAD files in current directory...",
|
|
188
169
|
);
|
|
189
170
|
} else {
|
|
190
171
|
console.log(
|
|
@@ -201,19 +182,47 @@ Examples:
|
|
|
201
182
|
} else {
|
|
202
183
|
// Process specific file(s)
|
|
203
184
|
const file = fileArgs[0];
|
|
185
|
+
let fileToProcess = file;
|
|
186
|
+
let tempFile = null;
|
|
187
|
+
let customOutputPath = null;
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
// For single file mode, ignore config is not applied
|
|
191
|
+
if (runFit) {
|
|
192
|
+
const ext = path.extname(file);
|
|
193
|
+
const dir = path.dirname(file);
|
|
194
|
+
const baseName = path.basename(file, ext);
|
|
195
|
+
tempFile = path.join(dir, `${baseName}_temp_${Date.now()}${ext}`);
|
|
204
196
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
197
|
+
fs.copyFileSync(file, tempFile);
|
|
198
|
+
fileToProcess = tempFile;
|
|
199
|
+
|
|
200
|
+
// Set output path based on original file
|
|
201
|
+
customOutputPath = path.join(dir, `${baseName}-preview.png`);
|
|
210
202
|
|
|
211
|
-
|
|
212
|
-
|
|
203
|
+
console.log(`🔧 Running FreeCAD isometric-fit on temp file: ${path.basename(tempFile)}`);
|
|
204
|
+
await runFreeCADIsometricFit(fileToProcess);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
console.log(`🔍 Extracting image from: ${fileToProcess}`);
|
|
208
|
+
await processSingleFile(fileToProcess, customOutputPath);
|
|
209
|
+
} catch (err) {
|
|
210
|
+
console.error(`❌ Error processing ${file}:`, err);
|
|
211
|
+
process.exitCode = 1;
|
|
212
|
+
} finally {
|
|
213
|
+
// Clean up temp file
|
|
214
|
+
if (tempFile && fs.existsSync(tempFile)) {
|
|
215
|
+
try {
|
|
216
|
+
fs.unlinkSync(tempFile);
|
|
217
|
+
} catch (e) {
|
|
218
|
+
console.warn(`⚠️ Failed to delete temp file: ${e.message}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
213
222
|
}
|
|
214
223
|
}
|
|
215
224
|
|
|
216
|
-
export { processAllFiles
|
|
225
|
+
export { processAllFiles };
|
|
217
226
|
export { loadIgnoreConfig, filterIgnoredFiles } from "./ignore-utils.js";
|
|
218
227
|
|
|
219
228
|
// Run the script
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
|
|
3
|
-
# Macro Begin:
|
|
3
|
+
# Macro Begin: isometric-fit.FCMacro +++++++++++++++++++++++++++++++++++++++++++++++++
|
|
4
4
|
import FreeCAD
|
|
5
5
|
|
|
6
6
|
try:
|
|
@@ -12,7 +12,7 @@ try:
|
|
|
12
12
|
Gui.SendMsgToActiveView("Save")
|
|
13
13
|
Gui.getMainWindow().close()
|
|
14
14
|
except Exception as e:
|
|
15
|
-
print("Error in
|
|
15
|
+
print("Error in isometric-fit macro: {}".format(e))
|
|
16
16
|
Gui.getMainWindow().close()
|
|
17
17
|
|
|
18
|
-
# Macro End:
|
|
18
|
+
# Macro End: isometric-fit.FCMacro +++++++++++++++++++++++++++++++++++++++++++++++++
|