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 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
- git clone https://github.com/andruhon/freecad-preview-extractor.git
20
- cd ./freecad-preview-extractor
21
- npm ci
22
- sudo npm install -g ./
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/isofit.FCMacro` FreeCAD macros for each file,
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 `isofit.FCMacro` macro to set Isometric View and Fit All
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/isofit.FCMacro` - FreeCAD macro for isometric view and fit (used by `--fit` option)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "freecad-preview-extractor",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Script extracting freecad previews",
5
5
  "keywords": [
6
6
  "FreeCAD"
@@ -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);
@@ -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 { readFileSync } from "fs";
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 isofit macro first
63
+ // If --fit flag is set, run FreeCAD with isometric-fit macro first
100
64
  if (runFit) {
101
- await runFreeCADIsofit(file);
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(file, pngPath);
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 isofit and extracting images from all FreeCAD files in current directory...",
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
- // 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
- }
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
- console.log(`🔍 Extracting image from: ${file}`);
212
- await processSingleFile(file);
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, runFreeCADIsofit };
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: isofit.FCMacro +++++++++++++++++++++++++++++++++++++++++++++++++
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 isofit macro: {}".format(e))
15
+ print("Error in isometric-fit macro: {}".format(e))
16
16
  Gui.getMainWindow().close()
17
17
 
18
- # Macro End: isofit.FCMacro +++++++++++++++++++++++++++++++++++++++++++++++++
18
+ # Macro End: isometric-fit.FCMacro +++++++++++++++++++++++++++++++++++++++++++++++++